From eb1ef6d2a71b93bb4de86b7b54e57c54225d2507 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 22 Oct 2023 22:02:38 +0400 Subject: [PATCH 01/99] Fix getting the screen of viewer/pip We need to workaround getting the actual screen for the parent by getting its position yet we need to get the setted screen for the widget itself as that's the screen used to compute the geometry --- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 2 +- Telegram/SourceFiles/media/view/media_view_pip.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index f7859e94dc..d8544ffc5f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -817,7 +817,7 @@ void OverlayWidget::moveToScreen(bool inMove) { ? Core::App().activeWindow()->widget().get() : nullptr; const auto activeWindowScreen = widgetScreen(applicationWindow); - const auto myScreen = widgetScreen(_window); + const auto myScreen = _window->screen(); if (activeWindowScreen && myScreen != activeWindowScreen) { const auto screenList = QGuiApplication::screens(); DEBUG_LOG(("Viewer Pos: Currently on screen %1, moving to screen %2") diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index dbee087b07..655e7cf410 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -514,8 +514,8 @@ void PipPanel::setPositionDefault() { return widget->screen(); }; const auto parentScreen = widgetScreen(_parent); - const auto myScreen = widgetScreen(widget()); - if (parentScreen && myScreen && myScreen != parentScreen) { + const auto myScreen = widget()->screen(); + if (parentScreen && myScreen != parentScreen) { widget()->windowHandle()->setScreen(parentScreen); } auto position = Position(); From 2b10e1e595067fb255cfefed4938551e58250354 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 22 Oct 2023 22:10:40 +0400 Subject: [PATCH 02/99] Update lib_base --- Telegram/lib_base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 013e7e9c3c..bb5e7fafd7 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 013e7e9c3ca475eaed9b3ec31df05e6e32434b18 +Subproject commit bb5e7fafd7aca5808217baeeb9ee3af8c3be6b1b From 5dfce5f7b23eabc2744d5179cd976d587b4cd733 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 22 Oct 2023 22:12:15 +0400 Subject: [PATCH 03/99] Fix direct include of third-party library --- Telegram/cmake/lib_prisma.cmake | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/cmake/lib_prisma.cmake b/Telegram/cmake/lib_prisma.cmake index f23e1a219f..89e203ace2 100644 --- a/Telegram/cmake/lib_prisma.cmake +++ b/Telegram/cmake/lib_prisma.cmake @@ -30,8 +30,6 @@ PRIVATE ) target_include_directories(lib_prisma -PRIVATE - ${libs_loc}/regex/include PUBLIC ${prisma_loc} ) From f2fa1cd70dc670113b3551ee11d79f47524ef869 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Oct 2023 08:59:29 +0400 Subject: [PATCH 04/99] Fix crash in non-clickable spoilers. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 1a9c4162cc..4d1a5686a7 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 1a9c4162cc7230ccc9179bb813a05ca3f713c4a5 +Subproject commit 4d1a5686a7620b163639492e1b81101b677d2472 From be38800a70b1642671f397ef6f218c918c2f59d4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 21 Oct 2023 23:55:17 +0300 Subject: [PATCH 05/99] Moved entry point for boosts statistic to channel context menu. --- Telegram/Resources/icons/menu/boosts.png | Bin 0 -> 589 bytes Telegram/Resources/icons/menu/boosts@2x.png | Bin 0 -> 1126 bytes Telegram/Resources/icons/menu/boosts@3x.png | Bin 0 -> 1703 bytes .../statistics/info_statistics_inner_widget.cpp | 15 --------------- Telegram/SourceFiles/ui/menu_icons.style | 1 + Telegram/SourceFiles/window/window_peer_menu.cpp | 8 ++++++++ 6 files changed, 9 insertions(+), 15 deletions(-) create mode 100644 Telegram/Resources/icons/menu/boosts.png create mode 100644 Telegram/Resources/icons/menu/boosts@2x.png create mode 100644 Telegram/Resources/icons/menu/boosts@3x.png diff --git a/Telegram/Resources/icons/menu/boosts.png b/Telegram/Resources/icons/menu/boosts.png new file mode 100644 index 0000000000000000000000000000000000000000..64a5ae3b9a31515c270f9a882903fcb35241ca74 GIT binary patch literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY`dq6V~B;| z-N}1TI|j-e-)nP8H&Er}GHZ_JloZ9oz8j5p+})M3Wy47owWt0El9arD^855m>2RHS z;496g(WcE;YS4TxK=*T-Dyb~%Ai>Wx|}mM2uYGQ>)*Ux2+iHS);8vR!xI z6ry(dRJzf1= J);T3K0RRyM`mq22 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/boosts@2x.png b/Telegram/Resources/icons/menu/boosts@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0478fa66047ac730819776863982601fee7e0a85 GIT binary patch literal 1126 zcmV-s1eyDZP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NF&`Cr=R9Fe^SW76hQ5YVVlzU1I z21zW~pfM@3urMidSy)&}?8t2~HJX}il|~}jAklA{4JDDwMoLm{3n^K+N4dn1yQlxH z)7N(!Grlwb;-9ma&UtUo>wNEd&vWQ>ff3k&zz%rWf#BfagoK39&`=L{eozMm1$B0I zK0iNyeSKYBT{Si~`Te8e|~+if%&nbFAB*I?metkOC>lU+ZEZzGMM+_%r>CJ^T3V8-w3p=P=i@*D@AUN4U@*u6 z?Cjh54pLyvZ3(>UtV5d)7I7|YoM7hCMITYZx2Xl%H`$d zN^vtXGGK$Twzl@?@96FAHJMCG_CCr%Cl(eK2&tu|MI}mUX(^cre{tgK>+4m#yjBel z4--INU!S^jY;5e*)D*5*Hg0Zi;ET%BtP3Iq4;tWOpd6-l*TOK`An zZ*P&%=H_N(WTe;rI$d{nH~l>*FCW~+F1XAoTuDiZSoecAd*H&7eGS&t)zSW~uC9iKg=vf}8*XG|1duyB zJ5p^yK>}-*i*AfLwj~=VlD#SoT zC&CXdP*zqZRPmjsr>9ei;Bm;1aKYi4BRQV1Ozw{fo;w7bmX^lwh(tp^G&ID7ewuW3 zbpaSR2EH9ZHZ(L)iXhRl=ZKilBCReRp;`aSz40ykfLCA#{`(#H26?iXtLol(-T(jq07*qoM6N<$f)EGpga7~l literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/boosts@3x.png b/Telegram/Resources/icons/menu/boosts@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..33590248c93a6017310a355cddc9df9bcde5006b GIT binary patch literal 1703 zcmV;Y23YxtP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS>5lKWrRA>e5T74*9UmTy!+YHUi z7HuXHdD$qXM2PaTO(bDjV{6m?Afn|ZVnQLMFp;tqnen9 z@Avt2f4<%B^E}tZ^*r~f=lSP3=X*Zib3Wg5&pF@k`QCfwisq-80nH3(WXw$4uCA{7`udxjn<}Is=FkrwJm~A|`{(5M{rmT@urM(PP)T22US2ypJ10i{ z|NrODpO-IRs)Ua?gWB2It*@_htYWIafB(L9>y|jfD7hC?Q`4!bDY9yOeEij`S3NyF zB+1OoR5B$IhSSy6?eFg=8|LQbZr{F*ej&%RV&qj-RU(X}4CntyGYEXFg>={u<;ra9DY|@V( zKc?2`=xBD6q|3g(z6S>fM7^-EkV7{uEsa{;+}t>=60JHoI3TJZ-c3zST3T8ht~E6^ z1h{eI2B%euRdaLmnVA_PJvcas=t-7nZ*MPySy)(*ZIzq^Hoy|H3Kqgnxh$8KmKYcu znp}W{gjjD_bz~J*Bi5u`eujpI1goyDmWz>)u&Ahr=;GwS`JsrMySqCfQ&LhC!6YGn z@!|z>MFD3$53MIpo)Gxq!-qUb6*s~2!v7N2w{PF_6UxoaB{1S+ez3|~5hWp3BBF?) zPNnkk<3|RM>9}@HmA77nR1wi4t|hAw?;^TYh(2i;85vT z;ckK(iV%WyotN0|Nsw z$3*C?w<%&J!0F)Vmv%N_V)J3FxqgMDj0|Ns#K+ zrD6$(2abDoTuUaQFf=s8B;q+LYEwmCfbFap+ichannelStats(), descriptor.api->supergroupStats(), }; - if (_state.stats.channel) { - ::Settings::AddSkip(inner); - const auto button = ::Settings::AddButton( - inner, - tr::lng_boosts_title(), - st::boostsButton); - const auto controller = _controller; - button->setClickedCallback([=, peer = descriptor.peer] { - controller->showSection(Info::Boosts::Make(peer)); - }); - ::Settings::AddSkip(inner); - ::Settings::AddDivider(inner); - ::Settings::AddSkip(inner); - } fill(); finishLoading(); diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 734651b521..1a840e0f48 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -52,6 +52,7 @@ menuIconClear: icon {{ "menu/clear", menuIconColor }}; menuIconManage: icon {{ "menu/manage", menuIconColor }}; menuIconDiscussion: icon {{ "menu/discussion", menuIconColor }}; menuIconStats: icon {{ "menu/stats", menuIconColor }}; +menuIconBoosts: icon {{ "menu/boosts", menuIconColor }}; menuIconCreatePoll: icon {{ "menu/create_poll", menuIconColor }}; menuIconQrCode: icon {{ "menu/qr_code", menuIconColor }}; menuIconExpand: icon {{ "menu/expand", menuIconColor }}; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 28afbfb4ce..13955e8dba 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -62,6 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "support/support_helper.h" #include "info/info_controller.h" #include "info/info_memento.h" +#include "info/boosts/info_boosts_widget.h" #include "info/profile/info_profile_values.h" #include "info/statistics/info_statistics_widget.h" #include "info/stories/info_stories_widget.h" @@ -1006,6 +1007,13 @@ void Filler::addViewStatistics() { controller->showSection(Info::Statistics::Make(peer, {})); } }, &st::menuIconStats); + if (!channel->isMegagroup()) { + _addAction(tr::lng_boosts_title(tr::now), [=] { + if (const auto strong = weak.get()) { + controller->showSection(Info::Boosts::Make(peer)); + } + }, &st::menuIconBoosts); + } } } } From 53c73accd0ff4af21151e83862878aa71a14a973 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 22 Oct 2023 01:16:21 +0300 Subject: [PATCH 06/99] Fixed typo in custom shortcut for full screen in media viewer. --- Telegram/SourceFiles/core/shortcuts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index 82d93ec288..f673aba28f 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -97,7 +97,7 @@ const auto CommandByName = base::flat_map{ { u"message"_q , Command::JustSendMessage }, { u"message_silently"_q , Command::SendSilentMessage }, { u"message_scheduled"_q , Command::ScheduleMessage }, - { u"mevia_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen }, + { u"media_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen }, // }; From 0aa1031402ce83ff8ec242e68acbcd1eea333bf6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 22 Oct 2023 11:09:04 +0300 Subject: [PATCH 07/99] Added ability to open message statistics from context menu. --- .../history/history_inner_widget.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index d296beeba5..a575ce312b 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -45,6 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "window/notifications_manager.h" +#include "info/info_memento.h" +#include "info/statistics/info_statistics_widget.h" #include "boxes/about_sponsored_box.h" #include "boxes/delete_messages_box.h" #include "boxes/report_messages_box.h" @@ -2212,11 +2214,26 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (pinItem->canPin()) { const auto isPinned = pinItem->isPinned(); const auto pinItemId = pinItem->fullId(); - const auto controller = _controller; _menu->addAction(isPinned ? tr::lng_context_unpin_msg(tr::now) : tr::lng_context_pin_msg(tr::now), crl::guard(controller, [=] { Window::ToggleMessagePinned(controller, pinItemId, !isPinned); }), isPinned ? &st::menuIconUnpin : &st::menuIconPin); } + if (!item->isService() + && peerIsChannel(itemId.peer) + && !_peer->isMegagroup()) { + if (const auto channel = _peer->asChannel()) { + if (channel->flags() & ChannelDataFlag::CanGetStatistics) { + auto callback = crl::guard(controller, [=] { + controller->showSection( + Info::Statistics::Make(channel, itemId)); + }); + _menu->addAction( + tr::lng_stats_title(tr::now), + std::move(callback), + &st::menuIconStats); + } + } + } }; const auto addPhotoActions = [&](not_null photo, HistoryItem *item) { const auto media = photo->activeMediaView(); From 0be2e8b6721c83678a97daf3f3fccc9233a158cd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 22 Oct 2023 21:33:58 +0300 Subject: [PATCH 08/99] Fixed statistics overview of old messages. --- Telegram/SourceFiles/api/api_statistics.cpp | 49 +++++++++++++++------ 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index 1bd5eb3149..79de3c2cde 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -434,19 +434,42 @@ void MessageStatistics::request(Fn done) { const auto requestPrivateForwards = [=]( const Data::StatisticalGraph &messageGraph) { - _api.request(MTPstats_GetBroadcastStats( - MTP_flags(MTPstats_GetBroadcastStats::Flags(0)), - _channel->inputChannel - )).done([=](const MTPstats_BroadcastStats &result) { - const auto channelStats = ChannelStatisticsFromTL(result.data()); - auto info = Data::StatisticsMessageInteractionInfo(); - for (const auto &r : channelStats.recentMessageInteractions) { - if (r.messageId == _fullId.msg) { - info = r; - break; - } - } - requestFirstPublicForwards(messageGraph, info); + _api.request(MTPchannels_GetMessages( + _channel->inputChannel, + MTP_vector( + 1, + MTP_inputMessageID(MTP_int(_fullId.msg)))) + ).done([=](const MTPmessages_Messages &result) { + const auto process = [&](const MTPVector &messages) { + const auto &message = messages.v.front(); + return message.match([&](const MTPDmessage &data) { + return Data::StatisticsMessageInteractionInfo{ + .messageId = IdFromMessage(message), + .viewsCount = data.vviews() + ? data.vviews()->v + : 0, + .forwardsCount = data.vforwards() + ? data.vforwards()->v + : 0, + }; + }, [](const MTPDmessageEmpty &) { + return Data::StatisticsMessageInteractionInfo(); + }, [](const MTPDmessageService &) { + return Data::StatisticsMessageInteractionInfo(); + }); + }; + + auto info = result.match([&](const MTPDmessages_messages &data) { + return process(data.vmessages()); + }, [&](const MTPDmessages_messagesSlice &data) { + return process(data.vmessages()); + }, [&](const MTPDmessages_channelMessages &data) { + return process(data.vmessages()); + }, [](const MTPDmessages_messagesNotModified &) { + return Data::StatisticsMessageInteractionInfo(); + }); + + requestFirstPublicForwards(messageGraph, std::move(info)); }).fail([=](const MTP::Error &error) { requestFirstPublicForwards(messageGraph, {}); }).send(); From d4db838d4346ba0723933a89e41c61ad7681f297 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 22 Oct 2023 22:07:53 +0300 Subject: [PATCH 09/99] Added message preview to message statistics. --- .../info_statistics_inner_widget.cpp | 11 ++++++++-- .../info_statistics_recent_message.cpp | 20 +++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp index 778f49fe11..3a57976c23 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp @@ -542,6 +542,14 @@ void InnerWidget::fill() { lifetime().make_state(&_peer->session().api()), _controller->uiShow()->toastParent(), }; + if (_state.stats.message) { + if (const auto i = _peer->owner().message(_contextId)) { + ::Settings::AddSkip(inner); + inner->add(object_ptr(this, i, -1, -1, QImage())); + ::Settings::AddSkip(inner); + ::Settings::AddDivider(inner); + } + } FillOverview(inner, _state.stats); FillStatistic(inner, descriptor, _state.stats); const auto &channel = _state.stats.channel; @@ -553,8 +561,7 @@ void InnerWidget::fill() { const auto showPeerInfo = [=](not_null peer) { _showRequests.fire({ .info = peer->id }); }; - const auto addSkip = [&]( - not_null c) { + const auto addSkip = [&](not_null c) { ::Settings::AddSkip(c); ::Settings::AddDivider(c); ::Settings::AddSkip(c); diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_recent_message.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_recent_message.cpp index e4825c712d..1a85458e93 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_recent_message.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_recent_message.cpp @@ -79,16 +79,20 @@ MessagePreview::MessagePreview( Ui::FormatDateTime(ItemDateTime(item))) , _views( st::defaultPeerListItem.nameStyle, - tr::lng_stats_recent_messages_views( - tr::now, - lt_count_decimal, - views)) + (views >= 0) + ? tr::lng_stats_recent_messages_views( + tr::now, + lt_count_decimal, + views) + : QString()) , _shares( st::statisticsHeaderTitleTextStyle, - tr::lng_stats_recent_messages_shares( - tr::now, - lt_count_decimal, - shares)) + (shares >= 0) + ? tr::lng_stats_recent_messages_shares( + tr::now, + lt_count_decimal, + shares) + : QString()) , _viewsWidth(_views.maxWidth()) , _sharesWidth(_shares.maxWidth()) , _preview(std::move(cachedPreview)) { From 29f8493a820571c9bfdb6c72ad121ac23b36a9c3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 22 Oct 2023 22:16:05 +0300 Subject: [PATCH 10/99] Removed error toast for async graphs. --- .../info/statistics/info_statistics_inner_widget.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp index 3a57976c23..c90ee395ce 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp @@ -124,10 +124,9 @@ void FillStatistic( ProcessZoom(descriptor, widget, graph.zoomToken, type); widget->setTitle(rpl::duplicate(title)); } else if (!graph.error.isEmpty()) { - Ui::Toast::Show(descriptor.toastParent, graph.error); } - }, [=](const QString &error) { - }, [=] { + }, [](const QString &error) { + }, [] { }, content->lifetime()); addSkip(wrap->entity()); From d0875a11782d84b01f99d9a45a89e0ef64c6a697 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 22 Oct 2023 22:25:35 +0300 Subject: [PATCH 11/99] Fixed sliders in footer in statistics info while restoring state. --- Telegram/SourceFiles/statistics/chart_widget.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/statistics/chart_widget.cpp b/Telegram/SourceFiles/statistics/chart_widget.cpp index f63fbfab38..a6a167185c 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.cpp +++ b/Telegram/SourceFiles/statistics/chart_widget.cpp @@ -282,9 +282,10 @@ ChartWidget::Footer::Footer(not_null parent) Qt::RoundCap) { sizeValue( ) | rpl::take(2) | rpl::start_with_next([=](const QSize &s) { - moveSide(false, s.width()); - moveSide(true, 0); - update(); + const auto current = xPercentageLimits(); + if (current.min == current.max) { + setXPercentageLimits({ 0., 1. }); + } }, lifetime()); mouseStateChanged( From f93b7a60f8f0e365d64be8b865ccdf473df3c6c6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 22 Oct 2023 22:37:07 +0300 Subject: [PATCH 12/99] Fixed display of button to show more boosts when boosts are too few. --- .../info/statistics/info_statistics_list_controllers.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 1666abbec8..db764dff6b 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -530,6 +530,9 @@ void AddBoostsList( object_ptr(container, &state->controller))); state->controller.setDelegate(&state->delegate); + if (max <= state->limit) { + return; + } const auto wrap = container->add( object_ptr>( container, From 0788f3d7b03784165d24dd66a5f4403d7ab8a6e6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 22 Oct 2023 22:46:41 +0300 Subject: [PATCH 13/99] Increased size of arrow in chart point details widget. --- Telegram/SourceFiles/statistics/statistics.style | 2 +- .../SourceFiles/statistics/widgets/point_details_widget.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index e27059a41d..f797dbc1a0 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -17,7 +17,7 @@ statisticsChartHeight: 200px; statisticsChartEntryPadding: margins(0px, 13px, 0px, 2px); -statisticsDetailsArrowShift: 2px; +statisticsDetailsArrowShift: 3px; statisticsDetailsArrowStroke: 1.5; statisticsDetailsPopupMargins: margins(12px, 8px, 12px, 11px); statisticsDetailsPopupPadding: margins(6px, 6px, 6px, 6px); diff --git a/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp b/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp index a9f057543b..4baa07539f 100644 --- a/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp +++ b/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp @@ -429,7 +429,7 @@ void PointDetailsWidget::paintEvent(QPaintEvent *e) { const auto s = _arrow.size() / style::DevicePixelRatio(); const auto x = rect::right(_textRect) - s.width(); const auto y = _textRect.y() - + (_headerStyle.font->height - s.height()) / 2.; + + (_headerStyle.font->ascent - s.height()); p.drawImage(x, y, _arrow); } } From 1f253012830b080ff8c4dba80051c0dbdd8a0177 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Oct 2023 20:20:31 +0400 Subject: [PATCH 14/99] Beta version 4.10.5. - Fix crash in replies to messages with spoilers. - Enter boosts stats from three-dot menu. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- changelog.txt | 5 +++++ 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index a95a14af9c..71ceac802c 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.10.5.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 3661e18ba0..5f159936d4 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,10,4,0 - PRODUCTVERSION 4,10,4,0 + FILEVERSION 4,10,5,0 + PRODUCTVERSION 4,10,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "4.10.4.0" + VALUE "FileVersion", "4.10.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.10.4.0" + VALUE "ProductVersion", "4.10.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 7c76ba5d81..fdb59b7198 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,10,4,0 - PRODUCTVERSION 4,10,4,0 + FILEVERSION 4,10,5,0 + PRODUCTVERSION 4,10,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "4.10.4.0" + VALUE "FileVersion", "4.10.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.10.4.0" + VALUE "ProductVersion", "4.10.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 8f4ce7eb22..f63118ca65 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 4010004; -constexpr auto AppVersionStr = "4.10.4"; +constexpr auto AppVersion = 4010005; +constexpr auto AppVersionStr = "4.10.5"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 94a9388049..5f32b2f0c1 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4010004 +AppVersion 4010005 AppVersionStrMajor 4.10 -AppVersionStrSmall 4.10.4 -AppVersionStr 4.10.4 +AppVersionStrSmall 4.10.5 +AppVersionStr 4.10.5 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 4.10.4.beta +AppVersionOriginal 4.10.5.beta diff --git a/changelog.txt b/changelog.txt index f79d9fbfde..6e5d10d740 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +4.10.5 beta (23.10.23) + +- Fix crash in replies to messages with spoilers. +- Enter boosts stats from three-dot menu. + 4.10.4 beta (21.10.23) - Statistics in channels and group chats. From cd0f58fa658941c5bd1cd1e8596c81ab220db9d1 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 19 Oct 2023 11:34:28 +0400 Subject: [PATCH 15/99] Implement monochrome tray icon on Windows --- .../platform/win/integration_win.cpp | 6 +- .../platform/win/main_window_win.cpp | 4 + .../SourceFiles/platform/win/tray_win.cpp | 85 +++++++++++++++++-- Telegram/SourceFiles/platform/win/tray_win.h | 1 + 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/platform/win/integration_win.cpp b/Telegram/SourceFiles/platform/win/integration_win.cpp index 72c1783b34..b8d53a506a 100644 --- a/Telegram/SourceFiles/platform/win/integration_win.cpp +++ b/Telegram/SourceFiles/platform/win/integration_win.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/core_settings.h" #include "core/sandbox.h" +#include "tray.h" #include "base/platform/win/base_windows_winrt.h" #include @@ -78,11 +79,12 @@ bool WindowsIntegration::processEvent( } break; -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) case WM_SETTINGCHANGE: +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) Core::App().settings().setSystemDarkMode(Platform::IsDarkMode()); - break; #endif // Qt < 6.5.0 + Core::App().tray().updateIconCounters(); + break; } return false; } diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index 45ec3fc6c8..8c852b837a 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -510,10 +510,12 @@ void MainWindow::updateTaskbarAndIconCounters() { auto iconSmallPixmap16 = Tray::IconWithCounter( Tray::CounterLayerArgs(16, counter, muted), true, + false, supportMode); auto iconSmallPixmap32 = Tray::IconWithCounter( Tray::CounterLayerArgs(32, counter, muted), true, + false, supportMode); QIcon iconSmall, iconBig; iconSmall.addPixmap(iconSmallPixmap16); @@ -524,10 +526,12 @@ void MainWindow::updateTaskbarAndIconCounters() { iconBig.addPixmap(Tray::IconWithCounter( Tray::CounterLayerArgs(32, bigCounter, muted), false, + false, supportMode)); iconBig.addPixmap(Tray::IconWithCounter( Tray::CounterLayerArgs(64, bigCounter, muted), false, + false, supportMode)); destroyCachedIcons(); diff --git a/Telegram/SourceFiles/platform/win/tray_win.cpp b/Telegram/SourceFiles/platform/win/tray_win.cpp index a0927702e3..1d99df1694 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.cpp +++ b/Telegram/SourceFiles/platform/win/tray_win.cpp @@ -30,13 +30,47 @@ namespace { constexpr auto kTooltipDelay = crl::time(10000); +[[nodiscard]] std::optional IsDarkTaskbar() { + static const auto kSystemVersion = QOperatingSystemVersion::current(); + static const auto kDarkModeAddedVersion = QOperatingSystemVersion( + QOperatingSystemVersion::Windows, + 10, + 0, + 17763); + static const auto kSupported = (kSystemVersion >= kDarkModeAddedVersion); + if (!kSupported) { + return std::nullopt; + } + + const auto keyName = L"" + "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; + const auto valueName = L"SystemUsesLightTheme"; + auto key = HKEY(); + auto result = RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key); + if (result != ERROR_SUCCESS) { + return std::nullopt; + } + + DWORD value = 0, type = 0, size = sizeof(value); + result = RegQueryValueEx(key, valueName, 0, &type, (LPBYTE)&value, &size); + RegCloseKey(key); + if (result != ERROR_SUCCESS) { + return std::nullopt; + } + + return (value == 0); +} + [[nodiscard]] QImage ImageIconWithCounter( Window::CounterLayerArgs &&args, bool supportMode, - bool smallIcon) { + bool smallIcon, + bool monochrome) { static constexpr auto kCount = 3; static auto ScaledLogo = std::array(); static auto ScaledLogoNoMargin = std::array(); + static auto ScaledLogoDark = std::array(); + static auto ScaledLogoLight = std::array(); struct Dimensions { int index = 0; @@ -63,19 +97,49 @@ constexpr auto kTooltipDelay = crl::time(10000); }(); Assert(d.index < kCount); - auto &scaled = smallIcon ? ScaledLogoNoMargin : ScaledLogo; + const auto darkMode = IsDarkTaskbar(); + auto &scaled = (monochrome && darkMode) + ? (*darkMode + ? ScaledLogoDark + : ScaledLogoLight) + : smallIcon + ? ScaledLogoNoMargin + : ScaledLogo; + auto result = [&] { auto &image = scaled[d.index]; if (image.isNull()) { - image = (smallIcon - ? Window::LogoNoMargin() - : Window::Logo()).scaledToWidth( - d.size, - Qt::SmoothTransformation); + if (monochrome && darkMode) { + const auto withColor = [&](QColor color) { + switch (d.size) { + case 16: + return st::macTrayIcon.instance(color, 100 / cIntRetinaFactor()); + case 32: + return st::macTrayIcon.instance(color, 200 / cIntRetinaFactor()); + default: + return st::macTrayIcon.instance(color, 300 / cIntRetinaFactor()); + } + }; + const auto darkModeResult = withColor({ 255, 255, 255 }); + const auto lightModeResult = withColor({ 0, 0, 0, 228 }); + image = *darkMode ? darkModeResult : lightModeResult; + const auto monochromeMargin = QPoint( + (image.width() - d.size) / 2, + (image.height() - d.size) / 2); + image = image.copy( + QRect(monochromeMargin, QSize(d.size, d.size))); + image.setDevicePixelRatio(1); + } else { + image = (smallIcon + ? Window::LogoNoMargin() + : Window::Logo()).scaledToWidth( + d.size, + Qt::SmoothTransformation); + } } return image; }(); - if (supportMode) { + if ((!monochrome || !darkMode) && supportMode) { Window::ConvertIconToBlack(result); } if (!args.count) { @@ -163,10 +227,12 @@ void Tray::updateIcon() { auto iconSmallPixmap16 = Tray::IconWithCounter( CounterLayerArgs(16, counter, muted), true, + true, supportMode); auto iconSmallPixmap32 = Tray::IconWithCounter( CounterLayerArgs(32, counter, muted), true, + true, supportMode); auto iconSmall = QIcon(); iconSmall.addPixmap(iconSmallPixmap16); @@ -271,9 +337,10 @@ Window::CounterLayerArgs Tray::CounterLayerArgs( QPixmap Tray::IconWithCounter( Window::CounterLayerArgs &&args, bool smallIcon, + bool monochrome, bool supportMode) { return Ui::PixmapFromImage( - ImageIconWithCounter(std::move(args), supportMode, smallIcon)); + ImageIconWithCounter(std::move(args), supportMode, smallIcon, monochrome)); } } // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/tray_win.h b/Telegram/SourceFiles/platform/win/tray_win.h index e399e59453..53631e5565 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.h +++ b/Telegram/SourceFiles/platform/win/tray_win.h @@ -57,6 +57,7 @@ public: [[nodiscard]] static QPixmap IconWithCounter( Window::CounterLayerArgs &&args, bool smallIcon, + bool monochrome, bool supportMode); private: From d19baeace2c674114ab1f7628355a2d7c66aed25 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 24 Oct 2023 12:15:12 +0300 Subject: [PATCH 16/99] Fixed show finishing of info layers. --- Telegram/SourceFiles/info/info_layer_widget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/info/info_layer_widget.cpp b/Telegram/SourceFiles/info/info_layer_widget.cpp index fc39cf621c..cbf5117c33 100644 --- a/Telegram/SourceFiles/info/info_layer_widget.cpp +++ b/Telegram/SourceFiles/info/info_layer_widget.cpp @@ -149,6 +149,7 @@ void LayerWidget::setContentHeight(int height) { void LayerWidget::showFinished() { floatPlayerShowVisible(); + _content->showFast(); } void LayerWidget::parentResized() { From 0b7d54461588fdc9a4730abf11b4da8db923aa5b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Oct 2023 13:52:24 +0400 Subject: [PATCH 17/99] Move Boost.regex definitions to cmake_helpers. --- Telegram/cmake/lib_prisma.cmake | 6 ------ cmake | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Telegram/cmake/lib_prisma.cmake b/Telegram/cmake/lib_prisma.cmake index 89e203ace2..b8dc8cc17f 100644 --- a/Telegram/cmake/lib_prisma.cmake +++ b/Telegram/cmake/lib_prisma.cmake @@ -23,12 +23,6 @@ PRIVATE TokenList.h ) -target_compile_definitions(lib_prisma -PRIVATE - BOOST_NO_INTRINSIC_WCHAR_T - BOOST_REGEX_NO_W32 -) - target_include_directories(lib_prisma PUBLIC ${prisma_loc} diff --git a/cmake b/cmake index 21a8d7996d..fef4eba1b7 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 21a8d7996d50d973976607360f2f563f38af1c29 +Subproject commit fef4eba1b747d3bb8d817386da72992167c277d2 From 3fc9ed0ccb0d89cb1af8d118c68f4e0d94e0c0e7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 28 Sep 2023 15:05:38 +0400 Subject: [PATCH 18/99] Update API scheme to layer 166. --- Telegram/SourceFiles/mtproto/scheme/api.tl | 8 ++++++++ Telegram/SourceFiles/mtproto/scheme/layer.tl | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 8a386d9885..4aede409ab 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1415,6 +1415,7 @@ help.premiumPromo#5334759c status_text:string status_entities:Vector boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1575,6 +1576,10 @@ stories.boostersList#f3dd3d1d flags:# count:int boosters:Vector next_of messages.webPage#fd5e12bd webpage:WebPage chats:Vector users:Vector = messages.WebPage; +premiumGiftCodeOption#d579436c flags:# users:int months:int store_product:flags.0?string = PremiumGiftCodeOption; + +payments.checkedGiftCode#ff70298c flags:# from_id:Peer to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2043,6 +2048,9 @@ payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoi payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaymentPurpose = Updates; payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates; payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; +payments.getPremiumGiftCodeOptions#2757ba54 flags:# boost_peer:flags.0?InputPeer = Vector; +payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode; +payments.applyGiftCode#f6e26854 slug:string = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/Telegram/SourceFiles/mtproto/scheme/layer.tl b/Telegram/SourceFiles/mtproto/scheme/layer.tl index 7a644419f6..ede5063647 100644 --- a/Telegram/SourceFiles/mtproto/scheme/layer.tl +++ b/Telegram/SourceFiles/mtproto/scheme/layer.tl @@ -1 +1 @@ -// LAYER 165 +// LAYER 166 From 744c1b925e61874292a660b9534c9d13af61465e Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 28 Sep 2023 22:44:22 +0400 Subject: [PATCH 19/99] Handle GiftCode links, show Gift Link box. --- Telegram/CMakeLists.txt | 28 +- Telegram/Resources/langs/lang.strings | 83 +++++ Telegram/SourceFiles/api/api_premium.cpp | 59 ++++ Telegram/SourceFiles/api/api_premium.h | 28 ++ .../SourceFiles/boxes/gift_premium_box.cpp | 321 ++++++++++++++++++ Telegram/SourceFiles/boxes/gift_premium_box.h | 13 + .../SourceFiles/core/local_url_handlers.cpp | 21 +- .../SourceFiles/info/info_wrap_widget.cpp | 21 +- Telegram/SourceFiles/info/info_wrap_widget.h | 2 + Telegram/SourceFiles/settings/settings.style | 23 -- .../SourceFiles/settings/settings_common.h | 3 + .../SourceFiles/settings/settings_premium.cpp | 23 +- Telegram/SourceFiles/ui/effects/premium.style | 103 ++++++ .../ui/effects/premium_top_bar.cpp | 26 +- .../SourceFiles/ui/effects/premium_top_bar.h | 18 +- 15 files changed, 715 insertions(+), 57 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f6adf0c140..e2078f7e62 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -816,20 +816,6 @@ PRIVATE history/history_view_highlight_manager.h history/history_widget.cpp history/history_widget.h - info/info_content_widget.cpp - info/info_content_widget.h - info/info_controller.cpp - info/info_controller.h - info/info_layer_widget.cpp - info/info_layer_widget.h - info/info_memento.cpp - info/info_memento.h - info/info_section_widget.cpp - info/info_section_widget.h - info/info_top_bar.cpp - info/info_top_bar.h - info/info_wrap_widget.cpp - info/info_wrap_widget.h info/boosts/info_boosts_inner_widget.cpp info/boosts/info_boosts_inner_widget.h info/boosts/info_boosts_widget.cpp @@ -916,6 +902,20 @@ PRIVATE info/userpic/info_userpic_emoji_builder_preview.h info/userpic/info_userpic_emoji_builder_widget.cpp info/userpic/info_userpic_emoji_builder_widget.h + info/info_content_widget.cpp + info/info_content_widget.h + info/info_controller.cpp + info/info_controller.h + info/info_layer_widget.cpp + info/info_layer_widget.h + info/info_memento.cpp + info/info_memento.h + info/info_section_widget.cpp + info/info_section_widget.h + info/info_top_bar.cpp + info/info_top_bar.h + info/info_wrap_widget.cpp + info/info_wrap_widget.h inline_bots/bot_attach_web_view.cpp inline_bots/bot_attach_web_view.h inline_bots/inline_bot_layout_internal.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a2ac91c507..5a7c0a7a13 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1640,6 +1640,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_story_mention_button" = "View Story"; "lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available."; "lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available."; +"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers."; "lng_premium_gift_duration_months#one" = "for {count} month"; "lng_premium_gift_duration_months#other" = "for {count} months"; @@ -2034,6 +2035,88 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?"; "lng_boost_now_replace" = "Replace"; +"lng_giveaway_new_title" = "Boosts via Gifts"; +"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers."; +"lng_giveaway_create_option" = "Create Giveaway"; +"lng_giveaway_create_subtitle" = "winners are chosen randomly"; +"lng_giveaway_award_option" = "Award Specific Users"; +"lng_giveaway_award_subtitle" = "Select recipients >"; +"lng_giveaway_award_chosen#one" = "{count} recipient >"; +"lng_giveaway_award_chosen#other" = "{count} recipients >"; +"lng_giveaway_quantity_title" = "Quantity of prizes / boosts"; +"lng_giveaway_quantity#one" = "{count} Subscription / Boost"; +"lng_giveaway_quantity#other" = "{count} Subscriptions / Boosts"; +"lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive."; +"lng_giveaway_channels_title" = "Channels included in the giveaway"; +"lng_giveaway_channels_this#one" = "this channel will receive {count} boost"; +"lng_giveaway_channels_this#other" = "this channel will receive {count} boosts"; +"lng_giveaway_channels_add" = "Add Channel"; +"lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway."; +"lng_giveaway_users_title" = "Users eligible for the giveaway"; +"lng_giveaway_users_all" = "All subscribers"; +"lng_giveaway_users_new" = "Only new subscribers"; +"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to the newly joined subscribers."; +"lng_giveaway_start" = "Start Giveaway"; +"lng_giveaway_award" = "Gift Premium"; +"lng_giveaway_date_title" = "Date when giveaway ends"; +"lng_giveaway_date" = "Date and Time"; +"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium."; +"lng_giveaway_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium."; +"lng_giveaway_duration_title#one" = "Duration of Premium subscription"; +"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions"; +"lng_giveaway_duration_price" = "{price} x {amount}"; +"lng_giveaway_duration_about" = "You can review the list of features and terms of use for Telegram Premium {link}."; +"lng_giveaway_duration_about_link" = "here"; +"lng_giveaway_date_select" = "Select Date and Time"; +"lng_giveaway_date_confirm" = "Confirm"; +"lng_giveaway_channels_select#one" = "Select up to {count} channel"; +"lng_giveaway_channels_select#other" = "Select up to {count} channels"; +"lng_giveaway_recipients_save" = "Save Recipients"; +"lng_giveaway_recipients_deselect" = "Deselect All"; + +"lng_prize_title" = "Congratulations!"; +"lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; +"lng_prize_duration" = "Your prize is **Telegram Premium** subscription for {duration}."; +"lng_prize_open" = "Open Gift Link"; + +"lng_prizes_title#one" = "Giveaway Prize"; +"lng_prizes_title#other" = "Giveaway Prizes"; +"lng_prizes_about#one" = "{count} Telegram Premium Subscription for {duration}."; +"lng_prizes_about#other" = "{count} Telegram Premium Subscriptions for {duration}."; +"lng_prizes_participants" = "Participants"; +"lng_prizes_participants_all#one" = "All subscribers of the channel:"; +"lng_prizes_participants_all#other" = "All subscribers of the channels:"; +"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:"; +"lng_prizes_participants_new#other" = "All users who joined the channels below after this date:"; +"lng_prizes_date" = "Winners Selection Date"; +"lng_prizes_how_works" = "How does it work?"; +"lng_prizes_how_text#one" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscription for {duration} for its followers."; +"lng_prizes_how_text#other" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions for {duration} for its followers."; +"lng_prizes_how_when_all_of_one#one" = "On {date}, Telegram will automatically select {count} random subscribers of {channel}."; +"lng_prizes_how_when_all_of_one#other" = "On {date}, Telegram will automatically select {count} random subscribers of {channel}."; +"lng_prizes_how_when_all_of_many#one" = "On {date}, Telegram will automatically select {count} random subscribers of {channel} or other listed channels."; +"lng_prizes_how_when_all_of_many#other" = "On {date}, Telegram will automatically select {count} random subscribers of {channel} or other listed channels."; +"lng_prizes_how_when_new_of_one#one" = "On {date}, Telegram will automatically select {count} random user that joined {channel} after {start_date}"; +"lng_prizes_how_when_new_of_one#other" = "On {date}, Telegram will automatically select {count} random users that joined {channel} after {start_date}"; +"lng_prizes_how_when_new_of_many#one" = "On {date}, Telegram will automatically select {count} random user that joined {channel} or other listed channels after {start_date}"; +"lng_prizes_how_when_new_of_many#other" = "On {date}, Telegram will automatically select {count} random users that joined {channel} or other listed channels after {start_date}"; + +"lng_gift_link_title" = "Gift Link"; +"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription."; +"lng_gift_link_label_from" = "From"; +"lng_gift_link_label_to" = "To"; +"lng_gift_link_label_gift" = "Gift"; +"lng_gift_link_gift_premium" = "Telegram Premium for {duration}"; +"lng_gift_link_label_reason" = "Reason"; +"lng_gift_link_reason_giveaway" = "Giveaway"; +"lng_gift_link_label_date" = "Date"; +"lng_gift_link_also_send" = "You can also {link} to a friend as a gift."; +"lng_gift_link_also_send_link" = "send this link"; +"lng_gift_link_use" = "Use Link"; +"lng_gift_link_used_title" = "Used Gift Link"; +"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription."; +"lng_gift_link_used_footer" = "This link was used on {date}."; + "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts."; "lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 975d36a656..936715834b 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -17,6 +17,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" namespace Api { +namespace { + +[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) { + return { + .from = peerFromMTP(data.vfrom_id()), + .to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(), + .date = data.vdate().v, + .used = false,// data.vused_date().value_or_empty(), + .months = data.vmonths().v, + }; +} + +} // namespace Premium::Premium(not_null api) : _session(&api->session()) @@ -183,6 +196,52 @@ void Premium::reloadCloudSet() { }).send(); } +void Premium::checkGiftCode( + const QString &slug, + Fn done) { + if (_giftCodeRequestId) { + if (_giftCodeSlug == slug) { + return; + } + _api.request(_giftCodeRequestId).cancel(); + } + _giftCodeSlug = slug; + _giftCodeRequestId = _api.request(MTPpayments_CheckGiftCode( + MTP_string(slug) + )).done([=](const MTPpayments_CheckedGiftCode &result) { + _giftCodeRequestId = 0; + + const auto &data = result.data(); + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + done(updateGiftCode(slug, Parse(data))); + }).fail([=](const MTP::Error &error) { + _giftCodeRequestId = 0; + + done(updateGiftCode(slug, {})); + }).send(); +} + +GiftCode Premium::updateGiftCode( + const QString &slug, + const GiftCode &code) { + auto &now = _giftCodes[slug]; + if (now != code) { + now = code; + _giftCodeUpdated.fire_copy(slug); + } + return code; +} + +rpl::producer Premium::giftCodeValue(const QString &slug) const { + return _giftCodeUpdated.events_starting_with_copy( + slug + ) | rpl::filter(rpl::mappers::_1 == slug) | rpl::map([=] { + const auto i = _giftCodes.find(slug); + return (i != end(_giftCodes)) ? i->second : GiftCode(); + }); +} + const Data::SubscriptionOptions &Premium::subscriptionOptions() const { return _subscriptionOptions; } diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 3582833607..6fe1c9d700 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -18,6 +18,22 @@ class Session; namespace Api { +struct GiftCode { + PeerId from = 0; + PeerId to = 0; + TimeId date = 0; + TimeId used = 0; // 0 if not used. + int months = 0; + + explicit operator bool() const { + return months != 0; + } + + friend inline bool operator==( + const GiftCode&, + const GiftCode&) = default; +}; + class Premium final { public: explicit Premium(not_null api); @@ -40,6 +56,13 @@ public: [[nodiscard]] int64 monthlyAmount() const; [[nodiscard]] QString monthlyCurrency() const; + void checkGiftCode( + const QString &slug, + Fn done); + GiftCode updateGiftCode(const QString &slug, const GiftCode &code); + [[nodiscard]] rpl::producer giftCodeValue( + const QString &slug) const; + [[nodiscard]] auto subscriptionOptions() const -> const Data::SubscriptionOptions &; @@ -71,6 +94,11 @@ private: int64 _monthlyAmount = 0; QString _monthlyCurrency; + mtpRequestId _giftCodeRequestId = 0; + QString _giftCodeSlug; + base::flat_map _giftCodes; + rpl::event_stream _giftCodeUpdated; + Data::SubscriptionOptions _subscriptionOptions; }; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 6720339c28..96be412ec7 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/gift_premium_box.h" #include "apiwrap.h" +#include "api/api_premium.h" #include "api/api_premium_option.h" +#include "base/unixtime.h" #include "base/weak_ptr.h" +#include "boxes/peers/prepare_short_info_box.h" #include "data/data_changes.h" #include "data/data_peer_values.h" // Data::PeerPremiumValue. #include "data/data_session.h" @@ -22,12 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/userpic_button.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_stars_colored.h" +#include "ui/effects/premium_top_bar.h" #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/gradient_round_button.h" #include "ui/wrap/padding_wrap.h" +#include "ui/wrap/table_layout.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" @@ -35,6 +40,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_info.h" #include "styles/style_premium.h" +#include + namespace { constexpr auto kDiscountDivider = 5.; @@ -225,6 +232,175 @@ void GiftBox( }, box->lifetime()); } +struct GiftCodeLink { + QString text; + QString link; +}; +[[nodiscard]] GiftCodeLink MakeGiftCodeLink( + not_null session, + const QString &slug) { + const auto path = u"giftcode/"_q + slug; + return { + session->createInternalLink(path), + session->createInternalLinkFull(path), + }; +} + +[[nodiscard]] object_ptr MakeLinkLabel( + not_null parent, + rpl::producer text, + rpl::producer link, + std::shared_ptr show) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + struct State { + State( + not_null parent, + rpl::producer value, + rpl::producer link) + : text(std::move(value)) + , link(std::move(link)) + , label(parent, text.value(), st::giveawayGiftCodeLink) + , bg(st::roundRadiusLarge, st::windowBgOver) { + } + + rpl::variable text; + rpl::variable link; + Ui::FlatLabel label; + Ui::RoundRect bg; + }; + + const auto state = raw->lifetime().make_state( + raw, + rpl::duplicate(text), + std::move(link)); + state->label.setSelectable(true); + + rpl::combine( + raw->widthValue(), + std::move(text) + ) | rpl::start_with_next([=](int outer, const auto&) { + const auto textWidth = state->label.textMaxWidth(); + const auto skipLeft = st::giveawayGiftCodeLink.margin.left(); + const auto skipRight = st::giveawayGiftCodeLinkCopyWidth; + const auto available = outer - skipRight - skipLeft; + const auto use = std::min(textWidth, available); + state->label.resizeToWidth(use); + const auto left = (outer >= 2 * skipRight + textWidth) + ? ((outer - textWidth) / 2) + : (outer - skipRight - use - skipLeft); + state->label.move(left, 0); + }, raw->lifetime()); + + raw->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(raw); + state->bg.paint(p, raw->rect()); + const auto outer = raw->width(); + const auto width = st::giveawayGiftCodeLinkCopyWidth; + const auto &icon = st::giveawayGiftCodeLinkCopy; + const auto left = outer - width + (width - icon.width()) / 2; + const auto top = (raw->height() - icon.height()) / 2; + icon.paint(p, left, top, raw->width()); + }, raw->lifetime()); + + state->label.setAttribute(Qt::WA_TransparentForMouseEvents); + + raw->resize(raw->width(), st::giveawayGiftCodeLinkHeight); + raw->setClickedCallback([=] { + QGuiApplication::clipboard()->setText(state->link.current()); + show->showToast(tr::lng_username_copied(tr::now)); + }); + + return result; +} + +[[nodiscard]] rpl::producer DurationValue(int months) { + return (months < 12) + ? tr::lng_months(lt_count, rpl::single(float64(months))) + : tr::lng_years(lt_count, rpl::single(float64(months / 12))); +} + +[[nodiscard]] object_ptr MakePeerTableValue( + not_null parent, + not_null controller, + PeerId id) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + const auto &st = st::giveawayGiftCodeUserpic; + raw->resize(raw->width(), st.photoSize); + + const auto peer = controller->session().data().peer(id); + const auto userpic = Ui::CreateChild(raw, peer, st); + const auto label = Ui::CreateChild( + raw, + peer->name(), + st::giveawayGiftCodeValue); + raw->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto position = st::giveawayGiftCodeNamePosition; + label->resizeToNaturalWidth(width - position.x()); + label->moveToLeft(position.x(), position.y(), width); + const auto top = (raw->height() - userpic->height()) / 2; + userpic->moveToLeft(0, top, width); + }, label->lifetime()); + + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setTextColorOverride(st::windowActiveTextFg->c); + + raw->setClickedCallback([=] { + controller->show(PrepareShortInfoBox(peer, controller)); + }); + + return result; +} + +void AddTableRow( + not_null table, + rpl::producer label, + object_ptr value, + style::margins valueMargins) { + table->addRow( + object_ptr( + table, + std::move(label), + st::giveawayGiftCodeLabel), + std::move(value), + st::giveawayGiftCodeLabelMargin, + valueMargins); +} + +void AddTableRow( + not_null table, + rpl::producer label, + rpl::producer value) { + AddTableRow( + table, + std::move(label), + object_ptr( + table, + std::move(value), + st::giveawayGiftCodeValue), + st::giveawayGiftCodeValueMargin); +} + +void AddTableRow( + not_null table, + rpl::producer label, + not_null controller, + PeerId id) { + if (!id) { + return; + } + AddTableRow( + table, + std::move(label), + MakePeerTableValue(table, controller, id), + st::giveawayGiftCodePeerMargin); +} + } // namespace GiftPremiumValidator::GiftPremiumValidator( @@ -263,3 +439,148 @@ void GiftPremiumValidator::showBox(not_null user) { _requestId = 0; }).send(); } + +void GiftCodeBox( + not_null box, + not_null controller, + const QString &slug) { + struct State { + rpl::variable data; + rpl::variable used; + }; + const auto session = &controller->session(); + const auto state = box->lifetime().make_state(State{}); + state->data = session->api().premium().giftCodeValue(slug); + state->used = state->data.value( + ) | rpl::map([=](const Api::GiftCode &data) { + return data.used; + }); + + box->setWidth(st::boxWideWidth); + box->setStyle(st::giveawayGiftCodeBox); + box->setNoContentMargin(true); + + const auto bar = box->setPinnedToTopContent( + object_ptr( + box, + st::giveawayGiftCodeCover, + nullptr, + rpl::conditional( + state->used.value(), + tr::lng_gift_link_used_title(), + tr::lng_gift_link_title()), + rpl::conditional( + state->used.value(), + tr::lng_gift_link_used_about(Ui::Text::RichLangValue), + tr::lng_gift_link_about(Ui::Text::RichLangValue)), + true)); + + const auto max = st::giveawayGiftCodeTopHeight; + bar->setMaximumHeight(max); + bar->setMinimumHeight(st::infoLayerTopBarHeight); + + bar->resize(bar->width(), bar->maximumHeight()); + + const auto link = MakeGiftCodeLink(&controller->session(), slug); + box->addRow( + MakeLinkLabel( + box, + rpl::single(link.text), + rpl::single(link.link), + box->uiShow()), + st::giveawayGiftCodeLinkMargin); + + auto table = box->addRow( + object_ptr( + box, + st::giveawayGiftCodeTable), + st::giveawayGiftCodeTableMargin); + const auto current = state->data.current(); + AddTableRow( + table, + tr::lng_gift_link_label_from(), + controller, + current.from); + AddTableRow( + table, + tr::lng_gift_link_label_to(), + controller, + current.to); + AddTableRow( + table, + tr::lng_gift_link_label_gift(), + tr::lng_gift_link_gift_premium( + lt_duration, + DurationValue(current.months))); + AddTableRow( + table, + tr::lng_gift_link_label_reason(), + tr::lng_gift_link_reason_giveaway()); + AddTableRow( + table, + tr::lng_gift_link_label_date(), + rpl::single(langDateTime(base::unixtime::parse(current.date)))); + + auto shareLink = tr::lng_gift_link_also_send_link( + ) | rpl::map([](const QString &text) { + return Ui::Text::Link(text); + }); + auto richDate = [](const Api::GiftCode &data) { + return TextWithEntities{ + langDateTime(base::unixtime::parse(data.used)), + }; + }; + const auto footer = box->addRow( + object_ptr( + box, + rpl::conditional( + state->used.value(), + tr::lng_gift_link_used_footer( + lt_date, + state->data.value() | rpl::map(richDate), + Ui::Text::WithEntities), + tr::lng_gift_link_also_send( + lt_link, + std::move(shareLink), + Ui::Text::WithEntities)), + st::giveawayGiftCodeFooter), + st::giveawayGiftCodeFooterMargin); + footer->setClickHandlerFilter([=](const auto &...) { + box->uiShow()->showToast(u"Sharing..."_q); + return false; + }); + + const auto close = Ui::CreateChild( + box.get(), + st::boxTitleClose); + close->setClickedCallback([=] { + box->closeBox(); + }); + box->widthValue( + ) | rpl::start_with_next([=](int width) { + close->moveToRight(0, 0); + }, box->lifetime()); + + const auto button = box->addButton(rpl::conditional( + state->used.value(), + tr::lng_box_ok(), + tr::lng_gift_link_use() + ), [=] { + if (state->used.current()) { + box->closeBox(); + } else { + auto copy = state->data.current(); + copy.used = base::unixtime::now(); + state->data = std::move(copy); + } + }); + const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding; + const auto buttonWidth = st::boxWideWidth + - buttonPadding.left() + - buttonPadding.right(); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); +} diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index cc11b23886..9fe4c972e5 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -11,6 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class UserData; +namespace Api { +struct GiftCode; +} // namespace Api + +namespace Ui { +class GenericBox; +} // namespace Ui + namespace Window { class SessionController; } // namespace Window @@ -29,3 +37,8 @@ private: mtpRequestId _requestId = 0; }; + +void GiftCodeBox( + not_null box, + not_null controller, + const QString &slug); diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 3defcfb6ae..799a192ec4 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "api/api_chat_filters.h" #include "api/api_chat_invite.h" +#include "api/api_premium.h" #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "lang/lang_cloud_manager.h" @@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "boxes/share_box.h" #include "boxes/connection_box.h" +#include "boxes/gift_premium_box.h" #include "boxes/sticker_set_box.h" #include "boxes/sessions_box.h" #include "boxes/language_box.h" @@ -348,6 +350,21 @@ bool ResolveUsernameOrPhone( const auto domainParam = params.value(u"domain"_q); const auto appnameParam = params.value(u"appname"_q); + if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) { + const auto done = [=](Api::GiftCode code) { + if (!code) { + controller->showToast(u"Gift code link expired"_q); + } else { + controller->show( + Box(GiftCodeBox, controller, appnameParam)); + } + }; + controller->session().api().premium().checkGiftCode( + appnameParam, + crl::guard(controller, done)); + return true; + } + // Fix t.me/s/username links. const auto webChannelPreviewLink = (domainParam == u"s"_q) && !appnameParam.isEmpty(); @@ -1078,7 +1095,7 @@ QString TryConvertUrlToLocal(QString url) { "(" "/?\\?|" "/?$|" - "/[a-zA-Z0-9\\.\\_]+/?(\\?|$)|" + "/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|" "/\\d+/?(\\?|$)|" "/s/\\d+/?(\\?|$)|" "/\\d+/\\d+/?(\\?|$)" @@ -1103,7 +1120,7 @@ QString TryConvertUrlToLocal(QString url) { added = u"&post="_q + postMatch->captured(1); } else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { added = u"&story="_q + storyMatch->captured(1); - } else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { + } else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { added = u"&appname="_q + appNameMatch->captured(1); } return base + added + (params.isEmpty() ? QString() : '&' + params); diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 0bfb715a8a..02d1f75e16 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -60,8 +60,8 @@ const style::InfoTopBar &TopBarStyle(Wrap wrap) { [[nodiscard]] bool HasCustomTopBar(not_null controller) { const auto section = controller->section(); - return (section.type() == Section::Type::Settings - && (section.settingsType() == ::Settings::PremiumId())); + return (section.type() == Section::Type::Settings) + && section.settingsType()->hasCustomTopBar(); } } // namespace @@ -788,8 +788,8 @@ void WrapWidget::showNewContent( newController->takeStepData(_controller.get()); } auto newContent = object_ptr(nullptr); - const auto enableBackButton = hasBackButton(); - const auto createInAdvance = needAnimation || enableBackButton; + const auto withBackButton = willHaveBackButton(params); + const auto createInAdvance = needAnimation || withBackButton; if (createInAdvance) { newContent = createContent(memento, newController.get()); } @@ -823,7 +823,7 @@ void WrapWidget::showNewContent( _historyStack.clear(); } - if (enableBackButton) { + if (withBackButton) { newContent->enableBackButton(); } @@ -969,6 +969,17 @@ bool WrapWidget::hasBackButton() const { return (wrap() == Wrap::Narrow || hasStackHistory()); } +bool WrapWidget::willHaveBackButton( + const Window::SectionShow ¶ms) const { + using Way = Window::SectionShow::Way; + const auto willSaveToStack = (_content != nullptr) + && (params.way == Way::Forward); + const auto willClearStack = (params.way == Way::ClearStack); + const auto willHaveStack = !willClearStack + && (hasStackHistory() || willSaveToStack); + return (wrap() == Wrap::Narrow) || willHaveStack; +} + WrapWidget::~WrapWidget() = default; } // namespace Info diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index 5445185e1c..d16108114d 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -176,6 +176,8 @@ private: void setupShortcuts(); [[nodiscard]] bool hasBackButton() const; + [[nodiscard]] bool willHaveBackButton( + const Window::SectionShow ¶ms) const; not_null topWidget() const; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 84cb8960ac..3ea1b81dd1 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -424,8 +424,6 @@ notifyPreviewBottomSkip: 9px; settingsPremiumButtonPadding: margins(11px, 11px, 11px, 3px); settingsPremiumTopBarBackIcon: icon {{ "info/info_back", premiumButtonFg }}; settingsPremiumTopBarBackIconOver: icon {{ "info/info_back", premiumButtonFg }}; -settingsPremiumStarSize: size(84px, 81px); -settingsPremiumStarTopSkip: 37px; settingsPremiumTopBarBack: IconButton(infoTopBarBack) { icon: settingsPremiumTopBarBackIcon; iconOver: settingsPremiumTopBarBackIconOver; @@ -467,21 +465,6 @@ settingsPremiumPreviewIconTitlePadding: margins(62px, 13px, 24px, 1px); settingsPremiumPreviewIconAboutPadding: margins(62px, 0px, 24px, 0px); settingsPremiumPreviewIconPosition: point(20px, 7px); -settingsPremiumTitlePadding: margins(0px, 18px, 0px, 11px); -settingsPremiumAboutTextStyle: TextStyle(defaultTextStyle) { - font: font(12px); - linkUnderline: kLinkUnderlineAlways; - lineHeight: 18px; -} -settingsPremiumAbout: FlatLabel(defaultFlatLabel) { - style: settingsPremiumAboutTextStyle; - palette: TextPalette(defaultTextPalette) { - linkFg: premiumButtonFg; - } - align: align(top); - textFg: premiumButtonFg; - minWidth: 190px; -} settingsPremiumArrowShift: point(-5px, -1px); settingsPremiumArrow: icon{{ "settings/premium/arrow", menuIconFg }}; settingsPremiumArrowOver: icon{{ "settings/premium/arrow", menuIconFgOver }}; @@ -499,12 +482,6 @@ settingsPremiumUserTitle: FlatLabel(boxTitle) { maxHeight: 0px; align: align(top); } -settingsPremiumUserAbout: FlatLabel(boxDividerLabel) { - style: settingsPremiumAboutTextStyle; - minWidth: 315px; - maxHeight: 0px; - align: align(top); -} settingsPremiumLock: icon{{ "emoji/premium_lock", windowActiveTextFg, point(0px, 1px) }}; settingsPremiumLockSkip: 3px; diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index 0a7a48384b..c0973b3d63 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -56,6 +56,9 @@ struct AbstractSectionFactory { [[nodiscard]] virtual object_ptr create( not_null parent, not_null controller) const = 0; + [[nodiscard]] virtual bool hasCustomTopBar() const { + return false; + } virtual ~AbstractSectionFactory() = default; }; diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index ba0acc981e..3dd3120748 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -518,10 +518,10 @@ TopBarUser::TopBarUser( not_null controller, not_null peer, rpl::producer<> showFinished) -: TopBarAbstract(parent) +: TopBarAbstract(parent, st::userPremiumCover) , _content(this) , _title(_content, st::settingsPremiumUserTitle) -, _about(_content, st::settingsPremiumUserAbout) +, _about(_content, st::userPremiumCover.about) , _ministars(_content) , _smallTop({ .widget = object_ptr(this), @@ -1173,6 +1173,7 @@ QPointer Premium::createPinnedToTop( }; return Ui::CreateChild( parent.get(), + st::defaultPremiumCover, clickContextOther, std::move(title), std::move(about)); @@ -1371,6 +1372,24 @@ QPointer Premium::createPinnedToBottom( } // namespace +template <> +struct SectionFactory : AbstractSectionFactory { + object_ptr create( + not_null parent, + not_null controller + ) const final override { + return object_ptr(parent, controller); + } + bool hasCustomTopBar() const final override { + return true; + } + + [[nodiscard]] static const std::shared_ptr &Instance() { + static const auto result = std::make_shared(); + return result; + } +}; + Type PremiumId() { return Premium::Id(); } diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 55e89602ad..b3442e8a70 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -26,6 +26,43 @@ PremiumBubble { tailSize: size; font: font; } +PremiumCover { + starSize: size; + starTopSkip: pixels; + titlePadding: margins; + titleFont: font; + about: FlatLabel; +} + +premiumAboutTextStyle: TextStyle(defaultTextStyle) { + font: font(12px); + linkUnderline: kLinkUnderlineAlways; + lineHeight: 18px; +} +defaultPremiumCover: PremiumCover { + starSize: size(84px, 81px); + starTopSkip: 37px; + titlePadding: margins(0px, 18px, 0px, 11px); + titleFont: boxTitleFont; + about: FlatLabel(defaultFlatLabel) { + style: premiumAboutTextStyle; + palette: TextPalette(defaultTextPalette) { + linkFg: premiumButtonFg; + } + align: align(top); + textFg: premiumButtonFg; + minWidth: 190px; + } +} +userPremiumCoverAbout: FlatLabel(boxDividerLabel) { + style: premiumAboutTextStyle; + minWidth: 315px; + maxHeight: 0px; + align: align(top); +} +userPremiumCover: PremiumCover(defaultPremiumCover) { + about: userPremiumCoverAbout; +} defaultPremiumBoxLabel: FlatLabel(defaultFlatLabel) { minWidth: 220px; @@ -239,3 +276,69 @@ boostReplaceIconSkip: 3px; boostReplaceIconOutline: 2px; boostReplaceIconAdd: point(4px, 2px); boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }}; + +giveawayGiftCodeTopHeight: 195px; +giveawayGiftCodeLink: FlatLabel(defaultFlatLabel) { + margin: margins(10px, 12px, 10px, 8px); + textFg: menuIconColor; + maxHeight: 24px; +} +giveawayGiftCodeLinkCopy: icon{{ "menu/copy", menuIconColor }}; +giveawayGiftCodeLinkHeight: 42px; +giveawayGiftCodeLinkCopyWidth: 40px; +giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px); + +giveawayGiftCodeTable: Table(defaultTable) { + labelMinWidth: 91px; +} +giveawayGiftCodeTableMargin: margins(24px, 4px, 24px, 4px); +giveawayGiftCodeLabel: FlatLabel(defaultFlatLabel) { + textFg: menuIconColor; + maxHeight: 24px; + style: TextStyle(semiboldTextStyle) { + font: font(12px semibold); + } +} +giveawayGiftCodeLabelMargin: margins(13px, 10px, 13px, 10px); +giveawayGiftCodeValue: FlatLabel(defaultFlatLabel) { + maxHeight: 24px; + style: TextStyle(defaultTextStyle) { + font: font(12px); + linkUnderline: kLinkUnderlineNever; + } +} +giveawayGiftCodeValueMargin: margins(13px, 9px, 13px, 9px); +giveawayGiftCodePeerMargin: margins(11px, 6px, 11px, 4px); +giveawayGiftCodeUserpic: UserpicButton(defaultUserpicButton) { + size: size(24px, 24px); + photoSize: 24px; + photoPosition: point(-1px, -1px); +} +giveawayGiftCodeNamePosition: point(32px, 4px); +giveawayGiftCodeCover: PremiumCover(userPremiumCover) { + starSize: size(92px, 90px); + starTopSkip: 20px; + titlePadding: margins(0px, 15px, 0px, 17px); + titleFont: font(15px semibold); + about: FlatLabel(userPremiumCoverAbout) { + textFg: windowBoldFg; + style: TextStyle(premiumAboutTextStyle) { + lineHeight: 17px; + } + } +} +giveawayGiftCodeFooter: FlatLabel(defaultFlatLabel) { + align: align(top); + textFg: windowBoldFg; +} +giveawayGiftCodeFooterMargin: margins(0px, 9px, 0px, 4px); +giveawayGiftCodeBox: Box(defaultBox) { + buttonPadding: margins(22px, 11px, 22px, 22px); + buttonHeight: 42px; + button: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + font: font(13px semibold); + } + shadowIgnoreTopSkip: true; +} diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp index 309db7a6a7..951eb8f219 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "styles/style_layers.h" #include "styles/style_settings.h" +#include "styles/style_premium.h" #include @@ -90,6 +91,13 @@ QImage GenerateStarForLightTopBar(QRectF rect) { return frame; } +TopBarAbstract::TopBarAbstract( + QWidget *parent, + const style::PremiumCover &st) +: RpWidget(parent) +, _st(st) { +} + void TopBarAbstract::setRoundEdges(bool value) { _roundEdges = value; update(); @@ -122,11 +130,11 @@ void TopBarAbstract::paintEdges(QPainter &p) const { QRectF TopBarAbstract::starRect( float64 topProgress, float64 sizeProgress) const { - const auto starSize = st::settingsPremiumStarSize * sizeProgress; + const auto starSize = _st.starSize * sizeProgress; return QRectF( QPointF( (width() - starSize.width()) / 2, - st::settingsPremiumStarTopSkip * topProgress), + _st.starTopSkip * topProgress), starSize); }; @@ -143,18 +151,16 @@ void TopBarAbstract::computeIsDark() { TopBar::TopBar( not_null parent, + const style::PremiumCover &st, Fn clickContextOther, rpl::producer title, rpl::producer about, bool light) -: TopBarAbstract(parent) +: TopBarAbstract(parent, st) , _light(light) -, _titleFont(st::boxTitle.style.font) -, _titlePadding(st::settingsPremiumTitlePadding) -, _about( - this, - std::move(about), - _light ? st::settingsPremiumUserAbout : st::settingsPremiumAbout) +, _titleFont(st.titleFont) +, _titlePadding(st.titlePadding) +, _about(this, std::move(about), st.about) , _ministars(this) { std::move( title @@ -219,7 +225,7 @@ void TopBar::setTextPosition(int x, int y) { rpl::producer TopBar::additionalHeight() const { return _about->heightValue( - ) | rpl::map([l = st::settingsPremiumAbout.style.lineHeight](int height) { + ) | rpl::map([l = st().about.style.lineHeight](int height) { return std::max(height - l * 2, 0); }); } diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.h b/Telegram/SourceFiles/ui/effects/premium_top_bar.h index 0cef38dc58..c0749df00a 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.h +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.h @@ -11,6 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/effects/premium_stars_colored.h" +namespace style { +struct PremiumCover; +} // namespace style + +namespace st { +extern const style::PremiumCover &defaultPremiumCover; +} // namespace st + namespace Ui { class FlatLabel; } // namespace Ui @@ -23,7 +31,9 @@ namespace Ui::Premium { class TopBarAbstract : public RpWidget { public: - using RpWidget::RpWidget; + TopBarAbstract( + QWidget *parent = nullptr, + const style::PremiumCover &st = st::defaultPremiumCover); void setRoundEdges(bool value); @@ -32,6 +42,10 @@ public: [[nodiscard]] virtual rpl::producer additionalHeight() const = 0; + [[nodiscard]] const style::PremiumCover &st() const { + return _st; + } + protected: void paintEdges(QPainter &p, const QBrush &brush) const; void paintEdges(QPainter &p) const; @@ -44,6 +58,7 @@ protected: void computeIsDark(); private: + const style::PremiumCover &_st; bool _roundEdges = true; bool _isDark = false; @@ -53,6 +68,7 @@ class TopBar final : public TopBarAbstract { public: TopBar( not_null parent, + const style::PremiumCover &st, Fn clickContextOther, rpl::producer title, rpl::producer about, From 926aae68477d13e794a95b08306c4c3d6d2f1f5f Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 3 Oct 2023 17:52:33 +0400 Subject: [PATCH 20/99] Update API scheme on layer 166. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 8 +- Telegram/SourceFiles/apiwrap.cpp | 22 +- Telegram/SourceFiles/data/data_drafts.cpp | 13 +- Telegram/SourceFiles/data/data_histories.cpp | 7 +- .../SourceFiles/data/data_media_types.cpp | 67 ++++ Telegram/SourceFiles/data/data_media_types.h | 39 +++ .../export/data/export_data_types.cpp | 56 +++- .../export/data/export_data_types.h | 18 +- .../export/output/export_output_html.cpp | 69 +++- .../export/output/export_output_json.cpp | 33 +- Telegram/SourceFiles/history/history.cpp | 4 +- Telegram/SourceFiles/history/history_item.cpp | 97 ++++-- .../history/history_item_helpers.cpp | 14 +- .../view/media/history_view_giveaway.cpp | 298 ++++++++++++++++++ .../view/media/history_view_giveaway.h | 102 ++++++ Telegram/SourceFiles/mtproto/scheme/api.tl | 20 +- Telegram/SourceFiles/ui/chat/chat.style | 7 + 18 files changed, 801 insertions(+), 75 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp create mode 100644 Telegram/SourceFiles/history/view/media/history_view_giveaway.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e2078f7e62..7b0d52e5f3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -677,6 +677,8 @@ PRIVATE history/view/media/history_view_game.h history/view/media/history_view_gif.cpp history/view/media/history_view_gif.h + history/view/media/history_view_giveaway.cpp + history/view/media/history_view_giveaway.h history/view/media/history_view_invoice.cpp history/view/media/history_view_invoice.h history/view/media/history_view_large_emoji.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5a7c0a7a13..1572839ca6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2076,13 +2076,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prize_title" = "Congratulations!"; "lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; -"lng_prize_duration" = "Your prize is **Telegram Premium** subscription for {duration}."; +"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription for {duration}."; +"lng_prize_gift_about" = "You've received a gift from {channel}."; +"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription for {duration}."; "lng_prize_open" = "Open Gift Link"; "lng_prizes_title#one" = "Giveaway Prize"; "lng_prizes_title#other" = "Giveaway Prizes"; -"lng_prizes_about#one" = "{count} Telegram Premium Subscription for {duration}."; -"lng_prizes_about#other" = "{count} Telegram Premium Subscriptions for {duration}."; +"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription for {duration}."; +"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions for {duration}."; "lng_prizes_participants" = "Participants"; "lng_prizes_participants_all#one" = "All subscribers of the channel:"; "lng_prizes_participants_all#other" = "All subscribers of the channels:"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 31b478af98..affdd55244 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2134,15 +2134,16 @@ void ApiWrap::saveDraftsToCloud() { } auto flags = MTPmessages_SaveDraft::Flags(0); + auto replyFlags = MTPDinputReplyToMessage::Flags(0); auto &textWithTags = cloudDraft->textWithTags; if (cloudDraft->previewState != Data::PreviewState::Allowed) { flags |= MTPmessages_SaveDraft::Flag::f_no_webpage; } - if (cloudDraft->msgId) { - flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id; - } - if (cloudDraft->topicRootId) { - flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id; + if (cloudDraft->msgId || cloudDraft->topicRootId) { + flags |= MTPmessages_SaveDraft::Flag::f_reply_to; + if (cloudDraft->topicRootId) { + replyFlags |= MTPDinputReplyToMessage::Flag::f_top_msg_id; + } } if (!textWithTags.tags.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_entities; @@ -2155,8 +2156,15 @@ void ApiWrap::saveDraftsToCloud() { history->startSavingCloudDraft(topicRootId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), - MTP_int(cloudDraft->msgId), - MTP_int(cloudDraft->topicRootId), + MTP_inputReplyToMessage( + MTP_flags(replyFlags), + MTP_int(cloudDraft->msgId + ? cloudDraft->msgId + : cloudDraft->topicRootId), + MTP_int(cloudDraft->topicRootId), + MTPInputPeer(), // reply_to_peer_id + MTPstring(), // quote_text + MTPVector()), // quote_entities history->peer->input, MTP_string(textWithTags.text), entities diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 00c9ba02d6..f0b937ca89 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -64,7 +64,18 @@ void ApplyPeerCloudDraft( session, draft.ventities().value_or_empty())) }; - const auto replyTo = draft.vreply_to_msg_id().value_or_empty(); + auto replyTo = MsgId(); + if (const auto reply = draft.vreply_to()) { + reply->match([&](const MTPDmessageReplyHeader &data) { + if (!data.vreply_to_peer_id() + || (peerFromMTP(*data.vreply_to_peer_id()) == peerId)) { + replyTo = data.vreply_to_msg_id().value_or_empty(); + } else { + // #TODO replies + } + }, [&](const MTPDmessageReplyStoryHeader &data) { + }); + } auto cloudDraft = std::make_unique( textWithTags, replyTo, diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 4ca3cb6353..7443aab62f 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -45,12 +45,15 @@ MTPInputReplyTo ReplyToForMTP( } } else if (replyTo.msgId || replyTo.topicRootId) { using Flag = MTPDinputReplyToMessage::Flag; - return MTP_inputReplyToMessage( + return MTP_inputReplyToMessage( // #TODO replies (replyTo.topicRootId ? MTP_flags(Flag::f_top_msg_id) : MTP_flags(0)), MTP_int(replyTo.msgId ? replyTo.msgId : replyTo.topicRootId), - MTP_int(replyTo.topicRootId)); + MTP_int(replyTo.topicRootId), + MTPInputPeer(), // reply_to_peer_id + MTPstring(), // quote_text + MTPVector()); // quote_entities } return MTPInputReplyTo(); } diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 8d87fb77a6..ca947219d6 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_contact.h" #include "history/view/media/history_view_location.h" #include "history/view/media/history_view_game.h" +#include "history/view/media/history_view_giveaway.h" #include "history/view/media/history_view_invoice.h" #include "history/view/media/history_view_call.h" #include "history/view/media/history_view_web_page.h" @@ -361,6 +362,22 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { return result; } +Giveaway ComputeGiveawayData( + not_null item, + const MTPDmessageMediaGiveaway &data) { + auto result = Giveaway{ + .untilDate = data.vuntil_date().v, + .quantity = data.vquantity().v, + .months = data.vmonths().v, + }; + result.channels.reserve(data.vchannels().v.size()); + const auto owner = &item->history()->owner(); + for (const auto &id : data.vchannels().v) { + result.channels.push_back(owner->channel(ChannelId(id))); + } + return result; +} + Media::Media(not_null parent) : _parent(parent) { } @@ -420,6 +437,10 @@ bool Media::storyMention() const { return false; } +const Giveaway *Media::giveaway() const { + return nullptr; +} + bool Media::uploading() const { return false; } @@ -2144,4 +2165,50 @@ std::unique_ptr MediaStory::createView( } } +MediaGiveaway::MediaGiveaway( + not_null parent, + const Giveaway &data) +: Media(parent) +, _giveaway(data) { +} + +std::unique_ptr MediaGiveaway::clone(not_null parent) { + return std::make_unique(parent, _giveaway); +} + +const Giveaway *MediaGiveaway::giveaway() const { + return &_giveaway; +} + +TextWithEntities MediaGiveaway::notificationText() const { + return { + .text = tr::lng_prizes_title(tr::now, lt_count, _giveaway.quantity), + }; +} + +QString MediaGiveaway::pinnedTextSubstring() const { + return QString::fromUtf8("\xC2\xAB") + + notificationText().text + + QString::fromUtf8("\xC2\xBB"); +} + +TextForMimeData MediaGiveaway::clipboardText() const { + return TextForMimeData(); +} + +bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) { + return true; +} + +bool MediaGiveaway::updateSentMedia(const MTPMessageMedia &media) { + return true; +} + +std::unique_ptr MediaGiveaway::createView( + not_null message, + not_null realParent, + HistoryView::Element *replacing) { + return std::make_unique(message, &_giveaway); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 7649a37edd..0ab5f51a80 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -90,6 +90,14 @@ struct Invoice { bool isTest = false; }; +struct Giveaway { + std::vector> channels; + TimeId untilDate = 0; + int quantity = 0; + int months = 0; + bool all = false; +}; + class Media { public: Media(not_null parent); @@ -116,6 +124,7 @@ public: virtual FullStoryId storyId() const; virtual bool storyExpired(bool revalidate = false); virtual bool storyMention() const; + virtual const Giveaway *giveaway() const; virtual bool uploading() const; virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; @@ -605,6 +614,32 @@ private: }; +class MediaGiveaway final : public Media { +public: + MediaGiveaway( + not_null parent, + const Giveaway &data); + + std::unique_ptr clone(not_null parent) override; + + const Giveaway *giveaway() const override; + + TextWithEntities notificationText() const override; + QString pinnedTextSubstring() const override; + TextForMimeData clipboardText() const override; + + bool updateInlineResultMedia(const MTPMessageMedia &media) override; + bool updateSentMedia(const MTPMessageMedia &media) override; + std::unique_ptr createView( + not_null message, + not_null realParent, + HistoryView::Element *replacing = nullptr) override; + +private: + Giveaway _giveaway; + +}; + [[nodiscard]] TextForMimeData WithCaptionClipboardText( const QString &attachType, TextForMimeData &&caption); @@ -615,4 +650,8 @@ private: [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); +[[nodiscard]] Giveaway ComputeGiveawayData( + not_null item, + const MTPDmessageMediaGiveaway &data); + } // namespace Data diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 723eeae9b8..6420c37592 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -576,6 +576,18 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) { return result; } +Giveaway ParseGiveaway(const MTPDmessageMediaGiveaway &data) { + auto result = Giveaway{ + .untilDate = data.vuntil_date().v, + .quantity = data.vquantity().v, + .months = data.vmonths().v, + }; + for (const auto &id : data.vchannels().v) { + result.channels.push_back(ChannelId(id)); + } + return result; +} + UserpicsSlice ParseUserpicsSlice( const MTPVector &data, int baseIndex) { @@ -1057,7 +1069,9 @@ Media ParseMedia( }, [](const MTPDmessageMediaDice &data) { // #TODO dice }, [](const MTPDmessageMediaStory &data) { - // #TODO stories export + // #TODO export stories + }, [&](const MTPDmessageMediaGiveaway &data) { + result.content = ParseGiveaway(data); }, [](const MTPDmessageMediaEmpty &data) {}); return result; } @@ -1298,6 +1312,14 @@ ServiceAction ParseServiceAction( content.peerId = ParsePeerId(data.vpeer()); content.buttonId = data.vbutton_id().v; result.content = content; + }, [&](const MTPDmessageActionGiftCode &data) { + auto content = ActionGiftCode(); + content.boostPeerId = data.vboost_peer() + ? peerFromMTP(*data.vboost_peer()) + : PeerId(); + content.viaGiveaway = data.is_via_giveaway(); + content.months = data.vmonths().v; + content.code = data.vslug().v; }, [](const MTPDmessageActionEmpty &data) {}); return result; } @@ -1358,15 +1380,19 @@ Message ParseMessage( } if (const auto replyTo = data.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { - result.replyToMsgId = data.vreply_to_msg_id().v; - result.replyToPeerId = data.vreply_to_peer_id() - ? ParsePeerId(*data.vreply_to_peer_id()) - : 0; - if (result.replyToPeerId == result.peerId) { - result.replyToPeerId = 0; + if (const auto replyToMsg = data.vreply_to_msg_id()) { + result.replyToMsgId = replyToMsg->v; + result.replyToPeerId = data.vreply_to_peer_id() + ? ParsePeerId(*data.vreply_to_peer_id()) + : 0; + if (result.replyToPeerId == result.peerId) { + result.replyToPeerId = 0; + } + } else { + // #TODO export replies } }, [&](const MTPDmessageReplyStoryHeader &data) { - // #TODO stories export + // #TODO export stories }); } } @@ -1410,12 +1436,16 @@ Message ParseMessage( } if (const auto replyTo = data.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { - result.replyToMsgId = data.vreply_to_msg_id().v; - result.replyToPeerId = data.vreply_to_peer_id() - ? ParsePeerId(*data.vreply_to_peer_id()) - : PeerId(0); + if (const auto replyToMsg = data.vreply_to_msg_id()) { + result.replyToMsgId = replyToMsg->v; + result.replyToPeerId = data.vreply_to_peer_id() + ? ParsePeerId(*data.vreply_to_peer_id()) + : PeerId(0); + } else { + // #TODO export replies + } }, [&](const MTPDmessageReplyStoryHeader &data) { - // #TODO stories export + // #TODO export stories }); } if (const auto viaBotId = data.vvia_bot_id()) { diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 4635ea269c..a4fd372f40 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -196,6 +196,13 @@ struct Poll { bool closed = false; }; +struct Giveaway { + std::vector channels; + TimeId untilDate = 0; + int quantity = 0; + int months = 0; +}; + struct UserpicsSlice { std::vector list; }; @@ -325,6 +332,7 @@ struct Media { Game, Invoice, Poll, + Giveaway, UnsupportedMedia> content; TimeId ttl = 0; @@ -527,6 +535,13 @@ struct ActionSetChatWallPaper { struct ActionSetSameChatWallPaper { }; +struct ActionGiftCode { + QByteArray code; + PeerId boostPeerId = 0; + int months = 0; + bool viaGiveaway = false; +}; + struct ActionRequestedPeer { PeerId peerId = 0; int buttonId = 0; @@ -570,7 +585,8 @@ struct ServiceAction { ActionSuggestProfilePhoto, ActionRequestedPeer, ActionSetChatWallPaper, - ActionSetSameChatWallPaper> content; + ActionSetSameChatWallPaper, + ActionGiftCode> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index d4581c7371..54dd8e33fd 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -611,6 +611,9 @@ private: const Data::Photo &data, const QString &basePath); [[nodiscard]] QByteArray pushPoll(const Data::Poll &data); + [[nodiscard]] QByteArray pushGiveaway( + const PeersMap &peers, + const Data::Giveaway &data); File _file; QByteArray _composedStart; @@ -1276,6 +1279,16 @@ auto HtmlWriter::Wrap::pushMessage( + " set " + wrapReplyToLink("the same background") + " for this chat"; + }, [&](const ActionGiftCode &data) { + return data.viaGiveaway + ? ("You won a Telegram Premium for " + + NumberToString(data.months) + + (data.months > 1 ? " months" : "month") + + " prize in a giveaway organized by a channel.") + : ("You've received a Telegram Premium for " + + NumberToString(data.months) + + (data.months > 1 ? " months" : "month") + + " gift from a channel."); }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { @@ -1459,8 +1472,9 @@ QByteArray HtmlWriter::Wrap::pushMedia( if (!data.classes.isEmpty()) { return pushGenericMedia(data); } + using namespace Data; const auto &content = message.media.content; - if (const auto document = std::get_if(&content)) { + if (const auto document = std::get_if(&content)) { Assert(!message.media.ttl); if (document->isSticker) { return pushStickerMedia(*document, basePath); @@ -1470,11 +1484,13 @@ QByteArray HtmlWriter::Wrap::pushMedia( return pushVideoFileMedia(*document, basePath); } Unexpected("Non generic document in HtmlWriter::Wrap::pushMedia."); - } else if (const auto photo = std::get_if(&content)) { + } else if (const auto photo = std::get_if(&content)) { Assert(!message.media.ttl); return pushPhotoMedia(*photo, basePath); - } else if (const auto poll = std::get_if(&content)) { + } else if (const auto poll = std::get_if(&content)) { return pushPoll(*poll); + } else if (const auto giveaway = std::get_if(&content)) { + return pushGiveaway(peers, *giveaway); } Assert(v::is_null(content)); return QByteArray(); @@ -1796,6 +1812,52 @@ QByteArray HtmlWriter::Wrap::pushPoll(const Data::Poll &data) { return result; } +QByteArray HtmlWriter::Wrap::pushGiveaway( + const PeersMap &peers, + const Data::Giveaway &data) { + auto result = pushDiv("media_wrap clearfix"); + result.append(pushDiv("media_giveaway")); + + result.append(pushDiv("section_title bold")); + result.append(SerializeString("Giveaway Prizes")); + result.append(popTag()); + result.append(pushDiv("section_body")); + result.append("" + + Data::NumberToString(data.quantity) + + " " + + SerializeString((data.quantity > 1) + ? "Telegram Premium Subscriptions" + : "Telegram Premium Subscription") + + " for " + Data::NumberToString(data.months) + " " + + (data.months > 1 ? "months." : "month.")); + result.append(popTag()); + + result.append(pushDiv("section_title bold")); + result.append(SerializeString("Participants")); + result.append(popTag()); + result.append(pushDiv("section_body")); + auto channels = QByteArrayList(); + for (const auto &channel : data.channels) { + channels.append("" + peers.wrapPeerName(channel) + ""); + } + result.append(SerializeString((channels.size() > 1) + ? "All subscribers of those channels: " + : "All subscribers of the channel: ") + + channels.join(", ")); + result.append(popTag()); + + result.append(pushDiv("section_title bold")); + result.append(SerializeString("Winners Selection Date")); + result.append(popTag()); + result.append(pushDiv("section_body")); + result.append(Data::FormatDateTime(data.untilDate)); + result.append(popTag()); + + result.append(popTag()); + result.append(popTag()); + return result; +} + MediaData HtmlWriter::Wrap::prepareMediaData( const Data::Message &message, const QString &basePath, @@ -1954,6 +2016,7 @@ MediaData HtmlWriter::Wrap::prepareMediaData( result.description = data.description; result.status = Data::FormatMoneyAmount(data.amount, data.currency); }, [](const Poll &data) { + }, [](const Giveaway &data) { }, [](const UnsupportedMedia &data) { Unexpected("Unsupported message."); }, [](v::null_t) {}); diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index a232b2788a..8655eeeec5 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -287,11 +287,12 @@ QByteArray SerializeMessage( } const auto push = [&](const QByteArray &key, const auto &value) { - if constexpr (std::is_arithmetic_v>) { + using V = std::decay_t; + if constexpr (std::is_same_v) { + pushBare(key, value ? "true" : "false"); + } else if constexpr (std::is_arithmetic_v) { pushBare(key, Data::NumberToString(value)); - } else if constexpr (std::is_same_v< - std::decay_t, - PeerId>) { + } else if constexpr (std::is_same_v) { if (const auto chat = peerToChat(value)) { pushBare( key, @@ -592,6 +593,14 @@ QByteArray SerializeMessage( pushAction("requested_peer"); push("button_id", data.buttonId); push("peer_id", data.peerId.value); + }, [&](const ActionGiftCode &data) { + pushAction("gift_code_prize"); + push("gift_code", data.code); + if (data.boostPeerId) { + push("boost_peer_id", data.boostPeerId); + } + push("months", data.months); + push("via_giveaway", data.viaGiveaway); }, [&](const ActionSetChatWallPaper &data) { pushActor(); pushAction("set_chat_wallpaper"); @@ -738,6 +747,22 @@ QByteArray SerializeMessage( { "total_voters", NumberToString(data.totalVotes) }, { "answers", serialized } })); + }, [&](const Giveaway &data) { + context.nesting.push_back(Context::kObject); + const auto channels = ranges::views::all( + data.channels + ) | ranges::views::transform([&](ChannelId id) { + return NumberToString(id.bare); + }) | ranges::to_vector; + const auto serialized = SerializeArray(context, channels); + context.nesting.pop_back(); + + push("giveaway_information", SerializeObject(context, { + { "quantity", NumberToString(data.quantity) }, + { "months", NumberToString(data.months) }, + { "until_date", SerializeDate(data.untilDate) }, + { "channels", serialized }, + })); }, [](const UnsupportedMedia &data) { Unexpected("Unsupported message."); }, [](v::null_t) {}); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 5b75925d8e..6ff35aea56 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1127,8 +1127,8 @@ void History::applyServiceChanges( }, [&](const MTPDmessageActionPinMessage &data) { if (replyTo) { replyTo->match([&](const MTPDmessageReplyHeader &data) { - const auto id = data.vreply_to_msg_id().v; - if (item) { + const auto id = data.vreply_to_msg_id().value_or_empty(); + if (id && item) { session().storage().add(Storage::SharedMediaAddSlice( peer->id, MsgId(0), diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f5d04718d4..e84631f230 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -291,6 +291,10 @@ std::unique_ptr HistoryItem::CreateMedia( peerFromMTP(media.vpeer()), media.vid().v, }, media.is_via_mention()); + }, [&](const MTPDmessageMediaGiveaway &media) -> Result { + return std::make_unique( + item, + Data::ComputeGiveawayData(item, media)); }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { @@ -1621,11 +1625,18 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { setForwardsCount(data.vforwards().value_or(-1)); if (const auto reply = data.vreply_to()) { reply->match([&](const MTPDmessageReplyHeader &data) { - setReplyFields( - data.vreply_to_msg_id().v, - data.vreply_to_top_id().value_or( - data.vreply_to_msg_id().v), - data.is_forum_topic()); + // #TODO replies + const auto replyToPeer = data.vreply_to_peer_id() + ? peerFromMTP(*data.vreply_to_peer_id()) + : PeerId(); + if (!replyToPeer || replyToPeer == history()->peer->id) { + if (const auto replyToId = data.vreply_to_msg_id()) { + setReplyFields( + replyToId->v, + data.vreply_to_top_id().value_or(replyToId->v), + data.is_forum_topic()); + } + } }, [](const MTPDmessageReplyStoryHeader &data) { }); } @@ -3382,18 +3393,21 @@ void HistoryItem::createComponents(const MTPDmessage &data) { } if (const auto reply = data.vreply_to()) { reply->match([&](const MTPDmessageReplyHeader &data) { - if (const auto peer = data.vreply_to_peer_id()) { - config.replyToPeer = peerFromMTP(*peer); - if (config.replyToPeer == _history->peer->id) { - config.replyToPeer = 0; + // #TODO replies + if (const auto id = data.vreply_to_msg_id().value_or_empty()) { + if (const auto peer = data.vreply_to_peer_id()) { + config.replyToPeer = peerFromMTP(*peer); + if (config.replyToPeer == _history->peer->id) { + config.replyToPeer = 0; + } } + const auto owner = &_history->owner(); + config.replyTo = data.is_reply_to_scheduled() + ? owner->scheduledMessages().localMessageId(id) + : id; + config.replyToTop = data.vreply_to_top_id().value_or(id); + config.replyIsTopicPost = data.is_forum_topic(); } - const auto id = data.vreply_to_msg_id().v; - config.replyTo = data.is_reply_to_scheduled() - ? _history->owner().scheduledMessages().localMessageId(id) - : id; - config.replyToTop = data.vreply_to_top_id().value_or(id); - config.replyIsTopicPost = data.is_forum_topic(); }, [&](const MTPDmessageReplyStoryHeader &data) { config.replyToPeer = peerFromUser(data.vuser_id()); config.replyToStory = data.vstory_id().v; @@ -3631,21 +3645,23 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { ? peerFromMTP(*data.vreply_to_peer_id()) : _history->peer->id; if (const auto dependent = GetServiceDependentData()) { - dependent->peerId = (peerId != _history->peer->id) - ? peerId - : 0; - const auto id = data.vreply_to_msg_id().v; - dependent->msgId = id; - dependent->topId = data.vreply_to_top_id().value_or(id); - dependent->topicPost = data.is_forum_topic() - || Has(); - if (!updateServiceDependent()) { - RequestDependentMessageItem( - this, - (dependent->peerId - ? dependent->peerId - : _history->peer->id), - dependent->msgId); + const auto id = data.vreply_to_msg_id().value_or_empty(); + if (id) { + dependent->peerId = (peerId != _history->peer->id) + ? peerId + : 0; + dependent->msgId = id; + dependent->topId = data.vreply_to_top_id().value_or(id); + dependent->topicPost = data.is_forum_topic() + || Has(); + if (!updateServiceDependent()) { + RequestDependentMessageItem( + this, + (dependent->peerId + ? dependent->peerId + : _history->peer->id), + dependent->msgId); + } } } }, [](const MTPDmessageReplyStoryHeader &data) { @@ -4426,6 +4442,25 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareGiftCode = [&](const MTPDmessageActionGiftCode &action) { + auto result = PreparedServiceText(); + _history->session().giftBoxStickersPacks().load(); + const auto months = action.vmonths().v; + + result.text = { + (action.is_via_giveaway() + ? tr::lng_prize_about + : tr::lng_prize_gift_about)( + tr::now, + lt_channel, + (action.vboost_peer() + ? _from->owner().peer( + peerFromMTP(*action.vboost_peer()))->name() + : "a channel")), + }; + return result; + }; + setServiceText(action.match([&]( const MTPDmessageActionChatAddUser &data) { return prepareChatAddUserText(data); @@ -4506,6 +4541,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return prepareSetChatWallPaper(data); }, [&](const MTPDmessageActionSetSameChatWallPaper &data) { return prepareSetSameChatWallPaper(data); + }, [&](const MTPDmessageActionGiftCode &data) { + return prepareGiftCode(data); }, [](const MTPDmessageActionEmpty &) { return PreparedServiceText{ { tr::lng_message_empty(tr::now) } }; })); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index c897882480..4129df0ad9 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -370,13 +370,19 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { : Flag(0))), MTP_int(replyTo.msgId), MTPPeer(), - MTP_int(replyToTop)); + MTPMessageFwdHeader(), // reply_header + MTP_int(replyToTop), + MTPstring(), // quote_text + MTPVector()); // quote_entities } return MTP_messageReplyHeader( MTP_flags(0), MTP_int(replyTo.msgId), - MTPPeer(), - MTPint()); + MTPPeer(), // reply_to_peer_id + MTPMessageFwdHeader(), // reply_header + MTPint(), // reply_to_top_id + MTPstring(), // quote_text + MTPVector()); // quote_entities } return MTPMessageReplyHeader(); } @@ -453,6 +459,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { return data.is_via_mention() ? Result::HasStoryMention : Result::Good; + }, [](const MTPDmessageMediaGiveaway &) { + return Result::Good; }, [](const MTPDmessageMediaUnsupported &) { return Result::Unsupported; }); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp new file mode 100644 index 0000000000..3835a1a3b5 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -0,0 +1,298 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/media/history_view_giveaway.h" + +#include "base/unixtime.h" +#include "data/data_channel.h" +#include "data/data_media_types.h" +#include "lang/lang_keys.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "ui/chat/chat_style.h" +#include "ui/text/text_utilities.h" +#include "styles/style_chat.h" + +namespace HistoryView { + +void TextRows::add(Ui::Text::String text, int skipTop) { +} + +bool TextRows::isEmpty() const { + return _rows.empty() + || (_rows.size() == 1 && _rows.front().text.isEmpty()); +} + +int TextRows::maxWidth() const { + return 0; +} + +int TextRows::minHeight() const { + return 0; +} + +int TextRows::countHeight(int newWidth) const { + return 0; +} + +int TextRows::length() const { + return 0; +} + +TextSelection UnshiftItemSelection( + TextSelection selection, + const TextRows &byText) { + return UnshiftItemSelection(selection, byText.length()); +} + +TextSelection ShiftItemSelection( + TextSelection selection, + const TextRows &byText) { + return ShiftItemSelection(selection, byText.length()); +} + +Giveaway::Giveaway( + not_null parent, + not_null giveaway) +: Media(parent) { + fillFromData(giveaway); +} + +Giveaway::~Giveaway() = default; + +void Giveaway::fillFromData(not_null giveaway) { + _rows.add(Ui::Text::String( + st::semiboldTextStyle, + tr::lng_prizes_title(tr::now, lt_count, giveaway->quantity), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayPrizesTop); + + const auto duration = (giveaway->months < 12) + ? tr::lng_months(tr::now, lt_count, giveaway->months) + : tr::lng_years(tr::now, lt_count, giveaway->months / 12); + _rows.add(Ui::Text::String( + st::defaultTextStyle, + tr::lng_prizes_about( + tr::now, + lt_count, + giveaway->quantity, + lt_duration, + Ui::Text::Bold(duration), + Ui::Text::RichLangValue), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayPrizesSkip); + + _rows.add(Ui::Text::String( + st::semiboldTextStyle, + tr::lng_prizes_participants(tr::now), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayParticipantsTop); + + for (const auto &channel : giveaway->channels) { + _channels.push_back({ Ui::Text::String( + st::semiboldTextStyle, + channel->name(), + kDefaultTextOptions, + st::msgMinWidth) + }); + } + + const auto channels = int(_channels.size()); + _rows.add(Ui::Text::String( + st::defaultTextStyle, + (giveaway->all + ? tr::lng_prizes_participants_all + : tr::lng_prizes_participants_new)(tr::now, lt_count, channels), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayParticipantsSkip); + + _date.add(Ui::Text::String( + st::semiboldTextStyle, + tr::lng_prizes_date(tr::now), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayDateTop); + + _rows.add(Ui::Text::String( + st::defaultTextStyle, + langDateTime(base::unixtime::parse(giveaway->untilDate)), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayDateSkip); +} + +QSize Giveaway::countOptimalSize() { + // init dimensions + auto skipBlockWidth = _parent->skipBlockWidth(); + auto maxWidth = skipBlockWidth; + auto minHeight = 0; + + accumulate_max(maxWidth, _rows.maxWidth()); + minHeight += _rows.minHeight(); + + //minHeight += + + accumulate_max(maxWidth, _date.maxWidth()); + minHeight += _date.minHeight(); + + auto padding = inBubblePadding(); + maxWidth += padding.left() + padding.right(); + minHeight += padding.top() + padding.bottom(); + return { maxWidth, minHeight }; +} + +QSize Giveaway::countCurrentSize(int newWidth) { + accumulate_min(newWidth, maxWidth()); + auto innerWidth = newWidth + - st::msgPadding.left() + - st::msgPadding.right(); + + auto newHeight = 0; + _rowsHeight = _rows.countHeight(innerWidth); + newHeight += _rowsHeight; + + //newHeight += + + newHeight += _date.countHeight(innerWidth); + _dateHeight = _date.minHeight(); + newHeight += _dateHeight; + + auto padding = inBubblePadding(); + newHeight += padding.top() + padding.bottom(); + + return { newWidth, newHeight }; +} + +TextSelection Giveaway::toDateSelection(TextSelection selection) const { + return UnshiftItemSelection(selection, _rows); +} + +TextSelection Giveaway::fromDateSelection(TextSelection selection) const { + return ShiftItemSelection(selection, _rows); +} + +void Giveaway::draw(Painter &p, const PaintContext &context) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + const auto st = context.st; + const auto sti = context.imageStyle(); + const auto stm = context.messageStyle(); + + auto &semibold = stm->msgServiceFg; + + auto padding = inBubblePadding(); + auto tshift = padding.top(); + + //_rows.draw(p, { + // .position = { padding.left(), tshift }, + // .outerWidth = width(), + // .availableWidth = paintw, + // .now = context.now, + // .selection = context.selection, + //}); + //tshift += _rows.countHeight(paintw); + + //_date.draw(p, { + // .position = { padding.left(), tshift }, + // .outerWidth = width(), + // .availableWidth = paintw, + // .now = context.now, + // .selection = toDateSelection(context.selection), + //}); +} + +TextState Giveaway::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + + auto symbolAdd = 0; + if (_rowsHeight > 0) { + if (point.y() >= tshift && point.y() < tshift + _rowsHeight) { + //result = TextState(_parent, _rows.getState( + // point - QPoint(padding.left(), tshift), + // paintw, + // width(), + // request.forText())); + } else if (point.y() >= tshift + _rowsHeight) { + symbolAdd += _rows.length(); + } + tshift += _rowsHeight; + } + if (_channelsHeight > 0) { + tshift += _channelsHeight; + } + if (_dateHeight > 0) { + if (point.y() >= tshift && point.y() < tshift + _dateHeight) { + //result = TextState(_parent, _date.getState( + // point - QPoint(padding.left(), tshift), + // paintw, + // width(), + // request.forText())); + } else if (point.y() >= tshift + _dateHeight) { + symbolAdd += _date.length(); + } + tshift += _dateHeight; + } + result.symbol += symbolAdd; + return result; +} + +TextSelection Giveaway::adjustSelection( + TextSelection selection, + TextSelectType type) const { + //if (_date.isEmpty() || selection.to <= _rows.length()) { + // return _rows.adjustSelection(selection, type); + //} + //const auto dateSelection = _date.adjustSelection( + // toDateSelection(selection), + // type); + //if (selection.from >= _rows.length()) { + // return fromDateSelection(dateSelection); + //} + //const auto rowsSelection = _rows.adjustSelection(selection, type); + //return { rowsSelection.from, fromDateSelection(dateSelection).to }; + return selection; +} + +bool Giveaway::hasHeavyPart() const { + return false; +} + +void Giveaway::unloadHeavyPart() { +} + +uint16 Giveaway::fullSelectionLength() const { + return 0; +} + +TextForMimeData Giveaway::selectedText(TextSelection selection) const { + //auto rowsResult = _rows.toTextForMimeData(selection); + //auto dateResult = _date.toTextForMimeData(toDateSelection(selection)); + //if (rowsResult.empty()) { + // return dateResult; + //} else if (dateResult.empty()) { + // return rowsResult; + //} + //return rowsResult.append('\n').append(std::move(dateResult)); + return {}; +} + +QMargins Giveaway::inBubblePadding() const { + auto lshift = st::msgPadding.left(); + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h new file mode 100644 index 0000000000..17dcdbebf7 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -0,0 +1,102 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/view/media/history_view_media.h" + +namespace Data { +struct Giveaway; +} // namespace Data + +namespace HistoryView { + +class TextRows final { +public: + void add(Ui::Text::String text, int skipTop); + + [[nodiscard]] bool isEmpty() const; + [[nodiscard]] int maxWidth() const; + [[nodiscard]] int minHeight() const; + + [[nodiscard]] int countHeight(int newWidth) const; + + [[nodiscard]] int length() const; + +private: + struct Row { + Ui::Text::String text; + int skipTop = 0; + }; + std::vector _rows; +}; + +[[nodiscard]] TextSelection UnshiftItemSelection( + TextSelection selection, + const TextRows &byText); +[[nodiscard]] TextSelection ShiftItemSelection( + TextSelection selection, + const TextRows &byText); + +class Giveaway final : public Media { +public: + Giveaway( + not_null parent, + not_null giveaway); + ~Giveaway(); + + void draw(Painter &p, const PaintContext &context) const override; + TextState textState(QPoint point, StateRequest request) const override; + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + + bool toggleSelectionByHandlerClick( + const ClickHandlerPtr &p) const override { + return true; + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return true; + } + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override; + uint16 fullSelectionLength() const override; + TextForMimeData selectedText(TextSelection selection) const override; + + void unloadHeavyPart() override; + bool hasHeavyPart() const override; + +private: + struct Channel { + Ui::Text::String name; + }; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + void fillFromData(not_null giveaway); + + TextSelection toDateSelection(TextSelection selection) const; + TextSelection fromDateSelection(TextSelection selection) const; + QMargins inBubblePadding() const; + + TextRows _rows; + std::vector _channels; + TextRows _date; + int _rowsHeight = 0; + int _channelsHeight = 0; + int _dateHeight = 0; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 4aede409ab..8d720264cc 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -130,6 +130,7 @@ messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; +messageMediaGiveaway#4291677c flags:# only_new_subscribers:flags.0?true channels:Vector quantity:int months:int until_date:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -170,6 +171,7 @@ messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; messageActionSetChatWallPaper#bc44a927 wallpaper:WallPaper = MessageAction; messageActionSetSameChatWallPaper#c0787d6d wallpaper:WallPaper = MessageAction; +messageActionGiftCode#d2cfdb0e flags:# via_giveaway:flags.0?true boost_peer:flags.1?Peer months:int slug:string = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -755,7 +757,7 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; +draftMessage#95b0528b flags:# no_webpage:flags.1?true reply_to:flags.4?MessageReplyHeader message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; @@ -1256,7 +1258,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; +messageReplyHeader#3d5c1693 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_header:flags.5?MessageFwdHeader reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector = MessageReplyHeader; messageReplyStoryHeader#9c98bfc1 user_id:long story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1406,6 +1408,7 @@ attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; inputInvoiceSlug#c326caef slug:string = InputInvoice; +inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1416,6 +1419,7 @@ help.premiumPromo#5334759c status_text:string status_entities:Vector boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentPremiumGiveaway#e94a2529 flags:# only_new_subscribers:flags.0?true boost_peer:InputPeer additional_peers:flags.1?Vector random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1547,7 +1551,7 @@ stories.storyViewsList#46e9b9ec flags:# count:int reactions_count:int views:Vect stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; -inputReplyToMessage#9c5386e4 flags:# reply_to_msg_id:int top_msg_id:flags.0?int = InputReplyTo; +inputReplyToMessage#73ec805 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector = InputReplyTo; inputReplyToStory#15b0f283 user_id:InputUser story_id:int = InputReplyTo; exportedStoryLink#3fc9053b link:string = ExportedStoryLink; @@ -1576,9 +1580,12 @@ stories.boostersList#f3dd3d1d flags:# count:int boosters:Vector next_of messages.webPage#fd5e12bd webpage:WebPage chats:Vector users:Vector = messages.WebPage; -premiumGiftCodeOption#d579436c flags:# users:int months:int store_product:flags.0?string = PremiumGiftCodeOption; +premiumGiftCodeOption#257e962b flags:# users:int months:int store_product:flags.0?string store_quantity:flags.1?int currency:string amount:long = PremiumGiftCodeOption; -payments.checkedGiftCode#ff70298c flags:# from_id:Peer to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; +payments.checkedGiftCode#b722f158 flags:# via_giveaway:flags.2?true from_id:Peer giveaway_msg_id:flags.3?int to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; + +payments.giveawayInfo#7a7bdc5a flags:# participating:flags.0?true preparing_results:flags.3?true joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long = payments.GiveawayInfo; +payments.giveawayInfoResults#38c32424 flags:# winner:flags.0?true refunded:flags.1?true gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo; ---functions--- @@ -1793,7 +1800,7 @@ messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputB messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#b4331e3f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int top_msg_id:flags.2?int peer:InputPeer message:string entities:flags.3?Vector = Bool; +messages.saveDraft#64a3026c flags:# no_webpage:flags.1?true reply_to:flags.0?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; @@ -2051,6 +2058,7 @@ payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; payments.getPremiumGiftCodeOptions#2757ba54 flags:# boost_peer:flags.0?InputPeer = Vector; payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode; payments.applyGiftCode#f6e26854 slug:string = Updates; +payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index f13781bfe8..2b16826cee 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -931,3 +931,10 @@ storyMentionUnreadStrokeTwice: 6px; storyMentionReadSkipTwice: 7px; storyMentionReadStrokeTwice: 3px; storyMentionButtonSkip: 5px; + +chatGiveawayPrizesTop: 4px; +chatGiveawayPrizesSkip: 4px; +chatGiveawayParticipantsTop: 16px; +chatGiveawayParticipantsSkip: 4px; +chatGiveawayDateTop: 16px; +chatGiveawayDateSkip: 4px; From e59a60b3b50011b1f0b90f5b73d9f95dec0c5cc4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Oct 2023 17:18:09 +0400 Subject: [PATCH 21/99] Handle new 'web_app_setup_settings_button' event. --- Telegram/SourceFiles/data/data_bot_app.h | 1 - .../SourceFiles/inline_bots/bot_attach_web_view.cpp | 10 +--------- Telegram/SourceFiles/inline_bots/bot_attach_web_view.h | 1 - .../SourceFiles/ui/chat/attach/attach_bot_webview.cpp | 8 +++++++- .../SourceFiles/ui/chat/attach/attach_bot_webview.h | 9 +++++---- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/data/data_bot_app.h b/Telegram/SourceFiles/data/data_bot_app.h index 715fb03c06..7de4aefe86 100644 --- a/Telegram/SourceFiles/data/data_bot_app.h +++ b/Telegram/SourceFiles/data/data_bot_app.h @@ -24,5 +24,4 @@ struct BotAppData { uint64 accessHash = 0; uint64 hash = 0; - bool hasSettings = false; }; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 8f77bbf4b2..e99f48e1b1 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -117,7 +117,6 @@ constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); .inMainMenu = data.is_show_in_side_menu(), .inAttachMenu = data.is_show_in_attach_menu(), .disclaimerRequired = data.is_side_menu_disclaimer_needed(), - .hasSettings = data.is_has_settings(), .requestWriteAccess = data.is_request_write_access(), } : std::optional(); }); @@ -1261,7 +1260,6 @@ void AttachWebView::requestApp( _bot->id, data.vapp()); _app = received ? received : already; - _app->hasSettings = data.is_has_settings(); if (!_app) { cancel(); showToast(tr::lng_username_app_not_found(tr::now)); @@ -1433,19 +1431,13 @@ void AttachWebView::show( _attachBots, not_null{ _bot }, &AttachWebViewBot::user); - const auto hasSettings = app - ? app->hasSettings - : ((attached != end(_attachBots)) - && !attached->inactive - && attached->hasSettings); const auto hasOpenBot = !_context || (_bot != _context->action.history->peer) || fromMainMenu; const auto hasRemoveFromMenu = !app && (attached != end(_attachBots)) && (!attached->inactive || attached->inMainMenu); - const auto buttons = (hasSettings ? Button::Settings : Button::None) - | (hasOpenBot ? Button::OpenBot : Button::None) + const auto buttons = (hasOpenBot ? Button::OpenBot : Button::None) | (!hasRemoveFromMenu ? Button::None : attached->inMainMenu diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 04fbd06f4f..67b1bead45 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -66,7 +66,6 @@ struct AttachWebViewBot { bool inMainMenu : 1 = false; bool inAttachMenu : 1 = false; bool disclaimerRequired : 1 = false; - bool hasSettings : 1 = false; bool requestWriteAccess : 1 = false; }; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 9b9f822151..6eff646ae5 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -522,7 +522,7 @@ bool Panel::showWebview( _webviewBottom->resize(_webviewBottom->width(), height); } _widget->setMenuAllowed([=](const Ui::Menu::MenuCallback &callback) { - if (_menuButtons & MenuButton::Settings) { + if (_hasSettingsButton) { callback(tr::lng_bot_settings(tr::now), [=] { postEvent("settings_button_pressed"); }, &st::menuIconSettings); @@ -628,6 +628,8 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { processMainButtonMessage(arguments); } else if (command == "web_app_setup_back_button") { processBackButtonMessage(arguments); + } else if (command == "web_app_setup_settings_button") { + processSettingsButtonMessage(arguments); } else if (command == "web_app_request_theme") { _themeUpdateForced.fire({}); } else if (command == "web_app_request_viewport") { @@ -1104,6 +1106,10 @@ void Panel::processBackButtonMessage(const QJsonObject &args) { _widget->setBackAllowed(args["is_visible"].toBool()); } +void Panel::processSettingsButtonMessage(const QJsonObject &args) { + _hasSettingsButton = args["is_visible"].toBool(); +} + void Panel::processHeaderColor(const QJsonObject &args) { if (const auto color = ParseColor(args["color"].toString())) { _widget->overrideTitleColor(color); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index d8e2883db1..28ea96cc2e 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -37,10 +37,9 @@ struct MainButtonArgs { enum class MenuButton { None = 0x00, - Settings = 0x01, - OpenBot = 0x02, - RemoveFromMenu = 0x04, - RemoveFromMainMenu = 0x08, + OpenBot = 0x01, + RemoveFromMenu = 0x02, + RemoveFromMainMenu = 0x04, }; inline constexpr bool is_flag_type(MenuButton) { return true; } using MenuButtons = base::flags; @@ -114,6 +113,7 @@ private: void switchInlineQueryMessage(const QJsonObject &args); void processMainButtonMessage(const QJsonObject &args); void processBackButtonMessage(const QJsonObject &args); + void processSettingsButtonMessage(const QJsonObject &args); void processHeaderColor(const QJsonObject &args); void openTgLink(const QJsonObject &args); void openExternalLink(const QJsonObject &args); @@ -145,6 +145,7 @@ private: QString _userDataPath; const not_null _delegate; bool _closeNeedConfirmation = false; + bool _hasSettingsButton = false; MenuButtons _menuButtons = {}; std::unique_ptr _widget; std::unique_ptr _webview; From 7f9461820bd543d3709c287f3376609b37317740 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Oct 2023 17:18:44 +0400 Subject: [PATCH 22/99] Don't close webview after 'web_app_open_tg_link'. --- Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp | 6 ++++-- Telegram/SourceFiles/inline_bots/bot_attach_web_view.h | 2 +- Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp | 7 ++++--- Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index e99f48e1b1..29842290b0 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -555,14 +555,16 @@ Webview::ThemeParams AttachWebView::botThemeParams() { return Window::Theme::WebViewParams(); } -bool AttachWebView::botHandleLocalUri(QString uri) { +bool AttachWebView::botHandleLocalUri(QString uri, bool keepOpen) { const auto local = Core::TryConvertUrlToLocal(uri); if (uri == local || Core::InternalPassportLink(local)) { return local.startsWith(u"tg://"_q); } else if (!local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) { return false; } - botClose(); + if (!keepOpen) { + botClose(); + } crl::on_main([=, shownUrl = _lastShownUrl] { const auto variant = QVariant::fromValue(ClickHandlerContext{ .attachBotWebviewUrl = shownUrl, diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 67b1bead45..ce8a167e41 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -159,7 +159,7 @@ private: Webview::ThemeParams botThemeParams() override; - bool botHandleLocalUri(QString uri) override; + bool botHandleLocalUri(QString uri, bool keepOpen) override; void botHandleInvoice(QString slug) override; void botHandleMenuButton(Ui::BotWebView::MenuButton button) override; void botSendData(QByteArray data) override; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 6eff646ae5..104ed02b66 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -658,7 +658,7 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { }); raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) { - if (_delegate->botHandleLocalUri(uri)) { + if (_delegate->botHandleLocalUri(uri, false)) { return false; } else if (newWindow) { return true; @@ -743,16 +743,17 @@ void Panel::switchInlineQueryMessage(const QJsonObject &args) { void Panel::openTgLink(const QJsonObject &args) { if (args.isEmpty()) { + LOG(("BotWebView Error: Bad arguments in 'web_app_open_tg_link'.")); _delegate->botClose(); return; } const auto path = args["path_full"].toString(); if (path.isEmpty()) { - LOG(("BotWebView Error: Bad 'path_full' in openTgLink.")); + LOG(("BotWebView Error: Bad 'path_full' in 'web_app_open_tg_link'.")); _delegate->botClose(); return; } - _delegate->botHandleLocalUri("https://t.me" + path); + _delegate->botHandleLocalUri("https://t.me" + path, true); } void Panel::openExternalLink(const QJsonObject &args) { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 28ea96cc2e..20a9a4f720 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -54,7 +54,7 @@ struct CustomMethodRequest { class Delegate { public: virtual Webview::ThemeParams botThemeParams() = 0; - virtual bool botHandleLocalUri(QString uri) = 0; + virtual bool botHandleLocalUri(QString uri, bool keepOpen) = 0; virtual void botHandleInvoice(QString slug) = 0; virtual void botHandleMenuButton(MenuButton button) = 0; virtual void botSendData(QByteArray data) = 0; From 0e45f3ebd977e40eca0bdfe0d4229a6ab76eb008 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Oct 2023 17:47:24 +0400 Subject: [PATCH 23/99] Provide more theme keys to webview bots. --- Telegram/SourceFiles/window/themes/window_theme.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index 4e870c9970..e8b06a5a6a 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -1496,6 +1496,12 @@ bool ReadPaletteValues(const QByteArray &content, Fn Date: Fri, 6 Oct 2023 17:47:02 +0400 Subject: [PATCH 24/99] Use webview bots window size like on mobiles. --- Telegram/SourceFiles/payments/ui/payments.style | 1 + Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/payments/ui/payments.style b/Telegram/SourceFiles/payments/ui/payments.style index 9c49d1be10..df4799309b 100644 --- a/Telegram/SourceFiles/payments/ui/payments.style +++ b/Telegram/SourceFiles/payments/ui/payments.style @@ -136,6 +136,7 @@ paymentsLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { thickness: 4px; } +botWebViewPanelSize: size(384px, 694px); botWebViewBottomButton: RoundButton(paymentsPanelSubmit) { height: 56px; font: boxButtonFont; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 104ed02b66..aa18ca2071 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -323,7 +323,7 @@ Panel::Panel( , _menuButtons(menuButtons) , _widget(std::make_unique()) , _allowClipboardRead(allowClipboardRead) { - _widget->setInnerSize(st::paymentsPanelSize); + _widget->setInnerSize(st::botWebViewPanelSize); _widget->setWindowFlag(Qt::WindowStaysOnTopHint, false); _widget->closeRequests( From d5147c9d286a320b39329750dc59418f7a490d15 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 5 Oct 2023 11:47:37 +0400 Subject: [PATCH 25/99] Partially (italic+colored) support blockquotes. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_text_entities.cpp | 3 +++ Telegram/SourceFiles/core/ui_integration.cpp | 4 ++++ Telegram/SourceFiles/core/ui_integration.h | 1 + .../SourceFiles/platform/linux/main_window_linux.cpp | 10 ++++++++++ .../SourceFiles/platform/linux/main_window_linux.h | 1 + Telegram/SourceFiles/platform/mac/main_window_mac.h | 1 + Telegram/SourceFiles/platform/mac/main_window_mac.mm | 10 ++++++++++ .../mac/touchbar/items/mac_formatter_item.mm | 12 +++++++++--- 9 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 1572839ca6..aca64ad73e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2759,6 +2759,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_menu_formatting_italic" = "Italic"; "lng_menu_formatting_underline" = "Underline"; "lng_menu_formatting_strike_out" = "Strike-through"; +"lng_menu_formatting_blockquote" = "Quote"; "lng_menu_formatting_monospace" = "Monospace"; "lng_menu_formatting_spoiler" = "Spoiler"; "lng_menu_formatting_link_create" = "Create link"; diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 1bfe1142f3..6c434bc9ba 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -114,6 +114,7 @@ EntitiesInText EntitiesFromMTP( case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break; + case mtpc_messageEntityBlockquote: { auto &d = entity.c_messageEntityBlockquote(); result.push_back({ EntityType::Blockquote, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityCustomEmoji: { @@ -142,6 +143,7 @@ MTPVector EntitiesToMTP( && entity.type() != EntityType::StrikeOut && entity.type() != EntityType::Code // #TODO entities && entity.type() != EntityType::Pre + && entity.type() != EntityType::Blockquote && entity.type() != EntityType::Spoiler && entity.type() != EntityType::MentionName && entity.type() != EntityType::CustomUrl @@ -170,6 +172,7 @@ MTPVector EntitiesToMTP( case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break; case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break; + case EntityType::Blockquote: v.push_back(MTP_messageEntityBlockquote(offset, length)); break; case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break; case EntityType::CustomEmoji: { if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) { diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 1fe33e6544..24a8bbee2c 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -348,6 +348,10 @@ QString UiIntegration::phraseFormattingStrikeOut() { return tr::lng_menu_formatting_strike_out(tr::now); } +QString UiIntegration::phraseFormattingBlockquote() { + return tr::lng_menu_formatting_blockquote(tr::now); +} + QString UiIntegration::phraseFormattingMonospace() { return tr::lng_menu_formatting_monospace(tr::now); } diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h index 0515c732f0..4e23a63db4 100644 --- a/Telegram/SourceFiles/core/ui_integration.h +++ b/Telegram/SourceFiles/core/ui_integration.h @@ -77,6 +77,7 @@ public: QString phraseFormattingItalic() override; QString phraseFormattingUnderline() override; QString phraseFormattingStrikeOut() override; + QString phraseFormattingBlockquote() override; QString phraseFormattingMonospace() override; QString phraseFormattingSpoiler() override; QString phraseButtonOk() override; diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 7fec6ec890..f4c400f32e 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -373,6 +373,15 @@ void MainWindow::createGlobalMenu() { }, Ui::kStrikeOutSequence); + psBlockquote = edit->addAction( + tr::lng_menu_formatting_blockquote(tr::now), + [] { + SendKeySequence( + Qt::Key_Period, + Qt::ControlModifier | Qt::ShiftModifier); + }, + Ui::kBlockquoteSequence); + psMonospace = edit->addAction( tr::lng_menu_formatting_monospace(tr::now), [] { @@ -534,6 +543,7 @@ void MainWindow::updateGlobalMenuHook() { ForceDisabled(psItalic, !markdownEnabled); ForceDisabled(psUnderline, !markdownEnabled); ForceDisabled(psStrikeOut, !markdownEnabled); + ForceDisabled(psBlockquote, !markdownEnabled); ForceDisabled(psMonospace, !markdownEnabled); ForceDisabled(psClearFormat, !markdownEnabled); } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index 467f690b73..311ed3a742 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -57,6 +57,7 @@ private: QAction *psItalic = nullptr; QAction *psUnderline = nullptr; QAction *psStrikeOut = nullptr; + QAction *psBlockquote = nullptr; QAction *psMonospace = nullptr; QAction *psClearFormat = nullptr; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.h b/Telegram/SourceFiles/platform/mac/main_window_mac.h index 96c0f49db2..36dc140bda 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.h +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.h @@ -78,6 +78,7 @@ private: QAction *psItalic = nullptr; QAction *psUnderline = nullptr; QAction *psStrikeOut = nullptr; + QAction *psBlockquote = nullptr; QAction *psMonospace = nullptr; QAction *psClearFormat = nullptr; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index e674ec8c88..d30a658097 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -406,6 +406,15 @@ void MainWindow::createGlobalMenu() { Qt::ControlModifier | Qt::ShiftModifier); }, Ui::kStrikeOutSequence); + psBlockquote = edit->addAction( + tr::lng_menu_formatting_blockquote(tr::now), + this, + [] { + SendKeySequence( + Qt::Key_Period, + Qt::ControlModifier | Qt::ShiftModifier); + }, + Ui::kBlockquoteSequence); psMonospace = edit->addAction( tr::lng_menu_formatting_monospace(tr::now), this, @@ -550,6 +559,7 @@ void MainWindow::updateGlobalMenuHook() { ForceDisabled(psItalic, !canApplyMarkdown); ForceDisabled(psUnderline, !canApplyMarkdown); ForceDisabled(psStrikeOut, !canApplyMarkdown); + ForceDisabled(psBlockquote, !canApplyMarkdown); ForceDisabled(psMonospace, !canApplyMarkdown); ForceDisabled(psClearFormat, !canApplyMarkdown); } diff --git a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_formatter_item.mm b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_formatter_item.mm index 2d04a140a5..c719f70069 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_formatter_item.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_formatter_item.mm @@ -24,9 +24,10 @@ constexpr auto kCommandBold = 0x010; constexpr auto kCommandItalic = 0x011; constexpr auto kCommandUnderline = 0x012; constexpr auto kCommandStrikeOut = 0x013; -constexpr auto kCommandMonospace = 0x014; -constexpr auto kCommandClear = 0x015; -constexpr auto kCommandLink = 0x016; +constexpr auto kCommandBlockquote = 0x014; +constexpr auto kCommandMonospace = 0x015; +constexpr auto kCommandClear = 0x016; +constexpr auto kCommandLink = 0x017; const auto kPopoverFormatter = @"popoverInputFormatter"; @@ -44,6 +45,10 @@ void SendKeyEvent(int command) { case kCommandItalic: key = Qt::Key_I; break; + case kCommandBlockquote: + key = Qt::Key_Period; + modifier |= Qt::ShiftModifier; + break; case kCommandMonospace: key = Qt::Key_M; modifier |= Qt::ShiftModifier; @@ -103,6 +108,7 @@ void SendKeyEvent(int command) { tr::lng_menu_formatting_italic, tr::lng_menu_formatting_underline, tr::lng_menu_formatting_strike_out, + tr::lng_menu_formatting_blockquote, tr::lng_menu_formatting_monospace, tr::lng_menu_formatting_clear, tr::lng_info_link_label, From b08869abdb63a2148e05258b4222e1824cc67e0d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Oct 2023 11:15:26 +0400 Subject: [PATCH 26/99] Support giveaway message layout. --- Telegram/Resources/langs/lang.strings | 2 +- .../history/history_inner_widget.cpp | 41 ++ .../history/view/history_view_element.cpp | 92 +++- .../history/view/history_view_element.h | 21 + .../history/view/history_view_message.cpp | 14 + .../history/view/history_view_view_button.cpp | 34 +- .../view/media/history_view_giveaway.cpp | 446 ++++++++++-------- .../view/media/history_view_giveaway.h | 77 ++- .../history/view/media/history_view_media.h | 3 + .../payments/payments_checkout_process.cpp | 50 +- .../payments/payments_checkout_process.h | 4 + .../SourceFiles/payments/payments_form.cpp | 87 +++- Telegram/SourceFiles/payments/payments_form.h | 28 +- Telegram/SourceFiles/ui/chat/chat.style | 11 +- 14 files changed, 659 insertions(+), 251 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index aca64ad73e..e3986b8004 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2091,7 +2091,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_participants_new#one" = "All users who joined the channel below after this date:"; "lng_prizes_participants_new#other" = "All users who joined the channels below after this date:"; "lng_prizes_date" = "Winners Selection Date"; -"lng_prizes_how_works" = "How does it work?"; +"lng_prizes_how_works" = "Learn more"; "lng_prizes_how_text#one" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscription for {duration} for its followers."; "lng_prizes_how_text#other" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions for {duration} for its followers."; "lng_prizes_how_when_all_of_one#one" = "On {date}, Telegram will automatically select {count} random subscribers of {channel}."; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index a575ce312b..23d3f1c4e4 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -96,6 +96,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" #include "styles/style_menu_icons.h" +#include "payments/payments_checkout_process.h" +#include "payments/payments_form.h" +#include "base/random.h" + #include #include #include @@ -449,6 +453,43 @@ HistoryInner::HistoryInner( _migrated->translateTo(_history->translatedTo()); } +#if 0 + if (const auto channel = _history->peer->asBroadcast()) { + if (channel->amCreator()) { + const auto weak = base::make_weak(_controller); + channel->session().api().request(MTPpayments_GetPremiumGiftCodeOptions( + MTP_flags(MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer), + channel->input + )).done(crl::guard(weak, [=](const MTPVector &result) { + if (result.v.isEmpty()) { + return; + } + const auto &data = result.v.front().data(); + const auto randomId = base::RandomValue(); + Payments::CheckoutProcess::Start( + Payments::InvoicePremiumGiftCode{ + .purpose = Payments::InvoicePremiumGiftCodeGiveaway{ + .boostPeer = channel, + //.additionalChannels = , + .untilDate = (base::unixtime::now() + 300), + .onlyNewSubscribers = true, + }, + .randomId = randomId, + .currency = qs(data.vcurrency()), + .amount = data.vamount().v, + .storeProduct = qs(data.vstore_product().value_or_empty()), + .storeQuantity = data.vstore_quantity().value_or_empty(), + .users = data.vusers().v, + .months = data.vmonths().v, + }, + crl::guard(weak, [=](auto) { weak->window().activate(); })); + })).fail(crl::guard(weak, [=](const MTP::Error &error) { + weak.get()->showToast(error.type()); + })).send(); + } + } +#endif + Window::ChatThemeValueFromPeer( controller, _peer diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 22dd45ab49..aab999319a 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -338,7 +338,6 @@ void UnreadBar::paint( text); } - void DateBadge::init(const QString &date) { text = date; width = st::msgServiceFont->width(text); @@ -361,6 +360,81 @@ void DateBadge::paint( ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide); } +void ServicePreMessage::init(TextWithEntities string) { + text = Ui::Text::String( + st::serviceTextStyle, + string, + kMarkupTextOptions, + st::msgMinWidth); +} + +int ServicePreMessage::resizeToWidth(int newWidth, bool chatWide) { + width = newWidth; + if (chatWide) { + accumulate_min( + width, + st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); + } + auto contentWidth = width; + contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins + if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) { + contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1; + } + + auto maxWidth = text.maxWidth() + + st::msgServicePadding.left() + + st::msgServicePadding.right(); + auto minHeight = text.minHeight(); + + auto nwidth = qMax(contentWidth + - st::msgServicePadding.left() + - st::msgServicePadding.right(), 0); + height = (contentWidth >= maxWidth) + ? minHeight + : text.countHeight(nwidth); + height += st::msgServicePadding.top() + + st::msgServicePadding.bottom() + + st::msgServiceMargin.top() + + st::msgServiceMargin.bottom(); + return height; +} + +void ServicePreMessage::paint( + Painter &p, + const PaintContext &context, + QRect g, + bool chatWide) const { + const auto top = g.top() - height - st::msgMargin.top(); + p.translate(0, top); + + const auto rect = QRect(0, 0, width, height) + - st::msgServiceMargin; + const auto trect = rect - st::msgServicePadding; + + ServiceMessagePainter::PaintComplexBubble( + p, + context.st, + rect.left(), + rect.width(), + text, + trect); + + p.setBrush(Qt::NoBrush); + p.setPen(context.st->msgServiceFg()); + p.setFont(st::msgServiceFont); + text.draw(p, { + .position = trect.topLeft(), + .availableWidth = trect.width(), + .align = style::al_top, + .palette = &context.st->serviceTextPalette(), + .now = context.now, + //.selection = context.selection, + .fullWidthSelection = false, + }); + + p.translate(0, -top); +} + void FakeBotAboutTop::init() { if (!text.isEmpty()) { return; @@ -971,7 +1045,9 @@ bool Element::computeIsAttachToPrevious(not_null previous) { || !item->from()->isChannel()); }; const auto item = data(); - if (!Has() && !Has()) { + if (!Has() + && !Has() + && !Has()) { const auto prev = previous->data(); const auto previousMarkup = prev->inlineReplyMarkup(); const auto possible = (std::abs(prev->date() - item->date()) @@ -1183,6 +1259,18 @@ void Element::setDisplayDate(bool displayDate) { } } +void Element::setServicePreMessage(TextWithEntities text) { + if (!text.empty()) { + AddComponents(ServicePreMessage::Bit()); + const auto service = Get(); + service->init(std::move(text)); + setPendingResize(); + } else if (Has()) { + RemoveComponents(ServicePreMessage::Bit()); + setPendingResize(); + } +} + void Element::setAttachToNext(bool attachToNext, Element *next) { Expects(next || !attachToNext); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 1824f71376..4f62303c76 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -234,6 +234,26 @@ struct DateBadge : public RuntimeComponent { }; +// Any HistoryView::Element can have this Component for +// displaying some text in layout of a service message above the message. +struct ServicePreMessage + : public RuntimeComponent { + void init(TextWithEntities string); + + int resizeToWidth(int newWidth, bool chatWide); + + void paint( + Painter &p, + const PaintContext &context, + QRect g, + bool chatWide) const; + + Ui::Text::String text; + int width = 0; + int height = 0; + +}; + struct FakeBotAboutTop : public RuntimeComponent { void init(); @@ -339,6 +359,7 @@ public: // For blocks context this should be called only from recountDisplayDate(). void setDisplayDate(bool displayDate); + void setServicePreMessage(TextWithEntities text); bool computeIsAttachToPrevious(not_null previous); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index dca4e0af0b..a50b92d27a 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -874,6 +874,9 @@ int Message::marginTop() const { if (const auto bar = Get()) { result += bar->height(); } + if (const auto service = Get()) { + result += service->height; + } return result; } @@ -911,6 +914,10 @@ void Message::draw(Painter &p, const PaintContext &context) const { } } + if (const auto service = Get()) { + service->paint(p, context, g, delegate()->elementIsChatWide()); + } + if (isHidden()) { return; } @@ -3043,6 +3050,9 @@ bool Message::hasFromName() const { if (hasOutLayout() && !item->from()->isChannel()) { return false; } else if (!peer->isUser()) { + if (const auto media = this->media()) { + return !media->hideFromName(); + } return true; } if (const auto forwarded = item->Get()) { @@ -3703,6 +3713,10 @@ int Message::resizeContentGetHeight(int newWidth) { auto newHeight = minHeight(); + if (const auto service = Get()) { + service->resizeToWidth(newWidth, delegate()->elementIsChatWide()); + } + const auto item = data(); const auto botTop = item->isFakeBotAbout() ? Get() diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index 197918968f..70c49452a2 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -83,12 +83,23 @@ inline auto WebPageToPhrase(not_null webpage) { : QString()); } -[[nodiscard]] ClickHandlerPtr MakeWebPageButtonClickHandler( +[[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler( not_null media) { - Expects(media->webpage() != nullptr); + if (const auto giveaway = media->giveaway()) { + return std::make_shared([=]( + ClickContext context) { + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + if (!controller) { + return; + } + }); + } + const auto webpage = media->webpage(); + Assert(webpage != nullptr); - const auto url = media->webpage()->url; - const auto type = media->webpage()->type; + const auto url = webpage->url; + const auto type = webpage->type; return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { @@ -105,6 +116,15 @@ inline auto WebPageToPhrase(not_null webpage) { }); } +[[nodiscard]] QString MakeMediaButtonText(not_null media) { + if (const auto giveaway = media->giveaway()) { + return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now)); + } + const auto webpage = media->webpage(); + Assert(webpage != nullptr); + return WebPageToPhrase(webpage); +} + [[nodiscard]] ClickHandlerPtr SponsoredLink( not_null sponsored) { if (!sponsored->externalLink.isEmpty()) { @@ -170,7 +190,7 @@ struct ViewButton::Inner { bool ViewButton::MediaHasViewButton(not_null media) { return media->webpage() ? MediaHasViewButton(media->webpage()) - : false; + : (media->giveaway() != nullptr); } bool ViewButton::MediaHasViewButton( @@ -209,10 +229,10 @@ ViewButton::Inner::Inner( not_null media, Fn updateCallback) : margins(st::historyViewButtonMargins) -, link(MakeWebPageButtonClickHandler(media)) +, link(MakeMediaButtonClickHandler(media)) , updateCallback(std::move(updateCallback)) , belowInfo(false) -, text(st::historyViewButtonTextStyle, WebPageToPhrase(media->webpage())) { +, text(st::historyViewButtonTextStyle, MakeMediaButtonText(media)) { } void ViewButton::Inner::updateMask(int height) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 3835a1a3b5..3c637ec98d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -8,74 +8,91 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_giveaway.h" #include "base/unixtime.h" +#include "chat_helpers/stickers_gift_box_pack.h" #include "data/data_channel.h" +#include "data/data_document.h" #include "data/data_media_types.h" -#include "lang/lang_keys.h" +#include "data/data_session.h" +#include "dialogs/ui/dialogs_stories_content.h" +#include "dialogs/ui/dialogs_stories_list.h" +#include "history/history.h" #include "history/history_item.h" +#include "history/history_item_components.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" #include "ui/chat/chat_style.h" #include "ui/text/text_utilities.h" +#include "ui/widgets/tooltip.h" +#include "ui/painter.h" +#include "ui/round_rect.h" #include "styles/style_chat.h" namespace HistoryView { +namespace { -void TextRows::add(Ui::Text::String text, int skipTop) { +constexpr auto kChannelBgAlpha = 32; + +[[nodiscard]] QSize CountOptimalTextSize( + const Ui::Text::String &text, + int minWidth, + int maxWidth) { + if (text.maxWidth() <= maxWidth) { + return { text.maxWidth(), text.minHeight() }; + } + const auto height = text.countHeight(maxWidth); + return { Ui::FindNiceTooltipWidth(minWidth, maxWidth, [&](int width) { + return text.countHeight(width); + }), height }; } -bool TextRows::isEmpty() const { - return _rows.empty() - || (_rows.size() == 1 && _rows.front().text.isEmpty()); -} - -int TextRows::maxWidth() const { - return 0; -} - -int TextRows::minHeight() const { - return 0; -} - -int TextRows::countHeight(int newWidth) const { - return 0; -} - -int TextRows::length() const { - return 0; -} - -TextSelection UnshiftItemSelection( - TextSelection selection, - const TextRows &byText) { - return UnshiftItemSelection(selection, byText.length()); -} - -TextSelection ShiftItemSelection( - TextSelection selection, - const TextRows &byText) { - return ShiftItemSelection(selection, byText.length()); -} +} // namespace Giveaway::Giveaway( not_null parent, not_null giveaway) -: Media(parent) { +: Media(parent) +, _prizesTitle(st::msgMinWidth) +, _prizes(st::msgMinWidth) +, _participantsTitle(st::msgMinWidth) +, _participants(st::msgMinWidth) +, _winnersTitle(st::msgMinWidth) +, _winners(st::msgMinWidth) { fillFromData(giveaway); + + if (!parent->data()->Has() + && ranges::contains( + giveaway->channels, + parent->data()->history()->peer)) { + parent->setServicePreMessage({ + tr::lng_action_giveaway_started( + tr::now, + lt_from, + parent->data()->history()->peer->name()), + }); + } } -Giveaway::~Giveaway() = default; +Giveaway::~Giveaway() { + if (hasHeavyPart()) { + unloadHeavyPart(); + _parent->checkHeavyPart(); + } +} void Giveaway::fillFromData(not_null giveaway) { - _rows.add(Ui::Text::String( + _months = giveaway->months; + + _prizesTitle.setText( st::semiboldTextStyle, tr::lng_prizes_title(tr::now, lt_count, giveaway->quantity), - kDefaultTextOptions, - st::msgMinWidth), st::chatGiveawayPrizesTop); + kDefaultTextOptions); const auto duration = (giveaway->months < 12) ? tr::lng_months(tr::now, lt_count, giveaway->months) : tr::lng_years(tr::now, lt_count, giveaway->months / 12); - _rows.add(Ui::Text::String( + _prizes.setMarkedText( st::defaultTextStyle, tr::lng_prizes_about( tr::now, @@ -84,94 +101,123 @@ void Giveaway::fillFromData(not_null giveaway) { lt_duration, Ui::Text::Bold(duration), Ui::Text::RichLangValue), - kDefaultTextOptions, - st::msgMinWidth), st::chatGiveawayPrizesSkip); - - _rows.add(Ui::Text::String( + kDefaultTextOptions); + _participantsTitle.setText( st::semiboldTextStyle, tr::lng_prizes_participants(tr::now), - kDefaultTextOptions, - st::msgMinWidth), st::chatGiveawayParticipantsTop); + kDefaultTextOptions); for (const auto &channel : giveaway->channels) { - _channels.push_back({ Ui::Text::String( - st::semiboldTextStyle, - channel->name(), - kDefaultTextOptions, - st::msgMinWidth) + _channels.push_back({ + .name = Ui::Text::String( + st::semiboldTextStyle, + channel->name(), + kDefaultTextOptions, + st::msgMinWidth), + .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel), + .link = channel->openLink(), }); } - const auto channels = int(_channels.size()); - _rows.add(Ui::Text::String( + + _participants.setText( st::defaultTextStyle, (giveaway->all ? tr::lng_prizes_participants_all : tr::lng_prizes_participants_new)(tr::now, lt_count, channels), - kDefaultTextOptions, - st::msgMinWidth), st::chatGiveawayParticipantsSkip); - - _date.add(Ui::Text::String( + kDefaultTextOptions); + _winnersTitle.setText( st::semiboldTextStyle, tr::lng_prizes_date(tr::now), - kDefaultTextOptions, - st::msgMinWidth), st::chatGiveawayDateTop); - - _rows.add(Ui::Text::String( + kDefaultTextOptions); + _winners.setText( st::defaultTextStyle, langDateTime(base::unixtime::parse(giveaway->untilDate)), - kDefaultTextOptions, - st::msgMinWidth), st::chatGiveawayDateSkip); + kDefaultTextOptions); + + ensureStickerCreated(); } QSize Giveaway::countOptimalSize() { - // init dimensions - auto skipBlockWidth = _parent->skipBlockWidth(); - auto maxWidth = skipBlockWidth; - auto minHeight = 0; + const auto maxWidth = st::chatGiveawayWidth; + const auto padding = inBubblePadding(); + const auto available = maxWidth - padding.left() - padding.right(); - accumulate_max(maxWidth, _rows.maxWidth()); - minHeight += _rows.minHeight(); + _stickerTop = st::chatGiveawayStickerTop; + _prizesTitleTop = _stickerTop + + st::msgServiceGiftBoxStickerSize.height() + + st::chatGiveawayPrizesTop; + _prizesTop = _prizesTitleTop + + _prizesTitle.countHeight(available) + + st::chatGiveawayPrizesSkip; + const auto prizesSize = CountOptimalTextSize( + _prizes, + st::msgMinWidth, + available); + _prizesWidth = prizesSize.width(); + _participantsTitleTop = _prizesTop + + prizesSize.height() + + st::chatGiveawayParticipantsTop; + _participantsTop = _participantsTitleTop + + _participantsTitle.countHeight(available) + + st::chatGiveawayParticipantsSkip; + const auto participantsSize = CountOptimalTextSize( + _participants, + st::msgMinWidth, + available); + _participantsWidth = participantsSize.width(); + const auto channelsTop = _participantsTop + + participantsSize.height() + + st::chatGiveawayChannelTop; + const auto channelsBottom = layoutChannels( + padding.left(), + channelsTop, + available); + _winnersTitleTop = channelsBottom + st::chatGiveawayDateTop; + _winnersTop = _winnersTitleTop + + _winnersTitle.countHeight(available) + + st::chatGiveawayDateSkip; + const auto height = _winnersTop + + _winners.countHeight(available) + + st::chatGiveawayBottomSkip; + return { maxWidth, height }; +} - //minHeight += - - accumulate_max(maxWidth, _date.maxWidth()); - minHeight += _date.minHeight(); - - auto padding = inBubblePadding(); - maxWidth += padding.left() + padding.right(); - minHeight += padding.top() + padding.bottom(); - return { maxWidth, minHeight }; +int Giveaway::layoutChannels(int x, int y, int available) { + const auto size = st::chatGiveawayChannelSize; + const auto skip = st::chatGiveawayChannelSkip; + const auto padding = st::chatGiveawayChannelPadding; + auto left = available; + const auto shiftRow = [&](int i, int top, int shift) { + for (auto j = i; j != 0; --j) { + auto &geometry = _channels[j - 1].geometry; + if (geometry.top() != top) { + break; + } + geometry.moveLeft(geometry.x() + shift); + } + }; + const auto count = int(_channels.size()); + for (auto i = 0; i != count; ++i) { + const auto desired = size + + padding.left() + + _channels[i].name.maxWidth() + + padding.right(); + const auto width = std::min(desired, available); + if (left < width) { + shiftRow(i, y, (left + skip) / 2); + left = available; + y += size + skip; + } + _channels[i].geometry = { x + available - left, y, width, size }; + left -= width + skip; + } + shiftRow(count, y, (left + skip) / 2); + return y + size + skip; } QSize Giveaway::countCurrentSize(int newWidth) { - accumulate_min(newWidth, maxWidth()); - auto innerWidth = newWidth - - st::msgPadding.left() - - st::msgPadding.right(); - - auto newHeight = 0; - _rowsHeight = _rows.countHeight(innerWidth); - newHeight += _rowsHeight; - - //newHeight += - - newHeight += _date.countHeight(innerWidth); - _dateHeight = _date.minHeight(); - newHeight += _dateHeight; - - auto padding = inBubblePadding(); - newHeight += padding.top() + padding.bottom(); - - return { newWidth, newHeight }; -} - -TextSelection Giveaway::toDateSelection(TextSelection selection) const { - return UnshiftItemSelection(selection, _rows); -} - -TextSelection Giveaway::fromDateSelection(TextSelection selection) const { - return ShiftItemSelection(selection, _rows); + return { maxWidth(), minHeight()}; } void Giveaway::draw(Painter &p, const PaintContext &context) const { @@ -184,24 +230,107 @@ void Giveaway::draw(Painter &p, const PaintContext &context) const { auto &semibold = stm->msgServiceFg; auto padding = inBubblePadding(); - auto tshift = padding.top(); - //_rows.draw(p, { - // .position = { padding.left(), tshift }, - // .outerWidth = width(), - // .availableWidth = paintw, - // .now = context.now, - // .selection = context.selection, - //}); - //tshift += _rows.countHeight(paintw); + const auto outer = width(); + const auto paintw = outer - padding.left() - padding.right(); + const auto stickerSize = st::msgServiceGiftBoxStickerSize; + const auto sticker = QRect( + (outer - stickerSize.width()) / 2, + _stickerTop, + stickerSize.width(), + stickerSize.height()); - //_date.draw(p, { - // .position = { padding.left(), tshift }, - // .outerWidth = width(), - // .availableWidth = paintw, - // .now = context.now, - // .selection = toDateSelection(context.selection), - //}); + if (_sticker) { + _sticker->draw(p, context, sticker); + } else { + ensureStickerCreated(); + } + const auto paintText = [&]( + const Ui::Text::String &text, + int top, + int width) { + p.setPen(stm->historyTextFg); + text.draw(p, { + .position = { padding.left() + (paintw - width) / 2, top}, + .outerWidth = outer, + .availableWidth = width, + .align = style::al_top, + .palette = &stm->textPalette, + .now = context.now, + }); + }; + paintText(_prizesTitle, _prizesTitleTop, paintw); + paintText(_prizes, _prizesTop, _prizesWidth); + paintText(_participantsTitle, _participantsTitleTop, paintw); + paintText(_participants, _participantsTop, _participantsWidth); + paintText(_winnersTitle, _winnersTitleTop, paintw); + paintText(_winners, _winnersTop, paintw); + paintChannels(p, context); +} + +void Giveaway::paintChannels( + Painter &p, + const PaintContext &context) const { + if (_channels.empty()) { + return; + } + + const auto size = _channels[0].geometry.height(); + const auto ratio = style::DevicePixelRatio(); + const auto stm = context.messageStyle(); + auto bg = stm->msgReplyBarColor->c; + bg.setAlpha(kChannelBgAlpha); + if (_channelCorners[0].isNull() || _channelBg != bg) { + _channelBg = bg; + _channelCorners = Images::CornersMask(size / 2); + for (auto &image : _channelCorners) { + style::colorizeImage(image, bg, &image); + } + } + p.setPen(stm->msgReplyBarColor); + const auto padding = st::chatGiveawayChannelPadding; + for (const auto &channel : _channels) { + const auto &thumbnail = channel.thumbnail; + const auto &geometry = channel.geometry; + if (!_subscribedToThumbnails) { + thumbnail->subscribeToUpdates([view = parent()] { + view->history()->owner().requestViewRepaint(view); + }); + } + Ui::DrawRoundedRect(p, geometry, _channelBg, _channelCorners); + p.drawImage(geometry.topLeft(), thumbnail->image(size)); + const auto left = size + padding.left(); + const auto top = padding.top(); + const auto available = geometry.width() - left - padding.right(); + channel.name.draw(p, { + .position = { geometry.left() + left, geometry.top() + top }, + .outerWidth = width(), + .availableWidth = available, + .align = style::al_left, + .palette = &stm->textPalette, + .now = context.now, + .elisionOneLine = true, + .elisionBreakEverywhere = true, + }); + } + _subscribedToThumbnails = true; +} + +void Giveaway::ensureStickerCreated() const { + if (_sticker) { + return; + } + const auto &session = _parent->history()->session(); + auto &packs = session.giftBoxStickersPacks(); + if (const auto document = packs.lookup(_months)) { + if (const auto sticker = document->sticker()) { + const auto skipPremiumEffect = false; + _sticker.emplace(_parent, document, skipPremiumEffect, _parent); + _sticker->setDiceIndex(sticker->alt, 1); + _sticker->setGiftBoxSticker(true); + _sticker->initSize(); + } + } } TextState Giveaway::textState(QPoint point, StateRequest request) const { @@ -211,80 +340,29 @@ TextState Giveaway::textState(QPoint point, StateRequest request) const { return result; } - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - - auto symbolAdd = 0; - if (_rowsHeight > 0) { - if (point.y() >= tshift && point.y() < tshift + _rowsHeight) { - //result = TextState(_parent, _rows.getState( - // point - QPoint(padding.left(), tshift), - // paintw, - // width(), - // request.forText())); - } else if (point.y() >= tshift + _rowsHeight) { - symbolAdd += _rows.length(); + for (const auto &channel : _channels) { + if (channel.geometry.contains(point)) { + result.link = channel.link; + return result; } - tshift += _rowsHeight; } - if (_channelsHeight > 0) { - tshift += _channelsHeight; - } - if (_dateHeight > 0) { - if (point.y() >= tshift && point.y() < tshift + _dateHeight) { - //result = TextState(_parent, _date.getState( - // point - QPoint(padding.left(), tshift), - // paintw, - // width(), - // request.forText())); - } else if (point.y() >= tshift + _dateHeight) { - symbolAdd += _date.length(); - } - tshift += _dateHeight; - } - result.symbol += symbolAdd; return result; } -TextSelection Giveaway::adjustSelection( - TextSelection selection, - TextSelectType type) const { - //if (_date.isEmpty() || selection.to <= _rows.length()) { - // return _rows.adjustSelection(selection, type); - //} - //const auto dateSelection = _date.adjustSelection( - // toDateSelection(selection), - // type); - //if (selection.from >= _rows.length()) { - // return fromDateSelection(dateSelection); - //} - //const auto rowsSelection = _rows.adjustSelection(selection, type); - //return { rowsSelection.from, fromDateSelection(dateSelection).to }; - return selection; +bool Giveaway::hideFromName() const { + return !parent()->data()->Has(); } bool Giveaway::hasHeavyPart() const { - return false; + return _subscribedToThumbnails; } void Giveaway::unloadHeavyPart() { -} - -uint16 Giveaway::fullSelectionLength() const { - return 0; -} - -TextForMimeData Giveaway::selectedText(TextSelection selection) const { - //auto rowsResult = _rows.toTextForMimeData(selection); - //auto dateResult = _date.toTextForMimeData(toDateSelection(selection)); - //if (rowsResult.empty()) { - // return dateResult; - //} else if (dateResult.empty()) { - // return rowsResult; - //} - //return rowsResult.append('\n').append(std::move(dateResult)); - return {}; + if (base::take(_subscribedToThumbnails)) { + for (const auto &channel : _channels) { + channel.thumbnail->subscribeToUpdates(nullptr); + } + } } QMargins Giveaway::inBubblePadding() const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index 17dcdbebf7..cdacdade85 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -8,40 +8,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "history/view/media/history_view_media.h" +#include "history/view/media/history_view_sticker.h" namespace Data { struct Giveaway; } // namespace Data +namespace Dialogs::Stories { +class Thumbnail; +} // namespace Dialogs::Stories + namespace HistoryView { -class TextRows final { -public: - void add(Ui::Text::String text, int skipTop); - - [[nodiscard]] bool isEmpty() const; - [[nodiscard]] int maxWidth() const; - [[nodiscard]] int minHeight() const; - - [[nodiscard]] int countHeight(int newWidth) const; - - [[nodiscard]] int length() const; - -private: - struct Row { - Ui::Text::String text; - int skipTop = 0; - }; - std::vector _rows; -}; - -[[nodiscard]] TextSelection UnshiftItemSelection( - TextSelection selection, - const TextRows &byText); -[[nodiscard]] TextSelection ShiftItemSelection( - TextSelection selection, - const TextRows &byText); - class Giveaway final : public Media { public: Giveaway( @@ -67,35 +45,54 @@ public: return true; } - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override; - uint16 fullSelectionLength() const override; - TextForMimeData selectedText(TextSelection selection) const override; + bool hideFromName() const override; void unloadHeavyPart() override; bool hasHeavyPart() const override; private: + using Thumbnail = Dialogs::Stories::Thumbnail; struct Channel { Ui::Text::String name; + std::shared_ptr thumbnail; + QRect geometry; + ClickHandlerPtr link; }; + void paintChannels(Painter &p, const PaintContext &context) const; + int layoutChannels(int x, int y, int available); QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; void fillFromData(not_null giveaway); + void ensureStickerCreated() const; - TextSelection toDateSelection(TextSelection selection) const; - TextSelection fromDateSelection(TextSelection selection) const; - QMargins inBubblePadding() const; + [[nodiscard]] QMargins inBubblePadding() const; - TextRows _rows; + mutable std::optional _sticker; + + Ui::Text::String _prizesTitle; + Ui::Text::String _prizes; + Ui::Text::String _participantsTitle; + Ui::Text::String _participants; std::vector _channels; - TextRows _date; - int _rowsHeight = 0; - int _channelsHeight = 0; - int _dateHeight = 0; + Ui::Text::String _winnersTitle; + Ui::Text::String _winners; + + mutable QColor _channelBg; + mutable std::array _channelCorners; + + int _months = 0; + int _stickerTop = 0; + int _prizesTitleTop = 0; + int _prizesTop = 0; + int _prizesWidth = 0; + int _participantsTitleTop = 0; + int _participantsTop = 0; + int _participantsWidth = 0; + int _winnersTitleTop = 0; + int _winnersTop = 0; + mutable bool _subscribedToThumbnails = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 768107f659..386f4e8684 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -101,6 +101,9 @@ public: [[nodiscard]] virtual bool hideServiceText() const { return false; } + [[nodiscard]] virtual bool hideFromName() const { + return false; + } [[nodiscard]] virtual bool allowsFastShare() const { return false; } diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 937358f1f9..b9e1b631dd 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -38,6 +38,7 @@ namespace { struct SessionProcesses { base::flat_map> byItem; base::flat_map> bySlug; + base::flat_map> byRandomId; base::flat_map paymentStartedByItem; base::flat_map paymentStartedBySlug; rpl::lifetime lifetime; @@ -118,6 +119,28 @@ void CheckoutProcess::Start( j->second->requestActivate(); } +void CheckoutProcess::Start( + InvoicePremiumGiftCode giftCodeInvoice, + Fn reactivate) { + const auto randomId = giftCodeInvoice.randomId; + auto id = InvoiceId{ std::move(giftCodeInvoice) }; + auto &processes = LookupSessionProcesses(SessionFromId(id)); + const auto i = processes.byRandomId.find(randomId); + if (i != end(processes.byRandomId)) { + i->second->setReactivateCallback(std::move(reactivate)); + i->second->requestActivate(); + return; + } + const auto j = processes.byRandomId.emplace( + randomId, + std::make_unique( + std::move(id), + Mode::Payment, + std::move(reactivate), + PrivateTag{})).first; + j->second->requestActivate(); +} + std::optional CheckoutProcess::InvoicePaid( not_null item) { const auto session = &item->history()->session(); @@ -139,7 +162,8 @@ std::optional CheckoutProcess::InvoicePaid( } else if (i->second.paymentStartedByItem.empty() && i->second.byItem.empty() && i->second.paymentStartedBySlug.empty() - && i->second.bySlug.empty()) { + && i->second.bySlug.empty() + && i->second.byRandomId.empty()) { Processes.erase(i); } return result; @@ -165,7 +189,8 @@ std::optional CheckoutProcess::InvoicePaid( } else if (i->second.paymentStartedByItem.empty() && i->second.byItem.empty() && i->second.paymentStartedBySlug.empty() - && i->second.bySlug.empty()) { + && i->second.bySlug.empty() + && i->second.byRandomId.empty()) { Processes.erase(i); } return result; @@ -192,6 +217,11 @@ void CheckoutProcess::RegisterPaymentStart( return; } } + for (const auto &[randomId, itemProcess] : i->second.byRandomId) { + if (itemProcess.get() == process) { + return; + } + } } void CheckoutProcess::UnregisterPaymentStart( @@ -212,10 +242,16 @@ void CheckoutProcess::UnregisterPaymentStart( break; } } + for (const auto &[randomId, itemProcess] : i->second.byRandomId) { + if (itemProcess.get() == process) { + break; + } + } if (i->second.paymentStartedByItem.empty() && i->second.byItem.empty() && i->second.paymentStartedBySlug.empty() - && i->second.bySlug.empty()) { + && i->second.bySlug.empty() + && i->second.byRandomId.empty()) { Processes.erase(i); } } @@ -497,8 +533,16 @@ void CheckoutProcess::close() { if (k != end(entry.bySlug)) { entry.bySlug.erase(k); } + const auto l = ranges::find( + entry.byRandomId, + this, + [](const auto &pair) { return pair.second.get(); }); + if (l != end(entry.byRandomId)) { + entry.byRandomId.erase(l); + } if (entry.byItem.empty() && entry.bySlug.empty() + && i->second.byRandomId.empty() && entry.paymentStartedByItem.empty() && entry.paymentStartedBySlug.empty()) { Processes.erase(i); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 5aa0502590..84c0624ccf 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -37,6 +37,7 @@ class Form; struct FormUpdate; struct Error; struct InvoiceId; +struct InvoicePremiumGiftCode; enum class Mode { Payment, @@ -68,6 +69,9 @@ public: not_null session, const QString &slug, Fn reactivate); + static void Start( + InvoicePremiumGiftCode giftCodeInvoice, + Fn reactivate); [[nodiscard]] static std::optional InvoicePaid( not_null item); [[nodiscard]] static std::optional InvoicePaid( diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 62682b9dbe..37bb927a8f 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/payments_form.h" #include "main/main_session.h" +#include "data/data_channel.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_user.h" @@ -112,10 +113,21 @@ constexpr auto kPasswordPeriod = 15 * TimeId(60); } // namespace not_null SessionFromId(const InvoiceId &id) { - if (const auto slug = std::get_if(&id.value)) { + if (const auto message = std::get_if(&id.value)) { + return &message->peer->session(); + } else if (const auto slug = std::get_if(&id.value)) { return slug->session; } - return &v::get(id.value).peer->session(); + const auto &giftCode = v::get(id.value); + const auto users = std::get_if( + &giftCode.purpose); + if (users) { + Assert(!users->users.empty()); + return &users->users.front()->session(); + } + const auto &giveaway = v::get( + giftCode.purpose); + return &giveaway.boostPeer->session(); } Form::Form(InvoiceId id, bool receipt) @@ -207,11 +219,10 @@ void Form::loadThumbnail(not_null photo) { } Data::FileOrigin Form::thumbnailFileOrigin() const { - if (const auto slug = std::get_if(&_id.value)) { - return Data::FileOrigin(); + if (const auto message = std::get_if(&_id.value)) { + return FullMsgId(message->peer->id, message->itemId); } - const auto message = v::get(_id.value); - return FullMsgId(message.peer->id, message.itemId); + return Data::FileOrigin(); } QImage Form::prepareGoodThumbnail( @@ -257,13 +268,67 @@ QImage Form::prepareEmptyThumbnail() const { } MTPInputInvoice Form::inputInvoice() const { - if (const auto slug = std::get_if(&_id.value)) { + if (const auto message = std::get_if(&_id.value)) { + return MTP_inputInvoiceMessage( + message->peer->input, + MTP_int(message->itemId.bare)); + } else if (const auto slug = std::get_if(&_id.value)) { return MTP_inputInvoiceSlug(MTP_string(slug->slug)); } - const auto message = v::get(_id.value); - return MTP_inputInvoiceMessage( - message.peer->input, - MTP_int(message.itemId.bare)); + const auto &giftCode = v::get(_id.value); + using Flag = MTPDpremiumGiftCodeOption::Flag; + const auto option = MTP_premiumGiftCodeOption( + MTP_flags((giftCode.storeQuantity ? Flag::f_store_quantity : Flag()) + | (giftCode.storeProduct.isEmpty() + ? Flag() + : Flag::f_store_product)), + MTP_int(giftCode.users), + MTP_int(giftCode.months), + MTP_string(giftCode.storeProduct), + MTP_int(giftCode.storeQuantity), + MTP_string(giftCode.currency), + MTP_long(giftCode.amount)); + const auto users = std::get_if( + &giftCode.purpose); + if (users) { + using Flag = MTPDinputStorePaymentPremiumGiftCode::Flag; + return MTP_inputInvoicePremiumGiftCode( + MTP_inputStorePaymentPremiumGiftCode( + MTP_flags(users->boostPeer ? Flag::f_boost_peer : Flag()), + MTP_vector(ranges::views::all( + users->users + ) | ranges::views::transform([](not_null user) { + return MTPInputUser(user->inputUser); + }) | ranges::to), + users->boostPeer ? users->boostPeer->input : MTPInputPeer(), + MTP_string(giftCode.currency), + MTP_long(giftCode.amount)), + option); + } else { + const auto &giveaway = v::get( + giftCode.purpose); + using Flag = MTPDinputStorePaymentPremiumGiveaway::Flag; + return MTP_inputInvoicePremiumGiftCode( + MTP_inputStorePaymentPremiumGiveaway( + MTP_flags(Flag() + | (giveaway.onlyNewSubscribers + ? Flag::f_only_new_subscribers + : Flag()) + | (giveaway.additionalChannels.empty() + ? Flag() + : Flag::f_additional_peers)), + giveaway.boostPeer->input, + MTP_vector(ranges::views::all( + giveaway.additionalChannels + ) | ranges::views::transform([](not_null c) { + return MTPInputPeer(c->input); + }) | ranges::to()), + MTP_long(giftCode.randomId), + MTP_int(giveaway.untilDate), + MTP_string(giftCode.currency), + MTP_long(giftCode.amount)), + option); + } } void Form::requestForm() { diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index b1e1797c4e..ee7850c16e 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -186,8 +186,34 @@ struct InvoiceSlug { QString slug; }; +struct InvoicePremiumGiftCodeGiveaway { + not_null boostPeer; + std::vector> additionalChannels; + TimeId untilDate = 0; + bool onlyNewSubscribers = false; +}; + +struct InvoicePremiumGiftCodeUsers { + std::vector> users; + ChannelData *boostPeer = nullptr; +}; + +struct InvoicePremiumGiftCode { + std::variant< + InvoicePremiumGiftCodeUsers, + InvoicePremiumGiftCodeGiveaway> purpose; + + uint64 randomId = 0; + QString currency; + uint64 amount = 0; + QString storeProduct; + int storeQuantity = 0; + int users = 0; + int months = 0; +}; + struct InvoiceId { - std::variant value; + std::variant value; }; [[nodiscard]] not_null SessionFromId(const InvoiceId &id); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 2b16826cee..80205783f6 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -932,9 +932,16 @@ storyMentionReadSkipTwice: 7px; storyMentionReadStrokeTwice: 3px; storyMentionButtonSkip: 5px; -chatGiveawayPrizesTop: 4px; +chatGiveawayWidth: 292px; +chatGiveawayStickerTop: -16px; +chatGiveawayPrizesTop: 16px; chatGiveawayPrizesSkip: 4px; chatGiveawayParticipantsTop: 16px; chatGiveawayParticipantsSkip: 4px; -chatGiveawayDateTop: 16px; +chatGiveawayChannelTop: 6px; +chatGiveawayChannelSize: 32px; +chatGiveawayChannelPadding: margins(5px, 7px, 12px, 0px); +chatGiveawayChannelSkip: 8px; +chatGiveawayDateTop: 6px; chatGiveawayDateSkip: 4px; +chatGiveawayBottomSkip: 16px; From caca67933637cfea10f80532a924241f2232e972 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Oct 2023 12:07:27 +0400 Subject: [PATCH 27/99] Add quantity badge to giveaway message. --- Telegram/Resources/langs/lang.strings | 1 + .../view/media/history_view_giveaway.cpp | 91 ++++++++++++++++++- .../view/media/history_view_giveaway.h | 7 ++ Telegram/SourceFiles/ui/chat/chat.style | 4 + 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e3986b8004..256eb21bd7 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2102,6 +2102,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_how_when_new_of_one#other" = "On {date}, Telegram will automatically select {count} random users that joined {channel} after {start_date}"; "lng_prizes_how_when_new_of_many#one" = "On {date}, Telegram will automatically select {count} random user that joined {channel} or other listed channels after {start_date}"; "lng_prizes_how_when_new_of_many#other" = "On {date}, Telegram will automatically select {count} random users that joined {channel} or other listed channels after {start_date}"; +"lng_prizes_badge" = "x{amount}"; "lng_gift_link_title" = "Gift Link"; "lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription."; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 3c637ec98d..5f3e64a24e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/chat/chat_style.h" +#include "ui/chat/message_bubble.h" #include "ui/text/text_utilities.h" #include "ui/widgets/tooltip.h" #include "ui/painter.h" @@ -83,21 +84,22 @@ Giveaway::~Giveaway() { void Giveaway::fillFromData(not_null giveaway) { _months = giveaway->months; + _quantity = giveaway->quantity; _prizesTitle.setText( st::semiboldTextStyle, - tr::lng_prizes_title(tr::now, lt_count, giveaway->quantity), + tr::lng_prizes_title(tr::now, lt_count, _quantity), kDefaultTextOptions); - const auto duration = (giveaway->months < 12) - ? tr::lng_months(tr::now, lt_count, giveaway->months) - : tr::lng_years(tr::now, lt_count, giveaway->months / 12); + const auto duration = (_months < 12) + ? tr::lng_months(tr::now, lt_count, _months) + : tr::lng_years(tr::now, lt_count, _months / 12); _prizes.setMarkedText( st::defaultTextStyle, tr::lng_prizes_about( tr::now, lt_count, - giveaway->quantity, + _quantity, lt_duration, Ui::Text::Bold(duration), Ui::Text::RichLangValue), @@ -242,6 +244,7 @@ void Giveaway::draw(Painter &p, const PaintContext &context) const { if (_sticker) { _sticker->draw(p, context, sticker); + paintBadge(p, context); } else { ensureStickerCreated(); } @@ -268,6 +271,41 @@ void Giveaway::draw(Painter &p, const PaintContext &context) const { paintChannels(p, context); } +void Giveaway::paintBadge(Painter &p, const PaintContext &context) const { + validateBadge(context); + + const auto badge = _badge.size() / _badge.devicePixelRatio(); + const auto left = (width() - badge.width()) / 2; + const auto top = st::chatGiveawayBadgeTop; + const auto rect = QRect(left, top, badge.width(), badge.height()); + const auto paintContent = [&](QPainter &q) { + q.drawImage(rect.topLeft(), _badge); + }; + + { + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(context.messageStyle()->msgFileBg); + const auto half = st::chatGiveawayBadgeStroke / 2.; + const auto inner = QRectF(rect).marginsRemoved( + { half, half, half, half }); + const auto radius = inner.height() / 2.; + p.drawRoundedRect(inner, radius, radius); + } + + if (!usesBubblePattern(context)) { + paintContent(p); + } else { + Ui::PaintPatternBubblePart( + p, + context.viewport, + context.bubblesPattern->pixmap, + rect, + paintContent, + _badgeCache); + } +} + void Giveaway::paintChannels( Painter &p, const PaintContext &context) const { @@ -333,6 +371,49 @@ void Giveaway::ensureStickerCreated() const { } } +void Giveaway::validateBadge(const PaintContext &context) const { + const auto stm = context.messageStyle(); + const auto &badgeFg = stm->historyFileRadialFg->c; + const auto &badgeBorder = stm->msgBg->c; + if (!_badge.isNull() + && _badgeFg == badgeFg + && _badgeBorder == badgeBorder) { + return; + } + const auto &font = st::chatGiveawayBadgeFont; + _badgeFg = badgeFg; + _badgeBorder = badgeBorder; + const auto text = tr::lng_prizes_badge( + tr::now, + lt_amount, + QString::number(_quantity)); + const auto width = font->width(text); + const auto inner = QRect(0, 0, width, font->height); + const auto rect = inner.marginsAdded(st::chatGiveawayBadgePadding); + const auto size = rect.size(); + const auto ratio = style::DevicePixelRatio(); + _badge = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied); + _badge.setDevicePixelRatio(ratio); + _badge.fill(Qt::transparent); + + auto p = QPainter(&_badge); + auto hq = PainterHighQualityEnabler(p); + p.setPen(QPen(_badgeBorder, st::chatGiveawayBadgeStroke * 1.)); + p.setBrush(Qt::NoBrush); + const auto half = st::chatGiveawayBadgeStroke / 2.; + const auto smaller = QRectF( + rect.translated(-rect.topLeft()) + ).marginsRemoved({ half, half, half, half }); + const auto radius = smaller.height() / 2.; + p.drawRoundedRect(smaller, radius, radius); + p.setPen(_badgeFg); + p.setFont(font); + p.drawText( + st::chatGiveawayBadgePadding.left(), + st::chatGiveawayBadgePadding.top() + font->ascent, + text); +} + TextState Giveaway::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index cdacdade85..1135396ed8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -59,6 +59,7 @@ private: ClickHandlerPtr link; }; + void paintBadge(Painter &p, const PaintContext &context) const; void paintChannels(Painter &p, const PaintContext &context) const; int layoutChannels(int x, int y, int available); QSize countOptimalSize() override; @@ -66,6 +67,7 @@ private: void fillFromData(not_null giveaway); void ensureStickerCreated() const; + void validateBadge(const PaintContext &context) const; [[nodiscard]] QMargins inBubblePadding() const; @@ -80,9 +82,14 @@ private: Ui::Text::String _winners; mutable QColor _channelBg; + mutable QColor _badgeFg; + mutable QColor _badgeBorder; mutable std::array _channelCorners; + mutable QImage _badge; + mutable QImage _badgeCache; int _months = 0; + int _quantity = 0; int _stickerTop = 0; int _prizesTitleTop = 0; int _prizesTop = 0; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 80205783f6..8fb9be0331 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -934,6 +934,10 @@ storyMentionButtonSkip: 5px; chatGiveawayWidth: 292px; chatGiveawayStickerTop: -16px; +chatGiveawayBadgeFont: font(12px bold); +chatGiveawayBadgeTop: 106px; +chatGiveawayBadgePadding: margins(7px, 1px, 5px, 3px); +chatGiveawayBadgeStroke: 2px; chatGiveawayPrizesTop: 16px; chatGiveawayPrizesSkip: 4px; chatGiveawayParticipantsTop: 16px; From a77131dfd660027e56a59ee51c19b06768393a88 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Oct 2023 16:21:23 +0400 Subject: [PATCH 28/99] Add giveaway prize service message layout. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_premium.cpp | 2 +- .../SourceFiles/boxes/gift_premium_box.cpp | 30 +++++++--- Telegram/SourceFiles/boxes/gift_premium_box.h | 3 + .../SourceFiles/core/local_url_handlers.cpp | 12 +--- .../SourceFiles/data/data_media_types.cpp | 23 ++++---- Telegram/SourceFiles/data/data_media_types.h | 19 ++++-- Telegram/SourceFiles/history/history_item.cpp | 15 +++++ .../view/media/history_view_premium_gift.cpp | 59 ++++++++++++++++--- .../view/media/history_view_premium_gift.h | 7 +-- Telegram/SourceFiles/ui/chat/chat.style | 2 +- 11 files changed, 121 insertions(+), 52 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 256eb21bd7..1ffc626c22 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2119,6 +2119,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_link_used_title" = "Used Gift Link"; "lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription."; "lng_gift_link_used_footer" = "This link was used on {date}."; +"lng_gift_link_expired" = "Gift code link expired"; "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 936715834b..05048e29fa 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -24,7 +24,7 @@ namespace { .from = peerFromMTP(data.vfrom_id()), .to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(), .date = data.vdate().v, - .used = false,// data.vused_date().value_or_empty(), + .used = data.vused_date().value_or_empty(), .months = data.vmonths().v, }; } diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 96be412ec7..f797aa116f 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -287,10 +287,7 @@ struct GiftCodeLink { const auto available = outer - skipRight - skipLeft; const auto use = std::min(textWidth, available); state->label.resizeToWidth(use); - const auto left = (outer >= 2 * skipRight + textWidth) - ? ((outer - textWidth) / 2) - : (outer - skipRight - use - skipLeft); - state->label.move(left, 0); + state->label.move(outer - skipRight - use - skipLeft, 0); }, raw->lifetime()); raw->paintRequest() | rpl::start_with_next([=] { @@ -516,10 +513,12 @@ void GiftCodeBox( table, tr::lng_gift_link_label_reason(), tr::lng_gift_link_reason_giveaway()); - AddTableRow( - table, - tr::lng_gift_link_label_date(), - rpl::single(langDateTime(base::unixtime::parse(current.date)))); + if (current.date) { + AddTableRow( + table, + tr::lng_gift_link_label_date(), + rpl::single(langDateTime(base::unixtime::parse(current.date)))); + } auto shareLink = tr::lng_gift_link_also_send_link( ) | rpl::map([](const QString &text) { @@ -584,3 +583,18 @@ void GiftCodeBox( button->resizeToWidth(buttonWidth); }, button->lifetime()); } + +void ResolveGiftCode( + 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->session().api().premium().checkGiftCode( + slug, + crl::guard(controller, done)); +} diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index 9fe4c972e5..6fbac2b76a 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -42,3 +42,6 @@ void GiftCodeBox( not_null box, not_null controller, const QString &slug); +void ResolveGiftCode( + not_null controller, + const QString &slug); diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 799a192ec4..e8121204da 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -351,17 +351,7 @@ bool ResolveUsernameOrPhone( const auto appnameParam = params.value(u"appname"_q); if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) { - const auto done = [=](Api::GiftCode code) { - if (!code) { - controller->showToast(u"Gift code link expired"_q); - } else { - controller->show( - Box(GiftCodeBox, controller, appnameParam)); - } - }; - controller->session().api().premium().checkGiftCode( - appnameParam, - crl::guard(controller, done)); + ResolveGiftCode(controller, appnameParam); return true; } diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index ca947219d6..916d9e5bcd 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -1908,21 +1908,28 @@ MediaGiftBox::MediaGiftBox( not_null parent, not_null from, int months) +: MediaGiftBox(parent, from, GiftCode{ .months = months }) { +} + +MediaGiftBox::MediaGiftBox( + not_null parent, + not_null from, + GiftCode data) : Media(parent) , _from(from) -, _months(months) { +, _data(std::move(data)) { } std::unique_ptr MediaGiftBox::clone(not_null parent) { - return std::make_unique(parent, _from, _months); + return std::make_unique(parent, _from, _data); } not_null MediaGiftBox::from() const { return _from; } -int MediaGiftBox::months() const { - return _months; +const GiftCode &MediaGiftBox::data() const { + return _data; } TextWithEntities MediaGiftBox::notificationText() const { @@ -1954,14 +1961,6 @@ std::unique_ptr MediaGiftBox::createView( std::make_unique(message, this)); } -bool MediaGiftBox::activated() const { - return _activated; -} - -void MediaGiftBox::setActivated(bool activated) { - _activated = activated; -} - MediaWallPaper::MediaWallPaper( not_null parent, const WallPaper &paper) diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 0ab5f51a80..1704e2ad3a 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -98,6 +98,13 @@ struct Giveaway { bool all = false; }; +struct GiftCode { + QString slug; + ChannelData *channel = nullptr; + int months = 0; + bool viaGiveaway = false; +}; + class Media { public: Media(not_null parent); @@ -526,14 +533,15 @@ public: not_null parent, not_null from, int months); + MediaGiftBox( + not_null parent, + not_null from, + GiftCode data); std::unique_ptr clone(not_null parent) override; [[nodiscard]] not_null from() const; - [[nodiscard]] int months() const; - - [[nodiscard]] bool activated() const; - void setActivated(bool activated); + [[nodiscard]] const GiftCode &data() const; TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; @@ -548,8 +556,7 @@ public: private: not_null _from; - int _months = 0; - bool _activated = false; + GiftCode _data; }; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index e84631f230..0c106f19f2 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4612,6 +4612,21 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { if (const auto paper = Data::WallPaper::Create(session, attached)) { _media = std::make_unique(this, *paper); } + }, [&](const MTPDmessageActionGiftCode &data) { + const auto boostedId = data.vboost_peer() + ? peerToChannel(peerFromMTP(*data.vboost_peer())) + : ChannelId(); + _media = std::make_unique( + this, + _from, + Data::GiftCode{ + .slug = qs(data.vslug()), + .channel = (peerIsChannel(boostedId) + ? history()->owner().channel(boostedId).get() + : nullptr), + .months = data.vmonths().v, + .viaGiveaway = data.is_via_giveaway(), + }); }, [](const auto &) { }); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp index 7df009fde1..fdb3943b2e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -7,15 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_premium_gift.h" +#include "boxes/gift_premium_box.h" // ResolveGiftCode #include "chat_helpers/stickers_gift_box_pack.h" #include "core/click_handler_types.h" // ClickHandlerContext #include "data/data_document.h" +#include "data/data_channel.h" #include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_premium.h" // Settings::ShowGiftPremium +#include "ui/text/text_utilities.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" @@ -37,7 +40,8 @@ PremiumGift::PremiumGift( not_null parent, not_null gift) : _parent(parent) -, _gift(gift) { +, _gift(gift) +, _data(gift->data()) { } PremiumGift::~PremiumGift() = default; @@ -51,27 +55,59 @@ QSize PremiumGift::size() { } QString PremiumGift::title() { - return tr::lng_premium_summary_title(tr::now); + return _data.slug.isEmpty() + ? tr::lng_premium_summary_title(tr::now) + : tr::lng_prize_title(tr::now); } TextWithEntities PremiumGift::subtitle() { - return { FormatGiftMonths(_gift->months()) }; + if (_data.slug.isEmpty()) { + return { FormatGiftMonths(_data.months) }; + } + const auto duration = (_data.months < 12) + ? tr::lng_months(tr::now, lt_count, _data.months) + : tr::lng_years(tr::now, lt_count, _data.months / 12); + const auto name = _data.channel ? _data.channel->name() : "channel"; + auto result = (_data.viaGiveaway + ? tr::lng_prize_about + : tr::lng_prize_gift_about)( + tr::now, + lt_channel, + Ui::Text::Bold(name), + Ui::Text::RichLangValue); + result.append("\n\n"); + result.append((_data.viaGiveaway + ? tr::lng_prize_duration + : tr::lng_prize_gift_duration)( + tr::now, + lt_duration, + Ui::Text::Bold(duration), + Ui::Text::RichLangValue)); + return result; } QString PremiumGift::button() { - return tr::lng_sticker_premium_view(tr::now); + return _data.slug.isEmpty() + ? tr::lng_sticker_premium_view(tr::now) + : tr::lng_prize_open(tr::now); } ClickHandlerPtr PremiumGift::createViewLink() { const auto from = _gift->from(); const auto to = _parent->history()->peer; - const auto months = _gift->months(); + const auto data = _gift->data(); return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { - const auto me = (from->id == controller->session().userPeerId()); - const auto peer = me ? to : from; - Settings::ShowGiftPremium(controller, peer, months, me); + if (data.slug.isEmpty()) { + const auto selfId = controller->session().userPeerId(); + const auto self = (from->id == selfId); + const auto peer = self ? to : from; + const auto months = data.months; + Settings::ShowGiftPremium(controller, peer, months, self); + } else { + ResolveGiftCode(controller, data.slug); + } } }); } @@ -91,6 +127,10 @@ void PremiumGift::draw( } } +bool PremiumGift::hideServiceText() { + return !_data.slug.isEmpty(); +} + void PremiumGift::stickerClearLoopPlayed() { if (_sticker) { _sticker->stickerClearLoopPlayed(); @@ -120,8 +160,9 @@ void PremiumGift::ensureStickerCreated() const { return; } const auto &session = _parent->history()->session(); + const auto months = _gift->data().months; auto &packs = session.giftBoxStickersPacks(); - if (const auto document = packs.lookup(_gift->months())) { + if (const auto document = packs.lookup(months)) { if (const auto sticker = document->sticker()) { const auto skipPremiumEffect = false; _sticker.emplace(_parent, document, skipPremiumEffect, _parent); diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h index f3839291db..e43c5f937b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { class MediaGiftBox; +struct GiftCode; } // namespace Data namespace HistoryView { @@ -35,10 +36,7 @@ public: const QRect &geometry) override; ClickHandlerPtr createViewLink() override; - bool hideServiceText() override { - return false; - } - + bool hideServiceText() override; void stickerClearLoopPlayed() override; std::unique_ptr stickerTakePlayer( not_null data, @@ -52,6 +50,7 @@ private: const not_null _parent; const not_null _gift; + const Data::GiftCode &_data; mutable std::optional _sticker; }; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 8fb9be0331..acb0af9a97 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -851,7 +851,7 @@ searchInChatPeerList: PeerList(defaultPeerList) { item: searchInChatPeerListItem; } -msgServiceGiftBoxSize: size(206px, 231px); // Plus msgServiceGiftBoxTopSkip. +msgServiceGiftBoxSize: size(236px, 231px); // Plus msgServiceGiftBoxTopSkip. msgServiceGiftBoxRadius: 20px; msgServiceGiftBoxTopSkip: 4px; msgServiceGiftBoxButtonHeight: 32px; From 4240568ea5406e4e83564e918500f9ea0b0f5079 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Oct 2023 10:49:22 +0400 Subject: [PATCH 29/99] Pass FullReplyTo everywhere. --- Telegram/SourceFiles/api/api_bot.cpp | 18 +- Telegram/SourceFiles/api/api_common.cpp | 6 +- Telegram/SourceFiles/api/api_polls.cpp | 2 +- Telegram/SourceFiles/api/api_sending.cpp | 4 +- Telegram/SourceFiles/apiwrap.cpp | 23 +- .../boxes/reactions_settings_box.cpp | 8 +- .../SourceFiles/chat_helpers/bot_command.h | 2 +- .../SourceFiles/core/click_handler_types.cpp | 1 - Telegram/SourceFiles/data/data_drafts.cpp | 45 +- Telegram/SourceFiles/data/data_drafts.h | 16 +- Telegram/SourceFiles/data/data_histories.cpp | 42 +- Telegram/SourceFiles/data/data_histories.h | 21 +- Telegram/SourceFiles/data/data_msg_id.h | 32 +- Telegram/SourceFiles/dialogs/dialogs_key.h | 3 +- .../admin_log/history_admin_log_inner.cpp | 2 +- .../admin_log/history_admin_log_inner.h | 2 +- Telegram/SourceFiles/history/history.cpp | 19 +- Telegram/SourceFiles/history/history.h | 6 +- .../history/history_inner_widget.cpp | 6 +- .../history/history_inner_widget.h | 2 +- Telegram/SourceFiles/history/history_item.cpp | 236 +++++------ Telegram/SourceFiles/history/history_item.h | 9 +- .../history/history_item_components.cpp | 399 ++++++++++++------ .../history/history_item_components.h | 103 +++-- .../history/history_item_helpers.cpp | 43 +- .../history/history_item_helpers.h | 6 +- .../SourceFiles/history/history_widget.cpp | 248 ++++++----- Telegram/SourceFiles/history/history_widget.h | 14 +- .../history_view_compose_controls.cpp | 109 ++--- .../controls/history_view_compose_controls.h | 5 +- .../controls/history_view_forward_panel.cpp | 4 +- .../history/view/history_view_bottom_info.cpp | 2 +- .../view/history_view_context_menu.cpp | 2 +- .../history/view/history_view_element.cpp | 14 +- .../history/view/history_view_element.h | 4 +- .../history/view/history_view_list_widget.cpp | 14 +- .../history/view/history_view_list_widget.h | 8 +- .../history/view/history_view_message.cpp | 23 +- .../view/history_view_replies_section.cpp | 33 +- .../view/history_view_replies_section.h | 4 +- .../view/history_view_translate_tracker.cpp | 2 +- .../history/view/media/history_view_gif.cpp | 4 +- .../media/history_view_media_unwrapped.cpp | 9 +- Telegram/SourceFiles/mainwidget.cpp | 7 +- Telegram/SourceFiles/mainwidget.h | 2 +- .../media/view/media_view_overlay_widget.cpp | 2 +- .../SourceFiles/storage/storage_account.cpp | 88 ++-- .../SourceFiles/storage/storage_account.h | 2 +- .../SourceFiles/support/support_helper.cpp | 3 +- .../window/notifications_manager.cpp | 12 +- .../SourceFiles/window/window_peer_menu.cpp | 24 +- .../SourceFiles/window/window_peer_menu.h | 3 +- .../window/window_session_controller.cpp | 10 +- 53 files changed, 964 insertions(+), 744 deletions(-) diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 65826aa2f3..b45474fb58 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -169,9 +169,7 @@ void SendBotCallbackData( void HideSingleUseKeyboard( not_null controller, not_null item) { - controller->content()->hideSingleUseKeyboard( - item->history()->peer, - item->id); + controller->content()->hideSingleUseKeyboard(item->fullId()); } } // namespace @@ -312,7 +310,9 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::Default: { // Copy string before passing it to the sending method // because the original button can be destroyed inside. - const auto replyTo = item->isRegular() ? item->id : 0; + const auto replyTo = item->isRegular() + ? item->fullId() + : FullMsgId(); controller->content()->sendBotCommand({ .peer = item->history()->peer, .command = QString(button->text), @@ -363,7 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::RequestPhone: { HideSingleUseKeyboard(controller, item); - const auto itemId = item->id; + const auto itemId = item->fullId(); const auto topicRootId = item->topicRootId(); const auto history = item->history(); controller->show(Ui::MakeConfirmBox({ @@ -376,7 +376,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { auto action = Api::SendAction(history); action.clearDraft = false; action.replyTo = { - .msgId = itemId, + .messageId = itemId, .topicRootId = topicRootId, }; history->session().api().shareContact( @@ -397,13 +397,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { chosen |= PollData::Flag::Quiz; } } - const auto replyToId = MsgId(0); - const auto topicRootId = MsgId(0); + const auto replyTo = FullReplyTo(); Window::PeerMenuCreatePoll( controller, item->history()->peer, - replyToId, - topicRootId, + replyTo, chosen, disabled); } break; diff --git a/Telegram/SourceFiles/api/api_common.cpp b/Telegram/SourceFiles/api/api_common.cpp index 0a44a916f0..cfb1e72207 100644 --- a/Telegram/SourceFiles/api/api_common.cpp +++ b/Telegram/SourceFiles/api/api_common.cpp @@ -19,8 +19,8 @@ SendAction::SendAction( SendOptions options) : history(thread->owningHistory()) , options(options) -, replyTo({ .msgId = thread->topicRootId() }) { - replyTo.topicRootId = replyTo.msgId; +, replyTo({ .messageId = { history->peer->id, thread->topicRootId() } }) { + replyTo.topicRootId = replyTo.messageId.msg; } SendOptions DefaultSendWhenOnlineOptions() { @@ -31,7 +31,7 @@ SendOptions DefaultSendWhenOnlineOptions() { } MTPInputReplyTo SendAction::mtpReplyTo() const { - return Data::ReplyToForMTP(&history->owner(), replyTo); + return Data::ReplyToForMTP(history, replyTo); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 227b6e9dba..7699894cfb 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -43,7 +43,7 @@ void Polls::create( const auto history = action.history; const auto peer = history->peer; - const auto topicRootId = action.replyTo.msgId + const auto topicRootId = action.replyTo.messageId ? action.replyTo.topicRootId : 0; auto sendFlags = MTPmessages_SendMedia::Flags(0); diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 291a2675a8..09001ae357 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -368,9 +368,9 @@ void SendConfirmedFile( if (!isEditing) { const auto histories = &session->data().histories(); - file->to.replyTo.msgId = histories->convertTopicReplyToId( + file->to.replyTo.messageId = histories->convertTopicReplyToId( history, - file->to.replyTo.msgId); + file->to.replyTo.messageId); file->to.replyTo.topicRootId = histories->convertTopicReplyToId( history, file->to.replyTo.topicRootId); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index affdd55244..006a99d51f 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2134,16 +2134,12 @@ void ApiWrap::saveDraftsToCloud() { } auto flags = MTPmessages_SaveDraft::Flags(0); - auto replyFlags = MTPDinputReplyToMessage::Flags(0); auto &textWithTags = cloudDraft->textWithTags; if (cloudDraft->previewState != Data::PreviewState::Allowed) { flags |= MTPmessages_SaveDraft::Flag::f_no_webpage; } - if (cloudDraft->msgId || cloudDraft->topicRootId) { + if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) { flags |= MTPmessages_SaveDraft::Flag::f_reply_to; - if (cloudDraft->topicRootId) { - replyFlags |= MTPDinputReplyToMessage::Flag::f_top_msg_id; - } } if (!textWithTags.tags.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_entities; @@ -2156,15 +2152,7 @@ void ApiWrap::saveDraftsToCloud() { history->startSavingCloudDraft(topicRootId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), - MTP_inputReplyToMessage( - MTP_flags(replyFlags), - MTP_int(cloudDraft->msgId - ? cloudDraft->msgId - : cloudDraft->topicRootId), - MTP_int(cloudDraft->topicRootId), - MTPInputPeer(), // reply_to_peer_id - MTPstring(), // quote_text - MTPVector()), // quote_entities + ReplyToForMTP(history, cloudDraft->reply), history->peer->input, MTP_string(textWithTags.text), entities @@ -3586,9 +3574,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { action.generateLocal = true; sendAction(action); - const auto replyToId = action.replyTo.msgId; - const auto replyTo = replyToId - ? peer->owner().message(peer, replyToId) + const auto replyTo = action.replyTo.messageId + ? peer->owner().message(action.replyTo.messageId) : nullptr; const auto topicRootId = replyTo ? replyTo->topicRootId() @@ -3789,7 +3776,7 @@ void ApiWrap::sendInlineResult( ? (*localMessageId) : _session->data().nextLocalMessageId()); const auto randomId = base::RandomValue(); - const auto topicRootId = action.replyTo.msgId + const auto topicRootId = action.replyTo.messageId ? action.replyTo.topicRootId : 0; diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index f0282306be..9b375e4c82 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -71,7 +71,7 @@ AdminLog::OwnedItem GenerateItem( not_null delegate, not_null history, PeerId from, - MsgId replyTo, + FullMsgId replyTo, const QString &text) { Expects(history->peer->isUser()); @@ -81,7 +81,7 @@ AdminLog::OwnedItem GenerateItem( | MessageFlag::HasFromId | MessageFlag::HasReplyInfo), UserId(), // via - FullReplyTo{ .msgId = replyTo }, + FullReplyTo{ .messageId = replyTo }, base::unixtime::now(), // date from, QString(), // postAuthor @@ -143,13 +143,13 @@ void AddMessage( GenerateUser( history, tr::lng_settings_chat_message_reply_from(tr::now)), - 0, + FullMsgId(), tr::lng_settings_chat_message_reply(tr::now)); auto message = GenerateItem( state->delegate.get(), history, history->peer->id, - state->reply->data()->fullId().msg, + state->reply->data()->fullId(), tr::lng_settings_chat_message(tr::now)); const auto view = message.get(); state->item = std::move(message); diff --git a/Telegram/SourceFiles/chat_helpers/bot_command.h b/Telegram/SourceFiles/chat_helpers/bot_command.h index d5e0c95117..b91ef69034 100644 --- a/Telegram/SourceFiles/chat_helpers/bot_command.h +++ b/Telegram/SourceFiles/chat_helpers/bot_command.h @@ -16,7 +16,7 @@ struct SendCommandRequest { not_null peer; QString command; FullMsgId context; - MsgId replyTo = 0; + FullReplyTo replyTo; }; [[nodiscard]] QString WrapCommandInChat( diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index e3bbba7d12..f796619713 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -308,7 +308,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const { .peer = peer, .command = _cmd, .context = my.itemId, - .replyTo = 0, }); } } diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index f0b937ca89..807af94041 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/message_field.h" #include "history/history.h" #include "history/history_widget.h" +#include "history/history_item_components.h" #include "main/main_session.h" #include "data/data_session.h" #include "mainwidget.h" @@ -21,14 +22,12 @@ namespace Data { Draft::Draft( const TextWithTags &textWithTags, - MsgId msgId, - MsgId topicRootId, + FullReplyTo reply, const MessageCursor &cursor, PreviewState previewState, mtpRequestId saveRequestId) : textWithTags(textWithTags) -, msgId(msgId) -, topicRootId(topicRootId) +, reply(std::move(reply)) , cursor(cursor) , previewState(previewState) , saveRequestId(saveRequestId) { @@ -36,13 +35,11 @@ Draft::Draft( Draft::Draft( not_null field, - MsgId msgId, - MsgId topicRootId, + FullReplyTo reply, PreviewState previewState, mtpRequestId saveRequestId) : textWithTags(field->getTextWithTags()) -, msgId(msgId) -, topicRootId(topicRootId) +, reply(std::move(reply)) , cursor(field) , previewState(previewState) { } @@ -64,22 +61,26 @@ void ApplyPeerCloudDraft( session, draft.ventities().value_or_empty())) }; - auto replyTo = MsgId(); - if (const auto reply = draft.vreply_to()) { - reply->match([&](const MTPDmessageReplyHeader &data) { - if (!data.vreply_to_peer_id() - || (peerFromMTP(*data.vreply_to_peer_id()) == peerId)) { - replyTo = data.vreply_to_msg_id().value_or_empty(); - } else { - // #TODO replies - } - }, [&](const MTPDmessageReplyStoryHeader &data) { - }); - } + const auto reply = draft.vreply_to() + ? ReplyFieldsFromMTP(history, *draft.vreply_to()) + : ReplyFields(); + const auto replyPeerId = reply.externalPeerId + ? reply.externalPeerId + : peerId; auto cloudDraft = std::make_unique( textWithTags, - replyTo, - topicRootId, + FullReplyTo{ + .messageId = FullMsgId(replyPeerId, reply.messageId), + .quote = TextWithTags{ + reply.quote.text, + TextUtilities::ConvertEntitiesToTextTags( + reply.quote.entities), + }, + .storyId = (reply.storyId + ? FullStoryId(replyPeerId, reply.storyId) + : FullStoryId()), + .topicRootId = topicRootId, + }, MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax), (draft.is_no_webpage() ? Data::PreviewState::Cancelled diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h index 5af044019f..ef295807c4 100644 --- a/Telegram/SourceFiles/data/data_drafts.h +++ b/Telegram/SourceFiles/data/data_drafts.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_msg_id.h" + namespace Ui { class InputField; } // namespace Ui @@ -38,22 +40,19 @@ struct Draft { Draft() = default; Draft( const TextWithTags &textWithTags, - MsgId msgId, - MsgId topicRootId, + FullReplyTo reply, const MessageCursor &cursor, PreviewState previewState, mtpRequestId saveRequestId = 0); Draft( not_null field, - MsgId msgId, - MsgId topicRootId, + FullReplyTo reply, PreviewState previewState, mtpRequestId saveRequestId = 0); TimeId date = 0; TextWithTags textWithTags; - MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft - MsgId topicRootId = 0; + FullReplyTo reply; // reply.messageId.msg is editMsgId for edit draft. MessageCursor cursor; PreviewState previewState = PreviewState::Allowed; mtpRequestId saveRequestId = 0; @@ -167,7 +166,8 @@ using HistoryDrafts = base::flat_map>; [[nodiscard]] inline bool DraftIsNull(const Draft *draft) { return !draft - || (!draft->msgId && DraftStringIsEmpty(draft->textWithTags.text)); + || (!draft->reply.messageId + && DraftStringIsEmpty(draft->textWithTags.text)); } [[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) { @@ -179,7 +179,7 @@ using HistoryDrafts = base::flat_map>; return false; } return (a->textWithTags == b->textWithTags) - && (a->msgId == b->msgId) + && (a->reply == b->reply) && (a->previewState == b->previewState); } diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 7443aab62f..ab002d24ff 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -33,8 +33,9 @@ constexpr auto kReadRequestTimeout = 3 * crl::time(1000); } // namespace MTPInputReplyTo ReplyToForMTP( - not_null owner, + not_null history, FullReplyTo replyTo) { + const auto owner = &history->owner(); if (replyTo.storyId) { if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) { if (const auto user = peer->asUser()) { @@ -43,15 +44,19 @@ MTPInputReplyTo ReplyToForMTP( MTP_int(replyTo.storyId.story)); } } - } else if (replyTo.msgId || replyTo.topicRootId) { + } else if (replyTo.messageId || replyTo.topicRootId) { + const auto external = (replyTo.messageId.peer != history->peer->id); using Flag = MTPDinputReplyToMessage::Flag; - return MTP_inputReplyToMessage( // #TODO replies - (replyTo.topicRootId - ? MTP_flags(Flag::f_top_msg_id) - : MTP_flags(0)), - MTP_int(replyTo.msgId ? replyTo.msgId : replyTo.topicRootId), + return MTP_inputReplyToMessage( + MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag()) + | (external ? Flag::f_reply_to_peer_id : Flag())), + MTP_int(replyTo.messageId + ? replyTo.messageId.msg + : replyTo.topicRootId), MTP_int(replyTo.topicRootId), - MTPInputPeer(), // reply_to_peer_id + (external + ? owner->peer(replyTo.messageId.peer)->input + : MTPInputPeer()), MTPstring(), // quote_text MTPVector()); // quote_entities } @@ -914,7 +919,7 @@ int Histories::sendPreparedMessage( not_null history, FullReplyTo replyTo, uint64 randomId, - Fn, FullReplyTo)> message, + Fn, FullReplyTo)> message, Fn done, Fn fail) { if (isCreatingTopic(history, replyTo.topicRootId)) { @@ -929,7 +934,7 @@ int Histories::sendPreparedMessage( } i->second.push_back({ .randomId = randomId, - .replyTo = replyTo.msgId, + .replyTo = replyTo.messageId, .message = std::move(message), .done = std::move(done), .fail = std::move(fail), @@ -939,11 +944,11 @@ int Histories::sendPreparedMessage( return id; } const auto realReplyTo = FullReplyTo{ - .msgId = convertTopicReplyToId(history, replyTo.msgId), - .topicRootId = convertTopicReplyToId(history, replyTo.topicRootId), + .messageId = convertTopicReplyToId(history, replyTo.messageId), .storyId = replyTo.storyId, + .topicRootId = convertTopicReplyToId(history, replyTo.topicRootId), }; - return v::match(message(_owner, realReplyTo), [&](const auto &request) { + return v::match(message(history, realReplyTo), [&](const auto &request) { const auto type = RequestType::Send; return sendRequest(history, type, [=](Fn finish) { const auto session = &_owner->session(); @@ -987,7 +992,7 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) { sendPreparedMessage( history, FullReplyTo{ - .msgId = entry.replyTo, + .messageId = entry.replyTo, .topicRootId = realRoot, }, entry.randomId, @@ -1009,6 +1014,15 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) { } } +FullMsgId Histories::convertTopicReplyToId( + not_null history, + FullMsgId replyToId) const { + const auto id = (history->peer->id == replyToId.peer) + ? convertTopicReplyToId(history, replyToId.msg) + : replyToId.msg; + return { replyToId.peer, id }; +} + MsgId Histories::convertTopicReplyToId( not_null history, MsgId replyToId) const { diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index c98dc352c8..336dcffc36 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -27,7 +27,7 @@ class Session; class Folder; [[nodiscard]] MTPInputReplyTo ReplyToForMTP( - not_null owner, + not_null history, FullReplyTo replyTo); class Histories final { @@ -108,7 +108,7 @@ public: not_null history, FullReplyTo replyTo, uint64 randomId, - Fn, FullReplyTo)> message, + Fn, FullReplyTo)> message, Fn done, Fn fail); @@ -116,14 +116,17 @@ public: }; template static auto PrepareMessage(const Args &...args) - -> Fn, FullReplyTo)> { - return [=](not_null owner, FullReplyTo replyTo) + -> Fn, FullReplyTo)> { + return [=](not_null history, FullReplyTo replyTo) -> RequestType { - return { ReplaceReplyIds(owner, args, replyTo)... }; + return { ReplaceReplyIds(history, args, replyTo)... }; }; } void checkTopicCreated(FullMsgId rootId, MsgId realRoot); + [[nodiscard]] FullMsgId convertTopicReplyToId( + not_null history, + FullMsgId replyToId) const; [[nodiscard]] MsgId convertTopicReplyToId( not_null history, MsgId replyToId) const; @@ -152,8 +155,8 @@ private: }; struct DelayedByTopicMessage { uint64 randomId = 0; - MsgId replyTo = 0; - Fn, FullReplyTo)> message; + FullMsgId replyTo; + Fn, FullReplyTo)> message; Fn done; Fn fail; int requestId = 0; @@ -169,11 +172,11 @@ private: template static auto ReplaceReplyIds( - not_null owner, + not_null history, Arg arg, FullReplyTo replyTo) { if constexpr (std::is_same_v) { - return ReplyToForMTP(owner, replyTo); + return ReplyToForMTP(history, replyTo); } else { return arg; } diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 6a9bc0d1c4..5d367d83e4 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_peer_id.h" +#include "ui/text/text_entity.h" struct MsgId { constexpr MsgId() noexcept = default; @@ -67,21 +68,6 @@ struct FullStoryId { friend inline bool operator==(FullStoryId, FullStoryId) = default; }; -struct FullReplyTo { - MsgId msgId = 0; - MsgId topicRootId = 0; - FullStoryId storyId; - - [[nodiscard]] bool valid() const { - return msgId || (storyId && peerIsUser(storyId.peer)); - } - explicit operator bool() const { - return valid(); - } - friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default; - friend inline bool operator==(FullReplyTo, FullReplyTo) = default; -}; - constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58)); constexpr auto ClientMsgIds = (1LL << 31); constexpr auto EndClientMsgId = MsgId(StartClientMsgId.bare + ClientMsgIds); @@ -169,6 +155,22 @@ struct FullMsgId { Q_DECLARE_METATYPE(FullMsgId); +struct FullReplyTo { + FullMsgId messageId; + TextWithTags quote; + FullStoryId storyId; + MsgId topicRootId = 0; + + [[nodiscard]] bool valid() const { + return messageId || (storyId && peerIsUser(storyId.peer)); + } + explicit operator bool() const { + return valid(); + } + friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default; + friend inline bool operator==(FullReplyTo, FullReplyTo) = default; +}; + struct GlobalMsgId { FullMsgId itemId; uint64 sessionUniqueId = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 4493cb5e1c..d542cfca3f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -108,8 +108,7 @@ struct EntryState { Key key; Section section = Section::History; FilterId filterId = 0; - MsgId rootId = 0; - MsgId currentReplyToId = 0; + FullReplyTo currentReplyTo; friend inline constexpr auto operator<=>(EntryState, EntryState) noexcept = default; 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 2b49f8c1ca..deb786a28d 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -659,7 +659,7 @@ not_null InnerWidget::elementPathShiftGradient() { return _pathGradient.get(); } -void InnerWidget::elementReplyTo(const FullMsgId &to) { +void InnerWidget::elementReplyTo(const FullReplyTo &to) { } void InnerWidget::elementStartInteraction(not_null view) { 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 684fba84f9..36c321810a 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -127,7 +127,7 @@ public: void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; not_null elementPathShiftGradient() override; - void elementReplyTo(const FullMsgId &to) override; + void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction( not_null view) override; void elementStartPremium( diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 6ff35aea56..adafbc39b1 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -177,8 +177,7 @@ void History::takeLocalDraft(not_null from) { && !_drafts.contains(Data::DraftKey::Local(topicRootId))) { // Edit and reply to drafts can't migrate. // Cloud drafts do not migrate automatically. - draft->msgId = 0; - + draft->reply = FullReplyTo(); setLocalDraft(std::move(draft)); } from->clearLocalDraft(topicRootId); @@ -194,6 +193,7 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) { return; } + draft->reply.topicRootId = topicRootId; auto existing = localDraft(topicRootId); if (Data::DraftIsNull(existing) || !existing->date @@ -201,15 +201,13 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) { if (!existing) { setLocalDraft(std::make_unique( draft->textWithTags, - draft->msgId, - topicRootId, + draft->reply, draft->cursor, draft->previewState)); existing = localDraft(topicRootId); } else if (existing != draft) { existing->textWithTags = draft->textWithTags; - existing->msgId = draft->msgId; - existing->topicRootId = draft->topicRootId; + existing->reply = draft->reply; existing->cursor = draft->cursor; existing->previewState = draft->previewState; } @@ -277,8 +275,7 @@ Data::Draft *History::createCloudDraft( if (Data::DraftIsNull(fromDraft)) { setCloudDraft(std::make_unique( TextWithTags(), - 0, - topicRootId, + FullReplyTo(), MessageCursor(), Data::PreviewState::Allowed)); cloudDraft(topicRootId)->date = TimeId(0); @@ -287,18 +284,18 @@ Data::Draft *History::createCloudDraft( if (!existing) { setCloudDraft(std::make_unique( fromDraft->textWithTags, - fromDraft->msgId, - topicRootId, + fromDraft->reply, fromDraft->cursor, fromDraft->previewState)); existing = cloudDraft(topicRootId); } else if (existing != fromDraft) { existing->textWithTags = fromDraft->textWithTags; - existing->msgId = fromDraft->msgId; + existing->reply = fromDraft->reply; existing->cursor = fromDraft->cursor; existing->previewState = fromDraft->previewState; } existing->date = base::unixtime::now(); + existing->reply.topicRootId = topicRootId; } if (const auto thread = threadFor(topicRootId)) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 933832d79c..2ef89cb460 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -329,17 +329,17 @@ public: } void setLocalDraft(std::unique_ptr &&draft) { setDraft( - Data::DraftKey::Local(draft->topicRootId), + Data::DraftKey::Local(draft->reply.topicRootId), std::move(draft)); } void setLocalEditDraft(std::unique_ptr &&draft) { setDraft( - Data::DraftKey::LocalEdit(draft->topicRootId), + Data::DraftKey::LocalEdit(draft->reply.topicRootId), std::move(draft)); } void setCloudDraft(std::unique_ptr &&draft) { setDraft( - Data::DraftKey::Cloud(draft->topicRootId), + Data::DraftKey::Cloud(draft->reply.topicRootId), std::move(draft)); } void clearLocalDraft(MsgId topicRootId) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 23d3f1c4e4..d4b3aaf46d 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -287,7 +287,7 @@ public: return _widget->elementPathShiftGradient(); } - void elementReplyTo(const FullMsgId &to) override { + void elementReplyTo(const FullReplyTo &to) override { if (_widget) { _widget->elementReplyTo(to); } @@ -2206,7 +2206,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }(); if (canReply) { _menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { - _widget->replyToMessage(itemId); + _widget->replyToMessage({ itemId }); }, &st::menuIconReply); } const auto repliesCount = item->repliesCount(); @@ -3481,7 +3481,7 @@ not_null HistoryInner::elementPathShiftGradient() { return _pathGradient.get(); } -void HistoryInner::elementReplyTo(const FullMsgId &to) { +void HistoryInner::elementReplyTo(const FullReplyTo &to) { return _widget->replyToMessage(to); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index e6855ef922..38d68cdde5 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -159,7 +159,7 @@ public: void elementHandleViaClick(not_null bot); bool elementIsChatWide(); not_null elementPathShiftGradient(); - void elementReplyTo(const FullMsgId &to); + void elementReplyTo(const FullReplyTo &to); void elementStartInteraction(not_null view); void elementStartPremium( not_null view, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 0c106f19f2..b1f1f1a611 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -125,23 +125,23 @@ void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { } struct HistoryItem::CreateConfig { - PeerId replyToPeer = 0; - MsgId replyTo = 0; - MsgId replyToTop = 0; - StoryId replyToStory = 0; - bool replyIsTopicPost = false; + ReplyFields reply; + UserId viaBotId = 0; int viewsCount = -1; int forwardsCount = -1; - QString author; - PeerId senderOriginal = 0; - QString senderNameOriginal; - QString forwardPsaType; + QString postAuthor; + MsgId originalId = 0; + TimeId originalDate = 0; + PeerId originalSenderId = 0; + QString originalSenderName; + QString originalPostAuthor; + + QString forwardPsaType; PeerId savedFromPeer = 0; MsgId savedFromMsgId = 0; - QString authorOriginal; - TimeId originalDate = 0; + TimeId editDate = 0; HistoryMessageMarkupData markup; HistoryMessageRepliesData replies; @@ -154,14 +154,14 @@ struct HistoryItem::CreateConfig { void HistoryItem::FillForwardedInfo( CreateConfig &config, const MTPDmessageFwdHeader &data) { - if (const auto fromId = data.vfrom_id()) { - config.senderOriginal = peerFromMTP(*fromId); - } - config.originalDate = data.vdate().v; - config.senderNameOriginal = qs(data.vfrom_name().value_or_empty()); - config.forwardPsaType = qs(data.vpsa_type().value_or_empty()); config.originalId = data.vchannel_post().value_or_empty(); - config.authorOriginal = qs(data.vpost_author().value_or_empty()); + config.originalDate = data.vdate().v; + if (const auto fromId = data.vfrom_id()) { + config.originalSenderId = peerFromMTP(*fromId); + } + config.originalSenderName = qs(data.vfrom_name().value_or_empty()); + config.originalPostAuthor = qs(data.vpost_author().value_or_empty()); + config.forwardPsaType = qs(data.vpsa_type().value_or_empty()); const auto savedFromPeer = data.vsaved_from_peer(); const auto savedFromMsgId = data.vsaved_from_msg_id(); if (savedFromPeer && savedFromMsgId) { @@ -435,21 +435,21 @@ HistoryItem::HistoryItem( const auto originalMedia = original->media(); const auto dropForwardInfo = original->computeDropForwardedInfo(); - config.replyTo = config.replyToTop = topicRootId; - config.replyIsTopicPost = (topicRootId != 0); + config.reply.messageId = config.reply.topMessageId = topicRootId; + config.reply.topicPost = (topicRootId != 0); if (!dropForwardInfo) { - config.originalDate = original->dateOriginal(); + config.originalDate = original->originalDate(); if (const auto info = original->hiddenSenderInfo()) { - config.senderNameOriginal = info->name; - } else if (const auto senderOriginal = original->senderOriginal()) { - config.senderOriginal = senderOriginal->id; - if (senderOriginal->isChannel()) { - config.originalId = original->idOriginal(); + config.originalSenderName = info->name; + } else if (const auto originalSender = original->originalSender()) { + config.originalSenderId = originalSender->id; + if (originalSender->isChannel()) { + config.originalId = original->originalId(); } } else { Unexpected("Corrupt forwarded information in message."); } - config.authorOriginal = original->authorOriginal(); + config.originalPostAuthor = original->originalPostAuthor(); } if (peer->isSelf()) { // @@ -465,12 +465,12 @@ HistoryItem::HistoryItem( //} } if (flags & MessageFlag::HasPostAuthor) { - config.author = postAuthor; + config.postAuthor = postAuthor; } if (const auto fwdViaBot = original->viaBot()) { config.viaBotId = peerToUser(fwdViaBot->id); } else if (originalMedia && originalMedia->game()) { - if (const auto sender = original->senderOriginal()) { + if (const auto sender = original->originalSender()) { if (const auto user = sender->asUser()) { if (user->isBot()) { config.viaBotId = peerToUser(user->id); @@ -482,8 +482,8 @@ HistoryItem::HistoryItem( if (fwdViewsCount > 0) { config.viewsCount = fwdViewsCount; } else if ((isPost() && !isScheduled()) - || (original->senderOriginal() - && original->senderOriginal()->isChannel())) { + || (original->originalSender() + && original->originalSender()->isChannel())) { config.viewsCount = 1; } @@ -1026,7 +1026,7 @@ void HistoryItem::setCommentsItemId(FullMsgId id) { if (id.peer == _history->peer->id) { if (id.msg != this->id) { if (const auto reply = Get()) { - reply->replyToMsgTop = id.msg; + reply->setTopMessageId(id.msg); } } } else if (const auto views = Get()) { @@ -1879,7 +1879,7 @@ void HistoryItem::changeReplyToTopCounter( this, Data::MessageUpdate::Flag::ReplyToTopAdded); } - const auto topId = reply->replyToTop(); + const auto topId = reply->topMessageId(); if (!topId) { return; } @@ -1931,8 +1931,8 @@ void HistoryItem::setRealId(MsgId newId) { _history->owner().requestItemResize(this); if (const auto reply = Get()) { - if (reply->replyToLink()) { - reply->setReplyToLinkFrom(this); + if (reply->link()) { + reply->setLinkFrom(this); } changeReplyToTopCounter(reply, 1); } @@ -2381,14 +2381,14 @@ not_null HistoryItem::author() const { return (isPost() && !isSponsored()) ? _history->peer : from(); } -TimeId HistoryItem::dateOriginal() const { +TimeId HistoryItem::originalDate() const { if (const auto forwarded = Get()) { return forwarded->originalDate; } return date(); } -PeerData *HistoryItem::senderOriginal() const { +PeerData *HistoryItem::originalSender() const { if (const auto forwarded = Get()) { return forwarded->originalSender; } @@ -2416,18 +2416,18 @@ not_null HistoryItem::fromOriginal() const { return from(); } -QString HistoryItem::authorOriginal() const { +QString HistoryItem::originalPostAuthor() const { if (const auto forwarded = Get()) { - return forwarded->originalAuthor; + return forwarded->originalPostAuthor; } else if (const auto msgsigned = Get()) { if (!msgsigned->isAnonymousRank) { - return msgsigned->author; + return msgsigned->postAuthor; } } return QString(); } -MsgId HistoryItem::idOriginal() const { +MsgId HistoryItem::originalId() const { if (const auto forwarded = Get()) { return forwarded->originalId; } @@ -2491,9 +2491,9 @@ void HistoryItem::setForwardsCount(int count) { history()->owner().notifyItemDataChange(this); } -void HistoryItem::setPostAuthor(const QString &author) { +void HistoryItem::setPostAuthor(const QString &postAuthor) { auto msgsigned = Get(); - if (author.isEmpty()) { + if (postAuthor.isEmpty()) { if (!msgsigned) { return; } @@ -2504,10 +2504,10 @@ void HistoryItem::setPostAuthor(const QString &author) { if (!msgsigned) { AddComponents(HistoryMessageSigned::Bit()); msgsigned = Get(); - } else if (msgsigned->author == author) { + } else if (msgsigned->postAuthor == postAuthor) { return; } - msgsigned->author = author; + msgsigned->postAuthor = postAuthor; msgsigned->isAnonymousRank = !isDiscussionPost() && this->author()->isMegagroup(); history()->owner().requestItemResize(this); @@ -2643,20 +2643,10 @@ void HistoryItem::setReplyFields( } } } else if (const auto reply = Get()) { - reply->topicPost = isForumPost; - if ((reply->replyToMsgId != replyTo) - && !IsServerMsgId(reply->replyToMsgId)) { - reply->replyToMsgId = replyTo; - if (!reply->updateData(this)) { - RequestDependentMessageItem( - this, - reply->replyToPeerId, - reply->replyToMsgId); - } - } - if ((reply->replyToMsgTop != replyToTop) - && !IsServerMsgId(reply->replyToMsgTop)) { - reply->replyToMsgTop = replyToTop; + const auto increment = (reply->topMessageId() != replyToTop) + && !IsServerMsgId(reply->topMessageId()); + reply->updateFields(this, replyTo, replyToTop, isForumPost); + if (increment) { changeReplyToTopCounter(reply, 1); } } @@ -2819,14 +2809,22 @@ bool HistoryItem::unread(not_null thread) const { MsgId HistoryItem::replyToId() const { if (const auto reply = Get()) { - return reply->replyToId(); + return reply->messageId(); } return 0; } +FullMsgId HistoryItem::replyToFullId() const { + if (const auto reply = Get()) { + const auto peer = reply->externalPeerId(); + return { peer ? peer : history()->peer->id, reply->messageId() }; + } + return {}; +} + MsgId HistoryItem::replyToTop() const { if (const auto reply = Get()) { - return reply->replyToTop(); + return reply->topMessageId(); } else if (const auto data = GetServiceDependentData()) { return data->topId; } @@ -2835,8 +2833,8 @@ MsgId HistoryItem::replyToTop() const { MsgId HistoryItem::topicRootId() const { if (const auto reply = Get() - ; reply && reply->topicPost) { - return reply->replyToTop(); + ; reply && reply->topicPost()) { + return reply->topMessageId(); } else if (const auto data = GetServiceDependentData() ; data && data->topicPost && data->topId) { return data->topId; @@ -2850,11 +2848,11 @@ MsgId HistoryItem::topicRootId() const { FullStoryId HistoryItem::replyToStory() const { if (const auto reply = Get()) { - if (reply->replyToStoryId) { - const auto peerId = reply->replyToPeerId - ? reply->replyToPeerId + if (reply->storyId()) { + const auto peerId = reply->externalPeerId() + ? reply->externalPeerId() : _history->peer->id; - return { .peer = peerId, .story = reply->replyToStoryId }; + return { .peer = peerId, .story = reply->storyId() }; } } return {}; @@ -2862,9 +2860,9 @@ FullStoryId HistoryItem::replyToStory() const { FullReplyTo HistoryItem::replyTo() const { return { - .msgId = replyToId(), - .topicRootId = topicRootId(), + .messageId = replyToFullId(), .storyId = replyToStory(), + .topicRootId = topicRootId(), }; } @@ -3049,7 +3047,10 @@ const std::vector &HistoryItem::customTextLinks() const { void HistoryItem::createComponents(CreateConfig &&config) { uint64 mask = 0; - if (config.replyTo || config.replyToStory) { + if (config.reply.messageId + || config.reply.externalSenderId + || !config.reply.externalSenderName.isEmpty() + || config.reply.storyId) { mask |= HistoryMessageReply::Bit(); } if (config.viaBotId) { @@ -3058,18 +3059,18 @@ void HistoryItem::createComponents(CreateConfig &&config) { if (config.viewsCount >= 0 || !config.replies.isNull) { mask |= HistoryMessageViews::Bit(); } - if (!config.author.isEmpty()) { + if (!config.postAuthor.isEmpty()) { mask |= HistoryMessageSigned::Bit(); } else if (_history->peer->isMegagroup() // Discussion posts signatures. && config.savedFromPeer - && !config.authorOriginal.isEmpty()) { + && !config.originalPostAuthor.isEmpty()) { const auto savedFrom = _history->owner().peerLoaded( config.savedFromPeer); if (savedFrom && savedFrom->isChannel()) { mask |= HistoryMessageSigned::Bit(); } } else if ((_history->peer->isSelf() || _history->peer->isRepliesChat()) - && !config.authorOriginal.isEmpty()) { + && !config.originalPostAuthor.isEmpty()) { mask |= HistoryMessageSigned::Bit(); } if (config.editDate != TimeId(0)) { @@ -3087,23 +3088,18 @@ void HistoryItem::createComponents(CreateConfig &&config) { UpdateComponents(mask); if (const auto reply = Get()) { - reply->replyToPeerId = config.replyToPeer; - reply->replyToMsgId = config.replyTo; - reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop; - reply->replyToStoryId = config.replyToStory; - reply->storyReply = (config.replyToStory != 0); - reply->topicPost = config.replyIsTopicPost; + reply->set(config.reply); if (!reply->updateData(this)) { - if (reply->replyToMsgId) { + if (const auto messageId = reply->messageId()) { RequestDependentMessageItem( this, - reply->replyToPeerId, - reply->replyToMsgId); - } else if (reply->replyToStoryId) { + reply->externalPeerId(), + reply->messageId()); + } else if (reply->storyId()) { RequestDependentMessageStory( this, - reply->replyToPeerId, - reply->replyToStoryId); + reply->externalPeerId(), + reply->storyId()); } } } @@ -3129,9 +3125,9 @@ void HistoryItem::createComponents(CreateConfig &&config) { edited->date = config.editDate; } if (const auto msgsigned = Get()) { - msgsigned->author = config.author.isEmpty() - ? config.authorOriginal - : config.author; + msgsigned->postAuthor = config.postAuthor.isEmpty() + ? config.originalPostAuthor + : config.postAuthor; msgsigned->isAnonymousRank = !isDiscussionPost() && author()->isMegagroup(); } @@ -3167,9 +3163,9 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) { return; } forwarded->originalDate = config.originalDate; - const auto originalSender = config.senderOriginal - ? config.senderOriginal - : !config.senderNameOriginal.isEmpty() + const auto originalSender = config.originalSenderId + ? config.originalSenderId + : !config.originalSenderName.isEmpty() ? PeerId() : from()->id; forwarded->originalSender = originalSender @@ -3177,11 +3173,11 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) { : nullptr; if (!forwarded->originalSender) { forwarded->hiddenSenderInfo = std::make_unique( - config.senderNameOriginal, + config.originalSenderName, config.imported); } forwarded->originalId = config.originalId; - forwarded->originalAuthor = config.authorOriginal; + forwarded->originalPostAuthor = config.originalPostAuthor; forwarded->psaType = config.forwardPsaType; forwarded->savedFromPeer = _history->owner().peerLoaded( config.savedFromPeer); @@ -3233,7 +3229,7 @@ TextWithEntities HistoryItem::withLocalEntities( : nullptr; if (document) { if (const auto duration = DurationForTimestampLinks(document)) { - const auto context = reply->replyToMsg->fullId(); + const auto context = reply->resolvedMessage->fullId(); return AddTimestampLinks( textWithEntities, duration, @@ -3241,7 +3237,7 @@ TextWithEntities HistoryItem::withLocalEntities( } } else if (webpage) { if (const auto duration = DurationForTimestampLinks(webpage)) { - const auto context = reply->replyToMsg->fullId(); + const auto context = reply->resolvedMessage->fullId(); return AddTimestampLinks( textWithEntities, duration, @@ -3290,19 +3286,28 @@ void HistoryItem::createComponentsHelper( auto config = CreateConfig(); config.viaBotId = viaBotId; if (flags & MessageFlag::HasReplyInfo) { - config.replyTo = replyTo.msgId; - config.replyToStory = replyTo.storyId.story; - config.replyToPeer = replyTo.storyId ? replyTo.storyId.peer : 0; - const auto to = LookupReplyTo(_history, replyTo.msgId); - const auto replyToTop = LookupReplyToTop(to); - config.replyToTop = replyToTop ? replyToTop : replyTo.msgId; + config.reply.messageId = replyTo.messageId.msg; + config.reply.storyId = replyTo.storyId.story; + config.reply.externalPeerId = replyTo.storyId + ? replyTo.storyId.peer + : (replyTo.messageId && replyTo.messageId.peer + != history()->peer->id) + ? replyTo.messageId.peer + : PeerId(); + const auto to = LookupReplyTo(_history, replyTo.messageId); + const auto replyToTop = LookupReplyToTop(_history, to); + config.reply.topMessageId = replyToTop + ? replyToTop + : (replyTo.messageId.peer == history()->peer->id) + ? replyTo.messageId.msg + : MsgId(); const auto forum = _history->asForum(); - config.replyIsTopicPost = LookupReplyIsTopicPost(to) + config.reply.topicPost = LookupReplyIsTopicPost(to) || (to && to->Has()) - || (forum && forum->creating(config.replyToTop)); + || (forum && forum->creating(config.reply.topMessageId)); } config.markup = std::move(markup); - if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor; + if (flags & MessageFlag::HasPostAuthor) config.postAuthor = postAuthor; if (flags & MessageFlag::HasViews) config.viewsCount = 1; createComponents(std::move(config)); @@ -3392,26 +3397,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) { }); } if (const auto reply = data.vreply_to()) { - reply->match([&](const MTPDmessageReplyHeader &data) { - // #TODO replies - if (const auto id = data.vreply_to_msg_id().value_or_empty()) { - if (const auto peer = data.vreply_to_peer_id()) { - config.replyToPeer = peerFromMTP(*peer); - if (config.replyToPeer == _history->peer->id) { - config.replyToPeer = 0; - } - } - const auto owner = &_history->owner(); - config.replyTo = data.is_reply_to_scheduled() - ? owner->scheduledMessages().localMessageId(id) - : id; - config.replyToTop = data.vreply_to_top_id().value_or(id); - config.replyIsTopicPost = data.is_forum_topic(); - } - }, [&](const MTPDmessageReplyStoryHeader &data) { - config.replyToPeer = peerFromUser(data.vuser_id()); - config.replyToStory = data.vstory_id().v; - }); + config.reply = ReplyFieldsFromMTP(history(), *reply); } config.viaBotId = data.vvia_bot_id().value_or_empty(); config.viewsCount = data.vviews().value_or(-1); @@ -3421,7 +3407,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) { : HistoryMessageRepliesData(data.vreplies()); config.markup = HistoryMessageMarkupData(data.vreply_markup()); config.editDate = data.vedit_date().value_or_empty(); - config.author = qs(data.vpost_author().value_or_empty()); + config.postAuthor = qs(data.vpost_author().value_or_empty()); createComponents(std::move(config)); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index d47660393b..c04e6f9de0 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -465,6 +465,7 @@ public: void setText(const TextWithEntities &textWithEntities); [[nodiscard]] MsgId replyToId() const; + [[nodiscard]] FullMsgId replyToFullId() const; [[nodiscard]] MsgId replyToTop() const; [[nodiscard]] MsgId topicRootId() const; [[nodiscard]] FullStoryId replyToStory() const; @@ -473,12 +474,12 @@ public: [[nodiscard]] not_null author() const; - [[nodiscard]] TimeId dateOriginal() const; - [[nodiscard]] PeerData *senderOriginal() const; + [[nodiscard]] TimeId originalDate() const; + [[nodiscard]] PeerData *originalSender() const; [[nodiscard]] const HiddenSenderInfo *hiddenSenderInfo() const; [[nodiscard]] not_null fromOriginal() const; - [[nodiscard]] QString authorOriginal() const; - [[nodiscard]] MsgId idOriginal() const; + [[nodiscard]] QString originalPostAuthor() const; + [[nodiscard]] MsgId originalId() const; [[nodiscard]] bool isEmpty() const; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 30d032bce2..7bcaeb6491 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_item_components.h" +#include "api/api_text_entities.h" #include "base/qt/qt_key_modifiers.h" #include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" @@ -38,6 +39,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_web_page.h" #include "data/data_file_click_handler.h" +#include "data/data_scheduled_messages.h" +#include "data/data_session.h" #include "data/data_stories.h" #include "main/main_session.h" #include "window/window_session_controller.h" @@ -64,7 +67,7 @@ void HistoryMessageVia::create( lt_inline_bot, '@' + bot->username())); link = std::make_shared([bot = this->bot]( - ClickContext context) { + ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { if (base::IsCtrlPressed()) { @@ -183,13 +186,13 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { ? originalSender->name() : hiddenSenderInfo->name) }; - if (!originalAuthor.isEmpty()) { + if (!originalPostAuthor.isEmpty()) { phrase = tr::lng_forwarded_signed( tr::now, lt_channel, name, lt_user, - { .text = originalAuthor }, + { .text = originalPostAuthor }, Ui::Text::WithEntities); } else { phrase = name; @@ -261,6 +264,51 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { } } +ReplyFields ReplyFieldsFromMTP( + not_null history, + const MTPMessageReplyHeader &reply) { + return reply.match([&](const MTPDmessageReplyHeader &data) { + 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()) { + result.messageId = data.is_reply_to_scheduled() + ? owner->scheduledMessages().localMessageId(id) + : id; + result.topMessageId + = data.vreply_to_top_id().value_or(id); + result.topicPost = data.is_forum_topic(); + } + if (const auto header = data.vreply_header()) { + const auto &data = header->data(); + result.externalPostAuthor + = qs(data.vpost_author().value_or_empty()); + result.externalSenderId = data.vfrom_id() + ? peerFromMTP(*data.vfrom_id()) + : PeerId(); + result.externalSenderName + = qs(data.vfrom_name().value_or_empty()); + } + result.quote = TextWithEntities{ + qs(data.vquote_text().value_or_empty()), + Api::EntitiesFromMTP( + &owner->session(), + data.vquote_entities().value_or_empty()), + }; + return result; + }, [&](const MTPDmessageReplyStoryHeader &data) { + return ReplyFields{ + .externalPeerId = peerFromUser(data.vuser_id()), + .storyId = data.vstory_id().v, + }; + }); +} + HistoryMessageReply::HistoryMessageReply() = default; HistoryMessageReply &HistoryMessageReply::operator=( @@ -268,8 +316,8 @@ HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply::~HistoryMessageReply() { // clearData() should be called by holder. - Expects(replyToMsg.empty()); - Expects(replyToVia == nullptr); + Expects(resolvedMessage.empty()); + Expects(originalVia == nullptr); } bool HistoryMessageReply::updateData( @@ -277,166 +325,228 @@ bool HistoryMessageReply::updateData( bool force) { const auto guard = gsl::finally([&] { refreshReplyToMedia(); }); if (!force) { - if ((replyToMsg || !replyToMsgId) - && (replyToStory || !replyToStoryId)) { + if (resolvedMessage || resolvedStory || _deleted) { return true; } } - const auto peerId = replyToPeerId - ? replyToPeerId + const auto peerId = _fields.externalPeerId + ? _fields.externalPeerId : holder->history()->peer->id; - if (!replyToMsg && replyToMsgId) { - replyToMsg = holder->history()->owner().message( + if (!resolvedMessage && _fields.messageId) { + resolvedMessage = holder->history()->owner().message( peerId, - replyToMsgId); - if (replyToMsg) { - if (replyToMsg->isEmpty()) { + _fields.messageId); + if (resolvedMessage) { + if (resolvedMessage->isEmpty()) { // Really it is deleted. - replyToMsg = nullptr; + resolvedMessage = nullptr; force = true; } else { holder->history()->owner().registerDependentMessage( holder, - replyToMsg.get()); + resolvedMessage.get()); } } } - if (!replyToStory && replyToStoryId) { + if (!resolvedStory && _fields.storyId) { const auto maybe = holder->history()->owner().stories().lookup({ peerId, - replyToStoryId, + _fields.storyId, }); if (maybe) { - replyToStory = *maybe; + resolvedStory = *maybe; holder->history()->owner().stories().registerDependentMessage( holder, - replyToStory.get()); + resolvedStory.get()); } else if (maybe.error() == Data::NoStory::Deleted) { force = true; } } - if (replyToMsg || replyToStory) { + const auto external = _fields.externalSenderId + || !_fields.externalSenderName.isEmpty(); + if (resolvedMessage || resolvedStory || (external && force)) { const auto repaint = [=] { holder->customEmojiRepaint(); }; const auto context = Core::MarkedTextContext{ .session = &holder->history()->session(), .customEmojiRepaint = repaint, }; - replyToText.setMarkedText( + const auto text = !_fields.quote.empty() + ? _fields.quote + : resolvedMessage + ? resolvedMessage->inReplyText() + : resolvedStory + ? resolvedStory->inReplyText() + : TextWithEntities{ u"..."_q }; + _text.setMarkedText( st::defaultTextStyle, - (replyToMsg - ? replyToMsg->inReplyText() - : replyToStory->inReplyText()), + text, Ui::DialogTextOptions(), context); updateName(holder); - - setReplyToLinkFrom(holder); - if (replyToMsg && !replyToMsg->Has()) { - if (auto bot = replyToMsg->viaBot()) { - replyToVia = std::make_unique(); - replyToVia->create( + setLinkFrom(holder); + if (resolvedMessage + && !resolvedMessage->Has()) { + if (const auto bot = resolvedMessage->viaBot()) { + originalVia = std::make_unique(); + originalVia->create( &holder->history()->owner(), peerToUser(bot->id)); } } - if (replyToMsg) { - const auto peer = replyToMsg->history()->peer; - replyToColorKey = (!holder->out() + if (resolvedMessage) { + const auto peer = resolvedMessage->history()->peer; + _colorKey = (!holder->out() && (peer->isMegagroup() || peer->isChat()) - && replyToMsg->from()->isUser()) - ? replyToMsg->from()->id - : PeerId(0); + && resolvedMessage->from()->isUser()) + ? resolvedMessage->from()->id + : PeerId(); } else { - replyToColorKey = PeerId(0); + resolvedMessage = 0; } - const auto media = replyToMsg ? replyToMsg->media() : nullptr; + const auto media = resolvedMessage + ? resolvedMessage->media() + : nullptr; if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) { spoiler = nullptr; } else if (!spoiler) { spoiler = std::make_unique(repaint); } } else if (force) { - replyToMsgId = 0; - replyToStoryId = 0; - replyToColorKey = PeerId(0); + if (_fields.messageId || _fields.storyId) { + _deleted = true; + } + _colorKey = 0; spoiler = nullptr; } if (force) { holder->history()->owner().requestItemResize(holder); } - return (replyToMsg || !replyToMsgId) - && (replyToStory || !replyToStoryId); + return resolvedMessage + || resolvedStory + || (external && !_fields.messageId) + || _deleted; } -void HistoryMessageReply::setReplyToLinkFrom( +void HistoryMessageReply::set(ReplyFields fields) { + _fields = std::move(fields); +} + +void HistoryMessageReply::updateFields( + not_null holder, + MsgId messageId, + MsgId topMessageId, + bool topicPost) { + _fields.topicPost = topicPost; + if ((_fields.messageId != messageId) + && !IsServerMsgId(_fields.messageId)) { + _fields.messageId = messageId; + if (!updateData(holder)) { + RequestDependentMessageItem( + holder, + _fields.externalPeerId, + _fields.messageId); + } + } + if ((_fields.topMessageId != topMessageId) + && !IsServerMsgId(_fields.topMessageId)) { + _fields.topMessageId = topMessageId; + } +} + +void HistoryMessageReply::setLinkFrom( not_null holder) { - replyToLnk = replyToMsg - ? JumpToMessageClickHandler(replyToMsg.get(), holder->fullId()) - : replyToStory - ? JumpToStoryClickHandler(replyToStory.get()) + const auto externalPeerId = _fields.externalSenderId; + const auto external = externalPeerId + || !_fields.externalSenderName.isEmpty(); + const auto externalLink = [=](ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + if (externalPeerId) { + controller->showPeerInfo( + controller->session().data().peer(externalPeerId)); + } else { + controller->showToast(u"External reply"_q); + } + } + }; + _link = resolvedMessage + ? JumpToMessageClickHandler(resolvedMessage.get(), holder->fullId()) + : resolvedStory + ? JumpToStoryClickHandler(resolvedStory.get()) + : (external && !_fields.messageId) + ? std::make_shared(externalLink) : nullptr; } +void HistoryMessageReply::setTopMessageId(MsgId topMessageId) { + _fields.topMessageId = topMessageId; +} + void HistoryMessageReply::clearData(not_null holder) { - replyToVia = nullptr; - if (replyToMsg) { + originalVia = nullptr; + if (resolvedMessage) { holder->history()->owner().unregisterDependentMessage( holder, - replyToMsg.get()); - replyToMsg = nullptr; + resolvedMessage.get()); + resolvedMessage = nullptr; } - if (replyToStory) { + if (resolvedStory) { holder->history()->owner().stories().unregisterDependentMessage( holder, - replyToStory.get()); - replyToStory = nullptr; + resolvedStory.get()); + resolvedStory = nullptr; } - replyToMsgId = 0; - replyToStoryId = 0; + _deleted = true; refreshReplyToMedia(); } -PeerData *HistoryMessageReply::replyToFrom( - not_null holder) const { - if (!replyToMsg) { - return nullptr; +PeerData *HistoryMessageReply::sender(not_null holder) const { + if (resolvedStory) { + return resolvedStory->peer(); + } else if (!resolvedMessage) { + if (!_externalSender && _fields.externalSenderId) { + _externalSender = holder->history()->owner().peer( + _fields.externalSenderId); + } + return _externalSender; } else if (holder->Has()) { - if (const auto fwd = replyToMsg->Get()) { - return fwd->originalSender; + // Forward of a reply. Show reply-to original sender. + const auto forwarded + = resolvedMessage->Get(); + if (forwarded) { + return forwarded->originalSender; } } - if (const auto from = replyToMsg->displayFrom()) { + if (const auto from = resolvedMessage->displayFrom()) { return from; } - return replyToMsg->author().get(); + return resolvedMessage->author().get(); } -QString HistoryMessageReply::replyToFromName( +QString HistoryMessageReply::senderName( not_null holder) const { - if (replyToStory) { - return replyToFromName(replyToStory->peer()); - } else if (!replyToMsg) { - return QString(); + if (const auto peer = sender(holder)) { + return senderName(peer); + } else if (!resolvedMessage) { + return _fields.externalSenderName; } else if (holder->Has()) { - if (const auto fwd = replyToMsg->Get()) { - return fwd->originalSender - ? replyToFromName(fwd->originalSender) - : fwd->hiddenSenderInfo->name; + // Forward of a reply. Show reply-to original sender. + const auto forwarded + = resolvedMessage->Get(); + if (forwarded) { + Assert(forwarded->hiddenSenderInfo != nullptr); + return forwarded->hiddenSenderInfo->name; } } - if (const auto from = replyToMsg->displayFrom()) { - return replyToFromName(from); - } - return replyToFromName(replyToMsg->author()); + return QString(); } -QString HistoryMessageReply::replyToFromName( - not_null peer) const { - if (const auto user = replyToVia ? peer->asUser() : nullptr) { +QString HistoryMessageReply::senderName(not_null peer) const { + if (const auto user = originalVia ? peer->asUser() : nullptr) { return user->firstName; } return peer->name(); @@ -444,9 +554,9 @@ QString HistoryMessageReply::replyToFromName( bool HistoryMessageReply::isNameUpdated( not_null holder) const { - if (const auto from = replyToFrom(holder)) { - if (replyToVersion < from->nameVersion()) { - updateName(holder); + if (const auto from = sender(holder)) { + if (_nameVersion < from->nameVersion()) { + updateName(holder, from); return true; } } @@ -454,55 +564,73 @@ bool HistoryMessageReply::isNameUpdated( } void HistoryMessageReply::updateName( - not_null holder) const { - if (const auto name = replyToFromName(holder); !name.isEmpty()) { - replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); - if (const auto from = replyToFrom(holder)) { - replyToVersion = from->nameVersion(); - } else if (replyToMsg) { - replyToVersion = replyToMsg->author()->nameVersion(); - } else { - replyToVersion = replyToStory->peer()->nameVersion(); + not_null holder, + std::optional resolvedSender) const { + const auto peer = resolvedSender.value_or(sender(holder)); + const auto name = peer ? senderName(peer) : senderName(holder); + if (!name.isEmpty()) { + _name.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); + if (peer) { + _nameVersion = peer->nameVersion(); } - bool hasPreview = (replyToStory && replyToStory->hasReplyPreview()) - || (replyToMsg - && replyToMsg->media() - && replyToMsg->media()->hasReplyPreview()); - int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - int32 w = replyToName.maxWidth(); - if (replyToVia) { - w += st::msgServiceFont->spacew + replyToVia->maxWidth; + bool hasPreview = (resolvedStory + && resolvedStory->hasReplyPreview()) + || (resolvedMessage + && resolvedMessage->media() + && resolvedMessage->media()->hasReplyPreview()); + int32 previewSkip = hasPreview + ? (st::msgReplyBarSize.height() + + st::msgReplyBarSkip + - st::msgReplyBarSize.width() + - st::msgReplyBarPos.x()) + : 0; + int32 w = _name.maxWidth(); + if (originalVia) { + w += st::msgServiceFont->spacew + originalVia->maxWidth; } - maxReplyWidth = previewSkip + _maxWidth = previewSkip + std::max( w, - std::min(replyToText.maxWidth(), st::maxSignatureSize)) - + (storyReply + std::min(_text.maxWidth(), st::maxSignatureSize)) + + (_fields.storyId ? (st::dialogsMiniReplyStory.skipText + st::dialogsMiniReplyStory.icon.icon.width()) : 0); } else { - maxReplyWidth = st::msgDateFont->width(statePhrase()); + _maxWidth = st::msgDateFont->width(statePhrase()); } - maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right(); + _maxWidth = st::msgReplyPadding.left() + + st::msgReplyBarSkip + + _maxWidth + + st::msgReplyPadding.right(); } void HistoryMessageReply::resize(int width) const { - if (replyToVia) { - bool hasPreview = (replyToStory && replyToStory->hasReplyPreview()) - || (replyToMsg - && replyToMsg->media() - && replyToMsg->media()->hasReplyPreview()); - int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew); + if (originalVia) { + bool hasPreview = (resolvedStory + && resolvedStory->hasReplyPreview()) + || (resolvedMessage + && resolvedMessage->media() + && resolvedMessage->media()->hasReplyPreview()); + int previewSkip = hasPreview + ? (st::msgReplyBarSize.height() + + st::msgReplyBarSkip + - st::msgReplyBarSize.width() + - st::msgReplyBarPos.x()) + : 0; + originalVia->resize(width + - st::msgReplyBarSkip + - previewSkip + - _name.maxWidth() + - st::msgServiceFont->spacew); } } void HistoryMessageReply::itemRemoved( not_null holder, not_null removed) { - if (replyToMsg.get() == removed) { + if (resolvedMessage.get() == removed) { clearData(holder); holder->history()->owner().requestItemResize(holder); } @@ -511,7 +639,7 @@ void HistoryMessageReply::itemRemoved( void HistoryMessageReply::storyRemoved( not_null holder, not_null removed) { - if (replyToStory.get() == removed) { + if (resolvedStory.get() == removed) { clearData(holder); holder->history()->owner().requestItemResize(holder); } @@ -533,8 +661,8 @@ void HistoryMessageReply::paint( const auto outerWidth = w + 2 * x; const auto &bar = !inBubble ? st->msgImgReplyBarColor() - : replyToColorKey - ? HistoryView::FromNameFg(context, replyToColorKey) + : _colorKey + ? HistoryView::FromNameFg(context, _colorKey) : stm->msgReplyBarColor; const auto rbar = style::rtlrect( x + st::msgReplyBarPos.x(), @@ -565,9 +693,10 @@ void HistoryMessageReply::paint( const auto pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler); if (w > st::msgReplyBarSkip) { - if (replyToMsg || replyToStory) { - const auto media = replyToMsg ? replyToMsg->media() : nullptr; - auto hasPreview = (replyToStory && replyToStory->hasReplyPreview()) || (media && media->hasReplyPreview()); + if (resolvedMessage || resolvedStory) { + const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; + auto hasPreview = (media && media->hasReplyPreview()) + || (resolvedStory && resolvedStory->hasReplyPreview()); if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) { hasPreview = false; } @@ -576,7 +705,7 @@ void HistoryMessageReply::paint( if (hasPreview) { const auto image = media ? media->replyPreview() - : replyToStory->replyPreview(); + : resolvedStory->replyPreview(); if (image) { auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x); const auto preview = image->pixSingle( @@ -604,26 +733,26 @@ void HistoryMessageReply::paint( if (w > st::msgReplyBarSkip + previewSkip) { p.setPen(!inBubble ? st->msgImgReplyBarColor() - : replyToColorKey - ? HistoryView::FromNameFg(context, replyToColorKey) + : _colorKey + ? HistoryView::FromNameFg(context, _colorKey) : stm->msgServiceFg); - replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); - if (replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) { + _name.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); + if (originalVia && w > st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew) { p.setFont(st::msgServiceFont); - p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia->text); + p.drawText(x + st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, originalVia->text); } p.setPen(inBubble ? stm->historyTextFg : st->msgImgReplyBarColor()); - holder->prepareCustomEmojiPaint(p, context, replyToText); + holder->prepareCustomEmojiPaint(p, context, _text); auto replyToTextPosition = QPoint( x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height); const auto replyToTextPalette = &(inBubble ? stm->replyTextPalette : st->imgReplyTextPalette()); - if (storyReply) { + if (_fields.storyId) { st::dialogsMiniReplyStory.icon.icon.paint( p, replyToTextPosition, @@ -634,7 +763,7 @@ void HistoryMessageReply::paint( + st::dialogsMiniReplyStory.icon.icon.width(), 0); } - replyToText.draw(p, { + _text.draw(p, { .position = replyToTextPosition, .availableWidth = w - st::msgReplyBarSkip - previewSkip, .palette = replyToTextPalette, @@ -657,10 +786,14 @@ void HistoryMessageReply::paint( } } +void HistoryMessageReply::unloadPersistentAnimation() { + _text.unloadPersistentAnimation(); +} + QString HistoryMessageReply::statePhrase() const { - return (replyToMsgId || replyToStoryId) + return ((_fields.messageId || _fields.storyId) && !_deleted) ? tr::lng_profile_loading(tr::now) - : storyReply + : _fields.storyId ? tr::lng_deleted_story(tr::now) : tr::lng_deleted_message(tr::now); } @@ -668,7 +801,7 @@ QString HistoryMessageReply::statePhrase() const { void HistoryMessageReply::refreshReplyToMedia() { replyToDocumentId = 0; replyToWebPageId = 0; - if (const auto media = replyToMsg ? replyToMsg->media() : nullptr) { + if (const auto media = resolvedMessage ? resolvedMessage->media() : nullptr) { if (const auto document = media->document()) { replyToDocumentId = document->id; } else if (const auto webpage = media->webpage()) { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 6416475ab4..4cd0338792 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -75,7 +75,7 @@ struct HistoryMessageViews : public RuntimeComponent { - QString author; + QString postAuthor; bool isAnonymousRank = false; }; @@ -123,7 +123,7 @@ struct HistoryMessageForwarded : public RuntimeComponent hiddenSenderInfo; - QString originalAuthor; + QString originalPostAuthor; QString psaType; MsgId originalId = 0; mutable Ui::Text::String text = { 1 }; @@ -226,6 +226,22 @@ private: }; +struct ReplyFields { + TextWithEntities quote; + PeerId externalSenderId = 0; + QString externalSenderName; + QString externalPostAuthor; + PeerId externalPeerId = 0; + MsgId messageId = 0; + MsgId topMessageId = 0; + StoryId storyId = 0; + bool topicPost = false; +}; + +[[nodiscard]] ReplyFields ReplyFieldsFromMTP( + not_null history, + const MTPMessageReplyHeader &reply); + struct HistoryMessageReply : public RuntimeComponent { HistoryMessageReply(); @@ -238,17 +254,25 @@ struct HistoryMessageReply static constexpr auto kBarAlpha = 230. / 255.; + void set(ReplyFields fields); + + void updateFields( + not_null holder, + MsgId messageId, + MsgId topMessageId, + bool topicPost); bool updateData(not_null holder, bool force = false); // Must be called before destructor. void clearData(not_null holder); - [[nodiscard]] PeerData *replyToFrom(not_null holder) const; - [[nodiscard]] QString replyToFromName( - not_null holder) const; - [[nodiscard]] QString replyToFromName(not_null peer) const; + [[nodiscard]] PeerData *sender(not_null holder) const; + [[nodiscard]] QString senderName(not_null holder) const; + [[nodiscard]] QString senderName(not_null peer) const; [[nodiscard]] bool isNameUpdated(not_null holder) const; - void updateName(not_null holder) const; + void updateName( + not_null holder, + std::optional resolvedSender = std::nullopt) const; void resize(int width) const; void itemRemoved( not_null holder, @@ -265,52 +289,63 @@ struct HistoryMessageReply int y, int w, bool inBubble) const; + void unloadPersistentAnimation(); - [[nodiscard]] PeerId replyToPeer() const { - return replyToPeerId; + [[nodiscard]] PeerId colorKey() const { + return _colorKey; } - [[nodiscard]] MsgId replyToId() const { - return replyToMsgId; + [[nodiscard]] PeerId externalPeerId() const { + return _fields.externalPeerId; } - [[nodiscard]] MsgId replyToTop() const { - return replyToMsgTop; + [[nodiscard]] MsgId messageId() const { + return _fields.messageId; } - [[nodiscard]] int replyToWidth() const { - return maxReplyWidth; + [[nodiscard]] StoryId storyId() const { + return _fields.storyId; } - [[nodiscard]] ClickHandlerPtr replyToLink() const { - return replyToLnk; + [[nodiscard]] MsgId topMessageId() const { + return _fields.topMessageId; + } + [[nodiscard]] int maxWidth() const { + return _maxWidth; + } + [[nodiscard]] ClickHandlerPtr link() const { + return _link; + } + [[nodiscard]] bool topicPost() const { + return _fields.topicPost; } [[nodiscard]] QString statePhrase() const; - void setReplyToLinkFrom(not_null holder); + + void setLinkFrom(not_null holder); + void setTopMessageId(MsgId topMessageId); void refreshReplyToMedia(); - PeerId replyToPeerId = 0; - MsgId replyToMsgId = 0; - MsgId replyToMsgTop = 0; - StoryId replyToStoryId = 0; - using ColorKey = PeerId; - ColorKey replyToColorKey = 0; DocumentId replyToDocumentId = 0; WebPageId replyToWebPageId = 0; - ReplyToMessagePointer replyToMsg; - ReplyToStoryPointer replyToStory; - std::unique_ptr replyToVia; + ReplyToMessagePointer resolvedMessage; + ReplyToStoryPointer resolvedStory; + std::unique_ptr originalVia; std::unique_ptr spoiler; - ClickHandlerPtr replyToLnk; - mutable Ui::Text::String replyToName, replyToText; - mutable int replyToVersion = 0; - mutable int maxReplyWidth = 0; int toWidth = 0; - bool topicPost = false; - bool storyReply = false; - struct final { + struct { mutable std::unique_ptr animation; QPoint lastPoint; } ripple; +private: + ReplyFields _fields; + PeerId _colorKey = 0; + ClickHandlerPtr _link; + mutable Ui::Text::String _name; + mutable Ui::Text::String _text; + mutable PeerData *_externalSender = nullptr; + mutable int _maxWidth = 0; + mutable int _nameVersion = 0; + bool _deleted = false; + }; struct HistoryMessageTranslation diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 4129df0ad9..be78380837 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -192,13 +192,14 @@ bool ShouldSendSilent( && peer->session().settings().supportAllSilent()); } -HistoryItem *LookupReplyTo(not_null history, MsgId replyToId) { - const auto &owner = history->owner(); - return owner.message(history->peer, replyToId); +HistoryItem *LookupReplyTo(not_null history, FullMsgId replyTo) { + return history->owner().message(replyTo); } -MsgId LookupReplyToTop(HistoryItem *replyTo) { - return replyTo ? replyTo->replyToTop() : 0; +MsgId LookupReplyToTop(not_null history, HistoryItem *replyTo) { + return (replyTo && replyTo->history() == history) + ? replyTo->replyToTop() + : 0; } bool LookupReplyIsTopicPost(HistoryItem *replyTo) { @@ -360,27 +361,21 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { MTP_long(peerToUser(replyTo.storyId.peer).bare), MTP_int(replyTo.storyId.story)); } - const auto to = LookupReplyTo(action.history, replyTo.msgId); - if (const auto replyToTop = LookupReplyToTop(to)) { - using Flag = MTPDmessageReplyHeader::Flag; - return MTP_messageReplyHeader( - MTP_flags(Flag::f_reply_to_top_id - | (LookupReplyIsTopicPost(to) - ? Flag::f_forum_topic - : Flag(0))), - MTP_int(replyTo.msgId), - MTPPeer(), - MTPMessageFwdHeader(), // reply_header - MTP_int(replyToTop), - MTPstring(), // quote_text - MTPVector()); // quote_entities - } + using Flag = MTPDmessageReplyHeader::Flag; + const auto historyPeer = action.history->peer->id; + 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); return MTP_messageReplyHeader( - MTP_flags(0), - MTP_int(replyTo.msgId), - MTPPeer(), // reply_to_peer_id + MTP_flags(Flag::f_reply_to_msg_id + | (replyToTop ? Flag::f_reply_to_top_id : Flag()) + | (externalPeerId ? Flag::f_reply_to_peer_id : Flag())), + MTP_int(replyTo.messageId.msg), + peerToMTP(externalPeerId), MTPMessageFwdHeader(), // reply_header - MTPint(), // reply_to_top_id + MTP_int(replyToTop), // reply_to_top_id MTPstring(), // quote_text MTPVector()); // quote_entities } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index e7ed742904..8dd151274a 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -85,8 +85,10 @@ void RequestDependentMessageStory( const Api::SendOptions &options); [[nodiscard]] HistoryItem *LookupReplyTo( not_null history, - MsgId replyToId); -[[nodiscard]] MsgId LookupReplyToTop(HistoryItem *replyTo); + FullMsgId replyToId); +[[nodiscard]] MsgId LookupReplyToTop( + not_null history, + HistoryItem *replyTo); [[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo); struct SendingErrorRequest { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9ce5a8af39..955263ef21 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -517,7 +517,7 @@ HistoryWidget::HistoryWidget( if (!_peer || isRecording()) { return false; } - const auto replyTo = (_replyToId && !_editMsgId) + const auto replyTo = (_replyTo && !_editMsgId) ? _replyEditMsg : 0; const auto topic = replyTo ? replyTo->topic() : nullptr; @@ -848,9 +848,8 @@ HistoryWidget::HistoryWidget( ) | rpl::filter([=](const Api::SendAction &action) { return (action.history == _history); }) | rpl::start_with_next([=](const Api::SendAction &action) { - const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId( - action.history->peer->id, - action.replyTo.msgId)); + const auto lastKeyboardUsed = lastForceReplyReplied( + action.replyTo.messageId); if (action.replaceMediaOf) { } else if (action.options.scheduled) { cancelReply(lastKeyboardUsed); @@ -909,7 +908,7 @@ Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const { return Dialogs::EntryState{ .key = _history, .section = Dialogs::EntryState::Section::History, - .currentReplyToId = replyToId(), + .currentReplyTo = replyTo(), }; } @@ -1354,7 +1353,7 @@ void HistoryWidget::insertHashtagOrBotCommand( // Send bot command at once, if it was not inserted by pressing Tab. if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) { - sendBotCommand({ _peer, str, FullMsgId(), replyToId() }); + sendBotCommand({ _peer, str, FullMsgId(), replyTo() }); session().api().finishForwarding(prepareSendAction({})); setFieldText(_field->getTextWithTagsPart(_field->textCursor().position())); } else { @@ -1663,21 +1662,22 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { if (_editMsgId) { _history->setLocalEditDraft(std::make_unique( _field, - _editMsgId, - topicRootId, + FullReplyTo{ + .messageId = FullMsgId(_history->peer->id, _editMsgId), + .topicRootId = topicRootId, + }, _previewState, _saveEditMsgRequestId)); } else { - if (_replyToId || !_field->empty()) { + if (_replyTo || !_field->empty()) { _history->setLocalDraft(std::make_unique( _field, - _replyToId, - topicRootId, + _replyTo, _previewState)); } else { - _history->clearLocalDraft({}); + _history->clearLocalDraft(topicRootId); } - _history->clearLocalEditDraft({}); + _history->clearLocalEditDraft(topicRootId); } } @@ -1779,8 +1779,7 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived( }; _history->setLocalDraft(std::make_unique( textWithTags, - 0, // replyTo - 0, // topicRootId + FullReplyTo(), cursor, Data::PreviewState::Allowed)); applyDraft(); @@ -1885,7 +1884,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { ? _history->localDraft({}) : nullptr; auto fieldAvailable = canWriteMessage(); - const auto editMsgId = editDraft ? editDraft->msgId : 0; + const auto editMsgId = editDraft ? editDraft->reply.messageId.msg : 0; if (_voiceRecordBar->isActive() || (!_canSendTexts && !editMsgId)) { if (!_canSendTexts) { clearFieldText(0, fieldHistoryAction); @@ -1898,7 +1897,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { clearFieldText(0, fieldHistoryAction); setInnerFocus(); _processingReplyItem = _replyEditMsg = nullptr; - _processingReplyId = _replyToId = 0; + _processingReplyTo = _replyTo = FullReplyTo(); setEditMsgId(0); if (fieldWillBeHiddenAfterEdit) { updateControlsVisibility(); @@ -1916,7 +1915,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { | TextUpdateEvent::SendTyping; _processingReplyItem = _replyEditMsg = nullptr; - _processingReplyId = _replyToId = 0; + _processingReplyTo = _replyTo = FullReplyTo(); setEditMsgId(editMsgId); updateCmdStartShown(); updateControlsVisibility(); @@ -1929,7 +1928,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { } } else if (!readyToForward()) { const auto draft = _history->localDraft({}); - _processingReplyId = draft ? draft->msgId : MsgId(); + _processingReplyTo = draft ? draft->reply : FullReplyTo(); processReply(); } @@ -2147,7 +2146,8 @@ void HistoryWidget::showHistory( _saveEditMsgRequestId = 0; _processingReplyItem = _replyEditMsg = nullptr; - _processingReplyId = _editMsgId = _replyToId = 0; + _processingReplyTo = _replyTo = FullReplyTo(); + _editMsgId = MsgId(); _canReplaceMedia = false; _photoEditMedia = nullptr; updateReplaceMediaButton(); @@ -2461,10 +2461,13 @@ void HistoryWidget::registerDraftSource() { if (!_history) { return; } + const auto peerId = _history->peer->id; const auto editMsgId = _editMsgId; const auto draft = [=] { return Storage::MessageDraft{ - editMsgId ? editMsgId : _replyToId, + (editMsgId + ? FullReplyTo{ FullMsgId(peerId, editMsgId) } + : _replyTo), _field->getTextWithTags(), _previewState, }; @@ -2901,7 +2904,11 @@ void HistoryWidget::updateControlsVisibility() { } updateFieldPlaceholder(); - if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) { + if (_editMsgId + || _replyTo + || readyToForward() + || (_previewData && _previewData->pendingTill >= 0) + || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); updateControlsGeometry(); @@ -2938,7 +2945,7 @@ void HistoryWidget::updateControlsVisibility() { _botMenuButton->hide(); } _kbScroll->hide(); - if (_replyToId || readyToForward() || _kbReplyTo) { + if (_replyTo || readyToForward() || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); updateControlsGeometry(); @@ -3889,7 +3896,7 @@ void HistoryWidget::hideSelectorControlsAnimated() { Api::SendAction HistoryWidget::prepareSendAction( Api::SendOptions options) const { auto result = Api::SendAction(_history, options); - result.replyTo = { .msgId = replyToId() }; + result.replyTo = replyTo(); result.options.sendAs = _sendAs ? _history->session().sendAsPeers().resolveChosen( _history->peer).get() @@ -4337,7 +4344,7 @@ void HistoryWidget::updateOverStates(QPoint pos) { _field->y() - st::historySendPadding - st::historyReplyHeight, width() - skip - _fieldBarCancel->width(), st::historyReplyHeight); - auto inReplyEditForward = (_editMsgId || replyToId() || isReadyToForward) + auto inReplyEditForward = (_editMsgId || replyTo() || isReadyToForward) && replyEditForwardInfoRect.contains(pos); auto inPhotoEdit = inReplyEditForward && _photoEditMedia @@ -4387,9 +4394,9 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { return; } - const auto lastKeyboardUsed = (_keyboard->forMsgId() - == FullMsgId(_peer->id, _history->lastKeyboardId)) - && (_keyboard->forMsgId() == FullMsgId(_peer->id, request.replyTo)); + const auto forMsgId = _keyboard->forMsgId(); + const auto lastKeyboardUsed = (forMsgId == request.replyTo.messageId) + && (forMsgId == FullMsgId(_peer->id, _history->lastKeyboardId)); // 'bot' may be nullptr in case of sending from FieldAutocomplete. const auto toSend = (request.replyTo/* || !bot*/) @@ -4398,14 +4405,14 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { auto message = Api::MessageToSend(prepareSendAction({})); message.textWithTags = { toSend, TextWithTags::Tags() }; - message.action.replyTo.msgId = request.replyTo + message.action.replyTo = request.replyTo ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) ? request.replyTo - : replyToId()) - : 0; + : replyTo()) + : FullReplyTo(); session().api().sendMessage(std::move(message)); if (request.replyTo) { - if (_replyToId == request.replyTo) { + if (_replyTo == request.replyTo) { cancelReply(); saveCloudDraft(); } @@ -4418,18 +4425,25 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { setInnerFocus(); } -void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { - if (!_peer || _peer != peer) return; +void HistoryWidget::hideSingleUseKeyboard(FullMsgId replyToId) { + if (!_peer || _peer->id != replyToId.peer) { + return; + } - bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_peer->id, _history->lastKeyboardId)) - && (_keyboard->forMsgId() == FullMsgId(_peer->id, replyTo)); - if (replyTo) { - if (_replyToId == replyTo) { + bool lastKeyboardUsed = (_keyboard->forMsgId() == replyToId) + && (_keyboard->forMsgId() + == FullMsgId(_peer->id, _history->lastKeyboardId)); + if (replyToId) { + if (_replyTo.messageId == replyToId) { cancelReply(); saveCloudDraft(); } - if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) { - if (_kbShown) toggleKeyboard(false); + if (_keyboard->singleUse() + && _keyboard->hasMarkup() + && lastKeyboardUsed) { + if (_kbShown) { + toggleKeyboard(false); + } _history->lastKeyboardUsed = true; } } @@ -4772,7 +4786,7 @@ void HistoryWidget::toggleKeyboard(bool manual) { _field->setMaxHeight(computeMaxFieldHeight()); _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyToId) { + if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -4797,7 +4811,7 @@ void HistoryWidget::toggleKeyboard(bool manual) { _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? session().data().message(_keyboard->forMsgId()) : nullptr; - if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) { + if (_kbReplyTo && !_editMsgId && !_replyTo && fieldEnabled) { updateReplyToName(); updateReplyEditText(_kbReplyTo); } @@ -4817,7 +4831,7 @@ void HistoryWidget::toggleKeyboard(bool manual) { _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? session().data().message(_keyboard->forMsgId()) : nullptr; - if (_kbReplyTo && !_editMsgId && !_replyToId) { + if (_kbReplyTo && !_editMsgId && !_replyTo) { updateReplyToName(); updateReplyEditText(_kbReplyTo); } @@ -5589,11 +5603,11 @@ void HistoryWidget::itemRemoved(not_null item) { if (item == _replyEditMsg && _editMsgId) { cancelEdit(); } - if (item == _replyEditMsg && _replyToId) { + if (item == _replyEditMsg && _replyTo) { cancelReply(); } if (item == _processingReplyItem) { - _processingReplyId = 0; + _processingReplyTo = {}; _processingReplyItem = nullptr; } if (_kbReplyTo && item == _kbReplyTo) { @@ -5617,8 +5631,12 @@ void HistoryWidget::itemEdited(not_null item) { } } -MsgId HistoryWidget::replyToId() const { - return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0); +FullReplyTo HistoryWidget::replyTo() const { + return _replyTo + ? _replyTo + : _kbReplyTo + ? FullReplyTo{ _kbReplyTo->fullId() } + : FullReplyTo(); } bool HistoryWidget::hasSavedScroll() const { @@ -5753,7 +5771,7 @@ void HistoryWidget::updateHistoryGeometry( } else if (writeRestriction().has_value()) { newScrollHeight -= _unblock->height(); } - if (_editMsgId || replyToId() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) { + if (_editMsgId || replyTo() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) { newScrollHeight -= st::historyReplyHeight; } if (_kbShown) { @@ -5991,9 +6009,9 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { const auto wasVisible = _kbShown || _kbReplyTo; const auto wasMsgId = _keyboard->forMsgId(); auto changed = false; - if ((_replyToId && !_replyEditMsg) || _editMsgId || !_history) { + if ((_replyTo && !_replyEditMsg) || _editMsgId || !_history) { changed = _keyboard->updateMarkup(nullptr, force); - } else if (_replyToId && _replyEditMsg) { + } else if (_replyTo && _replyEditMsg) { changed = _keyboard->updateMarkup(_replyEditMsg, force); } else { const auto keyboardItem = _history->lastKeyboardId @@ -6010,7 +6028,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _kbScroll->scrollTo({ 0, 0 }); } - bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyToId || !_replyEditMsg); + bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyTo || !_replyEditMsg); if (hasMarkup || forceReply) { if (_keyboard->singleUse() && _keyboard->hasMarkup() @@ -6019,7 +6037,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { && _history->lastKeyboardUsed) { _history->lastKeyboardHiddenId = _history->lastKeyboardId; } - if (!isSearching() && !isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!HasSendText(_field) && !kbWasHidden()))) { + if (!isSearching() && !isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyTo && _replyEditMsg) || (!HasSendText(_field) && !kbWasHidden()))) { if (!_showAnimation) { if (hasMarkup) { _kbScroll->show(); @@ -6040,7 +6058,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? session().data().message(_keyboard->forMsgId()) : nullptr; - if (_kbReplyTo && !_replyToId) { + if (_kbReplyTo && !_replyTo) { updateReplyToName(); updateReplyEditText(_kbReplyTo); } @@ -6055,7 +6073,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _field->setMaxHeight(computeMaxFieldHeight()); _kbShown = false; _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId) { + if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -6071,7 +6089,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _field->setMaxHeight(computeMaxFieldHeight()); _kbShown = false; _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId && !_editMsgId) { + if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyTo && !_editMsgId) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -6093,7 +6111,7 @@ void HistoryWidget::botCallbackSent(not_null item) { session().data().requestItemRepaint(item); - if (_replyToId == item->id) { + if (_replyTo.messageId == item->fullId()) { cancelReply(); } if (_keyboard->singleUse() @@ -6114,7 +6132,7 @@ int HistoryWidget::computeMaxFieldHeight() const { - (_groupCallBar ? _groupCallBar->height() : 0) - (_requestsBar ? _requestsBar->height() : 0) - ((_editMsgId - || replyToId() + || replyTo() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) ? st::historyReplyHeight @@ -6175,7 +6193,7 @@ bool HistoryWidget::cornerButtonsHas(HistoryView::CornerButtonType type) { void HistoryWidget::mousePressEvent(QMouseEvent *e) { const auto isReadyToForward = readyToForward(); const auto hasSecondLayer = (_editMsgId - || _replyToId + || _replyTo || isReadyToForward || _kbReplyTo); _replyForwardPressed = hasSecondLayer && QRect( @@ -6201,11 +6219,13 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } else { _forwardPanel->editOptions(controller()->uiShow()); } + } else if (replyTo() && replyTo().messageId.peer != _peer->id) { + // edit options } else { controller()->showPeerHistory( _peer, Window::SectionShow::Way::Forward, - _editMsgId ? _editMsgId : replyToId()); + _editMsgId ? _editMsgId : replyTo().messageId.msg); } } } @@ -6235,7 +6255,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (item && _field->empty() && !_editMsgId - && !_replyToId) { + && !_replyTo) { editMessage(item); return; } @@ -6303,14 +6323,17 @@ void HistoryWidget::handlePeerMigration() { } bool HistoryWidget::replyToPreviousMessage() { - if (!_history || _editMsgId || _history->isForum()) { + if (!_history + || _editMsgId + || _history->isForum() + || (_replyTo && _replyTo.messageId.peer != _history->peer->id)) { return false; } const auto fullId = FullMsgId( _history->peer->id, - _field->isVisible() - ? _replyToId - : _highlighter.latestSingleHighlightedMsgId()); + (_field->isVisible() + ? _replyTo.messageId.msg + : _highlighter.latestSingleHighlightedMsgId())); if (const auto item = session().data().message(fullId)) { if (const auto view = item->mainView()) { if (const auto previousView = view->previousDisplayedInBlocks()) { @@ -6334,14 +6357,17 @@ bool HistoryWidget::replyToPreviousMessage() { } bool HistoryWidget::replyToNextMessage() { - if (!_history || _editMsgId || _history->isForum()) { + if (!_history + || _editMsgId + || _history->isForum() + || (_replyTo && _replyTo.messageId.peer != _history->peer->id)) { return false; } const auto fullId = FullMsgId( _history->peer->id, - _field->isVisible() - ? _replyToId - : _highlighter.latestSingleHighlightedMsgId()); + (_field->isVisible() + ? _replyTo.messageId.msg + : _highlighter.latestSingleHighlightedMsgId())); if (const auto item = session().data().message(fullId)) { if (const auto view = item->mainView()) { if (const auto nextView = view->nextDisplayedInBlocks()) { @@ -7024,17 +7050,19 @@ void HistoryWidget::clearFieldText( setFieldText(TextWithTags(), events, fieldHistoryAction); } -void HistoryWidget::replyToMessage(FullMsgId itemId) { - if (const auto item = session().data().message(itemId)) { - replyToMessage(item); +void HistoryWidget::replyToMessage(FullReplyTo id) { + if (const auto item = session().data().message(id.messageId)) { + replyToMessage(item, id.quote); } } -void HistoryWidget::replyToMessage(not_null item) { +void HistoryWidget::replyToMessage( + not_null item, + TextWithTags quote) { if (isJoinChannel()) { return; } - _processingReplyId = item->id; + _processingReplyTo = { .messageId = item->fullId(), .quote = quote}; _processingReplyItem = item; processReply(); } @@ -7042,14 +7070,13 @@ void HistoryWidget::replyToMessage(not_null item) { void HistoryWidget::processReply() { const auto processContinue = [=] { return crl::guard(_list, [=] { - if (!_peer || !_processingReplyId) { + if (!_peer || !_processingReplyTo) { return; } else if (!_processingReplyItem) { _processingReplyItem = _peer->owner().message( - _peer, - _processingReplyId); + _processingReplyTo.messageId); if (!_processingReplyItem) { - _processingReplyId = 0; + _processingReplyTo = {}; } else { processReply(); } @@ -7057,16 +7084,16 @@ void HistoryWidget::processReply() { }); }; const auto processCancel = [=] { - _processingReplyId = 0; + _processingReplyTo = {}; _processingReplyItem = nullptr; }; - if (!_peer || !_processingReplyId) { + if (!_peer || !_processingReplyTo) { return processCancel(); } else if (!_processingReplyItem) { session().api().requestMessageData( - _peer, - _processingReplyId, + session().data().peer(_processingReplyTo.messageId.peer), + _processingReplyTo.messageId.msg, processContinue()); return; } else if (_processingReplyItem->history() == _migrated) { @@ -7107,7 +7134,7 @@ void HistoryWidget::processReply() { } void HistoryWidget::setReplyFieldsFromProcessing() { - if (!_processingReplyId || !_processingReplyItem) { + if (!_processingReplyTo || !_processingReplyItem) { return; } @@ -7116,22 +7143,21 @@ void HistoryWidget::setReplyFieldsFromProcessing() { _composeSearch->hideAnimated(); } - const auto id = base::take(_processingReplyId); + const auto id = base::take(_processingReplyTo); const auto item = base::take(_processingReplyItem); if (_editMsgId) { if (const auto localDraft = _history->localDraft({})) { - localDraft->msgId = id; + localDraft->reply = id; } else { _history->setLocalDraft(std::make_unique( TextWithTags(), id, - MsgId(), MessageCursor(), Data::PreviewState::Allowed)); } } else { _replyEditMsg = item; - _replyToId = id; + _replyTo = id; updateReplyEditText(_replyEditMsg); updateCanSendMessage(); updateBotKeyboard(); @@ -7170,11 +7196,10 @@ void HistoryWidget::editMessage(not_null item) { _send->clearState(); } if (!_editMsgId) { - if (_replyToId || !_field->empty()) { + if (_replyTo || !_field->empty()) { _history->setLocalDraft(std::make_unique( _field, - _replyToId, - MsgId(), // topicRootId + _replyTo, _previewState)); } else { _history->clearLocalDraft({}); @@ -7198,8 +7223,7 @@ void HistoryWidget::editMessage(not_null item) { : Data::PreviewState::EmptyOnEdit; _history->setLocalEditDraft(std::make_unique( editData, - item->id, - MsgId(), // topicRootId + FullReplyTo{ item->fullId() }, cursor, previewState)); applyDraft(); @@ -7261,17 +7285,18 @@ bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const { bool HistoryWidget::lastForceReplyReplied() const { return _peer && _keyboard->forceReply() - && _keyboard->forMsgId() == FullMsgId(_peer->id, _history->lastKeyboardId) - && _keyboard->forMsgId().msg == replyToId(); + && _keyboard->forMsgId() == replyTo().messageId + && (_keyboard->forMsgId() + == FullMsgId(_peer->id, _history->lastKeyboardId)); } bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { bool wasReply = false; - if (_replyToId) { + if (_replyTo) { wasReply = true; _processingReplyItem = _replyEditMsg = nullptr; - _processingReplyId = _replyToId = 0; + _processingReplyTo = _replyTo = FullReplyTo(); mouseMoveEvent(0); if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) { _fieldBarCancel->hide(); @@ -7284,11 +7309,11 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { updateControlsGeometry(); update(); } else if (const auto localDraft = (_history ? _history->localDraft({}) : nullptr)) { - if (localDraft->msgId) { + if (localDraft->reply) { if (localDraft->textWithTags.text.isEmpty()) { _history->clearLocalDraft({}); } else { - localDraft->msgId = 0; + localDraft->reply = {}; } } } @@ -7344,7 +7369,7 @@ void HistoryWidget::cancelEdit() { saveDraft(); mouseMoveEvent(nullptr); - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) { + if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyTo()) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -7376,7 +7401,7 @@ void HistoryWidget::cancelFieldAreaState() { cancelEdit(); } else if (readyToForward()) { _history->setForwardDraft(MsgId(), {}); - } else if (_replyToId) { + } else if (_replyTo) { cancelReply(); } else if (_kbReplyTo) { toggleKeyboard(); @@ -7512,7 +7537,7 @@ void HistoryWidget::updatePreview() { preview.description, Ui::DialogTextOptions()); } - } else if (!readyToForward() && !replyToId() && !_editMsgId) { + } else if (!readyToForward() && !replyTo() && !_editMsgId) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -7579,7 +7604,7 @@ bool HistoryWidget::updateCanSendMessage() { if (!_peer) { return false; } - const auto replyTo = (_replyToId && !_editMsgId) ? _replyEditMsg : 0; + const auto replyTo = (_replyTo && !_editMsgId) ? _replyEditMsg : 0; const auto topic = replyTo ? replyTo->topic() : nullptr; const auto allWithoutPolls = Data::AllSendRestrictions() & ~ChatRestriction::SendPolls; @@ -7657,7 +7682,7 @@ void HistoryWidget::escape() { } } else if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->hideAnimated(); - } else if (_replyToId && _field->getTextWithTags().text.isEmpty()) { + } else if (_replyTo && _field->getTextWithTags().text.isEmpty()) { cancelReply(); } else if (auto &voice = _voiceRecordBar; voice->isActive()) { voice->showDiscardBox(nullptr, anim::type::normal); @@ -7752,7 +7777,8 @@ void HistoryWidget::messageDataReceived( MsgId msgId) { if (!_peer || _peer != peer || !msgId) { return; - } else if (_editMsgId == msgId || _replyToId == msgId) { + } else if (_editMsgId == msgId + || (_replyTo.messageId == FullMsgId(peer->id, msgId))) { updateReplyEditTexts(true); } } @@ -7775,14 +7801,14 @@ void HistoryWidget::updateReplyEditText(not_null item) { void HistoryWidget::updateReplyEditTexts(bool force) { if (!force) { - if (_replyEditMsg || (!_editMsgId && !_replyToId)) { + if (_replyEditMsg || (!_editMsgId && !_replyTo)) { return; } } if (!_replyEditMsg && _peer) { _replyEditMsg = session().data().message( - _peer->id, - _editMsgId ? _editMsgId : _replyToId); + _editMsgId ? _peer->id : _replyTo.messageId.peer, + _editMsgId ? _editMsgId : _replyTo.messageId.msg); } if (_replyEditMsg) { const auto media = _replyEditMsg->media(); @@ -7828,7 +7854,7 @@ void HistoryWidget::updateForwarding() { void HistoryWidget::updateReplyToName() { if (_editMsgId) { return; - } else if (!_replyEditMsg && (_replyToId || !_kbReplyTo)) { + } else if (!_replyEditMsg && (_replyTo || !_kbReplyTo)) { return; } const auto from = [&] { @@ -7862,8 +7888,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { auto backy = _field->y() - st::historySendPadding; auto backh = fieldHeight() + 2 * st::historySendPadding; auto hasForward = readyToForward(); - auto drawMsgText = (_editMsgId || _replyToId) ? _replyEditMsg : _kbReplyTo; - if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) { + auto drawMsgText = (_editMsgId || _replyTo) ? _replyEditMsg : _kbReplyTo; + if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) { if (!_editMsgId && drawMsgText && (_replyToNameVersion @@ -7898,7 +7924,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { }); } - if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) { + if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) { const auto now = crl::now(); const auto paused = p.inactive(); const auto pausedSpoiler = paused || On(PowerSaving::kChatSpoiler); @@ -8097,7 +8123,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { const auto restrictionHidden = fieldOrDisabledShown() || isRecording(); if (restrictionHidden - || replyToId() + || replyTo() || readyToForward() || _kbShown) { drawField(p, clip); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 5011c52cb2..84e4b8be9f 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -182,12 +182,14 @@ public: MessageIdsList getSelectedItems() const; void itemEdited(not_null item); - void replyToMessage(FullMsgId itemId); - void replyToMessage(not_null item); + void replyToMessage(FullReplyTo id); + void replyToMessage( + not_null item, + TextWithTags quote = {}); void editMessage(FullMsgId itemId); void editMessage(not_null item); - MsgId replyToId() const; + [[nodiscard]] FullReplyTo replyTo() const; bool lastForceReplyReplied(const FullMsgId &replyTo) const; bool lastForceReplyReplied() const; bool cancelReply(bool lastKeyboardUsed = false); @@ -204,7 +206,7 @@ public: void escape(); void sendBotCommand(const Bot::SendCommandRequest &request); - void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); + void hideSingleUseKeyboard(FullMsgId replyToId); bool insertBotCommand(const QString &cmd); bool eventFilter(QObject *obj, QEvent *e) override; @@ -633,11 +635,11 @@ private: void searchInChat(); MTP::Sender _api; - MsgId _replyToId = 0; + FullReplyTo _replyTo; Ui::Text::String _replyToName; int _replyToNameVersion = 0; - MsgId _processingReplyId = 0; + FullReplyTo _processingReplyTo; HistoryItem *_processingReplyItem = nullptr; MsgId _editMsgId = 0; 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 d19da9a575..56f6cf752f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -353,7 +353,7 @@ public: void init(); void editMessage(FullMsgId id, bool photoEditAllowed = false); - void replyToMessage(FullMsgId id); + void replyToMessage(FullReplyTo id); void updateForwarding( Data::Thread *thread, Data::ResolvedForwardDraft items); @@ -367,7 +367,7 @@ public: [[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool readyToForward() const; [[nodiscard]] const HistoryItemsList &forwardItems() const; - [[nodiscard]] FullMsgId replyingToMessage() const; + [[nodiscard]] FullReplyTo replyingToMessage() const; [[nodiscard]] FullMsgId editMsgId() const; [[nodiscard]] rpl::producer editMsgIdValue() const; [[nodiscard]] rpl::producer scrollToItemRequests() const; @@ -375,7 +375,7 @@ public: [[nodiscard]] MessageToEdit queryToEdit(); [[nodiscard]] WebPageId webPageId() const; - [[nodiscard]] MsgId getDraftMessageId() const; + [[nodiscard]] FullReplyTo getDraftReply() const; [[nodiscard]] rpl::producer<> editCancelled() const { return _editCancelled.events(); } @@ -425,7 +425,7 @@ private: rpl::lifetime _previewLifetime; rpl::variable _editMsgId; - rpl::variable _replyToId; + rpl::variable _replyTo; std::unique_ptr _forwardPanel; rpl::producer<> _toForwardUpdated; @@ -508,14 +508,14 @@ void FieldHeader::init() { _editMsgId.value( ) | rpl::start_with_next([=](FullMsgId value) { - const auto shown = value ? value : _replyToId.current(); + const auto shown = value ? value : _replyTo.current().messageId; setShownMessage(_data->message(shown)); }, lifetime()); - _replyToId.value( - ) | rpl::start_with_next([=](FullMsgId value) { + _replyTo.value( + ) | rpl::start_with_next([=](const FullReplyTo &value) { if (!_editMsgId.current()) { - setShownMessage(_data->message(value)); + setShownMessage(_data->message(value.messageId)); } }, lifetime()); @@ -529,7 +529,7 @@ void FieldHeader::init() { if (_editMsgId.current() == update.item->fullId()) { _editCancelled.fire({}); } - if (_replyToId.current() == update.item->fullId()) { + if (_replyTo.current().messageId == update.item->fullId()) { _replyCancelled.fire({}); } } else { @@ -545,7 +545,7 @@ void FieldHeader::init() { _editCancelled.fire({}); } else if (readyToForward()) { _forwardCancelled.fire({}); - } else if (_replyToId.current()) { + } else if (_replyTo.current()) { _replyCancelled.fire({}); } updateVisible(); @@ -624,7 +624,7 @@ void FieldHeader::init() { } else { auto id = isEditingMessage() ? _editMsgId.current() - : replyingToMessage(); + : replyingToMessage().messageId; _scrollToItemRequests.fire(std::move(id)); } } @@ -692,16 +692,18 @@ void FieldHeader::setShownMessage(HistoryItem *item) { } void FieldHeader::resolveMessageData() { - const auto id = (isEditingMessage() ? _editMsgId : _replyToId).current(); + const auto id = isEditingMessage() + ? _editMsgId.current() + : _replyTo.current().messageId; if (!id) { return; } const auto peer = _data->peer(id.peer); const auto itemId = id.msg; const auto callback = crl::guard(this, [=] { - const auto now = (isEditingMessage() - ? _editMsgId - : _replyToId).current(); + const auto now = isEditingMessage() + ? _editMsgId.current() + : _replyTo.current().messageId; if (now == id && !_shownMessage) { if (const auto message = _data->message(peer, itemId)) { setShownMessage(message); @@ -950,8 +952,8 @@ const HistoryItemsList &FieldHeader::forwardItems() const { return _forwardPanel->items(); } -FullMsgId FieldHeader::replyingToMessage() const { - return _replyToId.current(); +FullReplyTo FieldHeader::replyingToMessage() const { + return _replyTo.current(); } bool FieldHeader::hasPreview() const { @@ -962,8 +964,10 @@ WebPageId FieldHeader::webPageId() const { return hasPreview() ? _preview.data->id : CancelledWebPageId; } -MsgId FieldHeader::getDraftMessageId() const { - return (isEditingMessage() ? _editMsgId : _replyToId).current().msg; +FullReplyTo FieldHeader::getDraftReply() const { + return isEditingMessage() + ? FullReplyTo{ _editMsgId.current() } + : _replyTo.current(); } void FieldHeader::updateControlsGeometry(QSize size) { @@ -992,8 +996,8 @@ void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) { update(); } -void FieldHeader::replyToMessage(FullMsgId id) { - _replyToId = id; +void FieldHeader::replyToMessage(FullReplyTo id) { + _replyTo = id; } void FieldHeader::updateForwarding( @@ -1156,6 +1160,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { } unregisterDraftSources(); _history = history; + _topicRootId = args.topicRootId; _historyLifetime.destroy(); _header->setHistory(args); registerDraftSource(); @@ -1193,8 +1198,10 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { orderControls(); } -void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { +void ComposeControls::setCurrentDialogsEntryState( + Dialogs::EntryState state) { unregisterDraftSources(); + state.currentReplyTo.topicRootId = _topicRootId; _currentDialogsEntryState = state; updateForwarding(); registerDraftSource(); @@ -1472,16 +1479,12 @@ void ComposeControls::saveFieldToHistoryLocalDraft() { if (!_history || !key) { return; } - const auto id = _header->getDraftMessageId(); + const auto id = _header->getDraftReply(); if (_preview && (id || !_field->empty())) { const auto key = draftKeyCurrent(); _history->setDraft( key, - std::make_unique( - _field, - _header->getDraftMessageId(), - key.topicRootId(), - _preview->state())); + std::make_unique(_field, id, _preview->state())); } else { _history->clearDraft(draftKeyCurrent()); } @@ -1757,7 +1760,7 @@ void ComposeControls::initKeyHandler() { } } _replyNextRequests.fire({ - .replyId = replyingToMessage(), + .replyId = replyingToMessage().messageId, .direction = (isDown ? ReplyNextRequest::Direction::Next : ReplyNextRequest::Direction::Previous) @@ -2037,8 +2040,8 @@ Data::DraftKey ComposeControls::draftKey(DraftType type) const { case Section::History: case Section::Replies: return (type == DraftType::Edit) - ? Key::LocalEdit(_currentDialogsEntryState.rootId) - : Key::Local(_currentDialogsEntryState.rootId); + ? Key::LocalEdit(_topicRootId) + : Key::Local(_topicRootId); case Section::Scheduled: return (type == DraftType::Edit) ? Key::ScheduledEdit() @@ -2102,7 +2105,7 @@ void ComposeControls::registerDraftSource() { if (key != Data::DraftKey::None()) { const auto draft = [=] { return Storage::MessageDraft{ - _header->getDraftMessageId(), + _header->getDraftReply(), _field->getTextWithTags(), _preview->state(), }; @@ -2150,8 +2153,8 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { const auto draft = editDraft ? editDraft : _history->draft(draftKey(DraftType::Normal)); - const auto editingId = (draft == editDraft) - ? FullMsgId{ _history->peer->id, draft ? draft->msgId : 0 } + const auto editingId = (draft && draft == editDraft) + ? draft->reply.messageId : FullMsgId(); InvokeQueued(_autocomplete.get(), [=] { updateStickersByEmoji(); }); @@ -2232,7 +2235,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { } else { _canReplaceMedia = false; _photoEditMedia = nullptr; - _header->replyToMessage({ _history->peer->id, draft->msgId }); + _header->replyToMessage(draft->reply); if (_header->replyingToMessage()) { cancelForward(); } @@ -2241,9 +2244,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { } void ComposeControls::cancelForward() { - _history->setForwardDraft( - _currentDialogsEntryState.rootId, - {}); + _history->setForwardDraft(_topicRootId, {}); updateForwarding(); } @@ -2840,8 +2841,7 @@ void ComposeControls::toggleTabbedSelectorMode() { && !_regularWindow->adaptive().isOneColumn()) { Core::App().settings().setTabbedSelectorSectionEnabled(true); Core::App().saveSettingsDelayed(); - const auto topic = _history->peer->forumTopicFor( - _currentDialogsEntryState.rootId); + const auto topic = _history->peer->forumTopicFor(_topicRootId); pushTabbedSelectorToThirdSection( (topic ? topic : (Data::Thread*)_history), Window::SectionShow::Way::ClearStack); @@ -2900,8 +2900,10 @@ void ComposeControls::editMessage(not_null item) { key, std::make_unique( editData, - item->id, - key.topicRootId(), + FullReplyTo{ + .messageId = item->fullId(), + .topicRootId = key.topicRootId(), + }, cursor, previewState)); applyDraft(); @@ -2968,25 +2970,26 @@ void ComposeControls::maybeCancelEditMessage() { } } -void ComposeControls::replyToMessage(FullMsgId id) { +void ComposeControls::replyToMessage(FullReplyTo id) { Expects(_history != nullptr); Expects(draftKeyCurrent() != Data::DraftKey::None()); + id.topicRootId = _topicRootId; if (!id) { cancelReplyMessage(); return; } if (isEditingMessage()) { const auto key = draftKey(DraftType::Normal); + Assert(key.topicRootId() == id.topicRootId); if (const auto localDraft = _history->draft(key)) { - localDraft->msgId = id.msg; + localDraft->reply = id; } else { _history->setDraft( key, std::make_unique( TextWithTags(), - id.msg, - key.topicRootId(), + id, MessageCursor(), Data::PreviewState::Allowed)); } @@ -3008,11 +3011,11 @@ void ComposeControls::cancelReplyMessage() { if (_history) { const auto key = draftKey(DraftType::Normal); if (const auto localDraft = _history->draft(key)) { - if (localDraft->msgId) { + if (localDraft->reply.messageId) { if (localDraft->textWithTags.text.isEmpty()) { _history->clearDraft(key); } else { - localDraft->msgId = 0; + localDraft->reply = {}; } } } @@ -3025,7 +3028,7 @@ void ComposeControls::cancelReplyMessage() { } void ComposeControls::updateForwarding() { - const auto rootId = _currentDialogsEntryState.rootId; + const auto rootId = _topicRootId; const auto thread = (_history && rootId) ? _history->peer->forumTopicFor(rootId) : (Data::Thread*)_history; @@ -3118,7 +3121,7 @@ void ComposeControls::initForwardProcess() { ) | rpl::start_with_next([=](const Data::EntryUpdate &update) { if (const auto topic = update.entry->asTopic()) { if (topic->history() == _history - && topic->rootId() == _currentDialogsEntryState.rootId) { + && topic->rootId() == _topicRootId) { updateForwarding(); } } @@ -3145,8 +3148,10 @@ bool ComposeControls::isEditingMessage() const { return _header->isEditingMessage(); } -FullMsgId ComposeControls::replyingToMessage() const { - return _header->replyingToMessage(); +FullReplyTo ComposeControls::replyingToMessage() const { + auto result = _header->replyingToMessage(); + result.topicRootId = _topicRootId; + return result; } bool ComposeControls::readyToForward() 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 3dc5e46fa8..b98cbca833 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -185,7 +185,7 @@ public: [[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool readyToForward() const; [[nodiscard]] const HistoryItemsList &forwardItems() const; - [[nodiscard]] FullMsgId replyingToMessage() const; + [[nodiscard]] FullReplyTo replyingToMessage() const; [[nodiscard]] bool preventsClose(Fn &&continueCallback) const; @@ -198,7 +198,7 @@ public: void cancelEditMessage(); void maybeCancelEditMessage(); // Confirm if changed and cancel. - void replyToMessage(FullMsgId id); + void replyToMessage(FullReplyTo id); void cancelReplyMessage(); void updateForwarding(); @@ -345,6 +345,7 @@ private: rpl::event_stream _stickerOrEmojiChosen; History *_history = nullptr; + MsgId _topicRootId = 0; Fn _showSlowmodeError; Fn _sendActionFactory; rpl::variable _slowmodeSecondsLeft; 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 f74453919f..d1bf5c965b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -117,7 +117,7 @@ void ForwardPanel::checkTexts() { : kNameNoCaptionsVersion; if (keepNames) { for (const auto item : _data.items) { - if (const auto from = item->senderOriginal()) { + if (const auto from = item->originalSender()) { version += from->nameVersion(); } else if (const auto info = item->hiddenSenderInfo()) { ++version; @@ -154,7 +154,7 @@ void ForwardPanel::updateTexts() { auto names = std::vector(); names.reserve(_data.items.size()); for (const auto item : _data.items) { - if (const auto from = item->senderOriginal()) { + if (const auto from = item->originalSender()) { if (!insertedPeers.contains(from)) { insertedPeers.emplace(from); names.push_back(from->shortName()); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 60c6e8191d..57862872a4 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -655,7 +655,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { } if (const auto msgsigned = item->Get()) { if (!msgsigned->isAnonymousRank) { - result.author = msgsigned->author; + result.author = msgsigned->postAuthor; } } if (message->displayedEditDate()) { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 7bc9c5b57d..3073d3495f 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -601,7 +601,7 @@ bool AddReplyToMessageAction( if (!item) { return; } - list->replyToMessageRequestNotify(item->fullId()); + list->replyToMessageRequestNotify({ item->fullId() }); }, &st::menuIconReply); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index aab999319a..58e1676b03 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -68,10 +68,10 @@ Element *MousedElement/* = nullptr*/; HistoryMessageForwarded *prevForwarded, not_null item, HistoryMessageForwarded *forwarded) { - const auto sender = previous->senderOriginal(); + const auto sender = previous->originalSender(); if ((prevForwarded != nullptr) != (forwarded != nullptr)) { return false; - } else if (sender != item->senderOriginal()) { + } else if (sender != item->originalSender()) { return false; } else if (!prevForwarded || sender) { return true; @@ -178,7 +178,7 @@ bool DefaultElementDelegate::elementIsChatWide() { return false; } -void DefaultElementDelegate::elementReplyTo(const FullMsgId &to) { +void DefaultElementDelegate::elementReplyTo(const FullReplyTo &to) { } void DefaultElementDelegate::elementStartInteraction( @@ -275,8 +275,10 @@ QString DateTooltipText(not_null view) { } if (view->isSignedAuthorElided()) { if (const auto msgsigned = item->Get()) { - dateText += '\n' - + tr::lng_signed_author(tr::now, lt_user, msgsigned->author); + dateText += '\n' + tr::lng_signed_author( + tr::now, + lt_user, + msgsigned->postAuthor); } } return dateText; @@ -1447,7 +1449,7 @@ void Element::unloadHeavyPart() { _heavyCustomEmoji = false; _text.unloadPersistentAnimation(); if (const auto reply = data()->Get()) { - reply->replyToText.unloadPersistentAnimation(); + reply->unloadPersistentAnimation(); } } } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 4f62303c76..02635118a9 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -100,7 +100,7 @@ public: virtual void elementHandleViaClick(not_null bot) = 0; virtual bool elementIsChatWide() = 0; virtual not_null elementPathShiftGradient() = 0; - virtual void elementReplyTo(const FullMsgId &to) = 0; + virtual void elementReplyTo(const FullReplyTo &to) = 0; virtual void elementStartInteraction(not_null view) = 0; virtual void elementStartPremium( not_null view, @@ -149,7 +149,7 @@ public: const FullMsgId &context) override; void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; - void elementReplyTo(const FullMsgId &to) override; + void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction(not_null view) override; void elementStartPremium( not_null view, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 90924281cf..f2ed79c395 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1735,7 +1735,7 @@ not_null ListWidget::elementPathShiftGradient() { return _pathGradient.get(); } -void ListWidget::elementReplyTo(const FullMsgId &to) { +void ListWidget::elementReplyTo(const FullReplyTo &to) { replyToMessageRequestNotify(to); } @@ -2474,7 +2474,7 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { mouseActionCancel(); switch (CurrentQuickAction()) { case DoubleClickQuickAction::Reply: { - replyToMessageRequestNotify(_overElement->data()->fullId()); + replyToMessageRequestNotify({ _overElement->data()->fullId() }); } break; case DoubleClickQuickAction::React: { toggleFavoriteReaction(_overElement); @@ -3855,12 +3855,12 @@ bool ListWidget::lastMessageEditRequestNotify() const { } } -rpl::producer ListWidget::replyToMessageRequested() const { +rpl::producer ListWidget::replyToMessageRequested() const { return _requestedToReplyToMessage.events(); } -void ListWidget::replyToMessageRequestNotify(FullMsgId item) { - _requestedToReplyToMessage.fire(std::move(item)); +void ListWidget::replyToMessageRequestNotify(FullReplyTo id) { + _requestedToReplyToMessage.fire(std::move(id)); } rpl::producer ListWidget::readMessageRequested() const { @@ -3878,10 +3878,10 @@ void ListWidget::replyNextMessage(FullMsgId fullId, bool next) { if (!view->data()->isRegular()) { return replyNextMessage(newFullId, next); } - replyToMessageRequestNotify(newFullId); + replyToMessageRequestNotify({ newFullId }); _requestedToShowMessage.fire_copy(newFullId); } else { - replyToMessageRequestNotify(FullMsgId()); + replyToMessageRequestNotify({}); _highlighter.clear(); } }; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 704fb16857..138f39cca0 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -277,8 +277,8 @@ public: [[nodiscard]] rpl::producer editMessageRequested() const; void editMessageRequestNotify(FullMsgId item) const; [[nodiscard]] bool lastMessageEditRequestNotify() const; - [[nodiscard]] rpl::producer replyToMessageRequested() const; - void replyToMessageRequestNotify(FullMsgId item); + [[nodiscard]] rpl::producer replyToMessageRequested() const; + void replyToMessageRequestNotify(FullReplyTo id); [[nodiscard]] rpl::producer readMessageRequested() const; [[nodiscard]] rpl::producer showMessageRequested() const; void replyNextMessage(FullMsgId fullId, bool next = true); @@ -323,7 +323,7 @@ public: void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; not_null elementPathShiftGradient() override; - void elementReplyTo(const FullMsgId &to) override; + void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction(not_null view) override; void elementStartPremium( not_null view, @@ -735,7 +735,7 @@ private: base::Timer _touchScrollTimer; rpl::event_stream _requestedToEditMessage; - rpl::event_stream _requestedToReplyToMessage; + rpl::event_stream _requestedToReplyToMessage; rpl::event_stream _requestedToReadMessage; rpl::event_stream _requestedToShowMessage; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index a50b92d27a..87802b4f21 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -477,7 +477,7 @@ void Message::refreshRightBadge() { } else if (data()->author()->isMegagroup()) { if (const auto msgsigned = data()->Get()) { Assert(msgsigned->isAnonymousRank); - return msgsigned->author; + return msgsigned->postAuthor; } } const auto channel = data()->history()->peer->asMegagroup(); @@ -801,9 +801,9 @@ QSize Message::performCountOptimalSize() { accumulate_max(maxWidth, namew); } if (reply) { - auto replyw = st::msgPadding.left() + reply->maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); - if (reply->replyToVia) { - replyw += st::msgServiceFont->spacew + reply->replyToVia->maxWidth; + auto replyw = st::msgPadding.left() + reply->maxWidth() - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); + if (reply->originalVia) { + replyw += st::msgServiceFont->spacew + reply->originalVia->maxWidth; } accumulate_max(maxWidth, replyw); } @@ -1736,8 +1736,8 @@ void Message::clickHandlerPressedChanged( toggleTopicButtonRipple(pressed); } else if (_viewButton) { _viewButton->checkLink(handler, pressed); - } else if (const auto reply = displayedReply(); - reply && (handler == reply->replyToLink())) { + } else if (const auto reply = displayedReply() + ; reply && (handler == reply->link())) { toggleReplyRipple(pressed); } } @@ -2469,10 +2469,11 @@ bool Message::getStateReplyInfo( trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()); - if ((reply->replyToMsg || reply->replyToStory) - && g.contains(point)) { - outResult->link = reply->replyToLink(); - reply->ripple.lastPoint = point - g.topLeft(); + if (g.contains(point)) { + if (const auto link = reply->link()) { + outResult->link = reply->link(); + reply->ripple.lastPoint = point - g.topLeft(); + } } return true; } @@ -3439,7 +3440,7 @@ ClickHandlerPtr Message::fastReplyLink() const { } const auto itemId = data()->fullId(); _fastReplyLink = std::make_shared([=] { - delegate()->elementReplyTo(itemId); + delegate()->elementReplyTo({ itemId }); }); return _fastReplyLink; } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 7b878ec52c..0809848473 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1002,7 +1002,7 @@ void RepliesWidget::sendingFilesConfirmed( album, action); } - if (_composeControls->replyingToMessage().msg == action.replyTo.msgId) { + if (_composeControls->replyingToMessage() == action.replyTo) { _composeControls->cancelReplyMessage(); refreshTopBarActiveChat(); } @@ -1123,9 +1123,9 @@ bool RepliesWidget::showSendingFilesError( } Api::SendAction RepliesWidget::prepareSendAction( - Api::SendOptions options) const { + Api::SendOptions options) const { auto result = Api::SendAction(_history, options); - result.replyTo = { .msgId = replyToId(), .topicRootId = _rootId }; + result.replyTo = replyTo(); result.options.sendAs = _composeControls->sendAsPeer(); return result; } @@ -1444,28 +1444,29 @@ SendMenu::Type RepliesWidget::sendMenuType() const { : SendMenu::Type::Scheduled; } +FullReplyTo RepliesWidget::replyTo() const { + if (auto custom = _composeControls->replyingToMessage()) { + custom.topicRootId = _rootId; + return custom; + } + return FullReplyTo{ + .messageId = FullMsgId(_history->peer->id, _rootId), + .topicRootId = _rootId, + }; +} + void RepliesWidget::refreshTopBarActiveChat() { using namespace Dialogs; const auto state = EntryState{ .key = (_topic ? Key{ _topic } : Key{ _history }), .section = EntryState::Section::Replies, - .rootId = _rootId, - .currentReplyToId = _composeControls->replyingToMessage().msg, + .currentReplyTo = replyTo(), }; _topBar->setActiveChat(state, _sendAction.get()); _composeControls->setCurrentDialogsEntryState(state); controller()->setCurrentDialogsEntryState(state); } -MsgId RepliesWidget::replyToId() const { - const auto custom = _composeControls->replyingToMessage().msg; - return custom - ? custom - : (_rootId == Data::ForumTopic::kGeneralId) - ? MsgId() - : _rootId; -} - void RepliesWidget::refreshUnreadCountBadge(std::optional count) { if (count.has_value()) { _cornerButtons.updateJumpDownVisibility(count); @@ -2052,8 +2053,8 @@ bool RepliesWidget::confirmSendingFiles( insertTextOnCancel); } -void RepliesWidget::replyToMessage(FullMsgId itemId) { - _composeControls->replyToMessage(itemId); +void RepliesWidget::replyToMessage(FullReplyTo id) { + _composeControls->replyToMessage(std::move(id)); refreshTopBarActiveChat(); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index b048dca361..14104e9133 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -243,7 +243,7 @@ private: mtpRequestId *const saveEditMsgRequestId); void chooseAttach(std::optional overrideSendImagesAsPhotos); [[nodiscard]] SendMenu::Type sendMenuType() const; - [[nodiscard]] MsgId replyToId() const; + [[nodiscard]] FullReplyTo replyTo() const; [[nodiscard]] HistoryItem *lookupRoot() const; [[nodiscard]] Data::ForumTopic *lookupTopic(); [[nodiscard]] bool computeAreComments() const; @@ -252,7 +252,7 @@ private: void pushReplyReturn(not_null item); void checkReplyReturns(); void recountChatWidth(); - void replyToMessage(FullMsgId itemId); + void replyToMessage(FullReplyTo id); void refreshTopBarActiveChat(); void refreshUnreadCountBadge(std::optional count); diff --git a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp index 5a3ca97bc2..cc7652f62e 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp @@ -112,7 +112,7 @@ bool TranslateTracker::add( } if (!skipDependencies) { if (const auto reply = item->Get()) { - if (const auto to = reply->replyToMsg.get()) { + if (const auto to = reply->resolvedMessage.get()) { add(to, true); } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 52008c66fc..2e01c30c9b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1069,7 +1069,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const { if (reply) { const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { - result.link = reply->replyToLink(); + result.link = reply->link(); reply->ripple.lastPoint = point - replyRect.topLeft(); if (!reply->ripple.animation) { reply->ripple.animation = std::make_unique( @@ -1737,7 +1737,7 @@ int Gif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); } if (reply) { - accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); + accumulate_max(result, st::msgReplyPadding.left() + reply->maxWidth()); } return result; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 4ff49e6043..4f5e9a5c5c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -464,7 +464,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { if (reply) { const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { - result.link = reply->replyToLink(); + result.link = reply->link(); reply->ripple.lastPoint = point - replyRect.topLeft(); if (!reply->ripple.animation) { reply->ripple.animation = std::make_unique( @@ -519,10 +519,9 @@ bool UnwrappedMedia::hasTextForCopy() const { return _content->hasTextForCopy(); } -bool UnwrappedMedia::dragItemByHandler( - const ClickHandlerPtr &p) const { +bool UnwrappedMedia::dragItemByHandler(const ClickHandlerPtr &p) const { const auto reply = _parent->displayedReply(); - return !(reply && (reply->replyToLink() == p)); + return !reply || (reply->link() != p); } QRect UnwrappedMedia::contentRectForReactions() const { @@ -642,7 +641,7 @@ int UnwrappedMedia::additionalWidth( accumulate_max(result, 2 * st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.right()); } if (reply) { - accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); + accumulate_max(result, st::msgReplyPadding.left() + reply->maxWidth()); } return result; } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 3473738cf7..d9a8d36060 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -602,8 +602,7 @@ bool MainWidget::shareUrl( const auto topicRootId = thread->topicRootId(); history->setLocalDraft(std::make_unique( textWithTags, - 0, // replyTo - topicRootId, + FullReplyTo{ .topicRootId = topicRootId }, cursor, Data::PreviewState::Allowed)); history->clearLocalEditDraft(topicRootId); @@ -746,8 +745,8 @@ void MainWidget::sendBotCommand(Bot::SendCommandRequest request) { } } -void MainWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { - _history->hideSingleUseKeyboard(peer, replyTo); +void MainWidget::hideSingleUseKeyboard(FullMsgId replyToId) { + _history->hideSingleUseKeyboard(replyToId); } void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index fa33b5d839..386434ecce 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -188,7 +188,7 @@ public: not_null data); void sendBotCommand(Bot::SendCommandRequest request); - void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); + void hideSingleUseKeyboard(FullMsgId replyToId); void searchMessages(const QString &query, Dialogs::Key inChat); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index d8544ffc5f..e51098febe 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -3037,7 +3037,7 @@ void OverlayWidget::refreshMediaViewer() { void OverlayWidget::refreshFromLabel() { if (_message) { - _from = _message->senderOriginal(); + _from = _message->originalSender(); if (const auto info = _message->hiddenSenderInfo()) { _fromName = info->name; } else { diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 348a11591b..8559bf6211 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -59,6 +59,7 @@ constexpr auto kMultiDraftTagOld = quint64(0xFFFF'FFFF'FFFF'FF01ULL); constexpr auto kMultiDraftCursorsTagOld = quint64(0xFFFF'FFFF'FFFF'FF02ULL); constexpr auto kMultiDraftTag = quint64(0xFFFF'FFFF'FFFF'FF03ULL); constexpr auto kMultiDraftCursorsTag = quint64(0xFFFF'FFFF'FFFF'FF04ULL); +constexpr auto kRichDraftsTag = quint64(0xFFFF'FFFF'FFFF'FF05ULL); enum { // Local Storage Keys lskUserMap = 0x00, @@ -1041,7 +1042,7 @@ void EnumerateDrafts( } callback( key, - draft->msgId, + draft->reply, draft->textWithTags, draft->previewState, draft->cursor); @@ -1049,12 +1050,12 @@ void EnumerateDrafts( for (const auto &[key, source] : sources) { const auto draft = source.draft(); const auto cursor = source.cursor(); - if (draft.msgId + if (draft.reply.messageId || !draft.textWithTags.text.isEmpty() || cursor != MessageCursor()) { callback( key, - draft.msgId, + draft.reply, draft.textWithTags, draft.previewState, cursor); @@ -1119,14 +1120,18 @@ void Account::writeDrafts(not_null history) { auto size = int(sizeof(quint64) * 2 + sizeof(quint32)); const auto sizeCallback = [&]( auto&&, // key - MsgId, // msgId + const FullReplyTo &reply, const TextWithTags &text, Data::PreviewState, auto&&) { // cursor size += sizeof(qint64) // key + Serialize::stringSize(text.text) + sizeof(qint64) + TextUtilities::SerializeTagsSize(text.tags) - + sizeof(qint64) + sizeof(qint32); // msgId, previewState + + sizeof(qint64) + sizeof(qint64) // messageId + + Serialize::stringSize(reply.quote.text) + + sizeof(qint64) + + TextUtilities::SerializeTagsSize(reply.quote.tags) + + sizeof(qint32); // previewState }; EnumerateDrafts( map, @@ -1136,13 +1141,13 @@ void Account::writeDrafts(not_null history) { EncryptedDescriptor data(size); data.stream - << quint64(kMultiDraftTag) + << quint64(kRichDraftsTag) << SerializePeerId(peerId) << quint32(count); const auto writeCallback = [&]( const Data::DraftKey &key, - MsgId msgId, + const FullReplyTo &reply, const TextWithTags &text, Data::PreviewState previewState, auto&&) { // cursor @@ -1150,7 +1155,10 @@ void Account::writeDrafts(not_null history) { << key.serialize() << text.text << TextUtilities::SerializeTags(text.tags) - << qint64(msgId.bare) + << qint64(reply.messageId.peer.value) + << qint64(reply.messageId.msg.bare) + << reply.quote.text + << TextUtilities::SerializeTags(reply.quote.tags) << qint32(previewState); }; EnumerateDrafts( @@ -1201,7 +1209,7 @@ void Account::writeDraftCursors(not_null history) { const auto writeCallback = [&]( const Data::DraftKey &key, - MsgId, // msgId + auto&&, // reply auto&&, // text Data::PreviewState, const MessageCursor &cursor) { // cursor @@ -1343,7 +1351,9 @@ void Account::readDraftsWithCursors(not_null history) { quint64 tag = 0; draft.stream >> tag; - if (tag != kMultiDraftTag && tag != kMultiDraftTagOld) { + if (tag != kRichDraftsTag + && tag != kMultiDraftTag + && tag != kMultiDraftTagOld) { readDraftsWithCursorsLegacy(history, draft, tag); return; } @@ -1359,24 +1369,43 @@ void Account::readDraftsWithCursors(not_null history) { } auto map = Data::HistoryDrafts(); const auto keysOld = (tag == kMultiDraftTagOld); + const auto rich = (tag == kRichDraftsTag); for (auto i = 0; i != count; ++i) { - TextWithTags data; - QByteArray tagsSerialized; - qint64 keyValue = 0, messageId = 0; + TextWithTags quote; + TextWithTags text; + QByteArray textTagsSerialized; + QByteArray quoteTagsSerialized; + qint64 keyValue = 0; + qint64 messageIdPeer = 0, messageIdMsg = 0; qint32 keyValueOld = 0, uncheckedPreviewState = 0; if (keysOld) { draft.stream >> keyValueOld; } else { draft.stream >> keyValue; } - draft.stream - >> data.text - >> tagsSerialized - >> messageId - >> uncheckedPreviewState; - data.tags = TextUtilities::DeserializeTags( - tagsSerialized, - data.text.size()); + if (!rich) { + draft.stream + >> text.text + >> textTagsSerialized + >> messageIdMsg + >> uncheckedPreviewState; + messageIdPeer = peerId.value; + } else { + draft.stream + >> text.text + >> textTagsSerialized + >> messageIdPeer + >> messageIdMsg + >> quote.text + >> quoteTagsSerialized + >> uncheckedPreviewState; + quote.tags = TextUtilities::DeserializeTags( + quoteTagsSerialized, + quote.text.size()); + } + text.tags = TextUtilities::DeserializeTags( + textTagsSerialized, + text.text.size()); auto previewState = Data::PreviewState::Allowed; switch (static_cast(uncheckedPreviewState)) { case Data::PreviewState::Cancelled: @@ -1388,9 +1417,14 @@ void Account::readDraftsWithCursors(not_null history) { : Data::DraftKey::FromSerialized(keyValue); if (key && !key.isCloud()) { map.emplace(key, std::make_unique( - data, - messageId, - key.topicRootId(), + text, + FullReplyTo{ + .messageId = FullMsgId( + PeerId(messageIdPeer), + MsgId(messageIdMsg)), + .quote = quote, + .topicRootId = key.topicRootId(), + }, MessageCursor(), previewState)); } @@ -1455,8 +1489,7 @@ void Account::readDraftsWithCursorsLegacy( Data::DraftKey::Local(topicRootId), std::make_unique( msgData, - msgReplyTo, - topicRootId, + FullReplyTo{ FullMsgId(peerId, MsgId(msgReplyTo)) }, MessageCursor(), (msgPreviewCancelled ? Data::PreviewState::Cancelled @@ -1467,8 +1500,7 @@ void Account::readDraftsWithCursorsLegacy( Data::DraftKey::LocalEdit(topicRootId), std::make_unique( editData, - editMsgId, - topicRootId, + FullReplyTo{ FullMsgId(peerId, editMsgId) }, MessageCursor(), (editPreviewCancelled ? Data::PreviewState::Cancelled diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index 3c1525ed7a..ebc5ace386 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -51,7 +51,7 @@ using FileKey = quint64; enum class StartResult : uchar; struct MessageDraft { - MsgId msgId = 0; + FullReplyTo reply; TextWithTags textWithTags; Data::PreviewState previewState = Data::PreviewState::Allowed; }; diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index fd273c09f8..c7cbb8e255 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -159,8 +159,7 @@ Data::Draft OccupiedDraft(const QString &normalizedName) { + QString::number(OccupationTag()) + ";n:" + normalizedName }, - MsgId(0), // replyTo - kTopicRootId, + FullReplyTo(), MessageCursor(), Data::PreviewState::Allowed }; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index d00a49e71a..b48e630e9e 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -1060,12 +1060,14 @@ void Manager::notificationActivated( const auto replyToId = (id.msgId > 0 && !history->peer->isUser() && id.msgId != topicRootId) - ? id.msgId - : 0; + ? FullMsgId(history->peer->id, id.msgId) + : FullMsgId(); auto draft = std::make_unique( reply, - replyToId, - topicRootId, + FullReplyTo{ + .messageId = replyToId, + .topicRootId = topicRootId, + }, MessageCursor{ int(reply.text.size()), int(reply.text.size()), @@ -1150,7 +1152,7 @@ void Manager::notificationReplied( ? topicRootId : MsgId(0); message.action.replyTo = { - .msgId = replyToId, + .messageId = { replyToId ? history->peer->id : 0, replyToId }, .topicRootId = topic ? topic->rootId() : 0, }; message.action.clearDraft = false; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 13955e8dba..e8ce10a780 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -112,7 +112,10 @@ void ShareBotGame( } histories.sendPreparedMessage( history, - FullReplyTo{ .msgId = replyTo, .topicRootId = topicRootId }, + FullReplyTo{ + .messageId = { replyTo ? history->peer->id : 0, replyTo }, + .topicRootId = topicRootId, + }, randomId, Data::Histories::PrepareMessage( MTP_flags(flags), @@ -1036,16 +1039,12 @@ void Filler::addCreatePoll() { ? SendMenu::Type::SilentOnly : SendMenu::Type::Scheduled; const auto flag = PollData::Flags(); - const auto topicRootId = _request.rootId; - const auto replyToId = _request.currentReplyToId - ? _request.currentReplyToId - : topicRootId; + const auto replyTo = _request.currentReplyTo; auto callback = [=] { PeerMenuCreatePoll( controller, peer, - replyToId, - topicRootId, + replyTo, flag, flag, source, @@ -1464,8 +1463,7 @@ void PeerMenuShareContactBox( void PeerMenuCreatePoll( not_null controller, not_null peer, - MsgId replyToId, - MsgId topicRootId, + FullReplyTo replyTo, PollData::Flags chosen, PollData::Flags disabled, Api::SendType sendType, @@ -1490,10 +1488,12 @@ void PeerMenuCreatePoll( auto action = Api::SendAction( peer->owner().history(peer), result.options); - action.clearDraft = false; - action.replyTo = { .msgId = replyToId, .topicRootId = topicRootId }; + action.replyTo = replyTo; + const auto topicRootId = replyTo.topicRootId; if (const auto local = action.history->localDraft(topicRootId)) { action.clearDraft = local->textWithTags.text.isEmpty(); + } else { + action.clearDraft = false; } const auto api = &peer->session().api(); api->polls().create(result.poll, action, crl::guard(weak, [=] { @@ -1637,7 +1637,7 @@ void BlockSenderFromRepliesBox( PeerMenuBlockUserBox( box, &controller->window(), - item->senderOriginal(), + item->originalSender(), true, Window::ClearReply{ id }); } diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index d5808fbf55..f5fff7f1f0 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -86,8 +86,7 @@ void PeerMenuAddChannelMembers( void PeerMenuCreatePoll( not_null controller, not_null peer, - MsgId replyToId = 0, - MsgId topicRootId = 0, + FullReplyTo replyTo = FullReplyTo(), PollData::Flags chosen = PollData::Flags(), PollData::Flags disabled = PollData::Flags(), Api::SendType sendType = Api::SendType::Normal, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 7d223ee01e..e17c9b28fb 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1526,8 +1526,7 @@ bool SessionController::switchInlineQuery( }; auto draft = std::make_unique( textWithTags, - to.currentReplyToId, - to.rootId, + to.currentReplyTo, cursor, Data::PreviewState::Allowed); @@ -1539,11 +1538,12 @@ bool SessionController::switchInlineQuery( std::make_shared(history), params); } else { + const auto topicRootId = to.currentReplyTo.topicRootId; history->setLocalDraft(std::move(draft)); - history->clearLocalEditDraft(to.rootId); + history->clearLocalEditDraft(topicRootId); if (to.section == Section::Replies) { const auto commentId = MsgId(); - showRepliesForMessage(history, to.rootId, commentId, params); + showRepliesForMessage(history, topicRootId, commentId, params); } else { showPeerHistory(history->peer, params); } @@ -1560,7 +1560,7 @@ bool SessionController::switchInlineQuery( .section = (thread->asTopic() ? Dialogs::EntryState::Section::Replies : Dialogs::EntryState::Section::History), - .rootId = thread->topicRootId(), + .currentReplyTo = { .topicRootId = thread->topicRootId() }, }; return switchInlineQuery(entryState, bot, query); } From 394883b986b9e2d542d64181bd89220459ef31cd Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Oct 2023 14:00:04 +0400 Subject: [PATCH 30/99] Support replying to a different chat. --- .../history/history_item_components.cpp | 20 +-- .../history/history_item_components.h | 2 +- .../SourceFiles/history/history_widget.cpp | 23 ++- .../controls/history_view_forward_panel.cpp | 141 ++++++++++++++++++ .../controls/history_view_forward_panel.h | 5 + 5 files changed, 173 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 7bcaeb6491..bbc8d3f106 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -325,7 +325,7 @@ bool HistoryMessageReply::updateData( bool force) { const auto guard = gsl::finally([&] { refreshReplyToMedia(); }); if (!force) { - if (resolvedMessage || resolvedStory || _deleted) { + if (resolvedMessage || resolvedStory || _unavailable) { return true; } } @@ -365,7 +365,9 @@ bool HistoryMessageReply::updateData( const auto external = _fields.externalSenderId || !_fields.externalSenderName.isEmpty(); - if (resolvedMessage || resolvedStory || (external && force)) { + if (resolvedMessage + || resolvedStory + || (external && (!_fields.messageId || force))) { const auto repaint = [=] { holder->customEmojiRepaint(); }; const auto context = Core::MarkedTextContext{ .session = &holder->history()->session(), @@ -403,8 +405,8 @@ bool HistoryMessageReply::updateData( && resolvedMessage->from()->isUser()) ? resolvedMessage->from()->id : PeerId(); - } else { - resolvedMessage = 0; + } else if (!resolvedStory) { + _unavailable = true; } const auto media = resolvedMessage @@ -417,7 +419,7 @@ bool HistoryMessageReply::updateData( } } else if (force) { if (_fields.messageId || _fields.storyId) { - _deleted = true; + _unavailable = true; } _colorKey = 0; spoiler = nullptr; @@ -428,7 +430,7 @@ bool HistoryMessageReply::updateData( return resolvedMessage || resolvedStory || (external && !_fields.messageId) - || _deleted; + || _unavailable; } void HistoryMessageReply::set(ReplyFields fields) { @@ -500,7 +502,7 @@ void HistoryMessageReply::clearData(not_null holder) { resolvedStory.get()); resolvedStory = nullptr; } - _deleted = true; + _unavailable = true; refreshReplyToMedia(); } @@ -693,7 +695,7 @@ void HistoryMessageReply::paint( const auto pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler); if (w > st::msgReplyBarSkip) { - if (resolvedMessage || resolvedStory) { + if (resolvedMessage || resolvedStory || !_text.isEmpty()) { const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; auto hasPreview = (media && media->hasReplyPreview()) || (resolvedStory && resolvedStory->hasReplyPreview()); @@ -791,7 +793,7 @@ void HistoryMessageReply::unloadPersistentAnimation() { } QString HistoryMessageReply::statePhrase() const { - return ((_fields.messageId || _fields.storyId) && !_deleted) + return ((_fields.messageId || _fields.storyId) && !_unavailable) ? tr::lng_profile_loading(tr::now) : _fields.storyId ? tr::lng_deleted_story(tr::now) diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 4cd0338792..c46f838b8f 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -344,7 +344,7 @@ private: mutable PeerData *_externalSender = nullptr; mutable int _maxWidth = 0; mutable int _nameVersion = 0; - bool _deleted = false; + bool _unavailable = false; }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 955263ef21..846be207ec 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1656,7 +1656,9 @@ void HistoryWidget::saveDraft(bool delayed) { } void HistoryWidget::saveFieldToHistoryLocalDraft() { - if (!_history) return; + if (!_history) { + return; + } const auto topicRootId = MsgId(); if (_editMsgId) { @@ -6219,13 +6221,16 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } else { _forwardPanel->editOptions(controller()->uiShow()); } - } else if (replyTo() && replyTo().messageId.peer != _peer->id) { - // edit options - } else { + } else if (const auto reply = replyTo()) { + HistoryView::Controls::EditReplyOptions( + controller(), + reply, + _history); + } else if (_editMsgId) { controller()->showPeerHistory( _peer, Window::SectionShow::Way::Forward, - _editMsgId ? _editMsgId : replyTo().messageId.msg); + _editMsgId); } } } @@ -7096,6 +7101,7 @@ void HistoryWidget::processReply() { _processingReplyTo.messageId.msg, processContinue()); return; +#if 0 // Now we can "reply" to old legacy group messages. } else if (_processingReplyItem->history() == _migrated) { if (_processingReplyItem->isService()) { controller()->showToast(tr::lng_reply_cant(tr::now)); @@ -7113,10 +7119,11 @@ void HistoryWidget::processReply() { })); } return processCancel(); - } else if (_processingReplyItem->history() != _history - || !_processingReplyItem->isRegular()) { +#endif + } else if (!_processingReplyItem->isRegular()) { return processCancel(); - } else if (const auto forum = _peer->forum()) { + } else if (const auto forum = _peer->forum() + ; forum && _processingReplyItem->history() == _history) { const auto topicRootId = _processingReplyItem->topicRootId(); if (forum->topicDeleted(topicRootId)) { return processCancel(); 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 d1bf5c965b..dd6fd636db 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -29,6 +29,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "apiwrap.h" +#include "boxes/peer_list_controllers.h" +#include "data/data_changes.h" +#include "settings/settings_common.h" +#include "ui/widgets/buttons.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + namespace HistoryView::Controls { namespace { @@ -395,4 +403,137 @@ void ForwardPanel::paint( }); } +void ShowReplyToChatBox( + not_null window, + FullReplyTo reply, + base::weak_ptr oldThread) { + class Controller final : public ChooseRecipientBoxController { + public: + using Chosen = not_null; + + Controller(not_null session) + : ChooseRecipientBoxController( + session, + [=](Chosen thread) mutable { _singleChosen.fire_copy(thread); }, + nullptr) { + } + + void rowClicked(not_null row) override final { + ChooseRecipientBoxController::rowClicked(row); + } + + [[nodiscard]] rpl::producer singleChosen() const{ + return _singleChosen.events(); + } + + private: + rpl::event_stream _singleChosen; + + }; + + struct State { + not_null box; + not_null controller; + base::unique_qptr menu; + }; + const auto session = &window->session(); + const auto state = [&] { + auto controller = std::make_unique(session); + const auto controllerRaw = controller.get(); + auto box = Box(std::move(controller), nullptr); + const auto boxRaw = box.data(); + window->uiShow()->show(std::move(box)); + auto state = State{ boxRaw, controllerRaw }; + return boxRaw->lifetime().make_state(std::move(state)); + }(); + + auto chosen = [=](not_null thread) mutable { + const auto history = thread->owningHistory(); + const auto topicRootId = thread->topicRootId(); + const auto draft = history->localDraft(topicRootId); + const auto textWithTags = draft + ? draft->textWithTags + : TextWithTags(); + const auto cursor = draft ? draft->cursor : MessageCursor(); + reply.topicRootId = topicRootId; + history->setLocalDraft(std::make_unique( + textWithTags, + reply, + cursor, + Data::PreviewState::Allowed)); + history->clearLocalEditDraft(topicRootId); + history->session().changes().entryUpdated( + thread, + Data::EntryUpdate::Flag::LocalDraftSet); + + // Clear old one. + crl::on_main(oldThread, [=] { + const auto old = oldThread.get(); + const auto history = old->owningHistory(); + const auto topicRootId = old->topicRootId(); + if (const auto local = history->localDraft(topicRootId)) { + if (local->reply.messageId == reply.messageId) { + auto draft = *local; + draft.reply = { .topicRootId = topicRootId }; + if (Data::DraftIsNull(&draft)) { + history->clearLocalDraft(topicRootId); + } else { + history->setLocalDraft( + std::make_unique( + std::move(draft))); + } + old->session().api().saveDraftToCloudDelayed(old); + } + } + }); + return true; + }; + auto callback = [=, chosen = std::move(chosen)]( + Controller::Chosen thread) mutable { + const auto weak = Ui::MakeWeak(state->box); + if (!chosen(thread)) { + return; + } else if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + state->controller->singleChosen( + ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); +} + +void EditReplyOptions( + not_null controller, + FullReplyTo reply, + not_null thread) { + const auto weak = base::make_weak(thread); + controller->uiShow()->show(Box([=](not_null box) { + box->setTitle(rpl::single(u"Reply to Message"_q)); + + Settings::AddButton( + box->verticalLayout(), + rpl::single(u"Reply in another chat"_q), + st::settingsButton, + { &st::menuIconReply } + )->setClickedCallback([=] { + ShowReplyToChatBox(controller, reply, weak); + }); + + Settings::AddButton( + box->verticalLayout(), + rpl::single(u"Show message"_q), + st::settingsButton, + { &st::menuIconShowInChat } + )->setClickedCallback([=] { + controller->showPeerHistory( + reply.messageId.peer, + Window::SectionShow::Way::Forward, + reply.messageId.msg); + }); + + box->addButton(tr::lng_box_ok(), [=] { + box->closeBox(); + }); + })); +} + } // namespace HistoryView::Controls 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 038a0fbf79..26464e36f3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h @@ -71,4 +71,9 @@ private: }; +void EditReplyOptions( + not_null controller, + FullReplyTo reply, + not_null thread); + } // namespace HistoryView::Controls From 6c19274eac1b999525e85118cfbe967e360a975e Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Oct 2023 17:51:27 +0400 Subject: [PATCH 31/99] Support external reply to channel posts. --- .../history/history_inner_widget.cpp | 20 ++++- .../SourceFiles/history/history_widget.cpp | 20 ++++- .../history_view_compose_controls.cpp | 18 ++++ .../controls/history_view_forward_panel.cpp | 84 +++++++++++-------- .../controls/history_view_forward_panel.h | 18 +++- .../view/history_view_context_menu.cpp | 24 ++++-- .../view/history_view_replies_section.cpp | 14 +++- 7 files changed, 145 insertions(+), 53 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index d4b3aaf46d..4d68ab0610 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/admin_log/history_admin_log_item.h" #include "history/history_item.h" #include "history/history_item_helpers.h" +#include "history/view/controls/history_view_forward_panel.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_web_page.h" @@ -2196,7 +2197,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { return; } const auto itemId = item->fullId(); - const auto canReply = [&] { + const auto canSendReply = [&] { const auto peer = item->history()->peer; const auto topic = item->topic(); return topic @@ -2204,9 +2205,24 @@ 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; + }(); if (canReply) { _menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { - _widget->replyToMessage({ itemId }); + if (canSendReply) { + _widget->replyToMessage({ itemId }); + } else { + HistoryView::Controls::ShowReplyToChatBox( + controller->uiShow(), + { itemId }); + } }, &st::menuIconReply); } const auto repliesCount = item->repliesCount(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 846be207ec..ad1ec6f5ec 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6222,10 +6222,19 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { _forwardPanel->editOptions(controller()->uiShow()); } } else if (const auto reply = replyTo()) { - HistoryView::Controls::EditReplyOptions( - controller(), + const auto highlight = [=] { + controller()->showPeerHistory( + reply.messageId.peer, + Window::SectionShow::Way::Forward, + reply.messageId.msg); + }; + const auto history = _history; + using namespace HistoryView::Controls; + EditReplyOptions( + controller()->uiShow(), reply, - _history); + highlight, + [=] { ClearDraftReplyTo(history, reply.messageId); }); } else if (_editMsgId) { controller()->showPeerHistory( _peer, @@ -7611,7 +7620,10 @@ bool HistoryWidget::updateCanSendMessage() { if (!_peer) { return false; } - const auto replyTo = (_replyTo && !_editMsgId) ? _replyEditMsg : 0; + const auto checkTopicFromReplyTo = _replyTo + && !_editMsgId + && (_replyTo.messageId.peer == _peer->id); + const auto replyTo = checkTopicFromReplyTo ? _replyEditMsg : 0; const auto topic = replyTo ? replyTo->topic() : nullptr; const auto allWithoutPolls = Data::AllSendRestrictions() & ~ChatRestriction::SendPolls; 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 56f6cf752f..0e793b9550 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -414,6 +414,7 @@ private: const std::shared_ptr _show; History *_history = nullptr; + MsgId _topicRootId = 0; rpl::variable _title; rpl::variable _description; @@ -468,6 +469,7 @@ FieldHeader::FieldHeader( void FieldHeader::setHistory(const SetHistoryArgs &args) { _history = *args.history; + _topicRootId = args.topicRootId; } void FieldHeader::init() { @@ -619,8 +621,24 @@ void FieldHeader::init() { } else if (isLeftButton && inPhotoEdit) { _editPhotoRequests.fire({}); } else if (isLeftButton && inPreviewRect) { + const auto reply = replyingToMessage(); if (!isEditingMessage() && readyToForward()) { _forwardPanel->editOptions(_show); + } else if (!isEditingMessage() && reply) { + using namespace Controls; + const auto highlight = [=] { + _scrollToItemRequests.fire_copy(reply.messageId); + }; + const auto history = _history; + const auto topicRootId = _topicRootId; + const auto clearOldReplyTo = [=, id = reply.messageId] { + ClearDraftReplyTo(history, topicRootId, id); + }; + EditReplyOptions( + _show, + reply, + highlight, + clearOldReplyTo); } else { auto id = isEditingMessage() ? _editMsgId.current() 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 dd6fd636db..6e9bb59034 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -403,10 +403,38 @@ void ForwardPanel::paint( }); } +void ClearDraftReplyTo(not_null thread, FullMsgId equalTo) { + ClearDraftReplyTo( + thread->owningHistory(), + thread->topicRootId(), + equalTo); +} + +void ClearDraftReplyTo( + not_null history, + MsgId topicRootId, + FullMsgId equalTo) { + const auto local = history->localDraft(topicRootId); + if (!local || (equalTo && local->reply.messageId != equalTo)) { + return; + } + auto draft = *local; + draft.reply = { .topicRootId = topicRootId }; + if (Data::DraftIsNull(&draft)) { + history->clearLocalDraft(topicRootId); + } else { + history->setLocalDraft( + std::make_unique(std::move(draft))); + } + if (const auto thread = history->threadFor(topicRootId)) { + history->session().api().saveDraftToCloudDelayed(thread); + } +} + void ShowReplyToChatBox( - not_null window, + std::shared_ptr show, FullReplyTo reply, - base::weak_ptr oldThread) { + Fn clearOldDraft) { class Controller final : public ChooseRecipientBoxController { public: using Chosen = not_null; @@ -426,7 +454,15 @@ void ShowReplyToChatBox( return _singleChosen.events(); } + bool respectSavedMessagesChat() const override { + return false; + } + private: + void prepareViewHook() override { + delegate()->peerListSetTitle(rpl::single(u"Reply in..."_q)); + } + rpl::event_stream _singleChosen; }; @@ -436,13 +472,13 @@ void ShowReplyToChatBox( not_null controller; base::unique_qptr menu; }; - const auto session = &window->session(); + const auto session = &show->session(); const auto state = [&] { auto controller = std::make_unique(session); const auto controllerRaw = controller.get(); auto box = Box(std::move(controller), nullptr); const auto boxRaw = box.data(); - window->uiShow()->show(std::move(box)); + show->show(std::move(box)); auto state = State{ boxRaw, controllerRaw }; return boxRaw->lifetime().make_state(std::move(state)); }(); @@ -466,26 +502,9 @@ void ShowReplyToChatBox( thread, Data::EntryUpdate::Flag::LocalDraftSet); - // Clear old one. - crl::on_main(oldThread, [=] { - const auto old = oldThread.get(); - const auto history = old->owningHistory(); - const auto topicRootId = old->topicRootId(); - if (const auto local = history->localDraft(topicRootId)) { - if (local->reply.messageId == reply.messageId) { - auto draft = *local; - draft.reply = { .topicRootId = topicRootId }; - if (Data::DraftIsNull(&draft)) { - history->clearLocalDraft(topicRootId); - } else { - history->setLocalDraft( - std::make_unique( - std::move(draft))); - } - old->session().api().saveDraftToCloudDelayed(old); - } - } - }); + if (clearOldDraft) { + crl::on_main(&history->session(), clearOldDraft); + } return true; }; auto callback = [=, chosen = std::move(chosen)]( @@ -502,11 +521,11 @@ void ShowReplyToChatBox( } void EditReplyOptions( - not_null controller, + std::shared_ptr show, FullReplyTo reply, - not_null thread) { - const auto weak = base::make_weak(thread); - controller->uiShow()->show(Box([=](not_null box) { + Fn highlight, + Fn clearOldDraft) { + show->show(Box([=](not_null box) { box->setTitle(rpl::single(u"Reply to Message"_q)); Settings::AddButton( @@ -515,7 +534,7 @@ void EditReplyOptions( st::settingsButton, { &st::menuIconReply } )->setClickedCallback([=] { - ShowReplyToChatBox(controller, reply, weak); + ShowReplyToChatBox(show, reply, clearOldDraft); }); Settings::AddButton( @@ -523,12 +542,7 @@ void EditReplyOptions( rpl::single(u"Show message"_q), st::settingsButton, { &st::menuIconShowInChat } - )->setClickedCallback([=] { - controller->showPeerHistory( - reply.messageId.peer, - Window::SectionShow::Way::Forward, - reply.messageId.msg); - }); + )->setClickedCallback(highlight); box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); 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 26464e36f3..963d780af6 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h @@ -71,9 +71,21 @@ private: }; -void EditReplyOptions( - not_null controller, +void ClearDraftReplyTo(not_null thread, FullMsgId equalTo); +void ClearDraftReplyTo( + not_null history, + MsgId topicRootId, + FullMsgId equalTo); + +void ShowReplyToChatBox( + std::shared_ptr show, FullReplyTo reply, - not_null thread); + Fn clearOldDraft = nullptr); + +void EditReplyOptions( + std::shared_ptr show, + FullReplyTo reply, + Fn highlight, + Fn clearOldDraft = nullptr); } // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 3073d3495f..56d9b9b8b2 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_stories.h" #include "data/data_groups.h" #include "data/data_channel.h" +#include "data/data_chat.h" #include "data/data_file_click_handler.h" #include "data/data_file_origin.h" #include "data/data_message_reactions.h" @@ -588,20 +589,33 @@ bool AddReplyToMessageAction( const auto peer = item ? item->history()->peer.get() : nullptr; if (!item || !item->isRegular() - || !(topic - ? Data::CanSendAnything(topic) - : Data::CanSendAnything(peer)) || (context != Context::History && context != Context::Replies)) { return false; } + 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; + }(); + if (!canReply) { + return false; + } + const auto owner = &item->history()->owner(); const auto itemId = item->fullId(); menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { - const auto item = owner->message(itemId); if (!item) { return; + } else { + list->replyToMessageRequestNotify({ itemId }); } - list->replyToMessageRequestNotify({ item->fullId() }); }, &st::menuIconReply); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 0809848473..49fac6d314 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_replies_section.h" #include "history/view/controls/history_view_compose_controls.h" +#include "history/view/controls/history_view_forward_panel.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" @@ -318,10 +319,15 @@ RepliesWidget::RepliesWidget( }, _inner->lifetime()); _inner->replyToMessageRequested( - ) | rpl::filter([=] { - return !_joinGroup; - }) | rpl::start_with_next([=](auto fullId) { - replyToMessage(fullId); + ) | rpl::start_with_next([=](auto fullId) { + const auto canSendReply = _topic + ? Data::CanSendAnything(_topic) + : Data::CanSendAnything(_history->peer); + if (_joinGroup || !canSendReply) { + Controls::ShowReplyToChatBox(controller->uiShow(), { fullId }); + } else { + replyToMessage(fullId); + } }, _inner->lifetime()); _inner->showMessageRequested( From e13768ea50d5188ee8a8f4d762557ad6a5ed3c03 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Oct 2023 08:43:32 +0400 Subject: [PATCH 32/99] Fix build with Xcode. --- Telegram/SourceFiles/api/api_bot.cpp | 2 +- Telegram/SourceFiles/data/data_drafts.cpp | 2 +- Telegram/SourceFiles/data/data_msg_id.h | 1 + Telegram/SourceFiles/dialogs/dialogs_key.h | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index b45474fb58..239d2d0ab8 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -317,7 +317,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { .peer = item->history()->peer, .command = QString(button->text), .context = item->fullId(), - .replyTo = replyTo, + .replyTo = { replyTo }, }); } break; diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 807af94041..55cb287086 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -77,7 +77,7 @@ void ApplyPeerCloudDraft( reply.quote.entities), }, .storyId = (reply.storyId - ? FullStoryId(replyPeerId, reply.storyId) + ? FullStoryId{ replyPeerId, reply.storyId } : FullStoryId()), .topicRootId = topicRootId, }, diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 5d367d83e4..4fa1fde54d 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/qt/qt_compare.h" #include "data/data_peer_id.h" #include "ui/text/text_entity.h" diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index d542cfca3f..34fd9aa296 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -110,7 +110,7 @@ struct EntryState { FilterId filterId = 0; FullReplyTo currentReplyTo; - friend inline constexpr auto operator<=>(EntryState, EntryState) noexcept + friend inline auto operator<=>(EntryState, EntryState) noexcept = default; }; From 0926bb1288404c8d5955e1cf09775fa1e0d94187 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Oct 2023 09:32:59 +0400 Subject: [PATCH 33/99] Update API scheme on layer 166. --- Telegram/Resources/langs/lang.strings | 34 +++++++++++++------ Telegram/SourceFiles/mtproto/scheme/api.tl | 9 +++-- .../SourceFiles/payments/payments_form.cpp | 10 +++++- Telegram/SourceFiles/payments/payments_form.h | 1 + 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 1ffc626c22..502ec5718f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2092,16 +2092,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_participants_new#other" = "All users who joined the channels below after this date:"; "lng_prizes_date" = "Winners Selection Date"; "lng_prizes_how_works" = "Learn more"; -"lng_prizes_how_text#one" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscription for {duration} for its followers."; -"lng_prizes_how_text#other" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions for {duration} for its followers."; -"lng_prizes_how_when_all_of_one#one" = "On {date}, Telegram will automatically select {count} random subscribers of {channel}."; -"lng_prizes_how_when_all_of_one#other" = "On {date}, Telegram will automatically select {count} random subscribers of {channel}."; -"lng_prizes_how_when_all_of_many#one" = "On {date}, Telegram will automatically select {count} random subscribers of {channel} or other listed channels."; -"lng_prizes_how_when_all_of_many#other" = "On {date}, Telegram will automatically select {count} random subscribers of {channel} or other listed channels."; -"lng_prizes_how_when_new_of_one#one" = "On {date}, Telegram will automatically select {count} random user that joined {channel} after {start_date}"; -"lng_prizes_how_when_new_of_one#other" = "On {date}, Telegram will automatically select {count} random users that joined {channel} after {start_date}"; -"lng_prizes_how_when_new_of_many#one" = "On {date}, Telegram will automatically select {count} random user that joined {channel} or other listed channels after {start_date}"; -"lng_prizes_how_when_new_of_many#other" = "On {date}, Telegram will automatically select {count} random users that joined {channel} or other listed channels after {start_date}"; +"lng_prizes_how_title" = "About this giveaway"; +"lng_prizes_end_title" = "Giveaway ended"; +"lng_prizes_how_text" = "This giveaway is sponsored by {admins}."; +"lng_prizes_end_text" = "This giveaway was sponsored by {admins}."; +"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription for {duration} for its followers"; +"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions for {duration} for its followers."; +"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}."; +"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}."; +"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}."; +"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}."; +"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels."; +"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels."; +"lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}"; +"lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}"; +"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}"; +"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed channels after {start_date}"; +"lng_prizes_how_participate_one" = "To take part in this giveaway please join channel {channel} before {date}."; +"lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}."; +"lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel})."; +"lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started."; +"lng_prizes_how_yes_joined" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels)."; +"lng_prizes_you_won" = "You won a prize in this giveaway {cup}"; +"lng_prizes_you_didnt" = "You didn't win a prize in this giveaway."; +"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them."; "lng_prizes_badge" = "x{amount}"; "lng_gift_link_title" = "Gift Link"; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 8d720264cc..b0bf61c2b4 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -130,7 +130,7 @@ messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; -messageMediaGiveaway#4291677c flags:# only_new_subscribers:flags.0?true channels:Vector quantity:int months:int until_date:int = MessageMedia; +messageMediaGiveaway#58260664 flags:# only_new_subscribers:flags.0?true channels:Vector countries_iso2:flags.1?Vector quantity:int months:int until_date:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -1419,7 +1419,7 @@ help.premiumPromo#5334759c status_text:string status_entities:Vector boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; -inputStorePaymentPremiumGiveaway#e94a2529 flags:# only_new_subscribers:flags.0?true boost_peer:InputPeer additional_peers:flags.1?Vector random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentPremiumGiveaway#7c9375e6 flags:# only_new_subscribers:flags.0?true boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1569,7 +1569,7 @@ peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector users:Vector = stories.PeerStories; -stories.boostsStatus#e5c1aa5c flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue boost_url:string = stories.BoostsStatus; +stories.boostsStatus#71f2770d flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue boost_url:string prepaid_giveaways:flags.3?Vector = stories.BoostsStatus; stories.canApplyBoostOk#c3173587 = stories.CanApplyBoostResult; stories.canApplyBoostReplace#712c4655 current_boost:Peer chats:Vector = stories.CanApplyBoostResult; @@ -1587,6 +1587,8 @@ payments.checkedGiftCode#b722f158 flags:# via_giveaway:flags.2?true from_id:Peer payments.giveawayInfo#7a7bdc5a flags:# participating:flags.0?true preparing_results:flags.3?true joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long = payments.GiveawayInfo; payments.giveawayInfoResults#38c32424 flags:# winner:flags.0?true refunded:flags.1?true gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo; +prepaidGiveaway#b2539d54 id:long months:int quantity:int date:int = PrepaidGiveaway; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2059,6 +2061,7 @@ payments.getPremiumGiftCodeOptions#2757ba54 flags:# boost_peer:flags.0?InputPeer payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode; payments.applyGiftCode#f6e26854 slug:string = Updates; payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo; +payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 37bb927a8f..6099314bed 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -316,13 +316,21 @@ MTPInputInvoice Form::inputInvoice() const { : Flag()) | (giveaway.additionalChannels.empty() ? Flag() - : Flag::f_additional_peers)), + : Flag::f_additional_peers) + | (giveaway.countries.empty() + ? Flag() + : Flag::f_countries_iso2)), giveaway.boostPeer->input, MTP_vector(ranges::views::all( giveaway.additionalChannels ) | ranges::views::transform([](not_null c) { return MTPInputPeer(c->input); }) | ranges::to()), + MTP_vector(ranges::views::all( + giveaway.countries + ) | ranges::views::transform([](QString value) { + return MTP_string(value); + }) | ranges::to()), MTP_long(giftCode.randomId), MTP_int(giveaway.untilDate), MTP_string(giftCode.currency), diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index ee7850c16e..706f9b81d4 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -189,6 +189,7 @@ struct InvoiceSlug { struct InvoicePremiumGiftCodeGiveaway { not_null boostPeer; std::vector> additionalChannels; + std::vector countries; TimeId untilDate = 0; bool onlyNewSubscribers = false; }; From 981babf30244e1ca8481d3d5f8321da71fe8c168 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Oct 2023 13:29:02 +0400 Subject: [PATCH 34/99] Support giveaway information box. --- Telegram/Resources/langs/lang.strings | 20 +- Telegram/SourceFiles/api/api_premium.cpp | 59 +++++ Telegram/SourceFiles/api/api_premium.h | 34 +++ .../SourceFiles/boxes/gift_premium_box.cpp | 216 +++++++++++++++++- Telegram/SourceFiles/boxes/gift_premium_box.h | 13 ++ .../history/view/history_view_view_button.cpp | 7 +- .../view/media/history_view_giveaway.cpp | 6 +- .../view/media/history_view_premium_gift.cpp | 19 +- Telegram/SourceFiles/ui/effects/premium.style | 6 + 9 files changed, 342 insertions(+), 38 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 502ec5718f..a6e68c0375 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2076,15 +2076,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prize_title" = "Congratulations!"; "lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; -"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription for {duration}."; +"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription {duration}."; "lng_prize_gift_about" = "You've received a gift from {channel}."; -"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription for {duration}."; +"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription {duration}."; "lng_prize_open" = "Open Gift Link"; "lng_prizes_title#one" = "Giveaway Prize"; "lng_prizes_title#other" = "Giveaway Prizes"; -"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription for {duration}."; -"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions for {duration}."; +"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}."; +"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}."; "lng_prizes_participants" = "Participants"; "lng_prizes_participants_all#one" = "All subscribers of the channel:"; "lng_prizes_participants_all#other" = "All subscribers of the channels:"; @@ -2096,10 +2096,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_end_title" = "Giveaway ended"; "lng_prizes_how_text" = "This giveaway is sponsored by {admins}."; "lng_prizes_end_text" = "This giveaway was sponsored by {admins}."; -"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription for {duration} for its followers"; -"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions for {duration} for its followers."; +"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers"; +"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers."; "lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}."; "lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}."; +"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link."; +"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links."; "lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}."; "lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}."; "lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels."; @@ -2112,8 +2114,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}."; "lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel})."; "lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started."; -"lng_prizes_how_yes_joined" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels)."; +"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}."; +"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels)."; "lng_prizes_you_won" = "You won a prize in this giveaway {cup}"; +"lng_prizes_view_prize" = "View my prize"; "lng_prizes_you_didnt" = "You didn't win a prize in this giveaway."; "lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them."; "lng_prizes_badge" = "x{amount}"; @@ -2123,7 +2127,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_link_label_from" = "From"; "lng_gift_link_label_to" = "To"; "lng_gift_link_label_gift" = "Gift"; -"lng_gift_link_gift_premium" = "Telegram Premium for {duration}"; +"lng_gift_link_gift_premium" = "Telegram Premium {duration}"; "lng_gift_link_label_reason" = "Reason"; "lng_gift_link_reason_giveaway" = "Giveaway"; "lng_gift_link_label_date" = "Date"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 05048e29fa..433660b0df 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -242,6 +242,65 @@ rpl::producer Premium::giftCodeValue(const QString &slug) const { }); } +void Premium::applyGiftCode(const QString &slug, Fn done) { + _api.request(MTPpayments_ApplyGiftCode( + MTP_string(slug) + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + done({}); + }).fail([=](const MTP::Error &error) { + done(error.type()); + }).send(); +} + +void Premium::resolveGiveawayInfo( + not_null peer, + MsgId messageId, + Fn done) { + Expects(done != nullptr); + + _giveawayInfoDone = std::move(done); + if (_giveawayInfoRequestId) { + if (_giveawayInfoPeer == peer + && _giveawayInfoMessageId == messageId) { + return; + } + _api.request(_giveawayInfoRequestId).cancel(); + } + _giveawayInfoPeer = peer; + _giveawayInfoMessageId = messageId; + _giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo( + _giveawayInfoPeer->input, + MTP_int(_giveawayInfoMessageId.bare) + )).done([=](const MTPpayments_GiveawayInfo &result) { + _giveawayInfoRequestId = 0; + + auto info = GiveawayInfo(); + result.match([&](const MTPDpayments_giveawayInfo &data) { + info.participating = data.is_participating(); + info.state = data.is_preparing_results() + ? GiveawayState::Preparing + : GiveawayState::Running; + info.adminChannelId = data.vadmin_disallowed_chat_id() + ? ChannelId(*data.vadmin_disallowed_chat_id()) + : ChannelId(); + info.tooEarlyDate + = data.vjoined_too_early_date().value_or_empty(); + }, [&](const MTPDpayments_giveawayInfoResults &data) { + info.state = data.is_refunded() + ? GiveawayState::Refunded + : GiveawayState::Finished; + info.giftCode = qs(data.vgift_code_slug().value_or_empty()); + info.activatedCount = data.vactivated_count().v; + info.finishDate = data.vfinish_date().v; + }); + _giveawayInfoDone(std::move(info)); + }).fail([=] { + _giveawayInfoRequestId = 0; + _giveawayInfoDone({}); + }).send(); +} + const Data::SubscriptionOptions &Premium::subscriptionOptions() const { return _subscriptionOptions; } diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 6fe1c9d700..62c7b4c9a7 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -34,6 +34,29 @@ struct GiftCode { const GiftCode&) = default; }; +enum class GiveawayState { + Invalid, + Running, + Preparing, + Finished, + Refunded, +}; + +struct GiveawayInfo { + QString giftCode; + ChannelId adminChannelId = 0; + GiveawayState state = GiveawayState::Invalid; + TimeId tooEarlyDate = 0; + TimeId finishDate = 0; + int winnersCount = 0; + int activatedCount = 0; + bool participating = false; + + explicit operator bool() const { + return state != GiveawayState::Invalid; + } +}; + class Premium final { public: explicit Premium(not_null api); @@ -62,6 +85,12 @@ public: GiftCode updateGiftCode(const QString &slug, const GiftCode &code); [[nodiscard]] rpl::producer giftCodeValue( const QString &slug) const; + void applyGiftCode(const QString &slug, Fn done); + + void resolveGiveawayInfo( + not_null peer, + MsgId messageId, + Fn done); [[nodiscard]] auto subscriptionOptions() const -> const Data::SubscriptionOptions &; @@ -99,6 +128,11 @@ private: base::flat_map _giftCodes; rpl::event_stream _giftCodeUpdated; + mtpRequestId _giveawayInfoRequestId = 0; + PeerData *_giveawayInfoPeer = nullptr; + MsgId _giveawayInfoMessageId = 0; + Fn _giveawayInfoDone; + Data::SubscriptionOptions _subscriptionOptions; }; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index f797aa116f..94ff50e13f 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/weak_ptr.h" #include "boxes/peers/prepare_short_info_box.h" #include "data/data_changes.h" +#include "data/data_channel.h" +#include "data/data_media_types.h" // Data::Giveaway #include "data/data_peer_values.h" // Data::PeerPremiumValue. #include "data/data_session.h" #include "data/data_subscription_option.h" @@ -312,10 +314,10 @@ struct GiftCodeLink { return result; } -[[nodiscard]] rpl::producer DurationValue(int months) { +[[nodiscard]] tr::phrase GiftDurationPhrase(int months) { return (months < 12) - ? tr::lng_months(lt_count, rpl::single(float64(months))) - : tr::lng_years(lt_count, rpl::single(float64(months / 12))); + ? tr::lng_premium_gift_duration_months + : tr::lng_premium_gift_duration_years; } [[nodiscard]] object_ptr MakePeerTableValue( @@ -437,6 +439,19 @@ void GiftPremiumValidator::showBox(not_null user) { }).send(); } +rpl::producer GiftDurationValue(int months) { + return GiftDurationPhrase(months)( + lt_count, + rpl::single(float64((months < 12) ? months : (months / 12)))); +} + +QString GiftDuration(int months) { + return GiftDurationPhrase(months)( + tr::now, + lt_count, + (months < 12) ? months : (months / 12)); +} + void GiftCodeBox( not_null box, not_null controller, @@ -444,6 +459,7 @@ void GiftCodeBox( struct State { rpl::variable data; rpl::variable used; + bool sent = false; }; const auto session = &controller->session(); const auto state = box->lifetime().make_state(State{}); @@ -508,7 +524,7 @@ void GiftCodeBox( tr::lng_gift_link_label_gift(), tr::lng_gift_link_gift_premium( lt_duration, - DurationValue(current.months))); + GiftDurationValue(current.months))); AddTableRow( table, tr::lng_gift_link_label_reason(), @@ -567,10 +583,18 @@ void GiftCodeBox( ), [=] { if (state->used.current()) { box->closeBox(); - } else { - auto copy = state->data.current(); - copy.used = base::unixtime::now(); - state->data = std::move(copy); + } else if (!state->sent) { + state->sent = true; + const auto done = crl::guard(box, [=](const QString &error) { + if (error.isEmpty()) { + auto copy = state->data.current(); + copy.used = base::unixtime::now(); + state->data = std::move(copy); + } else { + box->uiShow()->showToast(error); + } + }); + controller->session().api().premium().applyGiftCode(slug, done); } }); const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding; @@ -598,3 +622,179 @@ void ResolveGiftCode( slug, crl::guard(controller, done)); } + +void GiveawayInfoBox( + not_null box, + not_null controller, + Data::Giveaway giveaway, + Api::GiveawayInfo info) { + using State = Api::GiveawayState; + const auto finished = (info.state == State::Finished) + || (info.state == State::Refunded); + + box->setTitle((finished + ? tr::lng_prizes_end_title + : tr::lng_prizes_how_title)()); + + const auto first = !giveaway.channels.empty() + ? giveaway.channels.front()->name() + : u"channel"_q; + auto text = (finished + ? tr::lng_prizes_end_text + : tr::lng_prizes_how_text)( + tr::now, + lt_admins, + tr::lng_prizes_admins( + tr::now, + lt_count, + giveaway.quantity, + lt_channel, + Ui::Text::Bold(first), + lt_duration, + TextWithEntities{ GiftDuration(giveaway.months) }, + Ui::Text::RichLangValue), + Ui::Text::RichLangValue); + const auto many = (giveaway.channels.size() > 1); + const auto count = info.winnersCount + ? info.winnersCount + : giveaway.quantity; + auto winners = giveaway.all + ? (many + ? tr::lng_prizes_winners_all_of_many + : tr::lng_prizes_winners_all_of_one)( + tr::now, + lt_count, + count, + lt_channel, + Ui::Text::Bold(first), + Ui::Text::RichLangValue) + : (many + ? tr::lng_prizes_winners_new_of_many + : tr::lng_prizes_winners_new_of_one)( + tr::now, + lt_count, + count, + lt_channel, + Ui::Text::Bold(first), + lt_start_date, + Ui::Text::Bold("some date"), + Ui::Text::RichLangValue); + text.append("\n\n").append((finished + ? tr::lng_prizes_end_when_finish + : tr::lng_prizes_how_when_finish)( + tr::now, + lt_date, + Ui::Text::Bold(langDayOfMonthFull( + base::unixtime::parse(giveaway.untilDate).date())), + lt_winners, + winners, + Ui::Text::RichLangValue)); + if (info.activatedCount > 0) { + text.append(' ').append(tr::lng_prizes_end_activated( + tr::now, + lt_count, + info.activatedCount)); + } + if (!info.giftCode.isEmpty()) { + text.append("\n\n"); + text.append(tr::lng_prizes_you_won( + tr::now, + lt_cup, + QString::fromUtf8("\xf0\x9f\x8f\x86"))); + } else if (info.state == State::Finished) { + text.append("\n\n"); + text.append(tr::lng_prizes_you_didnt(tr::now)); + } else if (info.state == State::Preparing) { + + } else if (info.state != State::Refunded) { + if (info.adminChannelId) { + const auto channel = controller->session().data().channel( + info.adminChannelId); + text.append("\n\n").append(tr::lng_prizes_how_no_admin( + tr::now, + lt_channel, + Ui::Text::Bold(channel->name()), + Ui::Text::RichLangValue)); + } else if (info.tooEarlyDate) { + text.append("\n\n").append(tr::lng_prizes_how_no_joined( + tr::now, + lt_date, + Ui::Text::Bold( + langDateTime(base::unixtime::parse(info.tooEarlyDate))), + Ui::Text::RichLangValue)); + } else if (info.participating) { + text.append("\n\n").append((many + ? tr::lng_prizes_how_yes_joined_many + : tr::lng_prizes_how_yes_joined_one)( + tr::now, + lt_channel, + Ui::Text::Bold(first), + Ui::Text::RichLangValue)); + } else { + text.append("\n\n").append((many + ? tr::lng_prizes_how_participate_many + : tr::lng_prizes_how_participate_one)( + tr::now, + lt_channel, + Ui::Text::Bold(first), + lt_date, + Ui::Text::Bold(langDayOfMonthFull( + base::unixtime::parse(giveaway.untilDate).date())), + Ui::Text::RichLangValue)); + } + } + const auto padding = st::boxPadding; + box->addRow( + object_ptr( + box.get(), + rpl::single(std::move(text)), + st::boxLabel), + { padding.left(), 0, padding.right(), padding.bottom() }); + + if (info.state == State::Refunded) { + const auto wrap = box->addRow( + object_ptr>( + box.get(), + object_ptr( + box.get(), + tr::lng_prizes_cancelled(), + st::giveawayRefundedLabel), + st::giveawayRefundedPadding), + { padding.left(), 0, padding.right(), padding.bottom() }); + const auto bg = wrap->lifetime().make_state( + st::boxRadius, + st::attentionBoxButton.textBgOver); + wrap->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(wrap); + bg->paint(p, wrap->rect()); + }, wrap->lifetime()); + } + if (const auto slug = info.giftCode; !slug.isEmpty()) { + box->addButton(tr::lng_prizes_view_prize(), [=] { + ResolveGiftCode(controller, slug); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + } else { + box->addButton(tr::lng_close(), [=] { box->closeBox(); }); + } +} + +void ResolveGiveawayInfo( + not_null controller, + not_null peer, + MsgId messageId, + Data::Giveaway giveaway) { + const auto show = [=](Api::GiveawayInfo info) { + if (!info) { + controller->showToast( + tr::lng_confirm_phone_link_invalid(tr::now)); + } else { + controller->show( + Box(GiveawayInfoBox, controller, giveaway, info)); + } + }; + controller->session().api().premium().resolveGiveawayInfo( + peer, + messageId, + crl::guard(controller, show)); +} diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index 6fbac2b76a..3f6d273bc6 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -15,6 +15,10 @@ namespace Api { struct GiftCode; } // namespace Api +namespace Data { +struct Giveaway; +} // namespace Data + namespace Ui { class GenericBox; } // namespace Ui @@ -38,6 +42,9 @@ private: }; +[[nodiscard]] rpl::producer GiftDurationValue(int months); +[[nodiscard]] QString GiftDuration(int months); + void GiftCodeBox( not_null box, not_null controller, @@ -45,3 +52,9 @@ void GiftCodeBox( void ResolveGiftCode( not_null controller, const QString &slug); + +void ResolveGiveawayInfo( + not_null controller, + not_null peer, + MsgId messageId, + Data::Giveaway giveaway); diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index 70c49452a2..c5448392a2 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -16,8 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_sponsored_messages.h" #include "data/data_user.h" #include "data/data_web_page.h" -#include "history/history_item_components.h" #include "history/view/history_view_cursor_state.h" +#include "history/history_item_components.h" +#include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/click_handler.h" @@ -86,6 +87,9 @@ inline auto WebPageToPhrase(not_null webpage) { [[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler( not_null media) { if (const auto giveaway = media->giveaway()) { + const auto peer = media->parent()->history()->peer; + const auto messageId = media->parent()->id; + const auto info = *giveaway; return std::make_shared([=]( ClickContext context) { const auto my = context.other.value(); @@ -93,6 +97,7 @@ inline auto WebPageToPhrase(not_null webpage) { if (!controller) { return; } + ResolveGiveawayInfo(controller, peer, messageId, info); }); } const auto webpage = media->webpage(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 5f3e64a24e..5f0d3f51dd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_giveaway.h" #include "base/unixtime.h" +#include "boxes/gift_premium_box.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "data/data_channel.h" #include "data/data_document.h" @@ -91,9 +92,6 @@ void Giveaway::fillFromData(not_null giveaway) { tr::lng_prizes_title(tr::now, lt_count, _quantity), kDefaultTextOptions); - const auto duration = (_months < 12) - ? tr::lng_months(tr::now, lt_count, _months) - : tr::lng_years(tr::now, lt_count, _months / 12); _prizes.setMarkedText( st::defaultTextStyle, tr::lng_prizes_about( @@ -101,7 +99,7 @@ void Giveaway::fillFromData(not_null giveaway) { lt_count, _quantity, lt_duration, - Ui::Text::Bold(duration), + Ui::Text::Bold(GiftDuration(_months)), Ui::Text::RichLangValue), kDefaultTextOptions); _participantsTitle.setText( diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp index fdb3943b2e..1ec8cd507a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -23,18 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" namespace HistoryView { -namespace { - -[[nodiscard]] QString FormatGiftMonths(int months) { - return (months < 12) - ? tr::lng_premium_gift_duration_months(tr::now, lt_count, months) - : tr::lng_premium_gift_duration_years( - tr::now, - lt_count, - std::round(months / 12.)); -} - -} // namespace PremiumGift::PremiumGift( not_null parent, @@ -62,11 +50,8 @@ QString PremiumGift::title() { TextWithEntities PremiumGift::subtitle() { if (_data.slug.isEmpty()) { - return { FormatGiftMonths(_data.months) }; + return { GiftDuration(_data.months) }; } - const auto duration = (_data.months < 12) - ? tr::lng_months(tr::now, lt_count, _data.months) - : tr::lng_years(tr::now, lt_count, _data.months / 12); const auto name = _data.channel ? _data.channel->name() : "channel"; auto result = (_data.viaGiveaway ? tr::lng_prize_about @@ -81,7 +66,7 @@ TextWithEntities PremiumGift::subtitle() { : tr::lng_prize_gift_duration)( tr::now, lt_duration, - Ui::Text::Bold(duration), + Ui::Text::Bold(GiftDuration(_data.months)), Ui::Text::RichLangValue)); return result; } diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index b3442e8a70..d7521700e7 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -342,3 +342,9 @@ giveawayGiftCodeBox: Box(defaultBox) { } shadowIgnoreTopSkip: true; } +giveawayRefundedLabel: FlatLabel(boxLabel) { + align: align(top); + style: semiboldTextStyle; + textFg: attentionButtonFg; +} +giveawayRefundedPadding: margins(8px, 10px, 8px, 10px); From 714dae054a0eddbeaa01447a63e0b5180e719258 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Oct 2023 13:47:21 +0400 Subject: [PATCH 35/99] Jump to giveaway link from gift code box. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_premium.cpp | 2 + Telegram/SourceFiles/api/api_premium.h | 2 + .../SourceFiles/boxes/gift_premium_box.cpp | 41 ++++++++++++++----- Telegram/SourceFiles/ui/boxes/boost_box.cpp | 3 -- Telegram/SourceFiles/ui/boxes/boost_box.h | 2 + 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a6e68c0375..2f1f55dffe 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2130,6 +2130,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_link_gift_premium" = "Telegram Premium {duration}"; "lng_gift_link_label_reason" = "Reason"; "lng_gift_link_reason_giveaway" = "Giveaway"; +"lng_gift_link_reason_chosen" = "You were selected by the channel"; "lng_gift_link_label_date" = "Date"; "lng_gift_link_also_send" = "You can also {link} to a friend as a gift."; "lng_gift_link_also_send_link" = "send this link"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 433660b0df..23eb237a51 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -23,9 +23,11 @@ namespace { return { .from = peerFromMTP(data.vfrom_id()), .to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(), + .giveawayId = data.vgiveaway_msg_id().value_or_empty(), .date = data.vdate().v, .used = data.vused_date().value_or_empty(), .months = data.vmonths().v, + .giveaway = data.is_via_giveaway(), }; } diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 62c7b4c9a7..af7b2b14c1 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -21,9 +21,11 @@ namespace Api { struct GiftCode { PeerId from = 0; PeerId to = 0; + MsgId giveawayId = 0; TimeId date = 0; TimeId used = 0; // 0 if not used. int months = 0; + bool giveaway = false; explicit operator bool() const { return months != 0; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 94ff50e13f..0922607118 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "settings/settings_premium.h" #include "ui/basic_click_handlers.h" // UrlClickHandler::Open. +#include "ui/boxes/boost_box.h" // StartFireworks. #include "ui/controls/userpic_button.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_stars_colored.h" @@ -371,18 +372,21 @@ void AddTableRow( valueMargins); } -void AddTableRow( +not_null AddTableRow( not_null table, rpl::producer label, - rpl::producer value) { + rpl::producer value) { + auto widget = object_ptr( + table, + std::move(value), + st::giveawayGiftCodeValue); + const auto result = widget.data(); AddTableRow( table, std::move(label), - object_ptr( - table, - std::move(value), - st::giveawayGiftCodeValue), + std::move(widget), st::giveawayGiftCodeValueMargin); + return result; } void AddTableRow( @@ -524,16 +528,31 @@ void GiftCodeBox( tr::lng_gift_link_label_gift(), tr::lng_gift_link_gift_premium( lt_duration, - GiftDurationValue(current.months))); - AddTableRow( + GiftDurationValue(current.months) | Ui::Text::ToWithEntities(), + Ui::Text::WithEntities)); + const auto reason = AddTableRow( table, tr::lng_gift_link_label_reason(), - tr::lng_gift_link_reason_giveaway()); + (current.giveawayId + ? (tr::lng_gift_link_reason_giveaway() | Ui::Text::ToLink()) + : current.giveaway + ? (tr::lng_gift_link_reason_giveaway( + Ui::Text::WithEntities + ) | rpl::type_erased()) + : tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities))); + reason->setClickHandlerFilter([=](const auto &...) { + controller->showPeerHistory( + current.from, + Window::SectionShow::Way::Forward, + current.giveawayId); + return false; + }); if (current.date) { AddTableRow( table, tr::lng_gift_link_label_date(), - rpl::single(langDateTime(base::unixtime::parse(current.date)))); + rpl::single(Ui::Text::WithEntities( + langDateTime(base::unixtime::parse(current.date))))); } auto shareLink = tr::lng_gift_link_also_send_link( @@ -590,6 +609,8 @@ void GiftCodeBox( auto copy = state->data.current(); copy.used = base::unixtime::now(); state->data = std::move(copy); + + Ui::StartFireworks(box->parentWidget()); } else { box->uiShow()->showToast(error); } diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index abae42f218..20e2e71b68 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -17,7 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_premium.h" namespace Ui { -namespace { void StartFireworks(not_null parent) { const auto result = Ui::CreateChild(parent.get()); @@ -37,8 +36,6 @@ void StartFireworks(not_null parent) { }, lifetime); } -} // namespace - void BoostBox( not_null box, BoostBoxData data, diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index e35178fac7..2c688acda9 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { +void StartFireworks(not_null parent); + class GenericBox; class VerticalLayout; From d5429e769fba1bf9e29d9bbdcf89a6afb6505e81 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Oct 2023 14:33:59 +0400 Subject: [PATCH 36/99] Allow sharing gift code link. --- Telegram/SourceFiles/boxes/gift_premium_box.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 0922607118..9dfbaa4f89 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "mainwidget.h" #include "settings/settings_premium.h" #include "ui/basic_click_handlers.h" // UrlClickHandler::Open. #include "ui/boxes/boost_box.h" // StartFireworks. @@ -36,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/gradient_round_button.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/table_layout.h" +#include "window/window_peer_menu.h" // ShowChooseRecipientBox. #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" @@ -580,7 +582,14 @@ void GiftCodeBox( st::giveawayGiftCodeFooter), st::giveawayGiftCodeFooterMargin); footer->setClickHandlerFilter([=](const auto &...) { - box->uiShow()->showToast(u"Sharing..."_q); + const auto chosen = [=](not_null thread) { + const auto content = controller->content(); + return content->shareUrl( + thread, + MakeGiftCodeLink(&controller->session(), slug).link, + QString()); + }; + Window::ShowChooseRecipientBox(controller, chosen); return false; }); From 00db325e91362ed87a86c45edf2d451a67340d91 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Oct 2023 16:22:01 +0400 Subject: [PATCH 37/99] Fix view giveaway details button in forwards. --- Telegram/SourceFiles/history/view/history_view_message.cpp | 4 ++++ .../SourceFiles/history/view/history_view_view_button.cpp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 87802b4f21..0b46cc479f 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2955,6 +2955,10 @@ void Message::refreshDataIdHook() { if (base::take(_fastReplyLink)) { _fastReplyLink = fastReplyLink(); } + if (_viewButton) { + _viewButton = nullptr; + updateViewButtonExistence(); + } if (_comments) { _comments->link = nullptr; } diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index c5448392a2..0ba4f8f2c4 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -89,6 +89,9 @@ inline auto WebPageToPhrase(not_null webpage) { if (const auto giveaway = media->giveaway()) { const auto peer = media->parent()->history()->peer; const auto messageId = media->parent()->id; + if (media->parent()->isSending() || media->parent()->hasFailed()) { + return nullptr; + } const auto info = *giveaway; return std::make_shared([=]( ClickContext context) { From ef0539c9fcf986540bf07562e040aff52389398c Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Oct 2023 21:49:26 +0400 Subject: [PATCH 38/99] Allow replying with quoting message part. --- Telegram/SourceFiles/data/data_drafts.cpp | 6 +- Telegram/SourceFiles/data/data_histories.cpp | 18 ++++- Telegram/SourceFiles/data/data_msg_id.h | 2 +- .../history/history_inner_widget.cpp | 19 ++++- .../history/history_inner_widget.h | 2 + .../SourceFiles/history/history_widget.cpp | 2 +- Telegram/SourceFiles/history/history_widget.h | 2 +- .../history/view/history_view_element.h | 5 +- .../history/view/history_view_message.cpp | 74 +++++++++++++++++++ .../history/view/history_view_message.h | 4 + .../view/history_view_service_message.cpp | 10 +++ .../view/history_view_service_message.h | 4 + .../view/media/history_view_document.cpp | 16 ++++ .../view/media/history_view_document.h | 1 + .../history/view/media/history_view_gif.cpp | 4 + .../history/view/media/history_view_gif.h | 1 + .../history/view/media/history_view_media.h | 6 +- .../history/view/media/history_view_photo.cpp | 4 + .../history/view/media/history_view_photo.h | 1 + .../SourceFiles/storage/storage_account.cpp | 15 +--- 20 files changed, 167 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 55cb287086..58ad920ef1 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -71,11 +71,7 @@ void ApplyPeerCloudDraft( textWithTags, FullReplyTo{ .messageId = FullMsgId(replyPeerId, reply.messageId), - .quote = TextWithTags{ - reply.quote.text, - TextUtilities::ConvertEntitiesToTextTags( - reply.quote.entities), - }, + .quote = reply.quote, .storyId = (reply.storyId ? FullStoryId{ replyPeerId, reply.storyId } : FullStoryId()), diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index ab002d24ff..9c9b9059ac 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_histories.h" +#include "api/api_text_entities.h" #include "data/data_session.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -46,10 +47,20 @@ MTPInputReplyTo ReplyToForMTP( } } else if (replyTo.messageId || replyTo.topicRootId) { const auto external = (replyTo.messageId.peer != history->peer->id); + const auto quoteEntities = Api::EntitiesToMTP( + &history->session(), + replyTo.quote.entities, + Api::ConvertOption::SkipLocal); using Flag = MTPDinputReplyToMessage::Flag; return MTP_inputReplyToMessage( MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag()) - | (external ? Flag::f_reply_to_peer_id : Flag())), + | (external ? Flag::f_reply_to_peer_id : Flag()) + | (replyTo.quote.text.isEmpty() + ? Flag() + : Flag::f_quote_text) + | (quoteEntities.v.isEmpty() + ? Flag() + : Flag::f_quote_entities)), MTP_int(replyTo.messageId ? replyTo.messageId.msg : replyTo.topicRootId), @@ -57,8 +68,8 @@ MTPInputReplyTo ReplyToForMTP( (external ? owner->peer(replyTo.messageId.peer)->input : MTPInputPeer()), - MTPstring(), // quote_text - MTPVector()); // quote_entities + MTP_string(replyTo.quote.text), + quoteEntities); } return MTPInputReplyTo(); } @@ -945,6 +956,7 @@ int Histories::sendPreparedMessage( } const auto realReplyTo = FullReplyTo{ .messageId = convertTopicReplyToId(history, replyTo.messageId), + .quote = replyTo.quote, .storyId = replyTo.storyId, .topicRootId = convertTopicReplyToId(history, replyTo.topicRootId), }; diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 4fa1fde54d..43260a10c4 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -158,7 +158,7 @@ Q_DECLARE_METATYPE(FullMsgId); struct FullReplyTo { FullMsgId messageId; - TextWithTags quote; + TextWithEntities quote; FullStoryId storyId; MsgId topicRootId = 0; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 4d68ab0610..04f7c55182 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2101,6 +2101,20 @@ void HistoryInner::toggleFavoriteReaction(not_null view) const { item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick); } +TextWithEntities HistoryInner::selectedQuote( + not_null item) const { + if (_selected.size() != 1 + || _selected.begin()->first != item + || _selected.begin()->second == FullSelection) { + return {}; + } + const auto view = item->mainView(); + if (!view) { + return {}; + } + return view->selectedQuote(_selected.begin()->second); +} + void HistoryInner::contextMenuEvent(QContextMenuEvent *e) { showContextMenu(e); } @@ -2215,13 +2229,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { return true; }(); if (canReply) { + const auto quote = selectedQuote(item); _menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { if (canSendReply) { - _widget->replyToMessage({ itemId }); + _widget->replyToMessage({ itemId, quote }); } else { HistoryView::Controls::ShowReplyToChatBox( controller->uiShow(), - { itemId }); + { itemId, quote }); } }, &st::menuIconReply); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 38d68cdde5..981b9e4248 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -314,6 +314,8 @@ private: QPoint mapPointToItem(QPoint p, const Element *view) const; QPoint mapPointToItem(QPoint p, const HistoryItem *item) const; + [[nodiscard]] TextWithEntities selectedQuote( + not_null item) const; void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); void cancelContextDownload(not_null document); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ad1ec6f5ec..c8d72b6e10 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7072,7 +7072,7 @@ void HistoryWidget::replyToMessage(FullReplyTo id) { void HistoryWidget::replyToMessage( not_null item, - TextWithTags quote) { + TextWithEntities quote) { if (isJoinChannel()) { return; } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 84e4b8be9f..1e0358eaf9 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -185,7 +185,7 @@ public: void replyToMessage(FullReplyTo id); void replyToMessage( not_null item, - TextWithTags quote = {}); + TextWithEntities quote = {}); void editMessage(FullMsgId itemId); void editMessage(not_null item); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 02635118a9..6c16cef305 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -388,7 +388,10 @@ public: int bottom, QPoint point, InfoDisplayType type) const; - virtual TextForMimeData selectedText( + virtual TextForMimeData selectedText(TextSelection selection) const = 0; + virtual TextWithEntities selectedQuote(TextSelection selection) const = 0; + virtual TextWithEntities selectedQuote( + const Ui::Text::String &text, TextSelection selection) const = 0; [[nodiscard]] virtual TextSelection adjustSelection( TextSelection selection, diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 0b46cc479f..8b86fa2e80 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2611,6 +2611,80 @@ TextForMimeData Message::selectedText(TextSelection selection) const { return result; } +TextWithEntities Message::selectedQuote(TextSelection selection) const { + const auto item = data(); + const auto &translated = item->translatedText(); + const auto &original = item->originalText(); + if (&translated != &original + || selection.empty() + || selection == FullSelection) { + return {}; + } else if (hasVisibleText()) { + return selectedQuote(text(), selection); + } else if (const auto media = this->media()) { + if (media->isDisplayed() || isHiddenByGroup()) { + return media->selectedQuote(selection); + } + } + return {}; +} + +TextWithEntities Message::selectedQuote( + const Ui::Text::String &text, + TextSelection selection) const { + if (selection.to > text.length()) { + return {}; + } + auto modified = selection; + const auto &modifications = text.modifications(); + 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::adjustSelection( TextSelection selection, TextSelectType type) const { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 2c0ebaf0e5..1efc0892be 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -99,6 +99,10 @@ 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; TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 44ddb1c171..983d7ff114 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -669,6 +669,16 @@ 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 { + return {}; +} + TextSelection Service::adjustSelection( TextSelection selection, TextSelectType type) const { diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index 4fcd5748f8..d40a69c8bb 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -43,6 +43,10 @@ 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; TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index c8e25a5515..165f79dea8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -1210,6 +1210,22 @@ TextForMimeData Document::selectedText(TextSelection selection) const { return result; } +TextWithEntities Document::selectedQuote(TextSelection selection) const { + if (const auto voice = Get()) { + const auto length = voice->transcribeText.length(); + if (selection.from < length) { + return {}; + } + selection = HistoryView::UnshiftItemSelection( + selection, + voice->transcribeText); + } + if (const auto captioned = Get()) { + return parent()->selectedQuote(captioned->caption, selection); + } + return {}; +} + bool Document::uploading() const { return _data->uploading(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index 08f0c3ada9..6084820eb6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -46,6 +46,7 @@ public: bool hasTextForCopy() const override; TextForMimeData selectedText(TextSelection selection) const override; + TextWithEntities selectedQuote(TextSelection selection) const override; bool uploading() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 2e01c30c9b..cd475f2d2a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1181,6 +1181,10 @@ TextForMimeData Gif::selectedText(TextSelection selection) const { return _caption.toTextForMimeData(selection); } +TextWithEntities Gif::selectedQuote(TextSelection selection) const { + return parent()->selectedQuote(_caption, selection); +} + bool Gif::fullFeaturedGrouped(RectParts sides) const { return (sides & RectPart::Left) && (sides & RectPart::Right); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index c737d23914..9a81457ebb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -68,6 +68,7 @@ public: } TextForMimeData selectedText(TextSelection selection) const override; + TextWithEntities selectedQuote(TextSelection selection) const override; bool uploading() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 386f4e8684..ea60e186fc 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -86,7 +86,11 @@ public: [[nodiscard]] virtual TextForMimeData selectedText( TextSelection selection) const { - return TextForMimeData(); + return {}; + } + [[nodiscard]] virtual TextWithEntities selectedQuote( + TextSelection selection) const { + return {}; } [[nodiscard]] virtual bool isDisplayed() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 8e0763e395..5e912d33c5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -1049,6 +1049,10 @@ TextForMimeData Photo::selectedText(TextSelection selection) const { return _caption.toTextForMimeData(selection); } +TextWithEntities Photo::selectedQuote(TextSelection selection) const { + return parent()->selectedQuote(_caption, selection); +} + void Photo::hideSpoilers() { _caption.setSpoilerRevealed(false, anim::type::instant); if (_spoiler) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index cfe9e9627b..76c1ff6ad5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -57,6 +57,7 @@ public: } TextForMimeData selectedText(TextSelection selection) const override; + TextWithEntities selectedQuote(TextSelection selection) const override; PhotoData *getPhoto() const override { return _data; diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 8559bf6211..78374de821 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -1126,11 +1126,8 @@ void Account::writeDrafts(not_null history) { auto&&) { // cursor size += sizeof(qint64) // key + Serialize::stringSize(text.text) - + sizeof(qint64) + TextUtilities::SerializeTagsSize(text.tags) + + TextUtilities::SerializeTagsSize(text.tags) + sizeof(qint64) + sizeof(qint64) // messageId - + Serialize::stringSize(reply.quote.text) - + sizeof(qint64) - + TextUtilities::SerializeTagsSize(reply.quote.tags) + sizeof(qint32); // previewState }; EnumerateDrafts( @@ -1157,8 +1154,6 @@ void Account::writeDrafts(not_null history) { << TextUtilities::SerializeTags(text.tags) << qint64(reply.messageId.peer.value) << qint64(reply.messageId.msg.bare) - << reply.quote.text - << TextUtilities::SerializeTags(reply.quote.tags) << qint32(previewState); }; EnumerateDrafts( @@ -1371,10 +1366,8 @@ void Account::readDraftsWithCursors(not_null history) { const auto keysOld = (tag == kMultiDraftTagOld); const auto rich = (tag == kRichDraftsTag); for (auto i = 0; i != count; ++i) { - TextWithTags quote; TextWithTags text; QByteArray textTagsSerialized; - QByteArray quoteTagsSerialized; qint64 keyValue = 0; qint64 messageIdPeer = 0, messageIdMsg = 0; qint32 keyValueOld = 0, uncheckedPreviewState = 0; @@ -1396,12 +1389,7 @@ void Account::readDraftsWithCursors(not_null history) { >> textTagsSerialized >> messageIdPeer >> messageIdMsg - >> quote.text - >> quoteTagsSerialized >> uncheckedPreviewState; - quote.tags = TextUtilities::DeserializeTags( - quoteTagsSerialized, - quote.text.size()); } text.tags = TextUtilities::DeserializeTags( textTagsSerialized, @@ -1422,7 +1410,6 @@ void Account::readDraftsWithCursors(not_null history) { .messageId = FullMsgId( PeerId(messageIdPeer), MsgId(messageIdMsg)), - .quote = quote, .topicRootId = key.topicRootId(), }, MessageCursor(), From f90a010b842260760bd2f7703dc03d64f01532c4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Oct 2023 16:17:02 +0400 Subject: [PATCH 39/99] Update API scheme on layer 166. --- Telegram/Resources/langs/lang.strings | 3 ++ Telegram/SourceFiles/api/api_premium.cpp | 4 ++ Telegram/SourceFiles/api/api_premium.h | 2 + Telegram/SourceFiles/apiwrap.cpp | 6 ++- .../SourceFiles/boxes/gift_premium_box.cpp | 7 +++- .../boxes/reactions_settings_box.cpp | 4 +- Telegram/SourceFiles/data/data_session.cpp | 4 +- .../export/data/export_data_types.cpp | 4 ++ .../export/data/export_data_types.h | 6 ++- .../export/output/export_output_html.cpp | 3 ++ .../export/output/export_output_json.cpp | 2 + .../admin_log/history_admin_log_item.cpp | 24 ++++++++++- Telegram/SourceFiles/history/history_item.cpp | 13 ++++++ .../history/history_item_components.cpp | 2 +- .../history/history_item_helpers.cpp | 3 +- .../view/media/history_view_giveaway.cpp | 12 ------ Telegram/SourceFiles/main/main_account.cpp | 4 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 42 +++++++++++-------- 18 files changed, 106 insertions(+), 39 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2f1f55dffe..0f77401b70 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2114,6 +2114,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}."; "lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel})."; "lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started."; +"lng_prizes_how_no_country" = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway."; "lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}."; "lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels)."; "lng_prizes_you_won" = "You won a prize in this giveaway {cup}"; @@ -3459,6 +3460,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}"; "lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam"; "lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam"; +"lng_admin_log_change_color" = "{from} changed color"; +"lng_admin_log_change_background_emoji" = "{from} changed background emoji"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}"; "lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 23eb237a51..320f3f94a1 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -286,8 +286,11 @@ void Premium::resolveGiveawayInfo( info.adminChannelId = data.vadmin_disallowed_chat_id() ? ChannelId(*data.vadmin_disallowed_chat_id()) : ChannelId(); + info.disallowedCountry = qs( + data.vdisallowed_country().value_or_empty()); info.tooEarlyDate = data.vjoined_too_early_date().value_or_empty(); + info.startDate = data.vstart_date().v; }, [&](const MTPDpayments_giveawayInfoResults &data) { info.state = data.is_refunded() ? GiveawayState::Refunded @@ -295,6 +298,7 @@ void Premium::resolveGiveawayInfo( info.giftCode = qs(data.vgift_code_slug().value_or_empty()); info.activatedCount = data.vactivated_count().v; info.finishDate = data.vfinish_date().v; + info.startDate = data.vstart_date().v; }); _giveawayInfoDone(std::move(info)); }).fail([=] { diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index af7b2b14c1..01ed83cc7b 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -46,10 +46,12 @@ enum class GiveawayState { struct GiveawayInfo { QString giftCode; + QString disallowedCountry; ChannelId adminChannelId = 0; GiveawayState state = GiveawayState::Invalid; TimeId tooEarlyDate = 0; TimeId finishDate = 0; + TimeId startDate = 0; int winnersCount = 0; int activatedCount = 0; bool participating = false; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 006a99d51f..9f76547edb 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2155,7 +2155,8 @@ void ApiWrap::saveDraftsToCloud() { ReplyToForMTP(history, cloudDraft->reply), history->peer->input, MTP_string(textWithTags.text), - entities + entities, + MTPInputMedia() )).done([=](const MTPBool &result, const MTP::Response &response) { const auto requestId = response.requestId; history->finishSavingCloudDraft( @@ -3628,8 +3629,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) { } else if (message.webPageId) { auto page = _session->data().webpage(message.webPageId); media = MTP_messageMediaWebPage( + MTP_flags(0), MTP_webPagePending( + MTP_flags(0), MTP_long(page->id), + MTPstring(), // url MTP_int(page->pendingTill))); } const auto anonymousPost = peer->amAnonymous(); diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 9dfbaa4f89..71f3c42c46 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -707,7 +707,8 @@ void GiveawayInfoBox( lt_channel, Ui::Text::Bold(first), lt_start_date, - Ui::Text::Bold("some date"), + Ui::Text::Bold( + langDateTime(base::unixtime::parse(info.startDate))), Ui::Text::RichLangValue); text.append("\n\n").append((finished ? tr::lng_prizes_end_when_finish @@ -752,6 +753,10 @@ void GiveawayInfoBox( Ui::Text::Bold( langDateTime(base::unixtime::parse(info.tooEarlyDate))), Ui::Text::RichLangValue)); + } else if (!info.disallowedCountry.isEmpty()) { + text.append("\n\n").append(tr::lng_prizes_how_no_country( + tr::now, + Ui::Text::RichLangValue)); } else if (info.participating) { text.append("\n\n").append((many ? tr::lng_prizes_how_yes_joined_many diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index 9b375e4c82..952aa0c49c 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -63,7 +63,9 @@ PeerId GenerateUser(not_null history, const QString &name) { MTPstring(), // lang code MTPEmojiStatus(), MTPVector(), - MTPint())); // stories_max_id + MTPint(), // stories_max_id + MTP_int(0), // color + MTPlong())); // background_emoji_id return peerId; } diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 4eba210f48..6039dcf21a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4335,7 +4335,9 @@ void Session::serviceNotification( MTPstring(), // lang_code MTPEmojiStatus(), MTPVector(), - MTPint())); // stories_max_id + MTPint(), // stories_max_id + MTP_int(0), // color + MTPlong())); // background_emoji_id } const auto history = this->history(PeerData::kServiceNotificationsId); if (!history->folderKnown()) { diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 6420c37592..ab6cb3e507 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1320,6 +1320,10 @@ ServiceAction ParseServiceAction( content.viaGiveaway = data.is_via_giveaway(); content.months = data.vmonths().v; content.code = data.vslug().v; + result.content = content; + }, [&](const MTPDmessageActionGiveawayLaunch &) { + auto content = ActionGiveawayLaunch(); + result.content = content; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index a4fd372f40..6556a35b31 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -547,6 +547,9 @@ struct ActionRequestedPeer { int buttonId = 0; }; +struct ActionGiveawayLaunch { +}; + struct ServiceAction { std::variant< v::null_t, @@ -586,7 +589,8 @@ struct ServiceAction { ActionRequestedPeer, ActionSetChatWallPaper, ActionSetSameChatWallPaper, - ActionGiftCode> content; + ActionGiftCode, + ActionGiveawayLaunch> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 54dd8e33fd..e42aced3e8 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1289,6 +1289,9 @@ auto HtmlWriter::Wrap::pushMessage( + NumberToString(data.months) + (data.months > 1 ? " months" : "month") + " gift from a channel."); + }, [&](const ActionGiveawayLaunch &data) { + return serviceFrom + " just started a giveaway " + "of Telegram Premium subscriptions to its followers."; }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 8655eeeec5..02545e1fdd 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -601,6 +601,8 @@ QByteArray SerializeMessage( } push("months", data.months); push("via_giveaway", data.viaGiveaway); + }, [&](const ActionGiveawayLaunch &data) { + pushAction("giveaway_launch"); }, [&](const ActionSetChatWallPaper &data) { pushActor(); pushAction("set_chat_wallpaper"); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 4e2e3cd418..dbac38ff51 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -767,6 +767,8 @@ void GenerateItems( using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic; using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic; using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam; + using LogChangeColor = MTPDchannelAdminLogEventActionChangeColor; + using LogChangeBackgroundEmoji = MTPDchannelAdminLogEventActionChangeBackgroundEmoji; const auto session = &history->session(); const auto id = event.vid().v; @@ -1815,6 +1817,24 @@ void GenerateItems( addSimpleServiceMessage(text); }; + const auto createChangeColor = [&](const LogChangeColor &data) { + const auto text = tr::lng_admin_log_change_color( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); + }; + + const auto createChangeBackgroundEmoji = [&](const LogChangeBackgroundEmoji &data) { + const auto text = tr::lng_admin_log_change_background_emoji( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); + }; + action.match( createChangeTitle, createChangeAbout, @@ -1858,7 +1878,9 @@ void GenerateItems( createEditTopic, createDeleteTopic, createPinTopic, - createToggleAntiSpam); + createToggleAntiSpam, + createChangeColor, + createChangeBackgroundEmoji); } } // namespace AdminLog diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index b1f1f1a611..5ac0a761c9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4447,6 +4447,17 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareGiveawayLaunch = [&](const MTPDmessageActionGiveawayLaunch &action) { + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_action_giveaway_started( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + return result; + }; + setServiceText(action.match([&]( const MTPDmessageActionChatAddUser &data) { return prepareChatAddUserText(data); @@ -4529,6 +4540,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return prepareSetSameChatWallPaper(data); }, [&](const MTPDmessageActionGiftCode &data) { return prepareGiftCode(data); + }, [&](const MTPDmessageActionGiveawayLaunch &data) { + return prepareGiveawayLaunch(data); }, [](const MTPDmessageActionEmpty &) { return PreparedServiceText{ { tr::lng_message_empty(tr::now) } }; })); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index bbc8d3f106..7ba64d26f6 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -284,7 +284,7 @@ ReplyFields ReplyFieldsFromMTP( = data.vreply_to_top_id().value_or(id); result.topicPost = data.is_forum_topic(); } - if (const auto header = data.vreply_header()) { + if (const auto header = data.vreply_from()) { const auto &data = header->data(); result.externalPostAuthor = qs(data.vpost_author().value_or_empty()); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index be78380837..2e5fa74521 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -374,7 +374,8 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { | (externalPeerId ? Flag::f_reply_to_peer_id : Flag())), MTP_int(replyTo.messageId.msg), peerToMTP(externalPeerId), - MTPMessageFwdHeader(), // reply_header + MTPMessageFwdHeader(), // reply_from + MTPMessageMedia(), // reply_media MTP_int(replyToTop), // reply_to_top_id MTPstring(), // quote_text MTPVector()); // quote_entities diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 5f0d3f51dd..2d9a81fd8e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -62,18 +62,6 @@ Giveaway::Giveaway( , _winnersTitle(st::msgMinWidth) , _winners(st::msgMinWidth) { fillFromData(giveaway); - - if (!parent->data()->Has() - && ranges::contains( - giveaway->channels, - parent->data()->history()->peer)) { - parent->setServicePreMessage({ - tr::lng_action_giveaway_started( - tr::now, - lt_from, - parent->data()->history()->peer->name()), - }); - } } Giveaway::~Giveaway() { diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp index 0eae6e91b9..e39d487985 100644 --- a/Telegram/SourceFiles/main/main_account.cpp +++ b/Telegram/SourceFiles/main/main_account.cpp @@ -172,7 +172,9 @@ void Account::createSession( MTPstring(), // lang_code MTPEmojiStatus(), MTPVector(), - MTPint()), // stories_max_id + MTPint(), // stories_max_id + MTP_int(0), // color + MTPlong()), // background_emoji_id serialized, streamVersion, std::move(settings)); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index b0bf61c2b4..f7a7489a4e 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -43,6 +43,7 @@ inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia; +inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; @@ -81,7 +82,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#abb5f120 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int = User; +user#6fdee0df flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:int background_emoji_id:flags2.6?long = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -96,7 +97,7 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#94f592db flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int = Chat; +channel#2458af8c flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:int background_emoji_id:flags2.5?long = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; @@ -113,7 +114,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -122,7 +123,7 @@ messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; messageMediaDocument#4cf4d72d flags:# nopremium:flags.3?true spoiler:flags.4?true document:flags.0?Document alt_document:flags.5?Document ttl_seconds:flags.2?int = MessageMedia; -messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; +messageMediaWebPage#ddf10c3b flags:# force_large_media:flags.0?true force_small_media:flags.1?true manual:flags.3?true webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string extended_media:flags.4?MessageExtendedMedia = MessageMedia; @@ -172,6 +173,7 @@ messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; messageActionSetChatWallPaper#bc44a927 wallpaper:WallPaper = MessageAction; messageActionSetSameChatWallPaper#c0787d6d wallpaper:WallPaper = MessageAction; messageActionGiftCode#d2cfdb0e flags:# via_giveaway:flags.0?true boost_peer:flags.1?Peer months:int slug:string = MessageAction; +messageActionGiveawayLaunch#332ba9ed = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -550,9 +552,9 @@ messages.allStickers#cdbbcebb hash:long sets:Vector = messages.AllSt messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages; -webPageEmpty#eb1477e8 id:long = WebPage; -webPagePending#c586da1c id:long date:int = WebPage; -webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; +webPageEmpty#211a1788 flags:# id:long url:flags.0?string = WebPage; +webPagePending#b0d13e47 flags:# id:long url:flags.0?string date:int = WebPage; +webPage#e89c45b2 flags:# has_large_media:flags.13?true id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true encrypted_requests_disabled:flags.3?true call_requests_disabled:flags.4?true unconfirmed:flags.5?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; @@ -637,10 +639,10 @@ messageEntityPhone#9b69e34b offset:int length:int = MessageEntity; messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity; messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; -messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; messageEntityCustomEmoji#c8cf05f8 offset:int length:int document_id:long = MessageEntity; +messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; @@ -757,7 +759,7 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#95b0528b flags:# no_webpage:flags.1?true reply_to:flags.4?MessageReplyHeader message:string entities:flags.3?Vector date:int = DraftMessage; +draftMessage#db074fa8 flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?MessageReplyHeader message:string entities:flags.3?Vector media:flags.5?MessageMedia date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; @@ -962,6 +964,8 @@ channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:For channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction; channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic = ChannelAdminLogEventAction; channelAdminLogEventActionToggleAntiSpam#64f36dfc new_value:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeColor#3c2b247b prev_value:int new_value:int = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeBackgroundEmoji#445fc434 prev_value:long new_value:long = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1258,7 +1262,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#3d5c1693 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_header:flags.5?MessageFwdHeader reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector = MessageReplyHeader; +messageReplyHeader#6eebcabd flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector = MessageReplyHeader; messageReplyStoryHeader#9c98bfc1 user_id:long story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1584,8 +1588,8 @@ premiumGiftCodeOption#257e962b flags:# users:int months:int store_product:flags. payments.checkedGiftCode#b722f158 flags:# via_giveaway:flags.2?true from_id:Peer giveaway_msg_id:flags.3?int to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; -payments.giveawayInfo#7a7bdc5a flags:# participating:flags.0?true preparing_results:flags.3?true joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long = payments.GiveawayInfo; -payments.giveawayInfoResults#38c32424 flags:# winner:flags.0?true refunded:flags.1?true gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo; +payments.giveawayInfo#4367daa0 flags:# participating:flags.0?true preparing_results:flags.3?true start_date:int joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long disallowed_country:flags.4?string = payments.GiveawayInfo; +payments.giveawayInfoResults#cd5570 flags:# winner:flags.0?true refunded:flags.1?true start_date:int gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo; prepaidGiveaway#b2539d54 id:long months:int quantity:int date:int = PrepaidGiveaway; @@ -1710,6 +1714,7 @@ account.getAutoSaveSettings#adcbbcda = account.AutoSaveSettings; account.saveAutoSaveSettings#d69b8361 flags:# users:flags.0?true chats:flags.1?true broadcasts:flags.2?true peer:flags.3?InputPeer settings:AutoSaveSettings = Bool; account.deleteAutoSaveExceptions#53bc0020 = Bool; account.invalidateSignInCodes#ca8ae8ba codes:Vector = Bool; +account.updateColor#a001cc43 flags:# color:int background_emoji_id:flags.0?long = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -1750,8 +1755,8 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#280d096f flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; -messages.sendMedia#72ccc23d flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMessage#280d096f flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMedia#72ccc23d flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.forwardMessages#c661bbc4 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; @@ -1797,12 +1802,12 @@ messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_p messages.setInlineBotResults#bb12a419 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM switch_webview:flags.4?InlineBotWebView = Bool; messages.sendInlineBotResult#f7bc68ba flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to:flags.0?InputReplyTo random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; -messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; -messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; +messages.editMessage#48f71778 flags:# no_webpage:flags.1?true invert_media:flags.16?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; +messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true invert_media:flags.16?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#64a3026c flags:# no_webpage:flags.1?true reply_to:flags.0?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector = Bool; +messages.saveDraft#7ff3b806 flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; @@ -1830,7 +1835,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#456e8987 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMultiMedia#456e8987 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -2028,6 +2033,7 @@ channels.toggleAntiSpam#68f3e4eb channel:InputChannel enabled:Bool = Updates; channels.reportAntiSpamFalsePositive#a850a693 channel:InputChannel msg_id:int = Bool; channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates; channels.clickSponsoredMessage#18afbc93 channel:InputChannel random_id:bytes = Bool; +channels.updateColor#621a201f flags:# channel:InputChannel color:int background_emoji_id:flags.0?long = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; From 4b6107fa56883cd57653648b0ecacbb959dd85b6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Oct 2023 13:52:08 +0400 Subject: [PATCH 40/99] Use color index from API. --- Telegram/SourceFiles/calls/calls_userpic.cpp | 3 +- Telegram/SourceFiles/data/data_changes.h | 55 +++++------ Telegram/SourceFiles/data/data_peer.cpp | 35 ++++++- Telegram/SourceFiles/data/data_peer.h | 32 +++---- Telegram/SourceFiles/data/data_session.cpp | 6 ++ .../export/data/export_data_types.cpp | 34 ++++--- .../export/data/export_data_types.h | 13 ++- .../export/output/export_output_html.cpp | 8 +- .../export/output/export_output_html.h | 2 +- .../history/history_item_components.cpp | 28 +++--- .../history/history_item_components.h | 9 +- .../history/view/history_view_message.cpp | 33 +------ .../history/view/history_view_message.h | 2 +- .../view/media/history_view_contact.cpp | 2 +- .../view/media/history_view_document.cpp | 2 +- .../media/history_view_extended_preview.cpp | 2 +- .../history/view/media/history_view_gif.cpp | 2 +- .../view/media/history_view_media_grouped.cpp | 2 +- .../history/view/media/history_view_photo.cpp | 2 +- .../inline_bots/inline_bot_layout_item.cpp | 3 +- Telegram/SourceFiles/ui/chat/chat_style.cpp | 92 +++++++++++++++++-- Telegram/SourceFiles/ui/chat/chat_style.h | 41 ++++++++- .../ui/controls/userpic_button.cpp | 3 +- Telegram/SourceFiles/ui/empty_userpic.cpp | 13 ++- Telegram/SourceFiles/ui/empty_userpic.h | 5 +- .../window/themes/window_theme_preview.cpp | 2 +- 26 files changed, 282 insertions(+), 149 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_userpic.cpp b/Telegram/SourceFiles/calls/calls_userpic.cpp index f296ef0c60..38f4671709 100644 --- a/Telegram/SourceFiles/calls/calls_userpic.cpp +++ b/Telegram/SourceFiles/calls/calls_userpic.cpp @@ -202,8 +202,7 @@ void Userpic::createCache(Image *image) { { auto p = QPainter(&filled); Ui::EmptyUserpic( - Ui::EmptyUserpic::UserpicColor( - Data::PeerColorIndex(_peer->id)), + Ui::EmptyUserpic::UserpicColor(_peer->colorIndex()), _peer->name() ).paintCircle(p, 0, 0, size, size); } diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 12cfa7353e..a7f52b98bd 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -71,41 +71,42 @@ struct PeerUpdate { FullInfo = (1ULL << 11), Usernames = (1ULL << 12), TranslationDisabled = (1ULL << 13), + Color = (1ULL << 14), // For users - CanShareContact = (1ULL << 14), - IsContact = (1ULL << 15), - PhoneNumber = (1ULL << 16), - OnlineStatus = (1ULL << 17), - BotCommands = (1ULL << 18), - BotCanBeInvited = (1ULL << 19), - BotStartToken = (1ULL << 20), - CommonChats = (1ULL << 21), - HasCalls = (1ULL << 22), - SupportInfo = (1ULL << 23), - IsBot = (1ULL << 24), - EmojiStatus = (1ULL << 25), - StoriesState = (1ULL << 26), + CanShareContact = (1ULL << 15), + IsContact = (1ULL << 16), + PhoneNumber = (1ULL << 17), + OnlineStatus = (1ULL << 18), + BotCommands = (1ULL << 19), + BotCanBeInvited = (1ULL << 20), + BotStartToken = (1ULL << 21), + CommonChats = (1ULL << 22), + HasCalls = (1ULL << 23), + SupportInfo = (1ULL << 24), + IsBot = (1ULL << 25), + EmojiStatus = (1ULL << 26), + StoriesState = (1ULL << 27), // For chats and channels - InviteLinks = (1ULL << 27), - Members = (1ULL << 28), - Admins = (1ULL << 29), - BannedUsers = (1ULL << 30), - Rights = (1ULL << 31), - PendingRequests = (1ULL << 32), - Reactions = (1ULL << 33), + InviteLinks = (1ULL << 28), + Members = (1ULL << 29), + Admins = (1ULL << 30), + BannedUsers = (1ULL << 31), + Rights = (1ULL << 32), + PendingRequests = (1ULL << 33), + Reactions = (1ULL << 34), // For channels - ChannelAmIn = (1ULL << 34), - StickersSet = (1ULL << 35), - ChannelLinkedChat = (1ULL << 36), - ChannelLocation = (1ULL << 37), - Slowmode = (1ULL << 38), - GroupCall = (1ULL << 39), + ChannelAmIn = (1ULL << 35), + StickersSet = (1ULL << 36), + ChannelLinkedChat = (1ULL << 37), + ChannelLocation = (1ULL << 38), + Slowmode = (1ULL << 39), + GroupCall = (1ULL << 40), // For iteration - LastUsedBit = (1ULL << 39), + LastUsedBit = (1ULL << 40), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 00c1831613..76652a672a 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -59,8 +59,8 @@ using UpdateFlag = Data::PeerUpdate::Flag; namespace Data { -int PeerColorIndex(PeerId peerId) { - return Ui::EmptyUserpic::ColorIndex(peerId.value & PeerId::kChatTypeMask); +uint8 DecideColorIndex(PeerId peerId) { + return Ui::DecideColorIndex(peerId.value & PeerId::kChatTypeMask); } PeerId FakePeerIdForJustName(const QString &name) { @@ -230,7 +230,7 @@ not_null PeerData::ensureEmptyUserpic() const { if (!_userpicEmpty) { const auto user = asUser(); _userpicEmpty = std::make_unique( - Ui::EmptyUserpic::UserpicColor(Data::PeerColorIndex(id)), + Ui::EmptyUserpic::UserpicColor(colorIndex()), ((user && user->isInaccessible()) ? Ui::EmptyUserpic::InaccessibleName() : name())); @@ -251,7 +251,7 @@ void PeerData::setUserpic( const ImageLocation &location, bool hasVideo) { _userpicPhotoId = photoId; - _userpicHasVideo = hasVideo; + _userpicHasVideo = hasVideo ? 1 : 0; _userpic.set(&session(), ImageWithLocation{ .location = location }); } @@ -389,6 +389,22 @@ QImage PeerData::generateUserpicImage( return result; } +ImageLocation PeerData::userpicLocation() const { + return _userpic.location(); +} + +bool PeerData::userpicPhotoUnknown() const { + return (_userpicPhotoId == kUnknownPhotoId); +} + +PhotoId PeerData::userpicPhotoId() const { + return userpicPhotoUnknown() ? 0 : _userpicPhotoId; +} + +bool PeerData::userpicHasVideo() const { + return _userpicHasVideo != 0; +} + Data::FileOrigin PeerData::userpicOrigin() const { return Data::FileOriginPeerPhoto(id); } @@ -428,7 +444,7 @@ void PeerData::setUserpicChecked( bool hasVideo) { if (_userpicPhotoId != photoId || _userpic.location() != location - || _userpicHasVideo != hasVideo) { + || _userpicHasVideo != (hasVideo ? 1 : 0)) { const auto known = !userpicPhotoUnknown(); setUserpic(photoId, location, hasVideo); session().changes().peerUpdated(this, UpdateFlag::Photo); @@ -818,6 +834,15 @@ QString PeerData::userName() const { return QString(); } +bool PeerData::changeColorIndex(uint8 index) { + index %= Ui::kColorIndexCount; + if (_colorIndex == index) { + return false; + } + _colorIndex = index; + return true; +} + bool PeerData::isSelf() const { if (const auto user = asUser()) { return (user->flags() & UserDataFlag::Self); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 73c3d85f80..b793ff381d 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -39,7 +39,7 @@ class GroupCall; struct ReactionId; class WallPaper; -[[nodiscard]] int PeerColorIndex(PeerId peerId); +[[nodiscard]] uint8 DecideColorIndex(PeerId peerId); // Must be used only for PeerColor-s. [[nodiscard]] PeerId FakePeerIdForJustName(const QString &name); @@ -164,6 +164,11 @@ public: [[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Account &account() const; + [[nodiscard]] uint8 colorIndex() const { + return _colorIndex; + } + bool changeColorIndex(uint8 index); + [[nodiscard]] bool isUser() const { return peerIsUser(id); } @@ -285,20 +290,12 @@ public: Ui::PeerUserpicView &view, int size, std::optional radius = {}) const; - [[nodiscard]] ImageLocation userpicLocation() const { - return _userpic.location(); - } + [[nodiscard]] ImageLocation userpicLocation() const; static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL); - [[nodiscard]] bool userpicPhotoUnknown() const { - return (_userpicPhotoId == kUnknownPhotoId); - } - [[nodiscard]] PhotoId userpicPhotoId() const { - return userpicPhotoUnknown() ? 0 : _userpicPhotoId; - } - [[nodiscard]] bool userpicHasVideo() const { - return _userpicHasVideo; - } + [[nodiscard]] bool userpicPhotoUnknown() const; + [[nodiscard]] PhotoId userpicPhotoId() const; + [[nodiscard]] bool userpicHasVideo() const; [[nodiscard]] Data::FileOrigin userpicOrigin() const; [[nodiscard]] Data::FileOrigin userpicPhotoOrigin() const; @@ -460,14 +457,15 @@ private: TimeId _ttlPeriod = 0; + QString _requestChatTitle; + TimeId _requestChatDate = 0; + Settings _settings = PeerSettings(PeerSetting::Unknown); BlockStatus _blockStatus = BlockStatus::Unknown; LoadedStatus _loadedStatus = LoadedStatus::Not; TranslationFlag _translationFlag = TranslationFlag::Unknown; - bool _userpicHasVideo = false; - - QString _requestChatTitle; - TimeId _requestChatDate = 0; + uint8 _colorIndex : 7 = 0; + uint8 _userpicHasVideo : 1 = 0; QString _about; QString _themeEmoticon; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 6039dcf21a..26c019671d 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -705,6 +705,9 @@ not_null Session::processUser(const MTPUser &data) { if (canShareThisContact != result->canShareThisContactFast()) { flags |= UpdateFlag::CanShareContact; } + if (result->changeColorIndex(uint8(data.vcolor().v))) { + flags |= UpdateFlag::Color; + } }); if (minimal) { @@ -979,6 +982,9 @@ not_null Session::processChat(const MTPChat &data) { if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) { flags |= UpdateFlag::GroupCall; } + if (result->changeColorIndex(uint8(data.vcolor().v))) { + flags |= UpdateFlag::Color; + } }, [&](const MTPDchannelForbidden &data) { const auto channel = result->asChannel(); diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index ab6cb3e507..a0d07ba974 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -54,17 +54,16 @@ QString PrepareStoryFileName( } // namespace -int PeerColorIndex(BareId bareId) { - const auto index = bareId % 7; - const int map[] = { 0, 7, 4, 1, 6, 3, 5 }; - return map[index]; +uint8 PeerColorIndex(BareId bareId) { + const uint8 map[] = { 0, 7, 4, 1, 6, 3, 5 }; + return map[bareId % base::array_size(map)]; } BareId PeerToBareId(PeerId peerId) { return (peerId.value & PeerId::kChatTypeMask); } -int PeerColorIndex(PeerId peerId) { +uint8 PeerColorIndex(PeerId peerId) { return PeerColorIndex(PeerToBareId(peerId)); } @@ -78,7 +77,7 @@ BareId StringBarePeerId(const Utf8String &data) { return result; } -int ApplicationColorIndex(int applicationId) { +uint8 ApplicationColorIndex(int applicationId) { static const auto official = std::map { { 1, 0 }, // iOS { 7, 0 }, // iOS X @@ -767,6 +766,7 @@ ContactInfo ParseContactInfo(const MTPUser &data) { auto result = ContactInfo(); data.match([&](const MTPDuser &data) { result.userId = data.vid().v; + result.colorIndex = data.vcolor().v; if (const auto firstName = data.vfirst_name()) { result.firstName = ParseString(*firstName); } @@ -778,15 +778,13 @@ ContactInfo ParseContactInfo(const MTPUser &data) { } }, [&](const MTPDuserEmpty &data) { result.userId = data.vid().v; + result.colorIndex = PeerColorIndex(result.userId); }); return result; } -int ContactColorIndex(const ContactInfo &data) { - if (data.userId != 0) { - return PeerColorIndex(data.userId.bare); - } - return PeerColorIndex(StringBarePeerId(data.phoneNumber)); +uint8 ContactColorIndex(const ContactInfo &data) { + return data.colorIndex; } PeerId User::id() const { @@ -798,6 +796,7 @@ User ParseUser(const MTPUser &data) { result.info = ParseContactInfo(data); data.match([&](const MTPDuser &data) { result.bareId = data.vid().v; + result.colorIndex = data.vcolor().v; if (const auto username = data.vusername()) { result.username = ParseString(*username); } @@ -852,6 +851,7 @@ Chat ParseChat(const MTPChat &data) { result.input = MTP_inputPeerChat(MTP_long(result.bareId)); }, [&](const MTPDchannel &data) { result.bareId = data.vid().v; + result.colorIndex = data.vcolor().v; result.isBroadcast = data.is_broadcast(); result.isSupergroup = data.is_megagroup(); result.title = ParseString(data.vtitle()); @@ -935,6 +935,15 @@ MTPInputPeer Peer::input() const { Unexpected("Variant in Peer::id."); } +uint8 Peer::colorIndex() const { + if (const auto user = this->user()) { + return user->colorIndex; + } else if (const auto chat = this->chat()) { + return chat->colorIndex; + } + Unexpected("Variant in Peer::colorIndex."); +} + std::map ParsePeersLists( const MTPVector &users, const MTPVector &chats) { @@ -1541,6 +1550,8 @@ ContactsList ParseContactsList(const MTPVector &data) { info.lastName = ParseString(data.vlast_name()); info.phoneNumber = ParseString(data.vphone()); info.date = data.vdate().v; + info.colorIndex = PeerColorIndex( + StringBarePeerId(info.phoneNumber)); return info; }); result.list.push_back(std::move(info)); @@ -1758,6 +1769,7 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { info.lastName = peer.user() ? peer.user()->info.lastName : Utf8String(); + info.colorIndex = peer.colorIndex(); info.input = peer.input(); info.migratedToChannelId = peer.chat() ? peer.chat()->migratedToChannelId diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 6556a35b31..e670c9a5c0 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -24,10 +24,10 @@ namespace Data { using Utf8String = QByteArray; -int PeerColorIndex(BareId bareId); +uint8 PeerColorIndex(BareId bareId); BareId PeerToBareId(PeerId peerId); -int PeerColorIndex(PeerId peerId); -int ApplicationColorIndex(int applicationId); +uint8 PeerColorIndex(PeerId peerId); +uint8 ApplicationColorIndex(int applicationId); int DomainApplicationId(const Utf8String &data); Utf8String ParseString(const MTPstring &data); @@ -108,12 +108,13 @@ struct ContactInfo { Utf8String lastName; Utf8String phoneNumber; TimeId date = 0; + uint8 colorIndex = 0; Utf8String name() const; }; ContactInfo ParseContactInfo(const MTPUser &data); -int ContactColorIndex(const ContactInfo &data); +uint8 ContactColorIndex(const ContactInfo &data); struct Photo { uint64 id = 0; @@ -217,6 +218,7 @@ struct User { BareId bareId = 0; ContactInfo info; Utf8String username; + uint8 colorIndex = 0; bool isBot = false; bool isSelf = false; bool isReplies = false; @@ -236,6 +238,7 @@ struct Chat { ChannelId migratedToChannelId = 0; Utf8String title; Utf8String username; + uint8 colorIndex = 0; bool isBroadcast = false; bool isSupergroup = false; @@ -249,6 +252,7 @@ struct Peer { PeerId id() const; Utf8String name() const; MTPInputPeer input() const; + uint8 colorIndex() const; const User *user() const; const Chat *chat() const; @@ -746,6 +750,7 @@ struct DialogInfo { int32 topMessageId = 0; TimeId topMessageDate = 0; PeerId peerId = 0; + uint8 colorIndex = 0; MTPInputPeer migratedFromInput = MTP_inputPeerEmpty(); ChannelId migratedToChannelId = 0; diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index e42aced3e8..28018e8884 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -25,7 +25,7 @@ constexpr auto kPersonalUserpicSize = 90; constexpr auto kEntryUserpicSize = 48; constexpr auto kServiceMessagePhotoSize = 60; constexpr auto kHistoryUserpicSize = 42; -constexpr auto kSavedMessagesColorIndex = 3; +constexpr auto kSavedMessagesColorIndex = uint8(3); constexpr auto kJoinWithinSeconds = 900; constexpr auto kPhotoMaxWidth = 520; constexpr auto kPhotoMaxHeight = 520; @@ -351,7 +351,7 @@ QByteArray FormatTimeText(TimeId date) { namespace details { struct UserpicData { - int colorIndex = 0; + uint8 colorIndex = 0; int pixelSize = 0; QString imageLink; QString largeLink; @@ -991,7 +991,7 @@ QByteArray HtmlWriter::Wrap::pushServiceMessage( result.append(popTag()); if (photo) { auto userpic = UserpicData(); - userpic.colorIndex = Data::PeerColorIndex(dialog.peerId); + userpic.colorIndex = dialog.colorIndex; userpic.firstName = dialog.name; userpic.lastName = dialog.lastName; userpic.pixelSize = kServiceMessagePhotoSize; @@ -2170,7 +2170,7 @@ Result HtmlWriter::start( Result HtmlWriter::writePersonal(const Data::PersonalInfo &data) { Expects(_summary != nullptr); - _selfColorIndex = Data::PeerColorIndex(data.user.info.userId); + _selfColorIndex = data.user.info.colorIndex; if (_settings.types & Settings::Type::Userpics) { _delayedPersonalInfo = std::make_unique(data); return Result::Success(); diff --git a/Telegram/SourceFiles/export/output/export_output_html.h b/Telegram/SourceFiles/export/output/export_output_html.h index c1dee2ffd0..27d68a54a7 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.h +++ b/Telegram/SourceFiles/export/output/export_output_html.h @@ -150,7 +150,7 @@ private: bool _summaryNeedDivider = false; bool _haveSections = false; - int _selfColorIndex = 0; + uint8 _selfColorIndex = 0; std::unique_ptr _delayedPersonalInfo; int _userpicsCount = 0; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 7ba64d26f6..11fcddf292 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -110,9 +110,9 @@ void HistoryMessageVia::resize(int32 availw) const { HiddenSenderInfo::HiddenSenderInfo(const QString &name, bool external) : name(name) -, colorPeerId(Data::FakePeerIdForJustName(name)) +, colorIndex(Data::DecideColorIndex(Data::FakePeerIdForJustName(name))) , emptyUserpic( - Ui::EmptyUserpic::UserpicColor(Data::PeerColorIndex(colorPeerId)), + Ui::EmptyUserpic::UserpicColor(colorIndex), (external ? Ui::EmptyUserpic::ExternalName() : name)) { @@ -400,13 +400,13 @@ bool HistoryMessageReply::updateData( if (resolvedMessage) { const auto peer = resolvedMessage->history()->peer; - _colorKey = (!holder->out() + _colorIndexPlusOne = (!holder->out() && (peer->isMegagroup() || peer->isChat()) && resolvedMessage->from()->isUser()) - ? resolvedMessage->from()->id - : PeerId(); + ? (resolvedMessage->from()->colorIndex() + 1) + : uint8(); } else if (!resolvedStory) { - _unavailable = true; + _unavailable = 1; } const auto media = resolvedMessage @@ -419,9 +419,9 @@ bool HistoryMessageReply::updateData( } } else if (force) { if (_fields.messageId || _fields.storyId) { - _unavailable = true; + _unavailable = 1; } - _colorKey = 0; + _colorIndexPlusOne = 0; spoiler = nullptr; } if (force) { @@ -502,7 +502,7 @@ void HistoryMessageReply::clearData(not_null holder) { resolvedStory.get()); resolvedStory = nullptr; } - _unavailable = true; + _unavailable = 1; refreshReplyToMedia(); } @@ -663,8 +663,8 @@ void HistoryMessageReply::paint( const auto outerWidth = w + 2 * x; const auto &bar = !inBubble ? st->msgImgReplyBarColor() - : _colorKey - ? HistoryView::FromNameFg(context, _colorKey) + : _colorIndexPlusOne + ? HistoryView::FromNameFg(context, _colorIndexPlusOne - 1) : stm->msgReplyBarColor; const auto rbar = style::rtlrect( x + st::msgReplyBarPos.x(), @@ -735,8 +735,10 @@ void HistoryMessageReply::paint( if (w > st::msgReplyBarSkip + previewSkip) { p.setPen(!inBubble ? st->msgImgReplyBarColor() - : _colorKey - ? HistoryView::FromNameFg(context, _colorKey) + : _colorIndexPlusOne + ? HistoryView::FromNameFg( + context, + _colorIndexPlusOne - 1) : stm->msgServiceFg); _name.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); if (originalVia && w > st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew) { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index c46f838b8f..1f10c733d3 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -90,7 +90,7 @@ public: QString name; QString firstName; QString lastName; - PeerId colorPeerId = 0; + uint8 colorIndex = 0; Ui::EmptyUserpic emptyUserpic; mutable Data::CloudImage customUserpic; @@ -291,9 +291,6 @@ struct HistoryMessageReply bool inBubble) const; void unloadPersistentAnimation(); - [[nodiscard]] PeerId colorKey() const { - return _colorKey; - } [[nodiscard]] PeerId externalPeerId() const { return _fields.externalPeerId; } @@ -337,14 +334,14 @@ struct HistoryMessageReply private: ReplyFields _fields; - PeerId _colorKey = 0; ClickHandlerPtr _link; mutable Ui::Text::String _name; mutable Ui::Text::String _text; mutable PeerData *_externalSender = nullptr; mutable int _maxWidth = 0; mutable int _nameVersion = 0; - bool _unavailable = false; + uint8 _colorIndexPlusOne : 7 = 0; + uint8 _unavailable : 1 = 0; }; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 8b86fa2e80..60d18d46ad 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -368,33 +368,8 @@ QString FastReplyText() { style::color FromNameFg( const Ui::ChatPaintContext &context, - PeerId peerId) { - const auto st = context.st; - if (context.selected()) { - const style::color colors[] = { - st->historyPeer1NameFgSelected(), - st->historyPeer2NameFgSelected(), - st->historyPeer3NameFgSelected(), - st->historyPeer4NameFgSelected(), - st->historyPeer5NameFgSelected(), - st->historyPeer6NameFgSelected(), - st->historyPeer7NameFgSelected(), - st->historyPeer8NameFgSelected(), - }; - return colors[Data::PeerColorIndex(peerId)]; - } else { - const style::color colors[] = { - st->historyPeer1NameFg(), - st->historyPeer2NameFg(), - st->historyPeer3NameFg(), - st->historyPeer4NameFg(), - st->historyPeer5NameFg(), - st->historyPeer6NameFg(), - st->historyPeer7NameFg(), - st->historyPeer8NameFg(), - }; - return colors[Data::PeerColorIndex(peerId)]; - } + uint8 colorIndex) { + return Ui::FromNameFg(context.st, context.selected(), colorIndex); } struct Message::CommentsButton { @@ -1355,7 +1330,7 @@ void Message::paintFromName( const auto service = (context.outbg || item->isPost()); const auto st = context.st; const auto nameFg = !service - ? FromNameFg(context, from ? from->id : info->colorPeerId) + ? FromNameFg(context, from ? from->colorIndex() : info->colorIndex) : item->isSponsored() ? st->boxTextFgGood() : stm->msgServiceFg; @@ -1630,7 +1605,7 @@ void Message::paintText( .availableWidth = trect.width(), .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->blockquoteCache.get(), + .blockquote = stm->quoteCache.get(), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 1efc0892be..9c2a81ea42 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -58,7 +58,7 @@ struct BottomRippleMask { [[nodiscard]] style::color FromNameFg( const Ui::ChatPaintContext &context, - PeerId peerId); + uint8 colorIndex); class Message final : public Element { public: diff --git a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp index d33f1ba8a7..7b0ac35b95 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp @@ -122,7 +122,7 @@ QSize Contact::countOptimalSize() { } else { const auto full = _name.toString(); _photoEmpty = std::make_unique( - Ui::EmptyUserpic::UserpicColor(Data::PeerColorIndex(_userId + Ui::EmptyUserpic::UserpicColor(Data::DecideColorIndex(_userId ? peerFromUser(_userId) : Data::FakePeerIdForJustName(full))), full); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 165f79dea8..7184a7fc0b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -749,7 +749,7 @@ void Document::draw( .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->blockquoteCache.get(), + .blockquote = stm->quoteCache.get(), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, 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 f34ca75743..a4695e4a1c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -236,7 +236,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->blockquoteCache.get(), + .blockquote = stm->quoteCache.get(), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index cd475f2d2a..04a1688528 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -710,7 +710,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->blockquoteCache.get(), + .blockquote = stm->quoteCache.get(), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, 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 2333e334da..fc7263386f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -368,7 +368,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->blockquoteCache.get(), + .blockquote = stm->quoteCache.get(), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 5e912d33c5..a83991bf6c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -406,7 +406,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->blockquoteCache.get(), + .blockquote = stm->quoteCache.get(), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp index 1bbaad755e..d1d1cafade 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp @@ -161,7 +161,8 @@ QImage *ItemBase::getResultThumb(Data::FileOrigin origin) const { QPixmap ItemBase::getResultContactAvatar(int width, int height) const { if (_result->_type == Result::Type::Contact) { auto result = Ui::EmptyUserpic( - Ui::EmptyUserpic::UserpicColor(BareId(qHash(_result->_id))), + Ui::EmptyUserpic::UserpicColor(Ui::EmptyUserpic::ColorIndex( + BareId(qHash(_result->_id)))), _result->getLayoutTitle() ).generate(width); if (result.height() != height * cIntRetinaFactor()) { diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index b1e22f8084..6c15101168 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -25,7 +25,7 @@ void EnsureCorners( const style::color &color, const style::color *shadow = nullptr) { if (corners.p[0].isNull()) { - corners = Ui::PrepareCornerPixmaps(radius, color, shadow); + corners = PrepareCornerPixmaps(radius, color, shadow); } } @@ -482,7 +482,7 @@ void ChatStyle::applyAdjustedServiceBg(QColor serviceBg) { msgServiceBg().set(uchar(r), uchar(g), uchar(b), uchar(a)); } -std::span ChatStyle::highlightColors() const { +std::span ChatStyle::highlightColors() const { if (_highlightColors.empty()) { const auto push = [&](const style::color &color) { _highlightColors.push_back({ &color->p, &color->p }); @@ -523,7 +523,8 @@ void ChatStyle::assignPalette(not_null palette) { for (auto &style : _messageStyles) { style.msgBgCornersSmall = {}; style.msgBgCornersLarge = {}; - style.blockquoteCache = nullptr; + style.quoteCache = nullptr; + style.replyCache = nullptr; style.preCache = nullptr; } for (auto &style : _imageStyles) { @@ -560,7 +561,7 @@ const CornersPixmaps &ChatStyle::serviceBgCornersNormal() const { const CornersPixmaps &ChatStyle::serviceBgCornersInverted() const { if (_serviceBgCornersInverted.p[0].isNull()) { - _serviceBgCornersInverted = Ui::PrepareInvertedCornerPixmaps( + _serviceBgCornersInverted = PrepareInvertedCornerPixmaps( HistoryServiceMsgInvertedRadius(), msgServiceBg()); } @@ -580,12 +581,12 @@ const MessageStyle &ChatStyle::messageStyle(bool outbg, bool selected) const { result.msgBg, &result.msgShadow); EnsureBlockquoteCache( - result.blockquoteCache, + result.replyCache, result.msgReplyBarColor); const auto preBgOverride = [&] { const auto withBg = [&](const QColor &color) { - return Ui::CountContrast(windowBg()->c, color); + return CountContrast(windowBg()->c, color); }; const auto dark = (withBg({ 0, 0, 0 }) < withBg({ 255, 255, 255 })); return dark ? QColor(0, 0, 0, 192) : std::optional(); @@ -621,9 +622,44 @@ const MessageImageStyle &ChatStyle::imageStyle(bool selected) const { result.msgShadowCornersLarge, BubbleRadiusLarge(), result.msgShadow); + return result; } +not_null ChatStyle::serviceQuoteCache() const { + EnsureBlockquoteCache(_serviceQuoteCache, msgServiceFg()); + return _serviceQuoteCache.get(); +} + +not_null ChatStyle::serviceReplyCache() const { + EnsureBlockquoteCache(_serviceReplyCache, msgServiceFg()); + return _serviceReplyCache.get(); +} + +not_null ChatStyle::coloredQuoteCache( + bool selected, + uint8 colorIndex) const { + return coloredCache(_coloredQuoteCaches, selected, colorIndex); +} + +not_null ChatStyle::coloredReplyCache( + bool selected, + uint8 colorIndex) const { + return coloredCache(_coloredReplyCaches, selected, colorIndex); +} + +not_null ChatStyle::coloredCache( + ColoredQuotePaintCaches &caches, + bool selected, + uint8 colorIndex) const { + Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); + + const auto shift = (selected ? kColorIndexCount : 0); + auto &cache = caches[shift + colorIndex]; + EnsureBlockquoteCache(cache, FromNameFg(this, selected, colorIndex)); + return cache.get(); +} + const CornersPixmaps &ChatStyle::msgBotKbOverBgAddCornersSmall() const { EnsureCorners( _msgBotKbOverBgAddCornersSmall, @@ -751,6 +787,50 @@ void ChatStyle::make( make(imageSelected().*my, originalSelected); } +uint8 DecideColorIndex(uint64 id) { + return id % kColorIndexCount; +} + +uint8 ColorIndexToPaletteIndex(uint8 colorIndex) { + Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); + + const int8 map[] = { 0, 7, 4, 1, 6, 3, 5 }; + return map[colorIndex]; +} + +style::color FromNameFg( + not_null st, + bool selected, + uint8 colorIndex) { + Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); + + if (selected) { + const style::color colors[] = { + st->historyPeer1NameFgSelected(), + st->historyPeer2NameFgSelected(), + st->historyPeer3NameFgSelected(), + st->historyPeer4NameFgSelected(), + st->historyPeer5NameFgSelected(), + st->historyPeer6NameFgSelected(), + st->historyPeer7NameFgSelected(), + st->historyPeer8NameFgSelected(), + }; + return colors[ColorIndexToPaletteIndex(colorIndex)]; + } else { + const style::color colors[] = { + st->historyPeer1NameFg(), + st->historyPeer2NameFg(), + st->historyPeer3NameFg(), + st->historyPeer4NameFg(), + st->historyPeer5NameFg(), + st->historyPeer6NameFg(), + st->historyPeer7NameFg(), + st->historyPeer8NameFg(), + }; + return colors[ColorIndexToPaletteIndex(colorIndex)]; + } +} + void FillComplexOverlayRect( QPainter &p, QRect rect, diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index dda612b664..4c9510e07c 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -27,6 +27,8 @@ class ChatTheme; class ChatStyle; struct BubblePattern; +inline constexpr auto kColorIndexCount = uint8(7); + struct MessageStyle { CornersPixmaps msgBgCornersSmall; CornersPixmaps msgBgCornersLarge; @@ -76,9 +78,9 @@ struct MessageStyle { style::icon historyPollChoiceRight = { Qt::Uninitialized }; style::icon historyTranscribeIcon = { Qt::Uninitialized }; style::icon historyTranscribeHide = { Qt::Uninitialized }; - std::unique_ptr blockquoteCache; + std::unique_ptr quoteCache; + std::unique_ptr replyCache; std::unique_ptr preCache; - }; struct MessageImageStyle { @@ -170,7 +172,7 @@ public: void applyCustomPalette(const style::palette *palette); void applyAdjustedServiceBg(QColor serviceBg); - [[nodiscard]] std::span highlightColors() const; + [[nodiscard]] std::span highlightColors() const; [[nodiscard]] rpl::producer<> paletteChanged() const { return _paletteChanged.events(); @@ -200,6 +202,17 @@ public: bool selected) const; [[nodiscard]] const MessageImageStyle &imageStyle(bool selected) const; + [[nodiscard]] auto serviceQuoteCache() const + -> not_null; + [[nodiscard]] auto serviceReplyCache() const + -> not_null; + [[nodiscard]] not_null coloredQuoteCache( + bool selected, + uint8 colorIndex) const; + [[nodiscard]] not_null coloredReplyCache( + bool selected, + uint8 colorIndex) const; + [[nodiscard]] const CornersPixmaps &msgBotKbOverBgAddCornersSmall() const; [[nodiscard]] const CornersPixmaps &msgBotKbOverBgAddCornersLarge() const; [[nodiscard]] const CornersPixmaps &msgSelectOverlayCorners( @@ -285,8 +298,16 @@ public: } private: + using ColoredQuotePaintCaches = std::array< + std::unique_ptr, + kColorIndexCount * 2>; void assignPalette(not_null palette); + [[nodiscard]] not_null coloredCache( + ColoredQuotePaintCaches &caches, + bool selected, + uint8 colorIndex) const; + void make(style::color &my, const style::color &original) const; void make(style::icon &my, const style::icon &original) const; void make( @@ -336,7 +357,11 @@ private: mutable CornersPixmaps _msgSelectOverlayCorners[ int(CachedCornerRadius::kCount)]; - mutable std::vector _highlightColors; + mutable std::vector _highlightColors; + mutable std::unique_ptr _serviceQuoteCache; + mutable std::unique_ptr _serviceReplyCache; + mutable ColoredQuotePaintCaches _coloredQuoteCaches; + mutable ColoredQuotePaintCaches _coloredReplyCaches; style::TextPalette _historyPsaForwardPalette; style::TextPalette _imgReplyTextPalette; @@ -371,6 +396,14 @@ private: }; +[[nodiscard]] uint8 DecideColorIndex(uint64 id); +[[nodiscard]] uint8 ColorIndexToPaletteIndex(uint8 colorIndex); + +[[nodiscard]] style::color FromNameFg( + not_null st, + bool selected, + uint8 colorIndex); + void FillComplexOverlayRect( QPainter &p, QRect rect, diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.cpp b/Telegram/SourceFiles/ui/controls/userpic_button.cpp index 224fd7a358..bad612d203 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.cpp +++ b/Telegram/SourceFiles/ui/controls/userpic_button.cpp @@ -1050,8 +1050,7 @@ void UserpicButton::prepareUserpicPixmap() { } else { const auto user = _peer->asUser(); auto empty = Ui::EmptyUserpic( - Ui::EmptyUserpic::UserpicColor( - Data::PeerColorIndex(_peer->id)), + Ui::EmptyUserpic::UserpicColor(_peer->colorIndex()), ((user && user->isInaccessible()) ? Ui::EmptyUserpic::InaccessibleName() : _peer->name())); diff --git a/Telegram/SourceFiles/ui/empty_userpic.cpp b/Telegram/SourceFiles/ui/empty_userpic.cpp index a319c3454a..7c46b7826e 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.cpp +++ b/Telegram/SourceFiles/ui/empty_userpic.cpp @@ -7,8 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/empty_userpic.h" -#include "ui/emoji_config.h" +#include "ui/chat/chat_style.h" #include "ui/effects/animation_value.h" +#include "ui/emoji_config.h" #include "ui/painter.h" #include "ui/ui_utility.h" #include "styles/style_chat.h" @@ -221,13 +222,11 @@ QString EmptyUserpic::InaccessibleName() { return QChar(0) + u"inaccessible"_q; } -int EmptyUserpic::ColorIndex(uint64 id) { - const auto index = id % 7; - const int map[] = { 0, 7, 4, 1, 6, 3, 5 }; - return map[index]; +uint8 EmptyUserpic::ColorIndex(uint64 id) { + return DecideColorIndex(id); } -EmptyUserpic::BgColors EmptyUserpic::UserpicColor(int id) { +EmptyUserpic::BgColors EmptyUserpic::UserpicColor(uint8 colorIndex) { const EmptyUserpic::BgColors colors[] = { { st::historyPeer1UserpicBg, st::historyPeer1UserpicBg2 }, { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 }, @@ -238,7 +237,7 @@ EmptyUserpic::BgColors EmptyUserpic::UserpicColor(int id) { { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 }, { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }, }; - return colors[id]; + return colors[ColorIndexToPaletteIndex(colorIndex)]; } void EmptyUserpic::paint( diff --git a/Telegram/SourceFiles/ui/empty_userpic.h b/Telegram/SourceFiles/ui/empty_userpic.h index 2f9b9856ba..f71335a6a9 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.h +++ b/Telegram/SourceFiles/ui/empty_userpic.h @@ -18,8 +18,9 @@ public: const style::color color2; }; - [[nodiscard]] static int ColorIndex(uint64 id); - [[nodiscard]] static EmptyUserpic::BgColors UserpicColor(int id); + [[nodiscard]] static uint8 ColorIndex(uint64 id); + [[nodiscard]] static EmptyUserpic::BgColors UserpicColor( + uint8 colorIndex); [[nodiscard]] static QString ExternalName(); [[nodiscard]] static QString InaccessibleName(); diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp index 82698486be..a3ea8a7086 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp @@ -986,7 +986,7 @@ void Generator::paintService(QString text) { } void Generator::paintUserpic(int x, int y, Row::Type type, int index, QString letters) { - const auto colorIndex = Ui::EmptyUserpic::ColorIndex(index); + const auto colorIndex = Ui::DecideColorIndex(index); const auto colors = Ui::EmptyUserpic::UserpicColor(colorIndex); auto userpic = Ui::EmptyUserpic(colors, letters); From b9af4f3cb061ce183cd4f3e6c3c2f04dc9be8a31 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Oct 2023 10:19:58 +0400 Subject: [PATCH 41/99] Show nice replies with quotes. --- Telegram/SourceFiles/data/data_peer.cpp | 1 + .../SourceFiles/data/data_reply_preview.cpp | 10 +- Telegram/SourceFiles/history/history_item.cpp | 1 + .../history/history_item_components.cpp | 203 ++++++++++-------- .../history/history_item_components.h | 9 +- .../SourceFiles/history/history_widget.cpp | 41 ++-- .../history_view_compose_controls.cpp | 38 ++-- .../controls/history_view_forward_panel.cpp | 11 +- .../history/view/history_view_message.cpp | 78 +++---- .../history/view/media/history_view_gif.cpp | 52 +++-- .../media/history_view_media_unwrapped.cpp | 56 +++-- .../view/media/history_view_media_unwrapped.h | 3 +- Telegram/SourceFiles/ui/chat/chat.style | 8 +- Telegram/SourceFiles/ui/chat/chat_style.cpp | 4 + Telegram/SourceFiles/ui/chat/message_bar.cpp | 11 +- 15 files changed, 285 insertions(+), 241 deletions(-) diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 76652a672a..66e693c13c 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/main_window.h" // Window::LogoNoMargin. #include "ui/image/image.h" +#include "ui/chat/chat_style.h" #include "ui/empty_userpic.h" #include "ui/text/text_options.h" #include "ui/painter.h" diff --git a/Telegram/SourceFiles/data/data_reply_preview.cpp b/Telegram/SourceFiles/data/data_reply_preview.cpp index 2bf667be43..e31f30f38c 100644 --- a/Telegram/SourceFiles/data/data_reply_preview.cpp +++ b/Telegram/SourceFiles/data/data_reply_preview.cpp @@ -40,14 +40,14 @@ void ReplyPreview::prepare( if (h <= 0) h = 1; auto thumbSize = (w > h) ? QSize( - w * st::msgReplyBarSize.height() / h, - st::msgReplyBarSize.height()) + w * st::historyReplyPreview / h, + st::historyReplyPreview) : QSize( - st::msgReplyBarSize.height(), - h * st::msgReplyBarSize.height() / w); + st::historyReplyPreview, + h * st::historyReplyPreview / w); thumbSize *= style::DevicePixelRatio(); options |= Option::TransparentBackground; - auto outerSize = st::msgReplyBarSize.height(); + auto outerSize = st::historyReplyPreview; auto original = spoiler ? image->original().scaled( { 40, 40 }, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 5ac0a761c9..55ff76fa2a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -3305,6 +3305,7 @@ void HistoryItem::createComponentsHelper( config.reply.topicPost = LookupReplyIsTopicPost(to) || (to && to->Has()) || (forum && forum->creating(config.reply.topMessageId)); + config.reply.quote = std::move(replyTo.quote); } config.markup = std::move(markup); if (flags & MessageFlag::HasPostAuthor) config.postAuthor = postAuthor; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 11fcddf292..f90e4ca792 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -570,31 +570,32 @@ void HistoryMessageReply::updateName( std::optional resolvedSender) const { const auto peer = resolvedSender.value_or(sender(holder)); const auto name = peer ? senderName(peer) : senderName(holder); + const auto hasPreview = (resolvedStory + && resolvedStory->hasReplyPreview()) + || (resolvedMessage + && resolvedMessage->media() + && resolvedMessage->media()->hasReplyPreview()); + const auto textLeft = hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right()) + : st::historyReplyPadding.left(); if (!name.isEmpty()) { _name.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); if (peer) { _nameVersion = peer->nameVersion(); } - bool hasPreview = (resolvedStory - && resolvedStory->hasReplyPreview()) - || (resolvedMessage - && resolvedMessage->media() - && resolvedMessage->media()->hasReplyPreview()); - int32 previewSkip = hasPreview - ? (st::msgReplyBarSize.height() - + st::msgReplyBarSkip - - st::msgReplyBarSize.width() - - st::msgReplyBarPos.x()) - : 0; - int32 w = _name.maxWidth(); - if (originalVia) { - w += st::msgServiceFont->spacew + originalVia->maxWidth; - } - - _maxWidth = previewSkip - + std::max( - w, - std::min(_text.maxWidth(), st::maxSignatureSize)) + const auto w = _name.maxWidth() + + (originalVia + ? (st::msgServiceFont->spacew + originalVia->maxWidth) + : 0) + + (_fields.quote.empty() + ? 0 + : st::messageTextStyle.blockquote.icon.width()); + _maxWidth = std::max( + w, + std::min(_text.maxWidth(), st::maxSignatureSize)) + (_fields.storyId ? (st::dialogsMiniReplyStory.skipText + st::dialogsMiniReplyStory.icon.icon.width()) @@ -602,31 +603,48 @@ void HistoryMessageReply::updateName( } else { _maxWidth = st::msgDateFont->width(statePhrase()); } - _maxWidth = st::msgReplyPadding.left() - + st::msgReplyBarSkip + _maxWidth = textLeft + _maxWidth - + st::msgReplyPadding.right(); + + st::historyReplyPadding.right(); + _minHeight = st::historyReplyPadding.top() + + st::msgServiceNameFont->height + + st::normalFont->height + + st::historyReplyPadding.bottom(); } -void HistoryMessageReply::resize(int width) const { +int HistoryMessageReply::resizeToWidth(int width) const { + const auto hasPreview = (resolvedStory + && resolvedStory->hasReplyPreview()) + || (resolvedMessage + && resolvedMessage->media() + && resolvedMessage->media()->hasReplyPreview()); + const auto textLeft = hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right()) + : st::historyReplyPadding.left(); if (originalVia) { - bool hasPreview = (resolvedStory - && resolvedStory->hasReplyPreview()) - || (resolvedMessage - && resolvedMessage->media() - && resolvedMessage->media()->hasReplyPreview()); - int previewSkip = hasPreview - ? (st::msgReplyBarSize.height() - + st::msgReplyBarSkip - - st::msgReplyBarSize.width() - - st::msgReplyBarPos.x()) - : 0; originalVia->resize(width - - st::msgReplyBarSkip - - previewSkip + - textLeft + - st::historyReplyPadding.right() - _name.maxWidth() - st::msgServiceFont->spacew); } + if (width >= _maxWidth) { + _height = _minHeight; + return height(); + } + _height = _minHeight; + return height(); +} + +int HistoryMessageReply::height() const { + return _height + st::historyReplyTop + st::historyReplyBottom; +} + +QMargins HistoryMessageReply::margins() const { + return QMargins(0, st::historyReplyTop, 0, st::historyReplyBottom); } void HistoryMessageReply::itemRemoved( @@ -658,58 +676,66 @@ void HistoryMessageReply::paint( const auto st = context.st; const auto stm = context.messageStyle(); - { - const auto opacity = p.opacity(); - const auto outerWidth = w + 2 * x; - const auto &bar = !inBubble - ? st->msgImgReplyBarColor() - : _colorIndexPlusOne - ? HistoryView::FromNameFg(context, _colorIndexPlusOne - 1) - : stm->msgReplyBarColor; - const auto rbar = style::rtlrect( - x + st::msgReplyBarPos.x(), - y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), - st::msgReplyBarSize.width(), - st::msgReplyBarSize.height(), - outerWidth); - - if (ripple.animation) { - const auto colorOverride = &stm->msgWaveformInactive->c; - p.setOpacity(st::historyPollRippleOpacity); - ripple.animation->paint( - p, - x - st::msgReplyPadding.left(), - y, - outerWidth, - colorOverride); - if (ripple.animation->empty()) { - ripple.animation.reset(); - } - } - - p.setOpacity(opacity * kBarAlpha); - p.fillRect(rbar, bar); - p.setOpacity(opacity); + y += st::historyReplyTop; + const auto rect = QRect(x, y, w, _height); + const auto hasQuote = !_fields.quote.empty(); + const auto selected = context.selected(); + const auto cache = !inBubble + ? (hasQuote + ? st->serviceQuoteCache() + : st->serviceReplyCache()).get() + : _colorIndexPlusOne + ? (hasQuote + ? st->coloredQuoteCache(selected, _colorIndexPlusOne - 1) + : st->coloredReplyCache(selected, _colorIndexPlusOne - 1)).get() + : (hasQuote ? stm->quoteCache : stm->replyCache).get(); + const auto "eSt = hasQuote + ? st::messageTextStyle.blockquote + : st::messageQuoteStyle; + const auto rippleColor = cache->bg; + if (!inBubble) { + cache->bg = QColor(0, 0, 0, 0); + } + Ui::Text::ValidateQuotePaintCache(*cache, quoteSt); + Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt); + if (!inBubble) { + cache->bg = rippleColor; } + if (ripple.animation) { + ripple.animation->paint(p, x, y, w, &rippleColor); + if (ripple.animation->empty()) { + ripple.animation.reset(); + } + } + + const auto withPreviewLeft = st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right(); + auto textLeft = st::historyReplyPadding.left(); const auto pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler); - if (w > st::msgReplyBarSkip) { + if (w > textLeft) { if (resolvedMessage || resolvedStory || !_text.isEmpty()) { const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; auto hasPreview = (media && media->hasReplyPreview()) || (resolvedStory && resolvedStory->hasReplyPreview()); - if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) { + if (hasPreview && w <= withPreviewLeft) { hasPreview = false; } - auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - if (hasPreview) { + textLeft = withPreviewLeft; const auto image = media ? media->replyPreview() : resolvedStory->replyPreview(); if (image) { - auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x); + auto to = style::rtlrect( + x + st::historyReplyPreviewMargin.left(), + y + st::historyReplyPreviewMargin.top(), + st::historyReplyPreview, + st::historyReplyPreview, + w + 2 * x); const auto preview = image->pixSingle( image->size() / style::DevicePixelRatio(), { @@ -732,7 +758,8 @@ void HistoryMessageReply::paint( } } } - if (w > st::msgReplyBarSkip + previewSkip) { + if (w > textLeft + st::historyReplyPadding.right()) { + w -= textLeft + st::historyReplyPadding.right(); p.setPen(!inBubble ? st->msgImgReplyBarColor() : _colorIndexPlusOne @@ -740,10 +767,10 @@ void HistoryMessageReply::paint( context, _colorIndexPlusOne - 1) : stm->msgServiceFg); - _name.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); - if (originalVia && w > st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew) { + _name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); + if (originalVia && w > _name.maxWidth() + st::msgServiceFont->spacew) { p.setFont(st::msgServiceFont); - p.drawText(x + st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, originalVia->text); + p.drawText(x + textLeft + _name.maxWidth() + st::msgServiceFont->spacew, y + st::historyReplyPadding.top() + st::msgServiceFont->ascent, originalVia->text); } p.setPen(inBubble @@ -751,8 +778,8 @@ void HistoryMessageReply::paint( : st->msgImgReplyBarColor()); holder->prepareCustomEmojiPaint(p, context, _text); auto replyToTextPosition = QPoint( - x + st::msgReplyBarSkip + previewSkip, - y + st::msgReplyPadding.top() + st::msgServiceNameFont->height); + x + textLeft, + y + st::historyReplyPadding.top() + st::msgServiceNameFont->height); const auto replyToTextPalette = &(inBubble ? stm->replyTextPalette : st->imgReplyTextPalette()); @@ -760,7 +787,7 @@ void HistoryMessageReply::paint( st::dialogsMiniReplyStory.icon.icon.paint( p, replyToTextPosition, - w - st::msgReplyBarSkip - previewSkip, + w + 2 * x + 2 * textLeft, replyToTextPalette->linkFg->c); replyToTextPosition += QPoint( st::dialogsMiniReplyStory.skipText @@ -769,7 +796,7 @@ void HistoryMessageReply::paint( } _text.draw(p, { .position = replyToTextPosition, - .availableWidth = w - st::msgReplyBarSkip - previewSkip, + .availableWidth = w, .palette = replyToTextPalette, .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, @@ -782,10 +809,14 @@ void HistoryMessageReply::paint( } } else { p.setFont(st::msgDateFont); - p.setPen(inBubble - ? stm->msgDateFg - : st->msgDateImgFg()); - p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(statePhrase(), w - st::msgReplyBarSkip)); + p.setPen(cache->outline); + p.drawTextLeft( + x + textLeft, + (y + (_height - st::msgDateFont->height) / 2), + w + 2 * x + 2 * textLeft, + st::msgDateFont->elided( + statePhrase(), + w - textLeft - st::historyReplyPadding.right())); } } } diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 1f10c733d3..c632b5b070 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -273,7 +273,9 @@ struct HistoryMessageReply void updateName( not_null holder, std::optional resolvedSender = std::nullopt) const; - void resize(int width) const; + [[nodiscard]] int resizeToWidth(int width) const; + [[nodiscard]] int height() const; + [[nodiscard]] QMargins margins() const; void itemRemoved( not_null holder, not_null removed); @@ -325,7 +327,6 @@ struct HistoryMessageReply ReplyToStoryPointer resolvedStory; std::unique_ptr originalVia; std::unique_ptr spoiler; - int toWidth = 0; struct { mutable std::unique_ptr animation; @@ -339,8 +340,10 @@ private: mutable Ui::Text::String _text; mutable PeerData *_externalSender = nullptr; mutable int _maxWidth = 0; + mutable int _minHeight = 0; + mutable int _height = 0; mutable int _nameVersion = 0; - uint8 _colorIndexPlusOne : 7 = 0; + uint8 _colorIndexPlusOne : 6 = 0; uint8 _unavailable : 1 = 0; }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index c8d72b6e10..d53cdbd8a3 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4352,9 +4352,11 @@ void HistoryWidget::updateOverStates(QPoint pos) { && _photoEditMedia && QRect( replyEditForwardInfoRect.x(), - replyEditForwardInfoRect.y() + st::msgReplyPadding.top(), - st::msgReplyBarSize.height(), - st::msgReplyBarSize.height()).contains(pos); + (replyEditForwardInfoRect.y() + + (replyEditForwardInfoRect.height() + - st::historyReplyPreview) / 2), + st::historyReplyPreview, + st::historyReplyPreview).contains(pos); auto inClickable = inReplyEditForward; if (_inPhotoEdit != inPhotoEdit) { _inPhotoEdit = inPhotoEdit; @@ -7809,7 +7811,9 @@ void HistoryWidget::updateReplyEditText(not_null item) { }; _replyEditMsgText.setMarkedText( st::defaultTextStyle, - item->inReplyText(), + ((_editMsgId || _replyTo.quote.empty()) + ? item->inReplyText() + : _replyTo.quote), Ui::DialogTextOptions(), context); if (fieldOrDisabledShown() || isRecording()) { @@ -7956,7 +7960,11 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { const auto overEdit = _photoEditMedia ? _inPhotoEditOver.value(_inPhotoEdit ? 1. : 0.) : 0.; - auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); + auto to = QRect( + replyLeft, + backy + (st::historyReplyHeight - st::historyReplyPreview) / 2, + st::historyReplyPreview, + st::historyReplyPreview); p.drawPixmap(to.x(), to.y(), preview->pixSingle( preview->size() / style::DevicePixelRatio(), { @@ -7980,7 +7988,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { p.setOpacity(1.); } } - replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); + replyLeft += st::historyReplyPreview + st::msgReplyBarSkip; } p.setPen(st::historyReplyNameFg); if (_editMsgId) { @@ -8004,7 +8012,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { } else { p.setFont(st::msgDateFont); p.setPen(st::historyComposeAreaFgService); - p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(tr::lng_profile_loading(tr::now), width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right())); + p.drawText(replyLeft, backy + (st::historyReplyHeight - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(tr::lng_profile_loading(tr::now), width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right())); } } } else if (hasForward) { @@ -8020,24 +8028,15 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { } if (drawWebPagePreview) { const auto textTop = backy + st::msgReplyPadding.top(); - auto previewLeft = st::historyReplySkip + st::webPageLeft; - p.fillRect( - st::historyReplySkip, - textTop, - st::webPageBar, - st::msgReplyBarSize.height(), - st::msgInReplyBarColor); + auto previewLeft = st::historyReplySkip; const auto to = QRect( previewLeft, - textTop, - st::msgReplyBarSize.height(), - st::msgReplyBarSize.height()); + backy + (st::historyReplyHeight - st::historyReplyPreview) / 2, + st::historyReplyPreview, + st::historyReplyPreview); if (HistoryView::DrawWebPageDataPreview(p, _previewData, _peer, to)) { - previewLeft += st::msgReplyBarSize.height() - + st::msgReplyBarSkip - - st::msgReplyBarSize.width() - - st::msgReplyBarPos.x(); + previewLeft += st::historyReplyPreview + st::msgReplyBarSkip; } p.setPen(st::historyReplyNameFg); const auto elidedWidth = width() 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 0e793b9550..b5de792b1d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -776,23 +776,14 @@ void FieldHeader::paintWebPage(Painter &p, not_null context) { const auto textTop = st::msgReplyPadding.top(); auto previewLeft = st::historyReplySkip + st::webPageLeft; - p.fillRect( - st::historyReplySkip, - textTop, - st::webPageBar, - st::msgReplyBarSize.height(), - st::msgInReplyBarColor); const QRect to( previewLeft, - textTop, - st::msgReplyBarSize.height(), - st::msgReplyBarSize.height()); + (st::historyReplyHeight - st::historyReplyPreview) / 2, + st::historyReplyPreview, + st::historyReplyPreview); if (HistoryView::DrawWebPageDataPreview(p, _preview.data, context, to)) { - previewLeft += st::msgReplyBarSize.height() - + st::msgReplyBarSkip - - st::msgReplyBarSize.width() - - st::msgReplyBarPos.x(); + previewLeft += st::historyReplyPreview + st::msgReplyBarSkip; } const auto elidedWidth = width() - previewLeft @@ -826,8 +817,7 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { if (!_shownMessage) { p.setFont(st::msgDateFont); p.setPen(st::historyComposeAreaFgService); - const auto top = (st::msgReplyPadding.top() - + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2); + const auto top = (st::historyReplyHeight - st::msgDateFont->height) / 2; p.drawText( replySkip, top + st::msgDateFont->ascent, @@ -863,10 +853,8 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { update(); }); } - const auto previewSkipValue = st::msgReplyBarSize.height() - + st::msgReplyBarSkip - - st::msgReplyBarSize.width() - - st::msgReplyBarPos.x(); + const auto previewSkipValue = st::historyReplyPreview + + st::msgReplyBarSkip; const auto previewSkip = _shownMessageHasPreview ? previewSkipValue : 0; const auto textLeft = replySkip + previewSkip; const auto textAvailableWidth = availableWidth - previewSkip; @@ -876,9 +864,9 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { : 0.; const auto to = QRect( replySkip, - st::msgReplyPadding.top(), - st::msgReplyBarSize.height(), - st::msgReplyBarSize.height()); + (st::historyReplyHeight - st::historyReplyPreview) / 2, + st::historyReplyPreview, + st::historyReplyPreview); p.drawPixmap(to.x(), to.y(), preview->pixSingle( preview->size() / style::DevicePixelRatio(), { @@ -999,9 +987,9 @@ void FieldHeader::updateControlsGeometry(QSize size) { height()); _shownMessagePreviewRect = QRect( st::historyReplySkip, - st::msgReplyPadding.top(), - st::msgReplyBarSize.height(), - st::msgReplyBarSize.height()); + (st::historyReplyHeight - st::historyReplyPreview) / 2, + st::historyReplyPreview, + st::historyReplyPreview); } void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) { 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 6e9bb59034..f89cfa9e1d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -362,9 +362,9 @@ void ForwardPanel::paint( if (preview) { auto to = QRect( x, - y + st::msgReplyPadding.top(), - st::msgReplyBarSize.height(), - st::msgReplyBarSize.height()); + y + (st::historyReplyHeight - st::historyReplyPreview) / 2, + st::historyReplyPreview, + st::historyReplyPreview); p.drawPixmap(to.x(), to.y(), preview->pixSingle( preview->size() / style::DevicePixelRatio(), { @@ -375,10 +375,7 @@ void ForwardPanel::paint( Ui::FillSpoilerRect(p, to, Ui::DefaultImageSpoiler().frame( _spoiler->index(now, pausedSpoiler))); } - const auto skip = st::msgReplyBarSize.height() - + st::msgReplyBarSkip - - st::msgReplyBarSize.width() - - st::msgReplyBarPos.x(); + const auto skip = st::historyReplyPreview + st::msgReplyBarSkip; x += skip; available -= skip; } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 60d18d46ad..384c8bae63 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -776,9 +776,12 @@ QSize Message::performCountOptimalSize() { accumulate_max(maxWidth, namew); } if (reply) { - auto replyw = st::msgPadding.left() + reply->maxWidth() - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); + auto replyw = st::msgPadding.left() + + reply->maxWidth() + + st::msgPadding.right(); if (reply->originalVia) { - replyw += st::msgServiceFont->spacew + reply->originalVia->maxWidth; + replyw += st::msgServiceFont->spacew + + reply->originalVia->maxWidth; } accumulate_max(maxWidth, replyw); } @@ -1558,9 +1561,8 @@ void Message::paintReplyInfo( QRect &trect, const PaintContext &context) const { if (const auto reply = displayedReply()) { - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); reply->paint(p, this, context, trect.x(), trect.y(), trect.width(), true); - trect.setY(trect.y() + h); + trect.setY(trect.y() + reply->height()); } } @@ -1761,41 +1763,18 @@ void Message::toggleReplyRipple(bool pressed) { if (pressed) { if (!reply->ripple.animation && !unwrapped()) { - const auto smallTop = displayFromName() - || displayedTopicButton() - || displayForwardedFrom(); - const auto rounding = countBubbleRounding(); - - using Corner = Ui::BubbleCornerRounding; - using Radius = Ui::CachedCornerRadius; - const auto &small = Ui::CachedCornersMasks(Radius::ThumbSmall); - const auto &large = Ui::CachedCornersMasks(Radius::ThumbLarge); - const auto corners = std::array{{ - ((smallTop || (rounding.topLeft == Corner::Small)) - ? small - : large)[0], - ((smallTop || (rounding.topRight == Corner::Small)) - ? small - : large)[1], - small[2], - small[3], - }}; - - const auto &padding = st::msgReplyPadding; + const auto &padding = st::msgPadding; const auto geometry = countGeometry(); const auto item = data(); + const auto margins = reply->margins(); const auto size = QSize( - geometry.width() - - padding.left() / 2 - - padding.right(), - st::msgReplyBarSize.height() - + padding.top() - + padding.bottom()); + geometry.width() - padding.left() - padding.right(), + reply->height() - margins.top() - margins.bottom()); reply->ripple.animation = std::make_unique( st::defaultRippleAnimation, - Images::Round( - Ui::RippleAnimation::MaskByDrawer(size, true, nullptr), - corners), + Ui::RippleAnimation::RoundRectMask( + size, + st::messageQuoteStyle.radius), [=] { item->history()->owner().requestItemRepaint(item); }); } if (reply->ripple.animation) { @@ -2436,14 +2415,15 @@ bool Message::getStateReplyInfo( QPoint point, QRect &trect, not_null outResult) const { - if (auto reply = displayedReply()) { - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - if (point.y() >= trect.top() && point.y() < trect.top() + h) { + if (const auto reply = displayedReply()) { + const auto margins = reply->margins(); + const auto height = reply->height(); + if (point.y() >= trect.top() && point.y() < trect.top() + height) { const auto g = QRect( trect.x(), - trect.y() + st::msgReplyPadding.top(), + trect.y() + margins.top(), trect.width(), - st::msgReplyBarSize.height()); + height - margins.top() - margins.bottom()); if (g.contains(point)) { if (const auto link = reply->link()) { outResult->link = reply->link(); @@ -2452,7 +2432,7 @@ bool Message::getStateReplyInfo( } return true; } - trect.setTop(trect.top() + h); + trect.setTop(trect.top() + height); } return false; } @@ -2530,9 +2510,8 @@ void Message::updatePressed(QPoint point) { auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; trect.setTop(trect.top() + fwdheight); } - if (item->Get()) { - auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - trect.setTop(trect.top() + h); + if (const auto reply = item->Get()) { + trect.setTop(trect.top() + reply->height()); } if (const auto via = item->Get()) { if (!displayFromName() && !displayForwardedFrom()) { @@ -3642,13 +3621,9 @@ QRect Message::innerGeometry() const { + st::topicButtonSkip); } // Skip displayForwardedFrom() until there are no animations for it. - if (displayedReply()) { + if (const auto reply = displayedReply()) { // See paintReplyInfo(). - result.translate( - 0, - st::msgReplyPadding.top() - + st::msgReplyBarSize.height() - + st::msgReplyPadding.bottom()); + result.translate(0, reply->height()); } if (!displayFromName() && !displayForwardedFrom()) { // See paintViaBotIdInfo(). @@ -3900,9 +3875,10 @@ int Message::resizeContentGetHeight(int newWidth) { } if (reply) { - reply->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right()); + newHeight += reply->resizeToWidth(contentWidth + - st::msgPadding.left() + - st::msgPadding.right()); reply->ripple.animation = nullptr; - newHeight += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); } if (needInfoDisplay()) { newHeight += (bottomInfoHeight - st::msgDateFont->height); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 04a1688528..f30a8d550c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -286,7 +286,7 @@ QSize Gif::countCurrentSize(int newWidth) { via->resize(availw); } if (reply) { - reply->resize(availw); + [[maybe_unused]] int height = reply->resizeToWidth(availw); } } } @@ -652,16 +652,21 @@ void Gif::draw(Painter &p, const PaintContext &context) const { if (via || reply || forwarded) { auto rectw = width() - usew - st::msgReplyPadding.left(); auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); - auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + auto recth = 0; auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height); if (forwarded) { - recth += forwardedHeight; + recth += st::msgReplyPadding.top() + forwardedHeight; } else if (via) { - recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + recth += st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } if (reply) { - recth += st::msgReplyBarSize.height(); + const auto replyMargins = reply->margins(); + recth += reply->height() + - ((forwarded || via) ? 0 : replyMargins.top()) + - replyMargins.bottom(); + } else { + recth += st::msgReplyPadding.bottom(); } int rectx = rightAligned ? 0 : (usew + st::msgReplyPadding.left()); int recty = painty; @@ -669,25 +674,31 @@ void Gif::draw(Painter &p, const PaintContext &context) const { Ui::FillRoundRect(p, rectx, recty, rectw, recth, sti->msgServiceBg, sti->msgServiceBgCornersSmall); p.setPen(st->msgServiceFg()); - rectx += st::msgReplyPadding.left(); - rectw = innerw; + const auto textx = rectx + st::msgReplyPadding.left(); + const auto textw = rectw - st::msgReplyPadding.left() - st::msgReplyPadding.right(); if (forwarded) { p.setTextPalette(st->serviceTextPalette()); auto breakEverywhere = (forwardedHeightReal > forwardedHeight); - forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere); + forwarded->text.drawElided(p, textx, recty + st::msgReplyPadding.top(), textw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere); p.restoreTextPalette(); const auto skip = std::min( - forwarded->text.countHeight(rectw), + forwarded->text.countHeight(textw), kMaxGifForwardedBarLines * st::msgServiceNameFont->height); recty += skip; } else if (via) { p.setFont(st::msgServiceNameFont); - p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); + p.drawTextLeft(textx, recty + st::msgReplyPadding.top(), 2 * textx + textw, via->text); int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); recty += skip; } if (reply) { + if (forwarded || via) { + recty += st::msgReplyPadding.top(); + recth -= st::msgReplyPadding.top(); + } else { + recty -= reply->margins().top(); + } reply->paint(p, _parent, context, rectx, recty, rectw, false); } } @@ -1019,16 +1030,21 @@ TextState Gif::textState(QPoint point, StateRequest request) const { if (via || reply || forwarded) { auto rectw = paintw - usew - st::msgReplyPadding.left(); auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); - auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + auto recth = 0; auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height); if (forwarded) { - recth += forwardedHeight; + recth += st::msgReplyPadding.top() + forwardedHeight; } else if (via) { - recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + recth += st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } if (reply) { - recth += st::msgReplyBarSize.height(); + const auto replyMargins = reply->margins(); + recth += reply->height() + - ((forwarded || via) ? 0 : replyMargins.top()) + - replyMargins.bottom(); + } else { + recth += st::msgReplyPadding.bottom(); } auto rectx = rightAligned ? 0 : (usew + st::msgReplyPadding.left()); auto recty = painty; @@ -1067,6 +1083,12 @@ TextState Gif::textState(QPoint point, StateRequest request) const { recth -= skip; } if (reply) { + if (forwarded || via) { + recty += st::msgReplyPadding.top(); + recth -= st::msgReplyPadding.top() + reply->margins().top(); + } else { + recty -= reply->margins().top(); + } const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { result.link = reply->link(); @@ -1076,7 +1098,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const { st::defaultRippleAnimation, Ui::RippleAnimation::RoundRectMask( replyRect.size(), - st::roundRadiusSmall), + st::messageQuoteStyle.radius), [=] { item->history()->owner().requestItemRepaint(item); }); } return result; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 4f5e9a5c5c..413046ed3b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -123,7 +123,7 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { via->resize(availw); } if (reply) { - reply->resize(availw); + [[maybe_unused]] int height = reply->resizeToWidth(availw); } } return { newWidth, newHeight }; @@ -211,11 +211,16 @@ UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo( panelHeight += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } - if (reply) { - panelHeight += st::msgReplyBarSize.height(); - } if (panelHeight) { - panelHeight += st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + panelHeight += st::msgReplyPadding.top(); + } + if (reply) { + const auto replyMargins = reply->margins(); + panelHeight += reply->height() + - ((forwarded || via) ? 0 : replyMargins.top()) + - replyMargins.bottom(); + } else { + panelHeight += st::msgReplyPadding.bottom(); } const auto total = (topicSize.isEmpty() ? 0 : topicSize.height()) + ((panelHeight || !topicSize.height()) ? st::topicButtonSkip : 0) @@ -303,26 +308,32 @@ void UnwrappedMedia::drawSurrounding( Ui::FillRoundRect(p, rectx, recty, rectw, recth, sti->msgServiceBg, sti->msgServiceBgCornersSmall); p.setPen(st->msgServiceFg()); - rectx += st::msgReplyPadding.left(); - rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); + const auto textx = rectx + st::msgReplyPadding.left(); + const auto textw = rectw - st::msgReplyPadding.left() - st::msgReplyPadding.right(); if (forwarded) { p.setTextPalette(st->serviceTextPalette()); - forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxForwardedBarLines, style::al_left, 0, -1, 0, surrounding.forwardedBreakEverywhere); + forwarded->text.drawElided(p, textx, recty + st::msgReplyPadding.top(), textw, kMaxForwardedBarLines, style::al_left, 0, -1, 0, surrounding.forwardedBreakEverywhere); p.restoreTextPalette(); const auto skip = std::min( - forwarded->text.countHeight(rectw), + forwarded->text.countHeight(textw), kMaxForwardedBarLines * st::msgServiceNameFont->height); recty += skip; } else if (via) { p.setFont(st::msgDateFont); - p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); + p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * textx + textw, via->text); const auto skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); recty += skip; } if (reply) { + if (forwarded || via) { + recty += st::msgReplyPadding.top(); + recth -= st::msgReplyPadding.top(); + } else { + recty -= reply->margins().top(); + } reply->paint(p, _parent, context, rectx, recty, rectw, false); } replyRight = rectx + rectw; @@ -330,8 +341,9 @@ void UnwrappedMedia::drawSurrounding( } if (rightActionSize) { const auto position = calculateFastActionPosition( - fullBottom, replyRight, + reply ? reply->height() : 0, + fullBottom, fullRight, *rightActionSize); const auto outer = 2 * inner.x() + inner.width(); @@ -462,6 +474,12 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { recth -= skip; } if (reply) { + if (forwarded || via) { + recty += st::msgReplyPadding.top(); + recth -= st::msgReplyPadding.top() + reply->margins().top(); + } else { + recty -= reply->margins().top(); + } const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { result.link = reply->link(); @@ -471,7 +489,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { st::defaultRippleAnimation, Ui::RippleAnimation::RoundRectMask( replyRect.size(), - st::roundRadiusSmall), + st::messageQuoteStyle.radius), [=] { item->history()->owner().requestItemRepaint(item); }); } return result; @@ -495,8 +513,9 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { } if (rightActionSize) { const auto position = calculateFastActionPosition( - fullBottom, replyRight, + reply ? reply->height() : 0, + fullBottom, fullRight, *rightActionSize); if (QRect(position.x(), position.y(), rightActionSize->width(), rightActionSize->height()).contains(point)) { @@ -598,17 +617,16 @@ int UnwrappedMedia::calculateFullRight(const QRect &inner) const { } QPoint UnwrappedMedia::calculateFastActionPosition( - int fullBottom, int replyRight, + int replyHeight, + int fullBottom, int fullRight, QSize size) const { const auto fastShareTop = (fullBottom - st::historyFastShareBottom - size.height()); - const auto doesRightActionHitReply = replyRight && (fastShareTop < - st::msgReplyBarSize.height() - + st::msgReplyPadding.top() - + st::msgReplyPadding.bottom()); + const auto doesRightActionHitReply = replyRight + && (fastShareTop < replyHeight); const auto fastShareLeft = ((doesRightActionHitReply ? replyRight : fullRight) + st::historyFastShareLeft); @@ -641,7 +659,7 @@ int UnwrappedMedia::additionalWidth( accumulate_max(result, 2 * st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.right()); } if (reply) { - accumulate_max(result, st::msgReplyPadding.left() + reply->maxWidth()); + accumulate_max(result, reply->maxWidth()); } return result; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index ca80a80c19..9932c0521c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -145,8 +145,9 @@ private: int calculateFullRight(const QRect &inner) const; QPoint calculateFastActionPosition( - int fullBottom, int replyRight, + int replyHeight, + int fullBottom, int fullRight, QSize size) const; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index acb0af9a97..059b1dce41 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -22,11 +22,17 @@ msgDateFont: font(13px); msgMinWidth: 160px; msgPhotoSize: 33px; msgPhotoSkip: 40px; -msgPadding: margins(13px, 7px, 13px, 8px); +msgPadding: margins(10px, 8px, 10px, 8px); msgMargin: margins(16px, 6px, 56px, 2px); msgMarginTopAttached: 0px; msgShadow: 2px; +historyReplyTop: 2px; +historyReplyBottom: 2px; +historyReplyPreview: 32px; +historyReplyPreviewMargin: margins(7px, 4px, 4px, 4px); +historyReplyPadding: margins(11px, 2px, 6px, 2px); + msgReplyPadding: margins(6px, 6px, 11px, 6px); msgReplyBarPos: point(1px, 0px); msgReplyBarSize: size(2px, 36px); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 6c15101168..777e84ec62 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -583,6 +583,10 @@ const MessageStyle &ChatStyle::messageStyle(bool outbg, bool selected) const { EnsureBlockquoteCache( result.replyCache, result.msgReplyBarColor); + if (!result.quoteCache) { + result.quoteCache = std::make_unique( + *result.replyCache); + } const auto preBgOverride = [&] { const auto withBg = [&](const QColor &color) { diff --git a/Telegram/SourceFiles/ui/chat/message_bar.cpp b/Telegram/SourceFiles/ui/chat/message_bar.cpp index 9050e59a7b..d6b4417713 100644 --- a/Telegram/SourceFiles/ui/chat/message_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/message_bar.cpp @@ -222,8 +222,8 @@ void MessageBar::updateFromContent(MessageBarContent &&content) { QRect MessageBar::imageRect() const { const auto left = st::msgReplyBarSkip + st::msgReplyBarSkip; - const auto top = st::msgReplyPadding.top(); - const auto size = st::msgReplyBarSize.height(); + const auto top = (st::historyReplyHeight - st::historyReplyPreview) / 2; + const auto size = st::historyReplyPreview; return QRect(left, top, size, size); } @@ -243,14 +243,11 @@ QRect MessageBar::titleRangeRect(int from, int till) const { QRect MessageBar::bodyRect(bool withImage) const { const auto innerLeft = st::msgReplyBarSkip + st::msgReplyBarSkip; - const auto imageSkip = st::msgReplyBarSize.height() - + st::msgReplyBarSkip - - st::msgReplyBarSize.width() - - st::msgReplyBarPos.x(); + const auto imageSkip = st::historyReplyPreview + st::msgReplyBarSkip; const auto left = innerLeft + (withImage ? imageSkip : 0); const auto top = st::msgReplyPadding.top(); const auto width = _widget.width() - left - st::msgReplyPadding.right(); - const auto height = st::msgReplyBarSize.height(); + const auto height = (st::historyReplyHeight - 2 * top); return QRect(left, top, width, height) - _content.margins; } From f8d5a8a203f96bf0f10db5098e4ee9bddac89a41 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Oct 2023 12:57:35 +0400 Subject: [PATCH 42/99] Redesign web pages preview. --- .../history/history_item_components.cpp | 16 +- .../history_view_compose_controls.cpp | 2 +- .../history/view/media/history_view_game.cpp | 114 ++++++----- .../history/view/media/history_view_game.h | 28 +-- .../view/media/history_view_web_page.cpp | 181 ++++++++---------- .../view/media/history_view_web_page.h | 32 ++-- Telegram/SourceFiles/ui/chat/chat.style | 5 +- Telegram/SourceFiles/ui/chat/chat_style.cpp | 24 +++ Telegram/SourceFiles/ui/chat/chat_style.h | 13 ++ Telegram/SourceFiles/ui/text/text_options.cpp | 3 +- 10 files changed, 241 insertions(+), 177 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index f90e4ca792..71163b4e9d 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -780,9 +780,11 @@ void HistoryMessageReply::paint( auto replyToTextPosition = QPoint( x + textLeft, y + st::historyReplyPadding.top() + st::msgServiceNameFont->height); - const auto replyToTextPalette = &(inBubble - ? stm->replyTextPalette - : st->imgReplyTextPalette()); + auto replyToTextPalette = &(!inBubble + ? st->imgReplyTextPalette() + : _colorIndexPlusOne + ? st->coloredTextPalette(selected, _colorIndexPlusOne - 1) + : stm->replyTextPalette); if (_fields.storyId) { st::dialogsMiniReplyStory.icon.icon.paint( p, @@ -794,6 +796,14 @@ void HistoryMessageReply::paint( + st::dialogsMiniReplyStory.icon.icon.width(), 0); } + auto owned = std::optional(); + auto copy = std::optional(); + if (inBubble && _colorIndexPlusOne) { + copy.emplace(*replyToTextPalette); + owned.emplace(cache->outline); + copy->linkFg = owned->color(); + replyToTextPalette = &*copy; + } _text.draw(p, { .position = replyToTextPosition, .availableWidth = w, 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 b5de792b1d..151ad9cd36 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -775,7 +775,7 @@ void FieldHeader::paintWebPage(Painter &p, not_null context) { Expects(ShowWebPagePreview(_preview.data)); const auto textTop = st::msgReplyPadding.top(); - auto previewLeft = st::historyReplySkip + st::webPageLeft; + auto previewLeft = st::historyReplySkip + st::msgReplyBarSkip; const QRect to( previewLeft, diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 4767bfccee..8713011c0e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -32,9 +32,10 @@ Game::Game( not_null data, const TextWithEntities &consumed) : Media(parent) +, _st(st::historyPagePreview) , _data(data) -, _title(st::msgMinWidth - st::webPageLeft) -, _description(st::msgMinWidth - st::webPageLeft) { +, _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) +, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { if (!consumed.text.isEmpty()) { const auto context = Core::MarkedTextContext{ .session = &history()->session(), @@ -47,6 +48,13 @@ Game::Game( context); } history()->owner().registerGameView(_data, _parent); + + const auto from = parent->data()->displayFrom(); + const auto info = from ? nullptr : parent->data()->hiddenSenderInfo(); + Assert(from || info); + _colorIndexPlusOne = !parent->data()->isPost() + ? ((from ? from->colorIndex() : info->colorIndex) + 1) + : 0; } QSize Game::countOptimalSize() { @@ -126,8 +134,8 @@ QSize Game::countOptimalSize() { accumulate_max(maxWidth, maxMediaWidth); minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); } - maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); - auto padding = inBubblePadding(); + auto padding = inBubblePadding() + innerMargin(); + maxWidth += padding.left() + padding.right(); minHeight += padding.top() + padding.bottom(); if (!_gameTagWidth) { @@ -147,7 +155,8 @@ void Game::refreshParentId(not_null realParent) { QSize Game::countCurrentSize(int newWidth) { accumulate_min(newWidth, maxWidth()); - auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); + const auto padding = inBubblePadding() + innerMargin(); + auto innerWidth = newWidth - padding.left() - padding.right(); // enable any count of lines in game description / message auto linesMax = 4096; @@ -184,11 +193,7 @@ QSize Game::countCurrentSize(int newWidth) { _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); newHeight += _attach->height() - bubble.top() - bubble.bottom(); - if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { - newHeight += bottomInfoPadding(); - } } - auto padding = inBubblePadding(); newHeight += padding.top() + padding.bottom(); return { newWidth, newHeight }; @@ -205,38 +210,41 @@ TextSelection Game::fromDescriptionSelection( } void Game::draw(Painter &p, const PaintContext &context) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - auto paintw = width(); + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return; + } const auto st = context.st; const auto sti = context.imageStyle(); const auto stm = context.messageStyle(); - const auto &barfg = stm->msgReplyBarColor; - const auto &semibold = stm->msgServiceFg; + const auto bubble = _attach ? _attach->bubbleMargins() : QMargins(); + const auto full = QRect(0, 0, width(), height()); + auto outer = full.marginsRemoved(inBubblePadding()); + auto inner = outer.marginsRemoved(innerMargin()); + auto tshift = inner.top(); + auto paintw = inner.width(); - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - paintw -= padding.left() + padding.right(); - if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { - bshift += bottomInfoPadding(); - } - - QRect bar(style::rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); - p.fillRect(bar, barfg); + const auto selected = context.selected(); + const auto useColorIndex = context.outbg ? 0 : _colorIndexPlusOne; + const auto cache = useColorIndex + ? st->coloredReplyCache(selected, useColorIndex - 1).get() + : stm->replyCache.get(); + Ui::Text::ValidateQuotePaintCache(*cache, _st); + Ui::Text::FillQuotePaint(p, outer, *cache, _st); auto lineHeight = UnitedLineHeight(); if (_titleLines) { - p.setPen(semibold); - p.setTextPalette(stm->semiboldPalette); + p.setPen(cache->outline); + p.setTextPalette(useColorIndex + ? st->coloredTextPalette(selected, useColorIndex - 1) + : stm->semiboldPalette); auto endskip = 0; if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, context.selection); + _title.drawLeftElided(p, inner.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, context.selection); tshift += _titleLines * lineHeight; p.setTextPalette(stm->textPalette); @@ -249,7 +257,7 @@ void Game::draw(Painter &p, const PaintContext &context) const { } _parent->prepareCustomEmojiPaint(p, context, _description); _description.draw(p, { - .position = { padding.left(), tshift }, + .position = { inner.left(), tshift }, .outerWidth = width(), .availableWidth = paintw, .spoiler = Ui::Text::DefaultSpoilerCache(), @@ -266,7 +274,7 @@ void Game::draw(Painter &p, const PaintContext &context) const { auto attachAtTop = !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; - auto attachLeft = padding.left() - bubble.left(); + auto attachLeft = inner.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); @@ -301,16 +309,13 @@ TextState Game::textState(QPoint point, StateRequest request) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } - auto paintw = width(); - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { - bshift += bottomInfoPadding(); - } - paintw -= padding.left() + padding.right(); + const auto bubble = _attach ? _attach->bubbleMargins() : QMargins(); + const auto full = QRect(0, 0, width(), height()); + auto outer = full.marginsRemoved(inBubblePadding()); + auto inner = outer.marginsRemoved(innerMargin()); + auto tshift = inner.top(); + auto paintw = inner.width(); auto inThumb = false; auto symbolAdd = 0; @@ -320,7 +325,7 @@ TextState Game::textState(QPoint point, StateRequest request) const { Ui::Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; result = TextState(_parent, _title.getStateElidedLeft( - point - QPoint(padding.left(), tshift), + point - QPoint(inner.left(), tshift), paintw, width(), titleRequest)); @@ -334,7 +339,7 @@ TextState Game::textState(QPoint point, StateRequest request) const { Ui::Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; result = TextState(_parent, _description.getStateElidedLeft( - point - QPoint(padding.left(), tshift), + point - QPoint(inner.left(), tshift), paintw, width(), descriptionRequest)); @@ -351,11 +356,11 @@ TextState Game::textState(QPoint point, StateRequest request) const { auto attachAtTop = !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; - auto attachLeft = padding.left() - bubble.left(); + auto attachLeft = inner.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); - if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) { + if (QRect(attachLeft, tshift, _attach->width(), inner.top() + inner.height() - tshift).contains(point)) { if (_attach->isReadyForOpen()) { if (_parent->data()->isHistoryEntry()) { result.link = _openl; @@ -417,15 +422,24 @@ void Game::playAnimation(bool autoplay) { } QMargins Game::inBubblePadding() const { - auto lshift = st::msgPadding.left() + st::webPageLeft; - auto rshift = st::msgPadding.right(); - auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; - auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; - return QMargins(lshift, tshift, rshift, bshift); + return { + st::msgPadding.left(), + isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip, + st::msgPadding.right(), + (isBubbleBottom() + ? (st::msgPadding.left() + bottomInfoPadding()) + : st::mediaInBubbleSkip), + }; +} + +QMargins Game::innerMargin() const { + return _st.padding; } int Game::bottomInfoPadding() const { - if (!isBubbleBottom()) return 0; + if (!isBubbleBottom()) { + return 0; + } auto result = st::msgDateFont->height; @@ -452,7 +466,9 @@ void Game::parentTextUpdated() { Ui::ItemTextOptions(_parent->data()), context); } else { - _description = Ui::Text::String(st::msgMinWidth - st::webPageLeft); + _description = Ui::Text::String(st::msgMinWidth + - _st.padding.left() + - _st.padding.right()); } history()->owner().requestViewResize(_parent); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.h b/Telegram/SourceFiles/history/view/media/history_view_game.h index 0624185691..4cd5c613fd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.h +++ b/Telegram/SourceFiles/history/view/media/history_view_game.h @@ -87,23 +87,29 @@ public: private: void playAnimation(bool autoplay) override; - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; + [[nodiscard]] QSize countOptimalSize() override; + [[nodiscard]] QSize countCurrentSize(int newWidth) override; - TextSelection toDescriptionSelection(TextSelection selection) const; - TextSelection fromDescriptionSelection(TextSelection selection) const; - QMargins inBubblePadding() const; - int bottomInfoPadding() const; + [[nodiscard]] TextSelection toDescriptionSelection( + TextSelection selection) const; + [[nodiscard]] TextSelection fromDescriptionSelection( + TextSelection selection) const; + [[nodiscard]] QMargins inBubblePadding() const; + [[nodiscard]] QMargins innerMargin() const; + [[nodiscard]] int bottomInfoPadding() const; - not_null _data; + const style::QuoteStyle &_st; + const not_null _data; std::shared_ptr _openl; std::unique_ptr _attach; - int _titleLines, _descriptionLines; - - Ui::Text::String _title, _description; - int _gameTagWidth = 0; + int _descriptionLines = 0; + int _titleLines : 24 = 0; + int _colorIndexPlusOne : 8 = 0; + + Ui::Text::String _title; + Ui::Text::String _description; }; 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 454475369b..799fe826ab 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -90,26 +90,38 @@ WebPage::WebPage( not_null parent, not_null data) : Media(parent) +, _st(st::historyPagePreview) , _data(data) -, _siteName(st::msgMinWidth - st::webPageLeft) -, _title(st::msgMinWidth - st::webPageLeft) -, _description(st::msgMinWidth - st::webPageLeft) { +, _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) +, _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) +, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { history()->owner().registerWebPageView(_data, _parent); + + const auto from = parent->data()->displayFrom(); + const auto info = from ? nullptr : parent->data()->hiddenSenderInfo(); + Assert(from || info); + _colorIndexPlusOne = !parent->data()->isPost() + ? ((from ? from->colorIndex() : info->colorIndex) + 1) + : 0; } QSize WebPage::countOptimalSize() { if (_data->pendingTill) { return { 0, 0 }; } + const auto padding = inBubblePadding() + innerMargin(); const auto versionChanged = (_dataVersion != _data->version); if (versionChanged) { _dataVersion = _data->version; _openl = nullptr; _attach = nullptr; _collage = PrepareCollageMedia(_parent->data(), _data->collage); - _siteName = Ui::Text::String(st::msgMinWidth - st::webPageLeft); - _title = Ui::Text::String(st::msgMinWidth - st::webPageLeft); - _description = Ui::Text::String(st::msgMinWidth - st::webPageLeft); + const auto min = st::msgMinWidth + - _st.padding.left() + - _st.padding.right(); + _siteName = Ui::Text::String(min); + _title = Ui::Text::String(min); + _description = Ui::Text::String(min); } auto lineHeight = UnitedLineHeight(); @@ -201,11 +213,6 @@ QSize WebPage::countOptimalSize() { _hasViewButton = ViewButton::MediaHasViewButton(_data); - const auto textFloatsAroundInfo = !_asArticle - && !_attach - && isBubbleBottom() - && !_hasViewButton; - // init strings if (_description.isEmpty() && !_data->description.text.isEmpty()) { auto text = _data->description; @@ -213,9 +220,8 @@ QSize WebPage::countOptimalSize() { if (isLogEntryOriginal()) { // Fix layout for small bubbles (narrow media caption edit log entries). _description = Ui::Text::String(st::minPhotoSize - - st::msgPadding.left() - - st::msgPadding.right() - - st::webPageLeft); + - padding.left() + - padding.right()); } using MarkedTextContext = Core::MarkedTextContext; auto context = MarkedTextContext{ @@ -232,11 +238,6 @@ QSize WebPage::countOptimalSize() { text, Ui::WebpageTextDescriptionOptions(), context); - if (textFloatsAroundInfo) { - _description.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } } if (!displayedSiteName().isEmpty()) { _siteNameLines = 1; @@ -259,11 +260,6 @@ QSize WebPage::countOptimalSize() { title, Ui::WebpageTextTitleOptions()); } - if (textFloatsAroundInfo && _description.isEmpty()) { - _title.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } } // init dimensions @@ -282,11 +278,7 @@ QSize WebPage::countOptimalSize() { } if (!_siteName.isEmpty()) { - if (_title.isEmpty() && _description.isEmpty() && textFloatsAroundInfo) { - accumulate_max(maxWidth, _siteName.maxWidth() + _parent->skipBlockWidth()); - } else { - accumulate_max(maxWidth, _siteName.maxWidth() + articlePhotoMaxWidth); - } + accumulate_max(maxWidth, _siteName.maxWidth() + articlePhotoMaxWidth); minHeight += lineHeight; } if (!_title.isEmpty()) { @@ -309,16 +301,12 @@ QSize WebPage::countOptimalSize() { } accumulate_max(maxWidth, maxMediaWidth); minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); - if (!_attach->additionalInfoString().isEmpty()) { - minHeight += bottomInfoPadding(); - } } if (_data->type == WebPageType::Video && _data->duration) { _duration = Ui::FormatDurationText(_data->duration); _durationWidth = st::msgDateFont->width(_duration); } - maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); - auto padding = inBubblePadding(); + maxWidth += padding.left() + padding.right(); minHeight += padding.top() + padding.bottom(); if (_asArticle) { @@ -332,7 +320,8 @@ QSize WebPage::countCurrentSize(int newWidth) { return { newWidth, minHeight() }; } - auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); + auto padding = inBubblePadding() + innerMargin(); + auto innerWidth = newWidth - padding.left() - padding.right(); auto newHeight = 0; auto lineHeight = UnitedLineHeight(); @@ -373,7 +362,6 @@ QSize WebPage::countCurrentSize(int newWidth) { _pixh -= lineHeight; } while (_pixh > lineHeight); - newHeight += bottomInfoPadding(); } else { newHeight = siteNameHeight; @@ -410,16 +398,8 @@ QSize WebPage::countCurrentSize(int newWidth) { _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); newHeight += _attach->height() - bubble.top() - bubble.bottom(); - if (!_attach->additionalInfoString().isEmpty()) { - newHeight += bottomInfoPadding(); - } else if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { - newHeight += bottomInfoPadding(); - } - } else if (_hasViewButton) { - newHeight += bottomInfoPadding(); } } - auto padding = inBubblePadding(); newHeight += padding.top() + padding.bottom(); return { newWidth, newHeight }; @@ -476,34 +456,28 @@ void WebPage::unloadHeavyPart() { } void WebPage::draw(Painter &p, const PaintContext &context) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - auto paintw = width(); - + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return; + } const auto st = context.st; const auto sti = context.imageStyle(); const auto stm = context.messageStyle(); - const auto &barfg = stm->msgReplyBarColor; - const auto &semibold = stm->msgServiceFg; - - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - paintw -= padding.left() + padding.right(); + const auto bubble = _attach ? _attach->bubbleMargins() : QMargins(); + const auto full = QRect(0, 0, width(), height()); + auto outer = full.marginsRemoved(inBubblePadding()); + auto inner = outer.marginsRemoved(innerMargin()); + auto tshift = inner.top(); + auto paintw = inner.width(); auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); - if (asArticle()) { - bshift += bottomInfoPadding(); - } else if (!attachAdditionalInfoText.isEmpty()) { - bshift += bottomInfoPadding(); - } else if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { - bshift += bottomInfoPadding(); - } else if (_hasViewButton) { - bshift += bottomInfoPadding(); - } - QRect bar(style::rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); - p.fillRect(bar, barfg); + const auto selected = context.selected(); + const auto useColorIndex = context.outbg ? 0 : _colorIndexPlusOne; + const auto cache = useColorIndex + ? st->coloredReplyCache(selected, useColorIndex - 1).get() + : stm->replyCache.get(); + Ui::Text::ValidateQuotePaintCache(*cache, _st); + Ui::Text::FillQuotePaint(p, outer, *cache, _st); auto lineHeight = UnitedLineHeight(); if (asArticle()) { @@ -535,26 +509,28 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { } else if (const auto blurred = _photoMedia->thumbnailInline()) { pix = blurred->pixSingle(size, args.blurred()); } - p.drawPixmapLeft(padding.left() + paintw - pw, tshift, width(), pix); + p.drawPixmapLeft(inner.left() + paintw - pw, tshift, width(), pix); if (context.selected()) { const auto st = context.st; Ui::FillRoundRect( p, - style::rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()), + style::rtlrect(inner.left() + paintw - pw, tshift, pw, _pixh, width()), st->msgSelectOverlay(), st->msgSelectOverlayCorners(Ui::CachedCornerRadius::Small)); } paintw -= pw + st::webPagePhotoDelta; } if (_siteNameLines) { - p.setPen(semibold); - p.setTextPalette(stm->semiboldPalette); + p.setPen(cache->outline); + p.setTextPalette(useColorIndex + ? st->coloredTextPalette(selected, useColorIndex - 1) + : stm->semiboldPalette); auto endskip = 0; if (_siteName.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _siteName.drawLeftElided(p, padding.left(), tshift, paintw, width(), _siteNameLines, style::al_left, 0, -1, endskip, false, context.selection); + _siteName.drawLeftElided(p, inner.left(), tshift, paintw, width(), _siteNameLines, style::al_left, 0, -1, endskip, false, context.selection); tshift += lineHeight; p.setTextPalette(stm->textPalette); @@ -565,7 +541,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, toTitleSelection(context.selection)); + _title.drawLeftElided(p, inner.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, toTitleSelection(context.selection)); tshift += _titleLines * lineHeight; } if (_descriptionLines) { @@ -575,7 +551,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { } _parent->prepareCustomEmojiPaint(p, context, _description); _description.draw(p, { - .position = { padding.left(), tshift }, + .position = { inner.left(), tshift }, .outerWidth = width(), .availableWidth = paintw, .spoiler = Ui::Text::DefaultSpoilerCache(), @@ -596,7 +572,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { auto attachAtTop = !_siteNameLines && !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; - auto attachLeft = padding.left() - bubble.left(); + auto attachLeft = inner.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); @@ -641,7 +617,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { if (!attachAdditionalInfoText.isEmpty()) { p.setFont(st::msgDateFont); p.setPen(stm->msgDateFg); - p.drawTextLeft(st::msgPadding.left(), bar.y() + bar.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText); + p.drawTextLeft(st::msgPadding.left(), outer.y() + outer.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText); } } } @@ -656,22 +632,19 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } - auto paintw = width(); - - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - if (asArticle() || (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right())) { - bshift += bottomInfoPadding(); - } - paintw -= padding.left() + padding.right(); + const auto bubble = _attach ? _attach->bubbleMargins() : QMargins(); + const auto full = QRect(0, 0, width(), height()); + auto outer = full.marginsRemoved(inBubblePadding()); + auto inner = outer.marginsRemoved(innerMargin()); + auto tshift = inner.top(); + auto paintw = inner.width(); + auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); auto lineHeight = UnitedLineHeight(); auto inThumb = false; if (asArticle()) { auto pw = qMax(_pixw, lineHeight); - if (style::rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()).contains(point)) { + if (style::rtlrect(inner.left() + paintw - pw, tshift, pw, _pixh, width()).contains(point)) { inThumb = true; } paintw -= pw + st::webPagePhotoDelta; @@ -682,7 +655,7 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { Ui::Text::StateRequestElided siteNameRequest = request.forText(); siteNameRequest.lines = _siteNameLines; result = TextState(_parent, _siteName.getStateElidedLeft( - point - QPoint(padding.left(), tshift), + point - QPoint(inner.left(), tshift), paintw, width(), siteNameRequest)); @@ -696,7 +669,7 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { Ui::Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; result = TextState(_parent, _title.getStateElidedLeft( - point - QPoint(padding.left(), tshift), + point - QPoint(inner.left(), tshift), paintw, width(), titleRequest)); @@ -712,13 +685,13 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { Ui::Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; result = TextState(_parent, _description.getStateElidedLeft( - point - QPoint(padding.left(), tshift), + point - QPoint(inner.left(), tshift), paintw, width(), descriptionRequest)); } else { result = TextState(_parent, _description.getStateLeft( - point - QPoint(padding.left(), tshift), + point - QPoint(inner.left(), tshift), paintw, width(), request.forText())); @@ -734,14 +707,17 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { auto attachAtTop = !_siteNameLines && !_titleLines && !_descriptionLines; if (!attachAtTop) tshift += st::mediaInBubbleSkip; - if (QRect(padding.left(), tshift, paintw, height() - tshift - bshift).contains(point)) { - auto attachLeft = padding.left() - bubble.left(); + if (QRect(inner.left(), tshift, paintw, inner.top() + inner.height() - tshift).contains(point)) { + auto attachLeft = inner.left() - bubble.left(); auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = width() - attachLeft - _attach->width(); result = _attach->textState(point - QPoint(attachLeft, attachTop), request); result.link = replaceAttachLink(result.link); } } + if (!result.link && inner.contains(point)) { + result.link = _openl; + } result.symbol += symbolAdd; return result; @@ -859,11 +835,18 @@ TextForMimeData WebPage::selectedText(TextSelection selection) const { } QMargins WebPage::inBubblePadding() const { - auto lshift = st::msgPadding.left() + st::webPageLeft; - auto rshift = st::msgPadding.right(); - auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; - auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; - return QMargins(lshift, tshift, rshift, bshift); + return { + st::msgPadding.left(), + isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip, + st::msgPadding.right(), + (isBubbleBottom() + ? (st::msgPadding.left() + bottomInfoPadding()) + : st::mediaInBubbleSkip), + }; +} + +QMargins WebPage::innerMargin() const { + return _st.padding; } bool WebPage::isLogEntryOriginal() const { @@ -871,7 +854,9 @@ bool WebPage::isLogEntryOriginal() const { } int WebPage::bottomInfoPadding() const { - if (!isBubbleBottom()) return 0; + if (!isBubbleBottom()) { + return 0; + } auto result = st::msgDateFont->height; 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 86005d13ed..bbccbd9518 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -99,19 +99,26 @@ private: void ensurePhotoMediaCreated() const; - TextSelection toTitleSelection(TextSelection selection) const; - TextSelection fromTitleSelection(TextSelection selection) const; - TextSelection toDescriptionSelection(TextSelection selection) const; - TextSelection fromDescriptionSelection(TextSelection selection) const; - QMargins inBubblePadding() const; - int bottomInfoPadding() const; - bool isLogEntryOriginal() const; + [[nodiscard]] TextSelection toTitleSelection( + TextSelection selection) const; + [[nodiscard]] TextSelection fromTitleSelection( + TextSelection selection) const; + [[nodiscard]] TextSelection toDescriptionSelection( + TextSelection selection) const; + [[nodiscard]] TextSelection fromDescriptionSelection( + TextSelection selection) const; + [[nodiscard]] QMargins inBubblePadding() const; + [[nodiscard]] QMargins innerMargin() const; + [[nodiscard]] int bottomInfoPadding() const; + [[nodiscard]] bool isLogEntryOriginal() const; - QString displayedSiteName() const; - ClickHandlerPtr replaceAttachLink(const ClickHandlerPtr &link) const; - bool asArticle() const; + [[nodiscard]] QString displayedSiteName() const; + [[nodiscard]] ClickHandlerPtr replaceAttachLink( + const ClickHandlerPtr &link) const; + [[nodiscard]] bool asArticle() const; - not_null _data; + const style::QuoteStyle &_st; + const not_null _data; std::vector> _collage; ClickHandlerPtr _openl; std::unique_ptr _attach; @@ -121,8 +128,9 @@ private: bool _hasViewButton = false; int _dataVersion = -1; int _siteNameLines = 0; - int _titleLines = 0; int _descriptionLines = 0; + int _titleLines : 24 = 0; + int _colorIndexPlusOne : 8 = 0; Ui::Text::String _siteName, _title, _description; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 059b1dce41..0169149c04 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -66,6 +66,9 @@ messageTextStyle: TextStyle(defaultTextStyle) { iconPosition: point(4px, 2px); } } +historyPagePreview: QuoteStyle(messageQuoteStyle) { + padding: margins(10px, 5px, 7px, 7px); +} msgDateTextStyle: defaultTextStyle; serviceTextPalette: TextPalette(defaultTextPalette) { linkFg: msgServiceFg; @@ -563,8 +566,6 @@ historyPsaForwardPalette: TextPalette(defaultTextPalette) { linkFg: boxTextFgGood; } -webPageLeft: 10px; -webPageBar: 2px; webPageTitleFont: semiboldFont; webPageTitleStyle: semiboldTextStyle; webPageDescriptionFont: normalFont; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 777e84ec62..8e241d12f1 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -548,6 +548,10 @@ void ChatStyle::assignPalette(not_null palette) { = (stm.textPalette.linkFg->c == stm.historyTextFg->c); } + for (auto &palette : _coloredTextPalettes) { + palette.inited = false; + } + _paletteChanged.fire({}); } @@ -640,6 +644,26 @@ not_null ChatStyle::serviceReplyCache() const { return _serviceReplyCache.get(); } +const style::TextPalette &ChatStyle::coloredTextPalette( + bool selected, + uint8 colorIndex) const { + Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); + + const auto shift = (selected ? kColorIndexCount : 0); + auto &result = _coloredTextPalettes[shift + colorIndex]; + if (!result.inited) { + result.inited = true; + make( + result.data, + (selected + ? st::inReplyTextPaletteSelected + : st::inReplyTextPalette)); + result.data.linkFg = FromNameFg(this, selected, colorIndex); + result.data.selectLinkFg = result.data.linkFg; + } + return result.data; +} + not_null ChatStyle::coloredQuoteCache( bool selected, uint8 colorIndex) const { diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 4c9510e07c..8482d5936d 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -213,6 +213,10 @@ public: bool selected, uint8 colorIndex) const; + [[nodiscard]] const style::TextPalette &coloredTextPalette( + bool selected, + uint8 colorIndex) const; + [[nodiscard]] const CornersPixmaps &msgBotKbOverBgAddCornersSmall() const; [[nodiscard]] const CornersPixmaps &msgBotKbOverBgAddCornersLarge() const; [[nodiscard]] const CornersPixmaps &msgSelectOverlayCorners( @@ -301,6 +305,12 @@ private: using ColoredQuotePaintCaches = std::array< std::unique_ptr, kColorIndexCount * 2>; + + struct ColoredPalette { + style::TextPalette data; + bool inited = false; + }; + void assignPalette(not_null palette); [[nodiscard]] not_null coloredCache( @@ -362,6 +372,9 @@ private: mutable std::unique_ptr _serviceReplyCache; mutable ColoredQuotePaintCaches _coloredQuoteCaches; mutable ColoredQuotePaintCaches _coloredReplyCaches; + mutable std::array< + ColoredPalette, + 2 * kColorIndexCount> _coloredTextPalettes; style::TextPalette _historyPsaForwardPalette; style::TextPalette _imgReplyTextPalette; diff --git a/Telegram/SourceFiles/ui/text/text_options.cpp b/Telegram/SourceFiles/ui/text/text_options.cpp index 20e66737e0..3c9eb97a65 100644 --- a/Telegram/SourceFiles/ui/text/text_options.cpp +++ b/Telegram/SourceFiles/ui/text/text_options.cpp @@ -113,7 +113,8 @@ void InitTextOptions() { = WebpageDescriptionOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - - st::webPageLeft + - st::messageQuoteStyle.padding.left() + - st::messageQuoteStyle.padding.right() - st::msgPadding.right(); WebpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; } From 12fab565a43afe278fd90e69dc6483aa0486effb Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Oct 2023 09:08:31 +0400 Subject: [PATCH 43/99] Fix Release build with Xcode. --- Telegram/build/prepare/prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index ee3e77d9b0..d1c11bd1dd 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1421,7 +1421,7 @@ mac: stage('tg_owt', """ git clone https://github.com/desktop-app/tg_owt.git cd tg_owt - git checkout 592b14d13b + git checkout be153adaa3 git submodule init git submodule update win: From 16d18b437d04ae49957738d7950f39de182745df Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Oct 2023 11:29:42 +0400 Subject: [PATCH 44/99] Update API scheme on layer 166. --- Telegram/SourceFiles/data/data_peer.cpp | 22 ++- Telegram/SourceFiles/data/data_peer.h | 5 +- Telegram/SourceFiles/data/data_session.cpp | 4 +- .../export/data/export_data_types.cpp | 9 +- .../inline_bots/inline_bot_result.cpp | 6 + Telegram/SourceFiles/mtproto/scheme/api.tl | 43 +++--- .../window/window_session_controller.cpp | 146 +++++++++++------- 7 files changed, 149 insertions(+), 86 deletions(-) diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 66e693c13c..e58c941edd 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -158,7 +158,8 @@ void PeerClickHandler::onClick(ClickContext context) const { PeerData::PeerData(not_null owner, PeerId id) : id(id) -, _owner(owner) { +, _owner(owner) +, _colorIndex(Data::DecideColorIndex(id)) { } Data::Session &PeerData::owner() const { @@ -619,6 +620,13 @@ void PeerData::setSettings(const MTPPeerSettings &data) { }); } +bool PeerData::changeColorIndex( + const tl::conditional &cloudColorIndex) { + return cloudColorIndex + ? changeColorIndex(cloudColorIndex->v) + : clearColorIndex(); +} + void PeerData::fillNames() { _nameWords.clear(); _nameFirstLetters.clear(); @@ -837,13 +845,23 @@ QString PeerData::userName() const { bool PeerData::changeColorIndex(uint8 index) { index %= Ui::kColorIndexCount; - if (_colorIndex == index) { + if (_colorIndexCloud && _colorIndex == index) { return false; } + _colorIndexCloud = true; _colorIndex = index; return true; } +bool PeerData::clearColorIndex() { + if (!_colorIndexCloud) { + return false; + } + _colorIndexCloud = false; + _colorIndex = Data::DecideColorIndex(id); + return true; +} + bool PeerData::isSelf() const { if (const auto user = asUser()) { return (user->flags() & UserDataFlag::Self); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index b793ff381d..643dae0417 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -168,6 +168,7 @@ public: return _colorIndex; } bool changeColorIndex(uint8 index); + bool clearColorIndex(); [[nodiscard]] bool isUser() const { return peerIsUser(id); @@ -358,6 +359,7 @@ public: void saveTranslationDisabled(bool disabled); void setSettings(const MTPPeerSettings &data); + bool changeColorIndex(const tl::conditional &cloudColorIndex); enum class BlockStatus : char { Unknown, @@ -464,7 +466,8 @@ private: BlockStatus _blockStatus = BlockStatus::Unknown; LoadedStatus _loadedStatus = LoadedStatus::Not; TranslationFlag _translationFlag = TranslationFlag::Unknown; - uint8 _colorIndex : 7 = 0; + uint8 _colorIndex : 6 = 0; + uint8 _colorIndexCloud : 1 = 0; uint8 _userpicHasVideo : 1 = 0; QString _about; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 26c019671d..fe6ed6b566 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -705,7 +705,7 @@ not_null Session::processUser(const MTPUser &data) { if (canShareThisContact != result->canShareThisContactFast()) { flags |= UpdateFlag::CanShareContact; } - if (result->changeColorIndex(uint8(data.vcolor().v))) { + if (result->changeColorIndex(data.vcolor())) { flags |= UpdateFlag::Color; } }); @@ -982,7 +982,7 @@ not_null Session::processChat(const MTPChat &data) { if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) { flags |= UpdateFlag::GroupCall; } - if (result->changeColorIndex(uint8(data.vcolor().v))) { + if (result->changeColorIndex(data.vcolor())) { flags |= UpdateFlag::Color; } }, [&](const MTPDchannelForbidden &data) { diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index a0d07ba974..bd7203cdb4 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -766,7 +766,8 @@ ContactInfo ParseContactInfo(const MTPUser &data) { auto result = ContactInfo(); data.match([&](const MTPDuser &data) { result.userId = data.vid().v; - result.colorIndex = data.vcolor().v; + result.colorIndex = data.vcolor().value_or( + PeerColorIndex(result.userId)); if (const auto firstName = data.vfirst_name()) { result.firstName = ParseString(*firstName); } @@ -796,7 +797,8 @@ User ParseUser(const MTPUser &data) { result.info = ParseContactInfo(data); data.match([&](const MTPDuser &data) { result.bareId = data.vid().v; - result.colorIndex = data.vcolor().v; + result.colorIndex = data.vcolor().value_or( + PeerColorIndex(result.bareId)); if (const auto username = data.vusername()) { result.username = ParseString(*username); } @@ -851,7 +853,8 @@ Chat ParseChat(const MTPChat &data) { result.input = MTP_inputPeerChat(MTP_long(result.bareId)); }, [&](const MTPDchannel &data) { result.bareId = data.vid().v; - result.colorIndex = data.vcolor().v; + result.colorIndex = data.vcolor().value_or( + PeerColorIndex(result.bareId)); result.isBroadcast = data.is_broadcast(); result.isSupergroup = data.is_megagroup(); result.title = ParseString(data.vtitle()); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp index 2c10d887cb..100e3efdf9 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -262,6 +262,12 @@ std::unique_ptr Result::Create( result->sendData = std::make_unique( session, media); + }, [&](const MTPDbotInlineMessageMediaWebPage &data) { + result->sendData = std::make_unique( + session, + qs(data.vmessage()), + Api::EntitiesFromMTP(session, data.ventities().value_or_empty()), + false); }); if (!result->sendData || !result->sendData->isValid()) { diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index f7a7489a4e..cc4e42ade4 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -82,7 +82,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#6fdee0df flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:int background_emoji_id:flags2.6?long = User; +user#eb602f25 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.7?int background_emoji_id:flags2.6?long = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -97,7 +97,7 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#2458af8c flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:int background_emoji_id:flags2.5?long = Chat; +channel#1981ea7e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:flags2.6?int background_emoji_id:flags2.5?long = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; @@ -123,7 +123,7 @@ messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; messageMediaDocument#4cf4d72d flags:# nopremium:flags.3?true spoiler:flags.4?true document:flags.0?Document alt_document:flags.5?Document ttl_seconds:flags.2?int = MessageMedia; -messageMediaWebPage#ddf10c3b flags:# force_large_media:flags.0?true force_small_media:flags.1?true manual:flags.3?true webpage:WebPage = MessageMedia; +messageMediaWebPage#ddf10c3b flags:# force_large_media:flags.0?true force_small_media:flags.1?true manual:flags.3?true safe:flags.4?true webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string extended_media:flags.4?MessageExtendedMedia = MessageMedia; @@ -575,7 +575,7 @@ chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true r chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; -chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; +chatInvite#3033e855 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector color:flags.10?int = ChatInvite; chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; @@ -685,25 +685,27 @@ help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; messages.savedGifs#84a02a0d hash:long gifs:Vector = messages.SavedGifs; -inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaAuto#3380c786 flags:# invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaInvoice#d7e78225 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaWebPage#bddcc510 flags:# invert_media:flags.3?true force_large_media:flags.4?true force_small_media:flags.5?true optional:flags.6?true message:string entities:flags.1?Vector url:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult; -botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaAuto#764cf810 flags:# invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument currency:string total_amount:long reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaWebPage#809ad9a6 flags:# invert_media:flags.3?true force_large_media:flags.4?true force_small_media:flags.5?true manual:flags.7?true message:string entities:flags.1?Vector url:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult; botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult; @@ -1573,15 +1575,6 @@ peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector users:Vector = stories.PeerStories; -stories.boostsStatus#71f2770d flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue boost_url:string prepaid_giveaways:flags.3?Vector = stories.BoostsStatus; - -stories.canApplyBoostOk#c3173587 = stories.CanApplyBoostResult; -stories.canApplyBoostReplace#712c4655 current_boost:Peer chats:Vector = stories.CanApplyBoostResult; - -booster#e9e6380 user_id:long expires:int = Booster; - -stories.boostersList#f3dd3d1d flags:# count:int boosters:Vector next_offset:flags.0?string users:Vector = stories.BoostersList; - messages.webPage#fd5e12bd webpage:WebPage chats:Vector users:Vector = messages.WebPage; premiumGiftCodeOption#257e962b flags:# users:int months:int store_product:flags.0?string store_quantity:flags.1?int currency:string amount:long = PremiumGiftCodeOption; @@ -1593,6 +1586,16 @@ payments.giveawayInfoResults#cd5570 flags:# winner:flags.0?true refunded:flags.1 prepaidGiveaway#b2539d54 id:long months:int quantity:int date:int = PrepaidGiveaway; +boost#53c300c8 flags:# gift:flags.1?true giveaway:flags.2?true unclaimed:flags.3?true id:string user_id:flags.0?long giveaway_msg_id:flags.2?int date:int expires:int used_gift_slug:flags.4?string = Boost; + +premium.boostsList#86f8613c flags:# count:int boosts:Vector next_offset:flags.0?string users:Vector = premium.BoostsList; + +myBoost#c448415c flags:# slot:int peer:flags.0?Peer date:int expires:int cooldown_until_date:flags.1?int = MyBoost; + +premium.myBoosts#9ae228e2 my_boosts:Vector chats:Vector users:Vector = premium.MyBoosts; + +premium.boostsStatus#3d5daae6 flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int gift_boosts:flags.3?int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue boost_url:string prepaid_giveaways:flags.3?Vector my_boost_slots:flags.2?Vector = premium.BoostsStatus; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1715,6 +1718,7 @@ account.saveAutoSaveSettings#d69b8361 flags:# users:flags.0?true chats:flags.1?t account.deleteAutoSaveExceptions#53bc0020 = Bool; account.invalidateSignInCodes#ca8ae8ba codes:Vector = Bool; account.updateColor#a001cc43 flags:# color:int background_emoji_id:flags.0?long = Bool; +account.getDefaultBackgroundEmojis#a60ab9ce hash:long = EmojiList; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -2161,7 +2165,8 @@ stories.getAllReadPeerStories#9b5ae7f9 = Updates; stories.getPeerMaxIDs#535983c3 id:Vector = Vector; stories.getChatsToSend#a56a8b60 = messages.Chats; stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; -stories.getBoostsStatus#4c449472 peer:InputPeer = stories.BoostsStatus; -stories.getBoostersList#337ef980 peer:InputPeer offset:string limit:int = stories.BoostersList; -stories.canApplyBoost#db05c1bd peer:InputPeer = stories.CanApplyBoostResult; -stories.applyBoost#f29d7c2b peer:InputPeer = Bool; + +premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; +premium.getMyBoosts#be77b4a = premium.MyBoosts; +premium.applyBoost#46b6a16b flags:# slot:flags.0?int peer:InputPeer = Bool; +premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index e17c9b28fb..fae0ec3add 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -625,9 +625,9 @@ void SessionNavigation::resolveBoostState(not_null channel) { return; } _boostStateResolving = channel; - _api.request(MTPstories_GetBoostsStatus( + _api.request(MTPpremium_GetBoostsStatus( channel->input - )).done([=](const MTPstories_BoostsStatus &result) { + )).done([=](const MTPpremium_BoostsStatus &result) { _boostStateResolving = nullptr; const auto &data = result.data(); const auto submit = [=](Fn done) { @@ -653,69 +653,95 @@ void SessionNavigation::resolveBoostState(not_null channel) { void SessionNavigation::applyBoost( not_null channel, Fn done) { - _api.request(MTPstories_CanApplyBoost( - channel->input - )).done([=](const MTPstories_CanApplyBoostResult &result) { - result.match([&](const MTPDstories_canApplyBoostOk &) { - applyBoostChecked(channel, done); - }, [&](const MTPDstories_canApplyBoostReplace &data) { - _session->data().processChats(data.vchats()); - const auto peer = _session->data().peer( - peerFromMTP(data.vcurrent_boost())); - replaceBoostConfirm(peer, channel, done); - }); - }).fail([=](const MTP::Error &error) { - const auto type = error.type(); - if (type == u"PREMIUM_ACCOUNT_REQUIRED"_q) { - const auto jumpToPremium = [=] { - const auto id = peerToChannel(channel->id).bare; - Settings::ShowPremium( - parentController(), - "channel_boost__" + QString::number(id)); - }; - uiShow()->show(Ui::MakeConfirmBox({ - .text = tr::lng_boost_error_premium_text( - Ui::Text::RichLangValue), - .confirmed = jumpToPremium, - .confirmText = tr::lng_boost_error_premium_yes(), - .title = tr::lng_boost_error_premium_title(), - })); - } else if (type == u"PREMIUM_GIFTED_NOT_ALLOWED"_q) { - uiShow()->show(Ui::MakeConfirmBox({ - .text = tr::lng_boost_error_gifted_text( - Ui::Text::RichLangValue), - .title = tr::lng_boost_error_gifted_title(), - .inform = true, - })); - } else if (type == u"BOOST_NOT_MODIFIED"_q) { + _api.request(MTPpremium_GetMyBoosts( + )).done([=](const MTPpremium_MyBoosts &result) { + const auto &data = result.data(); + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + const auto &list = data.vmy_boosts().v; + if (list.isEmpty()) { + if (!_session->premium()) { + const auto jumpToPremium = [=] { + const auto id = peerToChannel(channel->id).bare; + Settings::ShowPremium( + parentController(), + "channel_boost__" + QString::number(id)); + }; + uiShow()->show(Ui::MakeConfirmBox({ + .text = tr::lng_boost_error_premium_text( + Ui::Text::RichLangValue), + .confirmed = jumpToPremium, + .confirmText = tr::lng_boost_error_premium_yes(), + .title = tr::lng_boost_error_premium_title(), + })); + } else { + uiShow()->show(Ui::MakeConfirmBox({ + .text = tr::lng_boost_error_gifted_text( + Ui::Text::RichLangValue), + .title = tr::lng_boost_error_gifted_title(), + .inform = true, + })); + } + done(false); + return; + } + auto already = false; + auto different = PeerId(); + auto earliest = TimeId(-1); + const auto now = base::unixtime::now(); + for (const auto &my : list) { + const auto &data = my.data(); + const auto cooldown = data.vcooldown_until_date().value_or(0); + const auto peerId = data.vpeer() + ? peerFromMTP(*data.vpeer()) + : PeerId(); + if (!peerId && cooldown <= now) { + applyBoostChecked(channel, done); + return; + } else if (peerId != channel->id) { + different = peerId; + if (earliest < 0 || cooldown < earliest) { + earliest = cooldown; + } + } + } + if (different) { + if (earliest > now) { + const auto seconds = earliest - now; + const auto days = seconds / 86400; + const auto hours = seconds / 3600; + const auto minutes = seconds / 60; + uiShow()->show(Ui::MakeConfirmBox({ + .text = tr::lng_boost_error_flood_text( + lt_left, + rpl::single(Ui::Text::Bold((days > 1) + ? tr::lng_days(tr::now, lt_count, days) + : (hours > 1) + ? tr::lng_hours(tr::now, lt_count, hours) + : (minutes > 1) + ? tr::lng_minutes(tr::now, lt_count, minutes) + : tr::lng_seconds(tr::now, lt_count, seconds))), + Ui::Text::RichLangValue), + .title = tr::lng_boost_error_flood_title(), + .inform = true, + })); + done(false); + } else { + const auto peer = _session->data().peer(different); + replaceBoostConfirm(peer, channel, done); + } + } else { uiShow()->show(Ui::MakeConfirmBox({ .text = tr::lng_boost_error_already_text( Ui::Text::RichLangValue), .title = tr::lng_boost_error_already_title(), .inform = true, })); - } else if (type.startsWith(u"FLOOD_WAIT_"_q)) { - const auto seconds = type.mid(u"FLOOD_WAIT_"_q.size()).toInt(); - const auto days = seconds / 86400; - const auto hours = seconds / 3600; - const auto minutes = seconds / 60; - uiShow()->show(Ui::MakeConfirmBox({ - .text = tr::lng_boost_error_flood_text( - lt_left, - rpl::single(Ui::Text::Bold((days > 1) - ? tr::lng_days(tr::now, lt_count, days) - : (hours > 1) - ? tr::lng_hours(tr::now, lt_count, hours) - : (minutes > 1) - ? tr::lng_minutes(tr::now, lt_count, minutes) - : tr::lng_seconds(tr::now, lt_count, seconds))), - Ui::Text::RichLangValue), - .title = tr::lng_boost_error_flood_title(), - .inform = true, - })); - } else { - showToast(u"Error: "_q + type); + done(false); } + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + showToast(u"Error: "_q + type); done(false); }).handleFloodErrors().send(); } @@ -757,7 +783,9 @@ void SessionNavigation::replaceBoostConfirm( void SessionNavigation::applyBoostChecked( not_null channel, Fn done) { - _api.request(MTPstories_ApplyBoost( + _api.request(MTPpremium_ApplyBoost( + MTP_flags(0), + MTPint(), // slot channel->input )).done([=](const MTPBool &result) { done(true); From 3a84c6afdd891e80b02b90b35a492517e293a994 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Oct 2023 13:57:03 +0400 Subject: [PATCH 45/99] Redesign webpage/giveaway/ads bottom button. --- Telegram/SourceFiles/history/history_item.cpp | 12 +- Telegram/SourceFiles/history/history_item.h | 1 + .../history/history_item_components.cpp | 36 +-- .../history/history_item_components.h | 5 +- .../history/view/history_view_message.cpp | 13 +- .../history/view/history_view_view_button.cpp | 206 ++++++------------ .../history/view/history_view_view_button.h | 8 +- .../history/view/media/history_view_game.cpp | 35 +-- .../history/view/media/history_view_game.h | 4 +- .../view/media/history_view_web_page.cpp | 170 ++++++++++++--- .../view/media/history_view_web_page.h | 39 ++-- Telegram/SourceFiles/ui/chat/chat.style | 8 +- 12 files changed, 315 insertions(+), 222 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 55ff76fa2a..2f020b2e08 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1126,6 +1126,15 @@ PeerData *HistoryItem::displayFrom() const { return author().get(); } +uint8 HistoryItem::computeColorIndex() const { + if (const auto from = displayFrom()) { + return from->colorIndex(); + } else if (const auto info = hiddenSenderInfo()) { + return info->colorIndex; + } + Unexpected("No displayFrom and no hiddenSenderInfo."); +} + std::unique_ptr HistoryItem::createView( not_null delegate, HistoryView::Element *replacing) { @@ -3253,7 +3262,8 @@ void HistoryItem::setSponsoredFrom(const Data::SponsoredFrom &from) { const auto sponsored = Get(); sponsored->sender = std::make_unique( from.title, - false); + false, + from.peer ? from.peer->colorIndex() : std::optional()); sponsored->recommended = from.isRecommended; sponsored->isForceUserpicDisplay = from.isForceUserpicDisplay; if (from.userpic.location.valid()) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index c04e6f9de0..24f6bc510f 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -498,6 +498,7 @@ public: [[nodiscard]] bool isDiscussionPost() const; [[nodiscard]] HistoryItem *lookupDiscussionPostOriginal() const; [[nodiscard]] PeerData *displayFrom() const; + [[nodiscard]] uint8 computeColorIndex() const; [[nodiscard]] std::unique_ptr createView( not_null delegate, diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 71163b4e9d..9968347b4c 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -108,11 +108,15 @@ void HistoryMessageVia::resize(int32 availw) const { } } -HiddenSenderInfo::HiddenSenderInfo(const QString &name, bool external) +HiddenSenderInfo::HiddenSenderInfo( + const QString &name, + bool external, + std::optional colorIndex) : name(name) -, colorIndex(Data::DecideColorIndex(Data::FakePeerIdForJustName(name))) +, colorIndex(colorIndex.value_or( + Data::DecideColorIndex(Data::FakePeerIdForJustName(name)))) , emptyUserpic( - Ui::EmptyUserpic::UserpicColor(colorIndex), + Ui::EmptyUserpic::UserpicColor(this->colorIndex), (external ? Ui::EmptyUserpic::ExternalName() : name)) { @@ -399,13 +403,10 @@ bool HistoryMessageReply::updateData( } if (resolvedMessage) { - const auto peer = resolvedMessage->history()->peer; - _colorIndexPlusOne = (!holder->out() - && (peer->isMegagroup() || peer->isChat()) - && resolvedMessage->from()->isUser()) - ? (resolvedMessage->from()->colorIndex() + 1) - : uint8(); - } else if (!resolvedStory) { + _colorIndexPlusOne = resolvedMessage->computeColorIndex() + 1; + } else if (resolvedStory) { + _colorIndexPlusOne = resolvedStory->peer()->colorIndex() + 1; + } else { _unavailable = 1; } @@ -680,14 +681,15 @@ void HistoryMessageReply::paint( const auto rect = QRect(x, y, w, _height); const auto hasQuote = !_fields.quote.empty(); const auto selected = context.selected(); + const auto colorIndexPlusOne = context.outbg ? 0 : _colorIndexPlusOne; const auto cache = !inBubble ? (hasQuote ? st->serviceQuoteCache() : st->serviceReplyCache()).get() - : _colorIndexPlusOne + : colorIndexPlusOne ? (hasQuote - ? st->coloredQuoteCache(selected, _colorIndexPlusOne - 1) - : st->coloredReplyCache(selected, _colorIndexPlusOne - 1)).get() + ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) + : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() : (hasQuote ? stm->quoteCache : stm->replyCache).get(); const auto "eSt = hasQuote ? st::messageTextStyle.blockquote @@ -762,10 +764,10 @@ void HistoryMessageReply::paint( w -= textLeft + st::historyReplyPadding.right(); p.setPen(!inBubble ? st->msgImgReplyBarColor() - : _colorIndexPlusOne + : colorIndexPlusOne ? HistoryView::FromNameFg( context, - _colorIndexPlusOne - 1) + colorIndexPlusOne - 1) : stm->msgServiceFg); _name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); if (originalVia && w > _name.maxWidth() + st::msgServiceFont->spacew) { @@ -782,8 +784,8 @@ void HistoryMessageReply::paint( y + st::historyReplyPadding.top() + st::msgServiceNameFont->height); auto replyToTextPalette = &(!inBubble ? st->imgReplyTextPalette() - : _colorIndexPlusOne - ? st->coloredTextPalette(selected, _colorIndexPlusOne - 1) + : colorIndexPlusOne + ? st->coloredTextPalette(selected, colorIndexPlusOne - 1) : stm->replyTextPalette); if (_fields.storyId) { st::dialogsMiniReplyStory.icon.icon.paint( diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index c632b5b070..507fa46da8 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -85,7 +85,10 @@ struct HistoryMessageEdited : public RuntimeComponent colorIndex = {}); QString name; QString firstName; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 384c8bae63..f9a82fe0af 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1330,12 +1330,9 @@ void Message::paintFromName( const auto from = item->displayFrom(); const auto info = from ? nullptr : item->hiddenSenderInfo(); Assert(from || info); - const auto service = (context.outbg || item->isPost()); const auto st = context.st; - const auto nameFg = !service + const auto nameFg = !context.outbg ? FromNameFg(context, from ? from->colorIndex() : info->colorIndex) - : item->isSponsored() - ? st->boxTextFgGood() : stm->msgServiceFg; const auto nameText = [&] { if (from) { @@ -3021,9 +3018,13 @@ void Message::updateViewButtonExistence() { return; } auto repainter = [=] { repaint(); }; + const auto index = item->computeColorIndex(); _viewButton = sponsored - ? std::make_unique(sponsored, std::move(repainter)) - : std::make_unique(media, std::move(repainter)); + ? std::make_unique( + sponsored, + index, + std::move(repainter)) + : std::make_unique(media, index, std::move(repainter)); } void Message::initLogEntryOriginal() { diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index 0ba4f8f2c4..448ec0624d 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -52,85 +52,31 @@ inline auto SponsoredPhrase(SponsoredType type) { return Ui::Text::Upper(phrase(tr::now)); } -inline auto WebPageToPhrase(not_null webpage) { - const auto type = webpage->type; - return Ui::Text::Upper((type == WebPageType::Theme) - ? tr::lng_view_button_theme(tr::now) - : (type == WebPageType::Story) - ? tr::lng_view_button_story(tr::now) - : (type == WebPageType::Message) - ? tr::lng_view_button_message(tr::now) - : (type == WebPageType::Group) - ? tr::lng_view_button_group(tr::now) - : (type == WebPageType::WallPaper) - ? tr::lng_view_button_background(tr::now) - : (type == WebPageType::Channel) - ? tr::lng_view_button_channel(tr::now) - : (type == WebPageType::GroupWithRequest - || type == WebPageType::ChannelWithRequest) - ? tr::lng_view_button_request_join(tr::now) - : (type == WebPageType::ChannelBoost) - ? tr::lng_view_button_boost(tr::now) - : (type == WebPageType::VoiceChat) - ? tr::lng_view_button_voice_chat(tr::now) - : (type == WebPageType::Livestream) - ? tr::lng_view_button_voice_chat_channel(tr::now) - : (type == WebPageType::Bot) - ? tr::lng_view_button_bot(tr::now) - : (type == WebPageType::User) - ? tr::lng_view_button_user(tr::now) - : (type == WebPageType::BotApp) - ? tr::lng_view_button_bot_app(tr::now) - : QString()); -} - [[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler( not_null media) { - if (const auto giveaway = media->giveaway()) { - const auto peer = media->parent()->history()->peer; - const auto messageId = media->parent()->id; - if (media->parent()->isSending() || media->parent()->hasFailed()) { - return nullptr; - } - const auto info = *giveaway; - return std::make_shared([=]( - ClickContext context) { - const auto my = context.other.value(); - const auto controller = my.sessionWindow.get(); - if (!controller) { - return; - } - ResolveGiveawayInfo(controller, peer, messageId, info); - }); + const auto giveaway = media->giveaway(); + Assert(giveaway != nullptr); + const auto peer = media->parent()->history()->peer; + const auto messageId = media->parent()->id; + if (media->parent()->isSending() || media->parent()->hasFailed()) { + return nullptr; } - const auto webpage = media->webpage(); - Assert(webpage != nullptr); - - const auto url = webpage->url; - const auto type = webpage->type; - return std::make_shared([=](ClickContext context) { + const auto info = *giveaway; + return std::make_shared([=]( + ClickContext context) { const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { - if (type == WebPageType::BotApp) { - // Bot Web Apps always show confirmation on hidden urls. - // - // But from the dedicated "Open App" button we don't want - // to request users confirmation on non-first app opening. - UrlClickHandler::Open(url, context.other); - } else { - HiddenUrlClickHandler::Open(url, context.other); - } + const auto controller = my.sessionWindow.get(); + if (!controller) { + return; } + ResolveGiveawayInfo(controller, peer, messageId, info); }); } [[nodiscard]] QString MakeMediaButtonText(not_null media) { - if (const auto giveaway = media->giveaway()) { - return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now)); - } - const auto webpage = media->webpage(); - Assert(webpage != nullptr); - return WebPageToPhrase(webpage); + const auto giveaway = media->giveaway(); + Assert(giveaway != nullptr); + return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now)); } [[nodiscard]] ClickHandlerPtr SponsoredLink( @@ -178,8 +124,12 @@ inline auto WebPageToPhrase(not_null webpage) { struct ViewButton::Inner { Inner( not_null sponsored, + uint8 colorIndex, + Fn updateCallback); + Inner( + not_null media, + uint8 colorIndex, Fn updateCallback); - Inner(not_null media, Fn updateCallback); void updateMask(int height); void toggleRipple(bool pressed); @@ -187,59 +137,40 @@ struct ViewButton::Inner { const style::margins &margins; const ClickHandlerPtr link; const Fn updateCallback; - bool belowInfo = true; - bool externalLink = false; - int lastWidth = 0; + uint32 lastWidth : 24 = 0; + uint32 colorIndex : 6 = 0; + uint32 aboveInfo : 1 = 0; + uint32 externalLink : 1 = 0; QPoint lastPoint; std::unique_ptr ripple; Ui::Text::String text; }; bool ViewButton::MediaHasViewButton(not_null media) { - return media->webpage() - ? MediaHasViewButton(media->webpage()) - : (media->giveaway() != nullptr); -} - -bool ViewButton::MediaHasViewButton( - not_null webpage) { - const auto type = webpage->type; - return (type == WebPageType::Message) - || (type == WebPageType::Group) - || (type == WebPageType::Channel) - || (type == WebPageType::ChannelBoost) - // || (type == WebPageType::Bot) - || (type == WebPageType::User) - || (type == WebPageType::VoiceChat) - || (type == WebPageType::Livestream) - || (type == WebPageType::BotApp) - || ((type == WebPageType::Theme) - && webpage->document - && webpage->document->isTheme()) - || ((type == WebPageType::Story) - && (webpage->photo || webpage->document)) - || ((type == WebPageType::WallPaper) - && webpage->document - && webpage->document->isWallPaper()); + return (media->giveaway() != nullptr); } ViewButton::Inner::Inner( not_null sponsored, + uint8 colorIndex, Fn updateCallback) : margins(st::historyViewButtonMargins) , link(SponsoredLink(sponsored)) , updateCallback(std::move(updateCallback)) -, externalLink(sponsored->type == SponsoredType::ExternalLink) +, colorIndex(colorIndex) +, externalLink((sponsored->type == SponsoredType::ExternalLink) ? 1 : 0) , text(st::historyViewButtonTextStyle, SponsoredPhrase(sponsored->type)) { } ViewButton::Inner::Inner( not_null media, + uint8 colorIndex, Fn updateCallback) : margins(st::historyViewButtonMargins) , link(MakeMediaButtonClickHandler(media)) , updateCallback(std::move(updateCallback)) -, belowInfo(false) +, colorIndex(colorIndex) +, aboveInfo(1) , text(st::historyViewButtonTextStyle, MakeMediaButtonText(media)) { } @@ -264,14 +195,22 @@ void ViewButton::Inner::toggleRipple(bool pressed) { ViewButton::ViewButton( not_null sponsored, + uint8 colorIndex, Fn updateCallback) -: _inner(std::make_unique(sponsored, std::move(updateCallback))) { +: _inner(std::make_unique( + sponsored, + colorIndex, + std::move(updateCallback))) { } ViewButton::ViewButton( not_null media, + uint8 colorIndex, Fn updateCallback) -: _inner(std::make_unique(media, std::move(updateCallback))) { +: _inner(std::make_unique( + media, + colorIndex, + std::move(updateCallback))) { } ViewButton::~ViewButton() { @@ -286,7 +225,7 @@ int ViewButton::height() const { } bool ViewButton::belowMessageInfo() const { - return _inner->belowInfo; + return !_inner->aboveInfo; } void ViewButton::draw( @@ -295,45 +234,40 @@ void ViewButton::draw( const Ui::ChatPaintContext &context) { const auto stm = context.messageStyle(); + const auto selected = context.selected(); + const auto cache = context.outbg + ? stm->replyCache.get() + : context.st->coloredReplyCache(selected, _inner->colorIndex).get(); + const auto radius = st::historyPagePreview.radius; + if (_inner->ripple && !_inner->ripple->empty()) { - const auto opacity = p.opacity(); - p.setOpacity(st::historyPollRippleOpacity); - const auto colorOverride = &stm->msgWaveformInactive->c; - _inner->ripple->paint(p, r.left(), r.top(), r.width(), colorOverride); - p.setOpacity(opacity); + _inner->ripple->paint(p, r.left(), r.top(), r.width(), &cache->bg); } - p.save(); - { - PainterHighQualityEnabler hq(p); - auto pen = stm->fwdTextPalette.linkFg->p; - pen.setWidth(st::lineWidth); - p.setPen(pen); - p.setBrush(Qt::NoBrush); - const auto half = st::lineWidth / 2.; - const auto rf = QRectF(r).marginsRemoved({ half, half, half, half }); - p.drawRoundedRect(rf, st::roundRadiusLarge, st::roundRadiusLarge); + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(cache->bg); + p.drawRoundedRect(r, radius, radius); - _inner->text.drawElided( + p.setPen(cache->outline); + _inner->text.drawElided( + p, + r.left(), + r.top() + (r.height() - _inner->text.minHeight()) / 2, + r.width(), + 1, + style::al_top); + + if (_inner->externalLink) { + const auto &icon = st::msgBotKbUrlIcon; + const auto padding = st::msgBotKbIconPadding; + icon.paint( p, - r.left(), - r.top() + (r.height() - _inner->text.minHeight()) / 2, + r.left() + r.width() - icon.width() - padding, + r.top() + padding, r.width(), - 1, - style::al_center); - - if (_inner->externalLink) { - const auto &icon = st::msgBotKbUrlIcon; - const auto padding = st::msgBotKbIconPadding; - icon.paint( - p, - r.left() + r.width() - icon.width() - padding, - r.top() + padding, - r.width(), - stm->fwdTextPalette.linkFg->c); - } + cache->outline); } - p.restore(); if (_inner->lastWidth != r.width()) { _inner->lastWidth = r.width(); resized(); diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.h b/Telegram/SourceFiles/history/view/history_view_view_button.h index d9b45b7f84..262f00dc0c 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.h +++ b/Telegram/SourceFiles/history/view/history_view_view_button.h @@ -25,14 +25,16 @@ class ViewButton { public: ViewButton( not_null sponsored, + uint8 colorIndex, + Fn updateCallback); + ViewButton( + not_null media, + uint8 colorIndex, Fn updateCallback); - ViewButton(not_null media, Fn updateCallback); ~ViewButton(); [[nodiscard]] static bool MediaHasViewButton( not_null media); - [[nodiscard]] static bool MediaHasViewButton( - not_null webpage); [[nodiscard]] int height() const; [[nodiscard]] bool belowMessageInfo() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 8713011c0e..80204eb5d0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -34,6 +34,7 @@ Game::Game( : Media(parent) , _st(st::historyPagePreview) , _data(data) +, _colorIndex(parent->data()->computeColorIndex()) , _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { if (!consumed.text.isEmpty()) { @@ -48,13 +49,6 @@ Game::Game( context); } history()->owner().registerGameView(_data, _parent); - - const auto from = parent->data()->displayFrom(); - const auto info = from ? nullptr : parent->data()->hiddenSenderInfo(); - Assert(from || info); - _colorIndexPlusOne = !parent->data()->isPost() - ? ((from ? from->colorIndex() : info->colorIndex) + 1) - : 0; } QSize Game::countOptimalSize() { @@ -226,25 +220,36 @@ void Game::draw(Painter &p, const PaintContext &context) const { auto paintw = inner.width(); const auto selected = context.selected(); - const auto useColorIndex = context.outbg ? 0 : _colorIndexPlusOne; - const auto cache = useColorIndex - ? st->coloredReplyCache(selected, useColorIndex - 1).get() - : stm->replyCache.get(); + const auto cache = context.outbg + ? stm->replyCache.get() + : st->coloredReplyCache(selected, _colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); auto lineHeight = UnitedLineHeight(); if (_titleLines) { p.setPen(cache->outline); - p.setTextPalette(useColorIndex - ? st->coloredTextPalette(selected, useColorIndex - 1) - : stm->semiboldPalette); + p.setTextPalette(context.outbg + ? stm->semiboldPalette + : st->coloredTextPalette(selected, _colorIndex)); auto endskip = 0; if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, inner.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, context.selection); + _title.drawLeftElided( + p, + inner.left(), + tshift, + paintw, + width(), + _titleLines, + style::al_left, + 0, + -1, + endskip, + false, + context.selection); tshift += _titleLines * lineHeight; p.setTextPalette(stm->textPalette); diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.h b/Telegram/SourceFiles/history/view/media/history_view_game.h index 4cd5c613fd..d942064fd2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.h +++ b/Telegram/SourceFiles/history/view/media/history_view_game.h @@ -105,8 +105,8 @@ private: int _gameTagWidth = 0; int _descriptionLines = 0; - int _titleLines : 24 = 0; - int _colorIndexPlusOne : 8 = 0; + uint32 _titleLines : 24 = 0; + uint32 _colorIndex : 8 = 0; Ui::Text::String _title; Ui::Text::String _description; 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 799fe826ab..268d216bcd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/chat/chat_style.h" #include "ui/cached_round_corners.h" +#include "ui/effects/ripple_animation.h" #include "ui/painter.h" #include "ui/power_saving.h" #include "data/data_session.h" @@ -84,6 +85,38 @@ std::vector> PrepareCollageMedia( return result; } +[[nodiscard]] QString PageToPhrase(not_null webpage) { + const auto type = webpage->type; + return Ui::Text::Upper((type == WebPageType::Theme) + ? tr::lng_view_button_theme(tr::now) + : (type == WebPageType::Story) + ? tr::lng_view_button_story(tr::now) + : (type == WebPageType::Message) + ? tr::lng_view_button_message(tr::now) + : (type == WebPageType::Group) + ? tr::lng_view_button_group(tr::now) + : (type == WebPageType::WallPaper) + ? tr::lng_view_button_background(tr::now) + : (type == WebPageType::Channel) + ? tr::lng_view_button_channel(tr::now) + : (type == WebPageType::GroupWithRequest + || type == WebPageType::ChannelWithRequest) + ? tr::lng_view_button_request_join(tr::now) + : (type == WebPageType::ChannelBoost) + ? tr::lng_view_button_boost(tr::now) + : (type == WebPageType::VoiceChat) + ? tr::lng_view_button_voice_chat(tr::now) + : (type == WebPageType::Livestream) + ? tr::lng_view_button_voice_chat_channel(tr::now) + : (type == WebPageType::Bot) + ? tr::lng_view_button_bot(tr::now) + : (type == WebPageType::User) + ? tr::lng_view_button_user(tr::now) + : (type == WebPageType::BotApp) + ? tr::lng_view_button_bot_app(tr::now) + : QString()); +} + } // namespace WebPage::WebPage( @@ -92,23 +125,47 @@ WebPage::WebPage( : Media(parent) , _st(st::historyPagePreview) , _data(data) +, _colorIndex(parent->data()->computeColorIndex()) , _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { history()->owner().registerWebPageView(_data, _parent); +} - const auto from = parent->data()->displayFrom(); - const auto info = from ? nullptr : parent->data()->hiddenSenderInfo(); - Assert(from || info); - _colorIndexPlusOne = !parent->data()->isPost() - ? ((from ? from->colorIndex() : info->colorIndex) + 1) - : 0; +bool WebPage::HasButton(not_null webpage) { + const auto type = webpage->type; + return (type == WebPageType::Message) + || (type == WebPageType::Group) + || (type == WebPageType::Channel) + || (type == WebPageType::ChannelBoost) + // || (type == WebPageType::Bot) + || (type == WebPageType::User) + || (type == WebPageType::VoiceChat) + || (type == WebPageType::Livestream) + || (type == WebPageType::BotApp) + || ((type == WebPageType::Theme) + && webpage->document + && webpage->document->isTheme()) + || ((type == WebPageType::Story) + && (webpage->photo || webpage->document)) + || ((type == WebPageType::WallPaper) + && webpage->document + && webpage->document->isWallPaper()); } QSize WebPage::countOptimalSize() { if (_data->pendingTill) { return { 0, 0 }; } + + // Detect _openButtonWidth before counting paddings. + _openButton = QString(); + _openButtonWidth = 0; + if (HasButton(_data)) { + _openButton = PageToPhrase(_data); + _openButtonWidth = st::semiboldFont->width(_openButton); + } + const auto padding = inBubblePadding() + innerMargin(); const auto versionChanged = (_dataVersion != _data->version); if (versionChanged) { @@ -127,6 +184,13 @@ QSize WebPage::countOptimalSize() { if (!_openl && !_data->url.isEmpty()) { const auto previewOfHiddenUrl = [&] { + if (_data->type == WebPageType::BotApp) { + // Bot Web Apps always show confirmation on hidden urls. + // + // But from the dedicated "Open App" button we don't want + // to request users confirmation on non-first app opening. + return false; + } const auto simplify = [](const QString &url) { auto result = url.toLower(); if (result.endsWith('/')) { @@ -175,7 +239,7 @@ QSize WebPage::countOptimalSize() { ? _data->author : _data->title); if (!_collage.empty()) { - _asArticle = false; + _asArticle = 0; } else if (!_data->document && _data->photo && _data->type != WebPageType::Photo @@ -183,22 +247,22 @@ QSize WebPage::countOptimalSize() { && _data->type != WebPageType::Story && _data->type != WebPageType::Video) { if (_data->type == WebPageType::Profile) { - _asArticle = true; + _asArticle = 1; } else if (_data->siteName == u"Twitter"_q || _data->siteName == u"Facebook"_q || _data->type == WebPageType::ArticleWithIV) { - _asArticle = false; + _asArticle = 0; } else { - _asArticle = true; + _asArticle = 1; } if (_asArticle && _data->description.text.isEmpty() && title.isEmpty() && _data->siteName.isEmpty()) { - _asArticle = false; + _asArticle = 0; } } else { - _asArticle = false; + _asArticle = 0; } // init attach @@ -211,8 +275,6 @@ QSize WebPage::countOptimalSize() { _data->url); } - _hasViewButton = ViewButton::MediaHasViewButton(_data); - // init strings if (_description.isEmpty() && !_data->description.text.isEmpty()) { auto text = _data->description; @@ -306,6 +368,10 @@ QSize WebPage::countOptimalSize() { _duration = Ui::FormatDurationText(_data->duration); _durationWidth = st::msgDateFont->width(_duration); } + if (_openButtonWidth) { + const auto &margins = st::historyPageButtonPadding; + maxWidth += margins.left() + _openButtonWidth + margins.right(); + } maxWidth += padding.left() + padding.right(); minHeight += padding.top() + padding.bottom(); @@ -472,13 +538,19 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); const auto selected = context.selected(); - const auto useColorIndex = context.outbg ? 0 : _colorIndexPlusOne; - const auto cache = useColorIndex - ? st->coloredReplyCache(selected, useColorIndex - 1).get() - : stm->replyCache.get(); + const auto cache = context.outbg + ? stm->replyCache.get() + : st->coloredReplyCache(selected, _colorIndex).get(); 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 (asArticle()) { ensurePhotoMediaCreated(); @@ -522,9 +594,9 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { } if (_siteNameLines) { p.setPen(cache->outline); - p.setTextPalette(useColorIndex - ? st->coloredTextPalette(selected, useColorIndex - 1) - : stm->semiboldPalette); + p.setTextPalette(context.outbg + ? stm->semiboldPalette + : st->coloredTextPalette(selected, _colorIndex)); auto endskip = 0; if (_siteName.hasSkipBlock()) { @@ -620,6 +692,21 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { p.drawTextLeft(st::msgPadding.left(), outer.y() + outer.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText); } } + + if (_openButtonWidth) { + p.setFont(st::semiboldFont); + p.setPen(cache->outline); + const auto end = inner.y() + inner.height() + _st.padding.bottom(); + const auto line = st::historyPageButtonLine; + auto color = cache->outline; + color.setAlphaF(color.alphaF() * 0.3); + p.fillRect(inner.x(), end, inner.width(), line, color); + const auto top = end + st::historyPageButtonPadding.top(); + p.drawText( + inner.x() + (inner.width() - _openButtonWidth) / 2, + top + st::semiboldFont->ascent, + _openButton); + } } bool WebPage::asArticle() const { @@ -715,9 +802,10 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { result.link = replaceAttachLink(result.link); } } - if (!result.link && inner.contains(point)) { + if (!result.link && outer.contains(point)) { result.link = _openl; } + _lastPoint = point - outer.topLeft(); result.symbol += symbolAdd; return result; @@ -773,13 +861,35 @@ TextSelection WebPage::adjustSelection(TextSelection selection, TextSelectType t return { siteNameSelection.from, fromDescriptionSelection(descriptionSelection).to }; } -void WebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { +void WebPage::clickHandlerActiveChanged( + const ClickHandlerPtr &p, + bool active) { if (_attach) { _attach->clickHandlerActiveChanged(p, active); } } -void WebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { +void WebPage::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); } @@ -811,6 +921,15 @@ QString WebPage::additionalInfoString() const { return _attach ? _attach->additionalInfoString() : QString(); } +bool WebPage::toggleSelectionByHandlerClick( + const ClickHandlerPtr &p) const { + return _attach && _attach->toggleSelectionByHandlerClick(p); +} + +bool WebPage::dragItemByHandler(const ClickHandlerPtr &p) const { + return _attach && _attach->dragItemByHandler(p); +} + TextForMimeData WebPage::selectedText(TextSelection selection) const { auto siteNameResult = _siteName.toTextForMimeData(selection); auto titleResult = _title.toTextForMimeData( @@ -846,7 +965,8 @@ QMargins WebPage::inBubblePadding() const { } QMargins WebPage::innerMargin() const { - return _st.padding; + const auto button = _openButtonWidth ? st::historyPageButtonHeight : 0; + return _st.padding + QMargins(0, 0, 0, button); } bool WebPage::isLogEntryOriginal() const { 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 bbccbd9518..1e473207c0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -14,6 +14,10 @@ class Media; class PhotoMedia; } // namespace Data +namespace Ui { +class RippleAnimation; +} // namespace Ui + namespace HistoryView { class WebPage : public Media { @@ -22,6 +26,8 @@ public: not_null parent, not_null data); + [[nodiscard]] static bool HasButton(not_null data); + void refreshParentId(not_null realParent) override; void draw(Painter &p, const PaintContext &context) const override; @@ -38,21 +44,21 @@ public: return _title.length() + _description.length(); } bool hasTextForCopy() const override { - return false; // we do not add _title and _description in FullSelection text copy. + // We do not add _title and _description in FullSelection text copy. + return false; } QString additionalInfoString() const override; - 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 dragItemByHandler(const ClickHandlerPtr &p) const override; TextForMimeData selectedText(TextSelection selection) const override; - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + void clickHandlerActiveChanged( + const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged( + const ClickHandlerPtr &p, bool pressed) override; bool isDisplayed() const override; PhotoData *getPhoto() const override { @@ -123,20 +129,25 @@ private: ClickHandlerPtr _openl; std::unique_ptr _attach; mutable std::shared_ptr _photoMedia; + mutable std::unique_ptr _ripple; - bool _asArticle = false; - bool _hasViewButton = false; int _dataVersion = -1; int _siteNameLines = 0; int _descriptionLines = 0; - int _titleLines : 24 = 0; - int _colorIndexPlusOne : 8 = 0; + uint32 _titleLines : 24 = 0; + uint32 _colorIndex : 7 = 0; + uint32 _asArticle : 1 = 0; - Ui::Text::String _siteName, _title, _description; + Ui::Text::String _siteName; + Ui::Text::String _title; + Ui::Text::String _description; + QString _openButton; QString _duration; + int _openButtonWidth = 0; int _durationWidth = 0; + mutable QPoint _lastPoint; int _pixw = 0; int _pixh = 0; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 0169149c04..6c2f61c277 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -624,10 +624,14 @@ historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgS historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }}; historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }}; -historyViewButtonHeight: 42px; -historyViewButtonMargins: margins(13px, 5px, 13px, 5px); +historyViewButtonHeight: 48px; +historyViewButtonMargins: margins(10px, 5px, 10px, 10px); historyViewButtonTextStyle: semiboldTextStyle; +historyPageButtonLine: 1px; +historyPageButtonHeight: 36px; +historyPageButtonPadding: margins(13px, 8px, 13px, 8px); + historyCommentsButtonHeight: 40px; historyCommentsSkipLeft: 9px; historyCommentsSkipText: 10px; From 84a1fec7b161ae6232d2f5aaa3175199472648b8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Oct 2023 17:11:05 +0400 Subject: [PATCH 46/99] Add flag emoji by country iso2 method. --- .../countries/countries_instance.cpp | 28 ++++++++++++++----- .../countries/countries_instance.h | 21 +++++++------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/countries/countries_instance.cpp b/Telegram/SourceFiles/countries/countries_instance.cpp index 0a78441790..ba159016d9 100644 --- a/Telegram/SourceFiles/countries/countries_instance.cpp +++ b/Telegram/SourceFiles/countries/countries_instance.cpp @@ -254,7 +254,7 @@ const std::array FallbackList = { { CountriesInstance::CountriesInstance() { } -const std::vector &CountriesInstance::list() { +const std::vector &CountriesInstance::list() const { if (_list.empty()) { _list = (FallbackList | ranges::to_vector); } @@ -268,7 +268,7 @@ void CountriesInstance::setList(std::vector &&infos) { _updated.fire({}); } -const CountriesInstance::Map &CountriesInstance::byCode() { +const CountriesInstance::Map &CountriesInstance::byCode() const { if (_byCode.empty()) { _byCode.reserve(list().size()); for (const auto &entry : list()) { @@ -280,7 +280,7 @@ const CountriesInstance::Map &CountriesInstance::byCode() { return _byCode; } -const CountriesInstance::Map &CountriesInstance::byISO2() { +const CountriesInstance::Map &CountriesInstance::byISO2() const { if (_byISO2.empty()) { _byISO2.reserve(list().size()); for (const auto &entry : list()) { @@ -290,7 +290,7 @@ const CountriesInstance::Map &CountriesInstance::byISO2() { return _byISO2; } -QString CountriesInstance::validPhoneCode(QString fullCode) { +QString CountriesInstance::validPhoneCode(QString fullCode) const { const auto &listByCode = byCode(); while (fullCode.length()) { const auto i = listByCode.constFind(fullCode); @@ -302,20 +302,34 @@ QString CountriesInstance::validPhoneCode(QString fullCode) { return QString(); } -QString CountriesInstance::countryNameByISO2(const QString &iso) { +QString CountriesInstance::countryNameByISO2(const QString &iso) const { const auto &listByISO2 = byISO2(); const auto i = listByISO2.constFind(iso); return (i != listByISO2.cend()) ? (*i)->name : QString(); } -QString CountriesInstance::countryISO2ByPhone(const QString &phone) { +QString CountriesInstance::countryISO2ByPhone(const QString &phone) const { const auto &listByCode = byCode(); const auto code = validPhoneCode(phone); const auto i = listByCode.find(code); return (i != listByCode.cend()) ? (*i)->iso2 : QString(); } -FormatResult CountriesInstance::format(FormatArgs args) { +QString CountriesInstance::flagEmojiByISO2(const QString &iso) const { + if (iso.size() != 2 + || iso.front() < 'A' + || iso.front() > 'Z' + || iso.back() < 'A' + || iso.back() > 'Z') { + return QString(); + } + auto result = QString(4, QChar(0xD83C)); + result[1] = QChar(iso.front().unicode() - 'A' + 0xDDE6); + result[3] = QChar(iso.back().unicode() - 'A' + 0xDDE6); + return result; +} + +FormatResult CountriesInstance::format(FormatArgs args) const { // Ported from TDLib. if (args.phone.isEmpty()) { return FormatResult(); diff --git a/Telegram/SourceFiles/countries/countries_instance.h b/Telegram/SourceFiles/countries/countries_instance.h index d8d7562e88..b7993930c2 100644 --- a/Telegram/SourceFiles/countries/countries_instance.h +++ b/Telegram/SourceFiles/countries/countries_instance.h @@ -43,25 +43,26 @@ public: using Map = QHash; CountriesInstance(); - [[nodiscard]] const std::vector &list(); + [[nodiscard]] const std::vector &list() const; void setList(std::vector &&infos); - [[nodiscard]] const Map &byCode(); - [[nodiscard]] const Map &byISO2(); + [[nodiscard]] const Map &byCode() const; + [[nodiscard]] const Map &byISO2() const; - [[nodiscard]] QString validPhoneCode(QString fullCode); - [[nodiscard]] QString countryNameByISO2(const QString &iso); - [[nodiscard]] QString countryISO2ByPhone(const QString &phone); + [[nodiscard]] QString validPhoneCode(QString fullCode) const; + [[nodiscard]] QString countryNameByISO2(const QString &iso) const; + [[nodiscard]] QString countryISO2ByPhone(const QString &phone) const; + [[nodiscard]] QString flagEmojiByISO2(const QString &iso) const; - [[nodiscard]] FormatResult format(FormatArgs args); + [[nodiscard]] FormatResult format(FormatArgs args) const; [[nodiscard]] rpl::producer<> updated() const; private: - std::vector _list; + mutable std::vector _list; - Map _byCode; - Map _byISO2; + mutable Map _byCode; + mutable Map _byISO2; rpl::event_stream<> _updated; From 486d5b63d3d29358ac9b603bcc58c4ed4b9cf05e Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Oct 2023 17:27:02 +0400 Subject: [PATCH 47/99] Add countries to giveaway messages. --- Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/data/data_media_types.cpp | 7 ++ Telegram/SourceFiles/data/data_media_types.h | 1 + .../view/media/history_view_giveaway.cpp | 117 ++++++++++++++++-- .../view/media/history_view_giveaway.h | 19 ++- Telegram/SourceFiles/ui/chat/chat.style | 1 + 6 files changed, 135 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0f77401b70..f03c46f20e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2090,6 +2090,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_participants_all#other" = "All subscribers of the channels:"; "lng_prizes_participants_new#one" = "All users who joined the channel below after this date:"; "lng_prizes_participants_new#other" = "All users who joined the channels below after this date:"; +"lng_prizes_countries" = "from {countries}"; +"lng_prizes_countries_and_one" = "{countries}, {country}"; +"lng_prizes_countries_and_last" = "{countries} and {country}"; "lng_prizes_date" = "Winners Selection Date"; "lng_prizes_how_works" = "Learn more"; "lng_prizes_how_title" = "About this giveaway"; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 916d9e5bcd..9ca2dd394d 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -375,6 +375,13 @@ Giveaway ComputeGiveawayData( for (const auto &id : data.vchannels().v) { result.channels.push_back(owner->channel(ChannelId(id))); } + if (const auto countries = data.vcountries_iso2()) { + result.countries.reserve(countries->v.size()); + for (const auto &country : countries->v) { + result.countries.push_back(qs(country)); + } + } + result.countries = { u"FR"_q, u"FI"_q, u"UA"_q, u"IT"_q }; return result; } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 1704e2ad3a..aea83ef417 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -92,6 +92,7 @@ struct Invoice { struct Giveaway { std::vector> channels; + std::vector countries; TimeId untilDate = 0; int quantity = 0; int months = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 2d9a81fd8e..47ecb030db 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "boxes/gift_premium_box.h" #include "chat_helpers/stickers_gift_box_pack.h" +#include "countries/countries_instance.h" #include "data/data_channel.h" #include "data/data_document.h" #include "data/data_media_types.h" @@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "ui/chat/chat_style.h" #include "ui/chat/message_bubble.h" +#include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/widgets/tooltip.h" #include "ui/painter.h" @@ -34,8 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { -constexpr auto kChannelBgAlpha = 32; - [[nodiscard]] QSize CountOptimalTextSize( const Ui::Text::String &text, int minWidth, @@ -59,8 +59,10 @@ Giveaway::Giveaway( , _prizes(st::msgMinWidth) , _participantsTitle(st::msgMinWidth) , _participants(st::msgMinWidth) +, _countries(st::msgMinWidth) , _winnersTitle(st::msgMinWidth) -, _winners(st::msgMinWidth) { +, _winners(st::msgMinWidth) +, _colorIndex(parent->data()->computeColorIndex()) { fillFromData(giveaway); } @@ -108,6 +110,33 @@ void Giveaway::fillFromData(not_null giveaway) { } const auto channels = int(_channels.size()); + const auto &instance = Countries::Instance(); ; + auto countries = QStringList(); + for (const auto &country : giveaway->countries) { + const auto name = instance.countryNameByISO2(country); + const auto flag = instance.flagEmojiByISO2(country); + countries.push_back(flag + QChar(0xA0) + name); + } + if (const auto count = countries.size()) { + auto united = countries.front(); + for (auto i = 1; i != count; ++i) { + united = ((i + 1 == count) + ? tr::lng_prizes_countries_and_last + : tr::lng_prizes_countries_and_one)( + tr::now, + lt_countries, + united, + lt_country, + countries[i]); + } + _countries.setText( + st::defaultTextStyle, + tr::lng_prizes_countries(tr::now, lt_countries, united), + kDefaultTextOptions); + } else { + _countries.clear(); + } + _participants.setText( st::defaultTextStyle, (giveaway->all @@ -161,7 +190,19 @@ QSize Giveaway::countOptimalSize() { padding.left(), channelsTop, available); - _winnersTitleTop = channelsBottom + st::chatGiveawayDateTop; + _countriesTop = channelsBottom; + if (_countries.isEmpty()) { + _winnersTitleTop = _countriesTop + st::chatGiveawayDateTop; + } else { + const auto countriesSize = CountOptimalTextSize( + _countries, + st::msgMinWidth, + available); + _countriesWidth = countriesSize.width(); + _winnersTitleTop = _countriesTop + + _countries.countHeight(available) + + st::chatGiveawayCountriesSkip; + } _winnersTop = _winnersTitleTop + _winnersTitle.countHeight(available) + st::chatGiveawayDateSkip; @@ -252,6 +293,9 @@ void Giveaway::draw(Painter &p, const PaintContext &context) const { paintText(_prizes, _prizesTop, _prizesWidth); paintText(_participantsTitle, _participantsTitleTop, paintw); paintText(_participants, _participantsTop, _participantsWidth); + if (!_countries.isEmpty()) { + paintText(_countries, _countriesTop, _countriesWidth); + } paintText(_winnersTitle, _winnersTitleTop, paintw); paintText(_winners, _winnersTop, paintw); paintChannels(p, context); @@ -302,16 +346,18 @@ void Giveaway::paintChannels( const auto size = _channels[0].geometry.height(); const auto ratio = style::DevicePixelRatio(); const auto stm = context.messageStyle(); - auto bg = stm->msgReplyBarColor->c; - bg.setAlpha(kChannelBgAlpha); - if (_channelCorners[0].isNull() || _channelBg != bg) { - _channelBg = bg; + const auto selected = context.selected(); + const auto cache = context.outbg + ? stm->replyCache.get() + : context.st->coloredReplyCache(selected, _colorIndex).get(); + if (_channelCorners[0].isNull() || _channelBg != cache->bg) { + _channelBg = cache->bg; _channelCorners = Images::CornersMask(size / 2); for (auto &image : _channelCorners) { - style::colorizeImage(image, bg, &image); + style::colorizeImage(image, cache->bg, &image); } } - p.setPen(stm->msgReplyBarColor); + p.setPen(cache->outline); const auto padding = st::chatGiveawayChannelPadding; for (const auto &channel : _channels) { const auto &thumbnail = channel.thumbnail; @@ -321,7 +367,20 @@ void Giveaway::paintChannels( view->history()->owner().requestViewRepaint(view); }); } + Ui::DrawRoundedRect(p, geometry, _channelBg, _channelCorners); + if (channel.ripple) { + channel.ripple->paint( + p, + geometry.x(), + geometry.y(), + width(), + &cache->bg); + if (channel.ripple->empty()) { + channel.ripple = nullptr; + } + } + p.drawImage(geometry.topLeft(), thumbnail->image(size)); const auto left = size + padding.left(); const auto top = padding.top(); @@ -337,7 +396,7 @@ void Giveaway::paintChannels( .elisionBreakEverywhere = true, }); } - _subscribedToThumbnails = true; + _subscribedToThumbnails = 1; } void Giveaway::ensureStickerCreated() const { @@ -410,12 +469,45 @@ TextState Giveaway::textState(QPoint point, StateRequest request) const { for (const auto &channel : _channels) { if (channel.geometry.contains(point)) { result.link = channel.link; + _lastPoint = point; return result; } } return result; } +void Giveaway::clickHandlerActiveChanged( + const ClickHandlerPtr &p, + bool active) { +} + +void Giveaway::clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed) { + for (auto &channel : _channels) { + if (channel.link != p) { + continue; + } + if (pressed) { + if (!channel.ripple) { + const auto full = QRect(0, 0, width(), height()); + const auto outer = full.marginsRemoved(inBubblePadding()); + const auto owner = &parent()->history()->owner(); + channel.ripple = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask( + channel.geometry.size(), + channel.geometry.height() / 2), + [=] { owner->requestViewRepaint(parent()); }); + } + channel.ripple->add(_lastPoint - channel.geometry.topLeft()); + } else if (channel.ripple) { + channel.ripple->lastStop(); + } + break; + } +} + bool Giveaway::hideFromName() const { return !parent()->data()->Has(); } @@ -425,7 +517,8 @@ bool Giveaway::hasHeavyPart() const { } void Giveaway::unloadHeavyPart() { - if (base::take(_subscribedToThumbnails)) { + if (_subscribedToThumbnails) { + _subscribedToThumbnails = 0; for (const auto &channel : _channels) { channel.thumbnail->subscribeToUpdates(nullptr); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index 1135396ed8..437c94c5c2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -18,6 +18,10 @@ namespace Dialogs::Stories { class Thumbnail; } // namespace Dialogs::Stories +namespace Ui { +class RippleAnimation; +} // namespace Ui + namespace HistoryView { class Giveaway final : public Media { @@ -30,6 +34,13 @@ public: void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; + void clickHandlerActiveChanged( + const ClickHandlerPtr &p, + bool active) override; + void clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed) override; + bool needsBubble() const override { return true; } @@ -57,6 +68,7 @@ private: std::shared_ptr thumbnail; QRect geometry; ClickHandlerPtr link; + mutable std::unique_ptr ripple; }; void paintBadge(Painter &p, const PaintContext &context) const; @@ -78,6 +90,7 @@ private: Ui::Text::String _participantsTitle; Ui::Text::String _participants; std::vector _channels; + Ui::Text::String _countries; Ui::Text::String _winnersTitle; Ui::Text::String _winners; @@ -88,6 +101,7 @@ private: mutable QImage _badge; mutable QImage _badgeCache; + mutable QPoint _lastPoint; int _months = 0; int _quantity = 0; int _stickerTop = 0; @@ -97,9 +111,12 @@ private: int _participantsTitleTop = 0; int _participantsTop = 0; int _participantsWidth = 0; + int _countriesTop = 0; + int _countriesWidth = 0; int _winnersTitleTop = 0; int _winnersTop = 0; - mutable bool _subscribedToThumbnails = false; + uint8 _colorIndex : 7 = 0; + mutable uint8 _subscribedToThumbnails : 1 = 0; }; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 6c2f61c277..7050667ae3 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -957,6 +957,7 @@ chatGiveawayChannelTop: 6px; chatGiveawayChannelSize: 32px; chatGiveawayChannelPadding: margins(5px, 7px, 12px, 0px); chatGiveawayChannelSkip: 8px; +chatGiveawayCountriesSkip: 16px; chatGiveawayDateTop: 6px; chatGiveawayDateSkip: 4px; chatGiveawayBottomSkip: 16px; From b2e8e0431e50b1d1f8c7b8635ac382e9467a5e60 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Oct 2023 11:05:39 +0400 Subject: [PATCH 48/99] Start customizable webpages. --- .../SourceFiles/data/data_media_types.cpp | 10 +- Telegram/SourceFiles/data/data_media_types.h | 6 +- Telegram/SourceFiles/data/data_session.cpp | 7 ++ Telegram/SourceFiles/data/data_session.h | 4 +- Telegram/SourceFiles/data/data_types.h | 52 ++++++----- Telegram/SourceFiles/data/data_web_page.cpp | 3 + Telegram/SourceFiles/data/data_web_page.h | 10 +- Telegram/SourceFiles/history/history_item.cpp | 19 +++- Telegram/SourceFiles/history/history_item.h | 3 + .../history/history_item_helpers.cpp | 7 +- .../history/view/history_view_message.cpp | 92 +++++++++++++------ .../history/view/history_view_message.h | 4 +- .../view/media/history_view_web_page.cpp | 19 ++-- .../view/media/history_view_web_page.h | 5 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- .../window/window_session_controller.cpp | 2 +- 16 files changed, 172 insertions(+), 73 deletions(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 9ca2dd394d..ffd9512819 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -1434,9 +1434,11 @@ QString MediaCall::Text( MediaWebPage::MediaWebPage( not_null parent, - not_null page) + not_null page, + MediaWebPageFlags flags) : Media(parent) -, _page(page) { +, _page(page) +, _flags(flags) { parent->history()->owner().registerWebPageItem(_page, parent); } @@ -1445,7 +1447,7 @@ MediaWebPage::~MediaWebPage() { } std::unique_ptr MediaWebPage::clone(not_null parent) { - return std::make_unique(parent, _page); + return std::make_unique(parent, _page, _flags); } DocumentData *MediaWebPage::document() const { @@ -1524,7 +1526,7 @@ std::unique_ptr MediaWebPage::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - return std::make_unique(message, _page); + return std::make_unique(message, _page, _flags); } MediaGame::MediaGame( diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index aea83ef417..e7e4361ae2 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -372,7 +372,8 @@ class MediaWebPage final : public Media { public: MediaWebPage( not_null parent, - not_null page); + not_null page, + MediaWebPageFlags flags); ~MediaWebPage(); std::unique_ptr clone(not_null parent) override; @@ -398,7 +399,8 @@ public: HistoryView::Element *replacing = nullptr) override; private: - not_null _page; + const not_null _page; + const MediaWebPageFlags _flags; }; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index fe6ed6b566..d91654e230 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3288,6 +3288,7 @@ not_null Session::processWebpage(const MTPDwebPagePending &data) { webpageApplyFields( result, WebPageType::Article, + false, QString(), QString(), QString(), @@ -3312,6 +3313,7 @@ not_null Session::webpage( return webpage( id, WebPageType::Article, + false, QString(), QString(), siteName, @@ -3328,6 +3330,7 @@ not_null Session::webpage( not_null Session::webpage( WebPageId id, WebPageType type, + bool hasLargeMedia, const QString &url, const QString &displayUrl, const QString &siteName, @@ -3343,6 +3346,7 @@ not_null Session::webpage( webpageApplyFields( result, type, + hasLargeMedia, url, displayUrl, siteName, @@ -3434,6 +3438,7 @@ void Session::webpageApplyFields( webpageApplyFields( page, (story ? WebPageType::Story : ParseWebPageType(data)), + data.is_has_large_media(), qs(data.vurl()), qs(data.vdisplay_url()), siteName, @@ -3459,6 +3464,7 @@ void Session::webpageApplyFields( void Session::webpageApplyFields( not_null page, WebPageType type, + bool hasLargeMedia, const QString &url, const QString &displayUrl, const QString &siteName, @@ -3474,6 +3480,7 @@ void Session::webpageApplyFields( const auto requestPending = (!page->pendingTill && pendingTill > 0); const auto changed = page->applyChanges( type, + hasLargeMedia, url, displayUrl, siteName, diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 86cb08c6fa..5f88f2235a 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class Image; class HistoryItem; struct WebPageCollage; -enum class WebPageType; +enum class WebPageType : uint8; enum class NewMessageType; namespace HistoryView { @@ -552,6 +552,7 @@ public: [[nodiscard]] not_null webpage( WebPageId id, WebPageType type, + bool hasLargeMedia, const QString &url, const QString &displayUrl, const QString &siteName, @@ -813,6 +814,7 @@ private: void webpageApplyFields( not_null page, WebPageType type, + bool hasLargeMedia, const QString &url, const QString &displayUrl, const QString &siteName, diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 6fd5cc51a5..611a912638 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -246,65 +246,75 @@ enum class MessageFlag : uint64 { MentionsMe = (1ULL << 15), IsOrWasScheduled = (1ULL << 16), NoForwards = (1ULL << 17), + InvertMedia = (1ULL << 18), // Needs to return back to inline mode. - HasSwitchInlineButton = (1ULL << 18), + HasSwitchInlineButton = (1ULL << 19), // For "shared links" indexing. - HasTextLinks = (1ULL << 19), + HasTextLinks = (1ULL << 20), // Group / channel create or migrate service message. - IsGroupEssential = (1ULL << 20), + IsGroupEssential = (1ULL << 21), // Edited media is generated on the client // and should not update media from server. - IsLocalUpdateMedia = (1ULL << 21), + IsLocalUpdateMedia = (1ULL << 22), // Sent from inline bot, need to re-set media when sent. - FromInlineBot = (1ULL << 22), + FromInlineBot = (1ULL << 23), // Generated on the client side and should be unread. - ClientSideUnread = (1ULL << 23), + ClientSideUnread = (1ULL << 24), // In a supergroup. - HasAdminBadge = (1ULL << 24), + HasAdminBadge = (1ULL << 25), // Outgoing message that is being sent. - BeingSent = (1ULL << 25), + BeingSent = (1ULL << 26), // Outgoing message and failed to be sent. - SendingFailed = (1ULL << 26), + SendingFailed = (1ULL << 27), // No media and only a several emoji or an only custom emoji text. - SpecialOnlyEmoji = (1ULL << 27), + SpecialOnlyEmoji = (1ULL << 28), // Message existing in the message history. - HistoryEntry = (1ULL << 28), + HistoryEntry = (1ULL << 29), // Local message, not existing on the server. - Local = (1ULL << 29), + Local = (1ULL << 30), // Fake message for some UI element. - FakeHistoryItem = (1ULL << 30), + FakeHistoryItem = (1ULL << 31), // Contact sign-up message, notification should be skipped for Silent. - IsContactSignUp = (1ULL << 31), + IsContactSignUp = (1ULL << 32), // Optimization for item text custom emoji repainting. - CustomEmojiRepainting = (1ULL << 32), + CustomEmojiRepainting = (1ULL << 33), // Profile photo suggestion, views have special media type. - IsUserpicSuggestion = (1ULL << 33), + IsUserpicSuggestion = (1ULL << 34), - OnlyEmojiAndSpaces = (1ULL << 34), - OnlyEmojiAndSpacesSet = (1ULL << 35), + OnlyEmojiAndSpaces = (1ULL << 35), + OnlyEmojiAndSpacesSet = (1ULL << 36), // Fake message with bot cover and information. - FakeBotAbout = (1ULL << 36), + FakeBotAbout = (1ULL << 37), - StoryItem = (1ULL << 37), + StoryItem = (1ULL << 38), - InHighlightProcess = (1ULL << 38), + InHighlightProcess = (1ULL << 39), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; + +enum class MediaWebPageFlag : uint8 { + ForceLargeMedia = (1 << 0), + ForceSmallMedia = (1 << 1), + Manual = (1 << 2), + Safe = (1 << 3), +}; +inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; } +using MediaWebPageFlags = base::flags; diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 789f15ea53..a2fadf8ad4 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -213,6 +213,7 @@ Main::Session &WebPageData::session() const { bool WebPageData::applyChanges( WebPageType newType, + bool newHasLargeMedia, const QString &newUrl, const QString &newDisplayUrl, const QString &newSiteName, @@ -254,6 +255,7 @@ bool WebPageData::applyChanges( }(); if (type == newType + && hasLargeMedia == newHasLargeMedia && url == resultUrl && displayUrl == resultDisplayUrl && siteName == resultSiteName @@ -272,6 +274,7 @@ bool WebPageData::applyChanges( _owner->session().api().clearWebPageRequest(this); } type = newType; + hasLargeMedia = newHasLargeMedia; url = resultUrl; displayUrl = resultDisplayUrl; siteName = resultSiteName; diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index 94ca383585..9bfa64534c 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/flags.h" #include "data/data_photo.h" #include "data/data_document.h" @@ -16,7 +17,7 @@ namespace Data { class Session; } // namespace Data -enum class WebPageType { +enum class WebPageType : uint8 { Message, Group, @@ -44,8 +45,7 @@ enum class WebPageType { VoiceChat, Livestream, }; - -WebPageType ParseWebPageType(const MTPDwebPage &type); +[[nodiscard]] WebPageType ParseWebPageType(const MTPDwebPage &type); struct WebPageCollage { using Item = std::variant; @@ -67,6 +67,7 @@ struct WebPageData { bool applyChanges( WebPageType newType, + bool newHasLargeMedia, const QString &newUrl, const QString &newDisplayUrl, const QString &newSiteName, @@ -87,17 +88,18 @@ struct WebPageData { WebPageId id = 0; WebPageType type = WebPageType::Article; + bool hasLargeMedia = false; QString url; QString displayUrl; QString siteName; QString title; TextWithEntities description; FullStoryId storyId; - int duration = 0; QString author; PhotoData *photo = nullptr; DocumentData *document = nullptr; WebPageCollage collage; + int duration = 0; int pendingTill = 0; int version = 0; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 2f020b2e08..f2b688748a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -252,16 +252,28 @@ std::unique_ptr HistoryItem::CreateMedia( return nullptr; }); }, [&](const MTPDmessageMediaWebPage &media) { + using Flag = MediaWebPageFlag; + const auto flags = Flag() + | (media.is_force_large_media() + ? Flag::ForceLargeMedia + : Flag()) + | (media.is_force_small_media() + ? Flag::ForceSmallMedia + : Flag()) + | (media.is_manual() ? Flag::Manual : Flag()) + | (media.is_safe() ? Flag::Safe : Flag()); return media.vwebpage().match([](const MTPDwebPageEmpty &) -> Result { return nullptr; }, [&](const MTPDwebPagePending &webpage) -> Result { return std::make_unique( item, - item->history()->owner().processWebpage(webpage)); + item->history()->owner().processWebpage(webpage), + flags); }, [&](const MTPDwebPage &webpage) -> Result { return std::make_unique( item, - item->history()->owner().processWebpage(webpage)); + item->history()->owner().processWebpage(webpage), + flags); }, [](const MTPDwebPageNotModified &) -> Result { LOG(("API Error: " "webPageNotModified is unexpected in message media.")); @@ -503,6 +515,9 @@ HistoryItem::HistoryItem( }; if (mediaOriginal && !ignoreMedia()) { _media = mediaOriginal->clone(this); + if (original->invertMedia()) { + _flags |= MessageFlag::InvertMedia; + } } const auto dropCustomEmoji = dropForwardInfo diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 24f6bc510f..55b5f14b72 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -240,6 +240,9 @@ public: [[nodiscard]] bool isPinned() const { return _flags & MessageFlag::Pinned; } + [[nodiscard]] bool invertMedia() const { + return _flags & MessageFlag::InvertMedia; + } [[nodiscard]] bool unread(not_null thread) const; [[nodiscard]] bool showNotification() const; void markClientSideAsRead(); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 2e5fa74521..43f17d19e4 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -331,9 +331,12 @@ MessageFlags FlagsFromMTP( | ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag()) | ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag()) | ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag()) - | ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag()) + | ((flags & MTP::f_from_scheduled) + ? Flag::IsOrWasScheduled + : Flag()) | ((flags & MTP::f_views) ? Flag::HasViews : Flag()) - | ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag()); + | ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag()) + | ((flags & MTP::f_invert_media) ? Flag::InvertMedia : Flag()); } MessageFlags FlagsFromMTP( diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index f9a82fe0af..9b59ae05c9 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -411,6 +411,7 @@ Message::Message( not_null data, Element *replacing) : Element(delegate, data, replacing, Flag(0)) +, _invertMedia(data->invertMedia() && !data->emptyText()) , _bottomInfo( &data->history()->owner().reactions(), BottomInfoDataFromMessage(this)) { @@ -1078,16 +1079,20 @@ void Message::draw(Painter &p, const PaintContext &context) const { trect.setHeight(trect.height() - (_bottomInfo.height() - st::msgDateFont->height)); } - paintText(p, trect, context); - if (mediaDisplayed) { - auto mediaHeight = media->height(); - auto mediaPosition = QPoint( - inner.left(), - trect.y() + trect.height() - mediaHeight); + auto textSelection = context.selection; + const auto mediaHeight = mediaDisplayed ? media->height() : 0; + const auto paintMedia = [&](int top) { + if (!mediaDisplayed) { + return; + } + const auto mediaSelection = _invertMedia + ? context.selection + : skipTextSelection(context.selection); + auto mediaPosition = QPoint(inner.left(), top); p.translate(mediaPosition); media->draw(p, context.translated( -mediaPosition - ).withSelection(skipTextSelection(context.selection))); + ).withSelection(mediaSelection)); if (context.reactionInfo && !displayInfo && !_reactions) { const auto add = QPoint(0, mediaHeight); context.reactionInfo->position = mediaPosition + add; @@ -1096,6 +1101,27 @@ void Message::draw(Painter &p, const PaintContext &context) const { } } p.translate(-mediaPosition); + }; + if (mediaDisplayed && _invertMedia) { + if (!mediaOnTop) { + trect.setY(trect.y() + st::mediaInBubbleSkip); + } + paintMedia(trect.y()); + trect.setY(trect.y() + + mediaHeight + + (mediaOnBottom ? 0 : st::mediaInBubbleSkip)); + textSelection = media->skipSelection(textSelection); + } + paintText(p, trect, context.withSelection(textSelection)); + if (mediaDisplayed && !_invertMedia) { + paintMedia(trect.y() + trect.height() - mediaHeight); + if (context.reactionInfo && !displayInfo && !_reactions) { + context.reactionInfo->position + = QPoint(inner.left(), trect.y() + trect.height()); + if (context.reactionInfo->effectPaint) { + context.reactionInfo->effectOffset -= QPoint(0, mediaHeight); + } + } } if (entry) { auto entryLeft = inner.left(); @@ -2106,25 +2132,33 @@ TextState Message::textState( } }; if (!result.symbol && inBubble) { - if (mediaDisplayed) { - auto mediaHeight = media->height(); - auto mediaLeft = trect.x() - st::msgPadding.left(); - auto mediaTop = (trect.y() + trect.height() - mediaHeight); - - if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) { - result = media->textState(point - QPoint(mediaLeft, mediaTop), request); + const auto mediaHeight = mediaDisplayed ? media->height() : 0; + const auto mediaLeft = trect.x() - st::msgPadding.left(); + const auto mediaTop = (!mediaDisplayed || _invertMedia) + ? (trect.y() + (mediaOnTop ? 0 : st::mediaInBubbleSkip)) + : (trect.y() + trect.height() - mediaHeight); + if (mediaDisplayed && _invertMedia) { + trect.setY(mediaTop + + mediaHeight + + (mediaOnBottom ? 0 : st::mediaInBubbleSkip)); + } + if (point.y() >= mediaTop + && point.y() < mediaTop + mediaHeight) { + result = media->textState( + point - QPoint(mediaLeft, mediaTop), + request); + if (!_invertMedia) { result.symbol += visibleTextLength(); - } else if (getStateText(point, trect, &result, request)) { - checkBottomInfoState(); - return result; - } else if (point.y() >= trect.y() + trect.height()) { - result.symbol = visibleTextLength(); } } else if (getStateText(point, trect, &result, request)) { + if (_invertMedia) { + result.symbol += visibleMediaTextLength(); + } checkBottomInfoState(); return result; } else if (point.y() >= trect.y() + trect.height()) { - result.symbol = visibleTextLength(); + result.symbol = visibleTextLength() + + visibleMediaTextLength(); } } checkBottomInfoState(); @@ -3031,7 +3065,8 @@ void Message::initLogEntryOriginal() { if (const auto log = data()->Get()) { AddComponents(LogEntryOriginal::Bit()); const auto entry = Get(); - entry->page = std::make_unique(this, log->page); + using Flags = MediaWebPageFlags; + entry->page = std::make_unique(this, log->page, Flags()); } } @@ -3491,7 +3526,8 @@ void Message::updateMediaInBubbleState() { } const auto reactionsInBubble = (_reactions && embedReactionsInBubble()); auto mediaHasSomethingBelow = (_viewButton != nullptr) - || reactionsInBubble; + || reactionsInBubble + || (invertMedia() && hasVisibleText()); auto mediaHasSomethingAbove = false; auto getMediaHasSomethingAbove = [&] { return displayFromName() @@ -3529,7 +3565,7 @@ void Message::updateMediaInBubbleState() { if (!entry) { mediaHasSomethingAbove = getMediaHasSomethingAbove(); } - if (hasVisibleText()) { + if (!invertMedia() && hasVisibleText()) { mediaHasSomethingAbove = true; } const auto state = [&] { @@ -3661,7 +3697,7 @@ QRect Message::countGeometry() const { // contentLeft += st::msgPhotoSkip - (hmaxwidth - hwidth); } accumulate_min(contentWidth, maxWidth()); - accumulate_min(contentWidth, _bubbleWidthLimit); + accumulate_min(contentWidth, int(_bubbleWidthLimit)); if (mediaWidth < contentWidth) { const auto textualWidth = plainMaxWidth(); if (mediaWidth < textualWidth @@ -3768,7 +3804,7 @@ int Message::resizeContentGetHeight(int newWidth) { } accumulate_min(contentWidth, maxWidth()); _bubbleWidthLimit = std::max(st::msgMaxWidth, monospaceMaxWidth()); - accumulate_min(contentWidth, _bubbleWidthLimit); + accumulate_min(contentWidth, int(_bubbleWidthLimit)); if (mediaDisplayed) { media->resizeGetHeight(contentWidth); if (media->width() < contentWidth) { @@ -3924,11 +3960,15 @@ bool Message::needInfoDisplay() const { const auto entry = logEntryOriginal(); return entry ? !entry->customInfoLayout() - : (mediaDisplayed + : ((mediaDisplayed && media->isBubbleBottom()) ? !media->customInfoLayout() : true); } +bool Message::invertMedia() const { + return _invertMedia; +} + bool Message::hasVisibleText() const { if (data()->emptyText()) { if (const auto media = data()->media()) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 9c2a81ea42..5556660079 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -270,6 +270,7 @@ private: [[nodiscard]] int visibleTextLength() const; [[nodiscard]] int visibleMediaTextLength() const; [[nodiscard]] bool needInfoDisplay() const; + [[nodiscard]] bool invertMedia() const; [[nodiscard]] bool isPinnedContext() const; @@ -309,7 +310,8 @@ private: mutable std::unique_ptr _fromNameStatus; Ui::Text::String _rightBadge; mutable int _fromNameVersion = 0; - int _bubbleWidthLimit = 0; + uint32 _bubbleWidthLimit : 31 = 0; + uint32 _invertMedia : 1 = 0; BottomInfo _bottomInfo; 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 268d216bcd..e58ca94108 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -121,14 +121,16 @@ std::vector> PrepareCollageMedia( WebPage::WebPage( not_null parent, - not_null data) + not_null data, + MediaWebPageFlags flags) : Media(parent) , _st(st::historyPagePreview) , _data(data) , _colorIndex(parent->data()->computeColorIndex()) , _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) -, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { +, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) +, _flags(flags) { history()->owner().registerWebPageView(_data, _parent); } @@ -238,7 +240,12 @@ QSize WebPage::countOptimalSize() { auto title = TextUtilities::SingleLine(_data->title.isEmpty() ? _data->author : _data->title); - if (!_collage.empty()) { + using Flag = MediaWebPageFlag; + if (_data->hasLargeMedia && (_flags & Flag::ForceLargeMedia)) { + _asArticle = 0; + } else if (_data->photo && (_flags & Flag::ForceSmallMedia)) { + _asArticle = 1; + } else if (!_collage.empty()) { _asArticle = 0; } else if (!_data->document && _data->photo @@ -956,11 +963,9 @@ TextForMimeData WebPage::selectedText(TextSelection selection) const { QMargins WebPage::inBubblePadding() const { return { st::msgPadding.left(), - isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip, + isBubbleTop() ? st::msgPadding.left() : 0, st::msgPadding.right(), - (isBubbleBottom() - ? (st::msgPadding.left() + bottomInfoPadding()) - : st::mediaInBubbleSkip), + isBubbleBottom() ? (st::msgPadding.left() + bottomInfoPadding()) : 0 }; } 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 1e473207c0..2bc65bdedc 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -24,7 +24,8 @@ class WebPage : public Media { public: WebPage( not_null parent, - not_null data); + not_null data, + MediaWebPageFlags flags); [[nodiscard]] static bool HasButton(not_null data); @@ -151,6 +152,8 @@ private: int _pixw = 0; int _pixh = 0; + const MediaWebPageFlags _flags; + }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index cc4e42ade4..c7c7704447 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -2168,5 +2168,5 @@ stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; premium.getMyBoosts#be77b4a = premium.MyBoosts; -premium.applyBoost#46b6a16b flags:# slot:flags.0?int peer:InputPeer = Bool; +premium.applyBoost#184bc3b9 flags:# slots:flags.0?Vector peer:InputPeer = Bool; premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index fae0ec3add..3e151c8516 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -785,7 +785,7 @@ void SessionNavigation::applyBoostChecked( Fn done) { _api.request(MTPpremium_ApplyBoost( MTP_flags(0), - MTPint(), // slot + MTPVector(), // slots channel->input )).done([=](const MTPBool &result) { done(true); From b1823d981bb432d779f805110e83ac91b800a264 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Oct 2023 18:20:27 +0400 Subject: [PATCH 49/99] Update API scheme, rich preview drafts. --- Telegram/SourceFiles/apiwrap.cpp | 20 ++- Telegram/SourceFiles/data/data_drafts.cpp | 49 ++++++- Telegram/SourceFiles/data/data_drafts.h | 25 ++-- .../SourceFiles/data/data_media_types.cpp | 8 ++ Telegram/SourceFiles/data/data_media_types.h | 2 + Telegram/SourceFiles/data/data_session.cpp | 3 +- Telegram/SourceFiles/data/data_web_page.cpp | 2 +- Telegram/SourceFiles/data/data_web_page.h | 5 +- Telegram/SourceFiles/history/history.cpp | 10 +- .../SourceFiles/history/history_widget.cpp | 133 ++++++++++-------- Telegram/SourceFiles/history/history_widget.h | 4 +- .../history_view_compose_controls.cpp | 79 ++++------- .../controls/history_view_compose_controls.h | 1 - .../controls/history_view_forward_panel.cpp | 2 +- Telegram/SourceFiles/mainwidget.cpp | 2 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 12 +- .../SourceFiles/storage/storage_account.cpp | 75 +++++++--- .../SourceFiles/storage/storage_account.h | 2 +- .../SourceFiles/support/support_helper.cpp | 2 +- .../window/notifications_manager.cpp | 2 +- .../window/window_session_controller.cpp | 4 +- 21 files changed, 268 insertions(+), 174 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 9f76547edb..6914ca752c 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2135,8 +2135,10 @@ void ApiWrap::saveDraftsToCloud() { auto flags = MTPmessages_SaveDraft::Flags(0); auto &textWithTags = cloudDraft->textWithTags; - if (cloudDraft->previewState != Data::PreviewState::Allowed) { + if (cloudDraft->webpage.removed) { flags |= MTPmessages_SaveDraft::Flag::f_no_webpage; + } else if (!cloudDraft->webpage.url.isEmpty()) { + flags |= MTPmessages_SaveDraft::Flag::f_media; } if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) { flags |= MTPmessages_SaveDraft::Flag::f_reply_to; @@ -2149,6 +2151,7 @@ void ApiWrap::saveDraftsToCloud() { TextUtilities::ConvertTextTagsToEntities(textWithTags.tags), Api::ConvertOption::SkipLocal); + using PageFlag = MTPDinputMediaWebPage::Flag; history->startSavingCloudDraft(topicRootId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), @@ -2156,7 +2159,15 @@ void ApiWrap::saveDraftsToCloud() { history->peer->input, MTP_string(textWithTags.text), entities, - MTPInputMedia() + MTP_inputMediaWebPage( + MTP_flags(PageFlag::f_optional + | (cloudDraft->webpage.forceLargeMedia + ? PageFlag::f_force_large_media + : PageFlag()) + | (cloudDraft->webpage.forceSmallMedia + ? PageFlag::f_force_small_media + : PageFlag())), + MTP_string(cloudDraft->webpage.url)) )).done([=](const MTPBool &result, const MTP::Response &response) { const auto requestId = response.requestId; history->finishSavingCloudDraft( @@ -2243,7 +2254,7 @@ void ApiWrap::gotStickerSet( } void ApiWrap::requestWebPageDelayed(not_null page) { - if (page->pendingTill <= 0) { + if (page->failed || !page->pendingTill) { return; } _webPagesPending.emplace(page, 0); @@ -2548,7 +2559,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) { if (i->second == req) { if (i->first->pendingTill > 0) { - i->first->pendingTill = -1; + i->first->pendingTill = 0; + i->first->failed = 1; _session->data().notifyWebPageUpdateDelayed(i->first); } i = _webPagesPending.erase(i); diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 58ad920ef1..50cad31fc5 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -15,33 +15,53 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "main/main_session.h" #include "data/data_session.h" +#include "data/data_web_page.h" #include "mainwidget.h" #include "storage/localstorage.h" namespace Data { +WebPageDraft WebPageDraft::FromItem(not_null item) { + const auto previewMedia = item->media(); + const auto previewPage = previewMedia + ? previewMedia->webpage() + : nullptr; + using PageFlag = MediaWebPageFlag; + const auto previewFlags = previewMedia + ? previewMedia->webpageFlags() + : PageFlag(); + return { + .id = previewPage ? previewPage->id : 0, + .url = previewPage ? previewPage->url : QString(), + .forceLargeMedia = !!(previewFlags & PageFlag::ForceLargeMedia), + .forceSmallMedia = !!(previewFlags & PageFlag::ForceSmallMedia), + .invert = item->invertMedia(), + .manual = !!(previewFlags & PageFlag::Manual), + }; +} + Draft::Draft( const TextWithTags &textWithTags, FullReplyTo reply, const MessageCursor &cursor, - PreviewState previewState, + WebPageDraft webpage, mtpRequestId saveRequestId) : textWithTags(textWithTags) , reply(std::move(reply)) , cursor(cursor) -, previewState(previewState) +, webpage(webpage) , saveRequestId(saveRequestId) { } Draft::Draft( not_null field, FullReplyTo reply, - PreviewState previewState, + WebPageDraft webpage, mtpRequestId saveRequestId) : textWithTags(field->getTextWithTags()) , reply(std::move(reply)) , cursor(field) -, previewState(previewState) { +, webpage(webpage) { } void ApplyPeerCloudDraft( @@ -67,6 +87,23 @@ void ApplyPeerCloudDraft( const auto replyPeerId = reply.externalPeerId ? reply.externalPeerId : peerId; + auto webpage = WebPageDraft{ + .invert = draft.is_invert_media(), + .removed = draft.is_no_webpage(), + }; + if (const auto media = draft.vmedia()) { + media->match([&](const MTPDmessageMediaWebPage &data) { + const auto parsed = session->data().processWebpage( + data.vwebpage()); + if (!parsed->failed) { + webpage.forceLargeMedia = data.is_force_large_media(); + webpage.forceSmallMedia = data.is_force_small_media(); + webpage.manual = data.is_manual(); + webpage.url = parsed->url; + webpage.id = parsed->id; + } + }, [](const auto &) {}); + } auto cloudDraft = std::make_unique( textWithTags, FullReplyTo{ @@ -78,9 +115,7 @@ void ApplyPeerCloudDraft( .topicRootId = topicRootId, }, MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax), - (draft.is_no_webpage() - ? Data::PreviewState::Cancelled - : Data::PreviewState::Allowed)); + std::move(webpage)); cloudDraft->date = date; history->setCloudDraft(std::move(cloudDraft)); diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h index ef295807c4..3f5e22a231 100644 --- a/Telegram/SourceFiles/data/data_drafts.h +++ b/Telegram/SourceFiles/data/data_drafts.h @@ -30,10 +30,19 @@ void ClearPeerCloudDraft( MsgId topicRootId, TimeId date); -enum class PreviewState : char { - Allowed, - Cancelled, - EmptyOnEdit, +struct WebPageDraft { + [[nodiscard]] static WebPageDraft FromItem(not_null item); + + WebPageId id = 0; + QString url; + bool forceLargeMedia : 1 = false; + bool forceSmallMedia : 1 = false; + bool invert : 1 = false; + bool manual : 1 = false; + bool removed : 1 = false; + + friend inline bool operator==(const WebPageDraft&, const WebPageDraft&) + = default; }; struct Draft { @@ -42,19 +51,19 @@ struct Draft { const TextWithTags &textWithTags, FullReplyTo reply, const MessageCursor &cursor, - PreviewState previewState, + WebPageDraft webpage, mtpRequestId saveRequestId = 0); Draft( not_null field, FullReplyTo reply, - PreviewState previewState, + WebPageDraft webpage, mtpRequestId saveRequestId = 0); TimeId date = 0; TextWithTags textWithTags; FullReplyTo reply; // reply.messageId.msg is editMsgId for edit draft. MessageCursor cursor; - PreviewState previewState = PreviewState::Allowed; + WebPageDraft webpage; mtpRequestId saveRequestId = 0; }; @@ -180,7 +189,7 @@ using HistoryDrafts = base::flat_map>; } return (a->textWithTags == b->textWithTags) && (a->reply == b->reply) - && (a->previewState == b->previewState); + && (a->webpage == b->webpage); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index ffd9512819..769fa0df1b 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -404,6 +404,10 @@ WebPageData *Media::webpage() const { return nullptr; } +MediaWebPageFlags Media::webpageFlags() const { + return {}; +} + const SharedContact *Media::sharedContact() const { return nullptr; } @@ -1462,6 +1466,10 @@ WebPageData *MediaWebPage::webpage() const { return _page; } +MediaWebPageFlags MediaWebPage::webpageFlags() const { + return _flags; +} + bool MediaWebPage::hasReplyPreview() const { if (const auto document = MediaWebPage::document()) { return document->hasThumbnail() diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index e7e4361ae2..02d321720d 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -122,6 +122,7 @@ public: virtual DocumentData *document() const; virtual PhotoData *photo() const; virtual WebPageData *webpage() const; + virtual MediaWebPageFlags webpageFlags() const; virtual const SharedContact *sharedContact() const; virtual const Call *call() const; virtual GameData *game() const; @@ -381,6 +382,7 @@ public: DocumentData *document() const override; PhotoData *photo() const override; WebPageData *webpage() const override; + MediaWebPageFlags webpageFlags() const override; bool hasReplyPreview() const override; Image *replyPreview() const override; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index d91654e230..a174d2db7a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3261,7 +3261,8 @@ not_null Session::processWebpage(const MTPWebPage &data) { case mtpc_webPageEmpty: { const auto result = webpage(data.c_webPageEmpty().vid().v); if (result->pendingTill > 0) { - result->pendingTill = -1; // failed + result->pendingTill = 0; + result->failed = 1; notifyWebPageUpdateDelayed(result); } return result; diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index a2fadf8ad4..e6809f89c6 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -227,7 +227,7 @@ bool WebPageData::applyChanges( const QString &newAuthor, int newPendingTill) { if (newPendingTill != 0 - && (!url.isEmpty() || pendingTill < 0) + && (!url.isEmpty() || failed) && (!pendingTill || pendingTill == newPendingTill || newPendingTill < -1)) { diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index 9bfa64534c..8272e9f056 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -100,8 +100,9 @@ struct WebPageData { DocumentData *document = nullptr; WebPageCollage collage; int duration = 0; - int pendingTill = 0; - int version = 0; + TimeId pendingTill = 0; + uint32 version : 31 = 0; + uint32 failed : 1 = 0; private: void replaceDocumentGoodThumbnail(); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index adafbc39b1..9d9cbca18b 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -203,13 +203,13 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) { draft->textWithTags, draft->reply, draft->cursor, - draft->previewState)); + draft->webpage)); existing = localDraft(topicRootId); } else if (existing != draft) { existing->textWithTags = draft->textWithTags; existing->reply = draft->reply; existing->cursor = draft->cursor; - existing->previewState = draft->previewState; + existing->webpage = draft->webpage; } existing->date = draft->date; } @@ -277,7 +277,7 @@ Data::Draft *History::createCloudDraft( TextWithTags(), FullReplyTo(), MessageCursor(), - Data::PreviewState::Allowed)); + Data::WebPageDraft())); cloudDraft(topicRootId)->date = TimeId(0); } else { auto existing = cloudDraft(topicRootId); @@ -286,13 +286,13 @@ Data::Draft *History::createCloudDraft( fromDraft->textWithTags, fromDraft->reply, fromDraft->cursor, - fromDraft->previewState)); + fromDraft->webpage)); existing = cloudDraft(topicRootId); } else if (existing != fromDraft) { existing->textWithTags = fromDraft->textWithTags; existing->reply = fromDraft->reply; existing->cursor = fromDraft->cursor; - existing->previewState = fromDraft->previewState; + existing->webpage = fromDraft->webpage; } existing->date = base::unixtime::now(); existing->reply.topicRootId = topicRootId; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index d53cdbd8a3..30d653198a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -206,7 +206,6 @@ HistoryWidget::HistoryWidget( , _updateEditTimeLeftDisplay([=] { updateField(); }) , _fieldBarCancel(this, st::historyReplyCancel) , _previewTimer([=] { requestPreview(); }) -, _previewState(Data::PreviewState::Allowed) , _topBar(this, controller) , _scroll( this, @@ -443,10 +442,6 @@ HistoryWidget::HistoryWidget( _fieldLinksParser = std::make_unique(_field); _fieldLinksParser->list().changes( ) | rpl::start_with_next([=](QStringList &&parsed) { - if (_previewState == Data::PreviewState::EmptyOnEdit - && _parsedLinks != parsed) { - _previewState = Data::PreviewState::Allowed; - } _parsedLinks = std::move(parsed); checkPreview(); }, lifetime()); @@ -1604,7 +1599,7 @@ void HistoryWidget::fieldChanged() { updateSendButtonType(); if (!HasSendText(_field)) { - _previewState = Data::PreviewState::Allowed; + _previewDraft = {}; _fieldIsEmpty = true; } else if (_fieldIsEmpty) { _fieldIsEmpty = false; @@ -1668,14 +1663,14 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { .messageId = FullMsgId(_history->peer->id, _editMsgId), .topicRootId = topicRootId, }, - _previewState, + _previewDraft, _saveEditMsgRequestId)); } else { if (_replyTo || !_field->empty()) { _history->setLocalDraft(std::make_unique( _field, _replyTo, - _previewState)); + _previewDraft)); } else { _history->clearLocalDraft(topicRootId); } @@ -1783,7 +1778,7 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived( textWithTags, FullReplyTo(), cursor, - Data::PreviewState::Allowed)); + Data::WebPageDraft())); applyDraft(); return true; } @@ -1935,7 +1930,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { } // Save links from _field to _parsedLinks without generating preview. - _previewState = Data::PreviewState::Cancelled; + _previewDraft = { .removed = true }; if (_editMsgId) { _fieldLinksParser->setDisabled(!_replyEditMsg || (_replyEditMsg->media() @@ -1943,7 +1938,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { } _fieldLinksParser->parseNow(); _parsedLinks = _fieldLinksParser->list().current(); - _previewState = draft->previewState; + _previewDraft = draft->webpage; checkPreview(); return true; @@ -2471,7 +2466,7 @@ void HistoryWidget::registerDraftSource() { ? FullReplyTo{ FullMsgId(peerId, editMsgId) } : _replyTo), _field->getTextWithTags(), - _previewState, + _previewDraft, }; }; auto draftSource = Storage::MessageDraftSource{ @@ -2909,7 +2904,7 @@ void HistoryWidget::updateControlsVisibility() { if (_editMsgId || _replyTo || readyToForward() - || (_previewData && _previewData->pendingTill >= 0) + || (_previewData && !_previewData->failed) || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); @@ -3766,11 +3761,11 @@ void HistoryWidget::saveEditMsg() { cancelEdit(); return; } - const auto webPageId = (_previewState != Data::PreviewState::Allowed) + const auto webPageId = _previewDraft.removed ? CancelledWebPageId - : ((_previewData && _previewData->pendingTill >= 0) - ? _previewData->id - : WebPageId(0)); + : (_previewData && !_previewData->failed) + ? _previewData->id + : WebPageId(); const auto textWithTags = _field->getTextWithAppliedMarkdown(); const auto prepareFlags = Ui::ItemTextOptions( @@ -3925,11 +3920,11 @@ void HistoryWidget::send(Api::SendOptions options) { _cornerButtons.clearReplyReturns(); } - const auto webPageId = (_previewState != Data::PreviewState::Allowed) + const auto webPageId = _previewDraft.removed ? CancelledWebPageId - : ((_previewData && _previewData->pendingTill >= 0) - ? _previewData->id - : WebPageId(0)); + : (_previewData && !_previewData->failed) + ? _previewData->id + : WebPageId(); auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = _field->getTextWithAppliedMarkdown(); @@ -3950,7 +3945,9 @@ void HistoryWidget::send(Api::SendOptions options) { hideSelectorControlsAnimated(); - if (_previewData && _previewData->pendingTill) previewCancel(); + if (_previewData && _previewData->pendingTill) { + previewCancel(); + } setInnerFocus(); if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) { @@ -4790,7 +4787,10 @@ void HistoryWidget::toggleKeyboard(bool manual) { _field->setMaxHeight(computeMaxFieldHeight()); _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyTo) { + if (!readyToForward() + && (!_previewData || _previewData->failed) + && !_editMsgId + && !_replyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -5775,7 +5775,10 @@ void HistoryWidget::updateHistoryGeometry( } else if (writeRestriction().has_value()) { newScrollHeight -= _unblock->height(); } - if (_editMsgId || replyTo() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) { + if (_editMsgId + || replyTo() + || readyToForward() + || (_previewData && !_previewData->failed)) { newScrollHeight -= st::historyReplyHeight; } if (_kbShown) { @@ -6077,7 +6080,9 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _field->setMaxHeight(computeMaxFieldHeight()); _kbShown = false; _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyTo) { + if (!readyToForward() + && (!_previewData || _previewData->failed) + && !_replyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -6093,7 +6098,10 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _field->setMaxHeight(computeMaxFieldHeight()); _kbShown = false; _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyTo && !_editMsgId) { + if (!readyToForward() + && (!_previewData || _previewData->failed) + && !_replyTo + && !_editMsgId) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -6138,7 +6146,7 @@ int HistoryWidget::computeMaxFieldHeight() const { - ((_editMsgId || replyTo() || readyToForward() - || (_previewData && _previewData->pendingTill >= 0)) + || (_previewData && !_previewData->failed)) ? st::historyReplyHeight : 0) - (2 * st::historySendPadding) @@ -6242,6 +6250,15 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { _peer, Window::SectionShow::Way::Forward, _editMsgId); + } else if (_previewData + && !_previewData->failed + && !_previewData->pendingTill) { + //const auto history = _history; + //using namespace HistoryView::Controls; + //EditWebPageOptions( + // controller()->uiShow(), + // _previewData, + // _previewDraft); } } } @@ -7057,7 +7074,7 @@ void HistoryWidget::setFieldText( | TextUpdateEvent::SendTyping; previewCancel(); - _previewState = Data::PreviewState::Allowed; + _previewDraft = {}; } void HistoryWidget::clearFieldText( @@ -7171,7 +7188,7 @@ void HistoryWidget::setReplyFieldsFromProcessing() { TextWithTags(), id, MessageCursor(), - Data::PreviewState::Allowed)); + Data::WebPageDraft())); } } else { _replyEditMsg = item; @@ -7218,7 +7235,7 @@ void HistoryWidget::editMessage(not_null item) { _history->setLocalDraft(std::make_unique( _field, _replyTo, - _previewState)); + _previewDraft)); } else { _history->clearLocalDraft({}); } @@ -7230,23 +7247,17 @@ void HistoryWidget::editMessage(not_null item) { int(editData.text.size()), Ui::kQFixedMax }; - const auto previewPage = [&]() -> WebPageData* { - if (const auto media = item->media()) { - return media->webpage(); - } - return nullptr; - }(); - const auto previewState = previewPage - ? Data::PreviewState::Allowed - : Data::PreviewState::EmptyOnEdit; + const auto previewDraft = Data::WebPageDraft::FromItem(item); _history->setLocalEditDraft(std::make_unique( editData, FullReplyTo{ item->fullId() }, cursor, - previewState)); + previewDraft)); applyDraft(); - _previewData = previewPage; + _previewData = previewDraft.id + ? session().data().webpage(previewDraft.id).get() + : nullptr; if (_previewData) { updatePreview(); } @@ -7316,7 +7327,9 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { _processingReplyItem = _replyEditMsg = nullptr; _processingReplyTo = _replyTo = FullReplyTo(); mouseMoveEvent(0); - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) { + if (!readyToForward() + && (!_previewData || _previewData->failed) + && !_kbReplyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -7387,7 +7400,9 @@ void HistoryWidget::cancelEdit() { saveDraft(); mouseMoveEvent(nullptr); - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyTo()) { + if (!readyToForward() + && (!_previewData || _previewData->failed) + && !replyTo()) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -7408,8 +7423,8 @@ void HistoryWidget::cancelEdit() { void HistoryWidget::cancelFieldAreaState() { controller()->hideLayer(); _replyForwardPressed = false; - if (_previewData && _previewData->pendingTill >= 0) { - _previewState = Data::PreviewState::Cancelled; + if (_previewData && !_previewData->failed) { + _previewDraft = { .removed = true }; previewCancel(); _saveDraftText = true; @@ -7437,7 +7452,7 @@ void HistoryWidget::checkPreview() { const auto previewRestricted = [&] { return _peer && _peer->amRestricted(ChatRestriction::EmbedLinks); }(); - if (_previewState != Data::PreviewState::Allowed || previewRestricted) { + if (_previewDraft.removed || previewRestricted) { previewCancel(); return; } @@ -7446,7 +7461,7 @@ void HistoryWidget::checkPreview() { _api.request(base::take(_previewRequest)).cancel(); _previewLinks = links; if (_previewLinks.isEmpty()) { - if (_previewData && _previewData->pendingTill >= 0) { + if (_previewData && !_previewData->failed) { previewCancel(); } } else { @@ -7462,7 +7477,7 @@ void HistoryWidget::checkPreview() { } else if (i.value()) { _previewData = session().data().webpage(i.value()); updatePreview(); - } else if (_previewData && _previewData->pendingTill >= 0) { + } else if (_previewData && !_previewData->failed) { previewCancel(); } } @@ -7470,9 +7485,7 @@ void HistoryWidget::checkPreview() { } void HistoryWidget::requestPreview() { - if (!_previewData - || (_previewData->pendingTill <= 0) - || _previewLinks.isEmpty()) { + if (!_previewData || _previewData->failed || _previewLinks.isEmpty()) { return; } const auto links = _previewLinks; @@ -7498,11 +7511,11 @@ void HistoryWidget::gotPreview( _previewCache.insert(links, page->id); if (page->pendingTill > 0 && page->pendingTill <= base::unixtime::now()) { - page->pendingTill = -1; + page->pendingTill = 0; + page->failed = true; } - if (links == _previewLinks - && _previewState == Data::PreviewState::Allowed) { - _previewData = (page->id && page->pendingTill >= 0) + if (links == _previewLinks && !_previewDraft.removed) { + _previewData = (page->id && !page->failed) ? page.get() : nullptr; updatePreview(); @@ -7510,8 +7523,7 @@ void HistoryWidget::gotPreview( session().data().sendWebPageGamePollNotifications(); } else if (result.type() == mtpc_messageMediaEmpty) { _previewCache.insert(links, 0); - if (links == _previewLinks - && _previewState == Data::PreviewState::Allowed) { + if (links == _previewLinks && !_previewDraft.removed) { _previewData = nullptr; updatePreview(); } @@ -7520,7 +7532,7 @@ void HistoryWidget::gotPreview( void HistoryWidget::updatePreview() { _previewTimer.cancel(); - if (_previewData && _previewData->pendingTill >= 0) { + if (_previewData && !_previewData->failed) { _fieldBarCancel->show(); updateMouseTracking(); if (_previewData->pendingTill) { @@ -7924,11 +7936,12 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { } else if (hasForward) { backy -= st::historyReplyHeight; backh += st::historyReplyHeight; - } else if (_previewData && _previewData->pendingTill >= 0) { + } else if (_previewData && !_previewData->failed) { backy -= st::historyReplyHeight; backh += st::historyReplyHeight; } - auto drawWebPagePreview = (_previewData && _previewData->pendingTill >= 0) && !_replyForwardPressed; + auto drawWebPagePreview = (_previewData && !_previewData->failed) + && !_replyForwardPressed; p.setInactive( controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any)); p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1e0358eaf9..832931ef99 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "chat_helpers/bot_command.h" #include "chat_helpers/field_autocomplete.h" +#include "data/data_drafts.h" #include "window/section_widget.h" #include "ui/widgets/fields/input_field.h" #include "mtproto/sender.h" @@ -30,7 +31,6 @@ class Error; } // namespace MTP namespace Data { -enum class PreviewState : char; class PhotoMedia; } // namespace Data @@ -682,7 +682,7 @@ private: Ui::Text::String _previewTitle; Ui::Text::String _previewDescription; base::Timer _previewTimer; - Data::PreviewState _previewState = Data::PreviewState(); + Data::WebPageDraft _previewDraft; bool _replyForwardPressed = 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 151ad9cd36..bb7c842bde 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -102,7 +102,7 @@ using VoiceRecordBar = Controls::VoiceRecordBar; using ForwardPanel = Controls::ForwardPanel; [[nodiscard]] auto ShowWebPagePreview(WebPageData *page) { - return page && (page->pendingTill >= 0); + return page && !page->failed; } WebPageText ProcessWebPageData(WebPageData *page) { @@ -128,9 +128,9 @@ public: void cancel(); void checkPreview(); - [[nodiscard]] Data::PreviewState state() const; - void setState(Data::PreviewState value); - void refreshState(Data::PreviewState value, bool disable); + [[nodiscard]] Data::WebPageDraft draft() const; + void setAllowed(bool allowed); + void refreshDraft(Data::WebPageDraft draft, bool disable); [[nodiscard]] rpl::producer<> paintRequests() const; [[nodiscard]] rpl::producer titleChanges() const; @@ -145,7 +145,7 @@ private: MTP::Sender _api; MessageLinksParser _fieldLinksParser; - Data::PreviewState _previewState = Data::PreviewState(); + Data::WebPageDraft _previewDraft; QStringList _parsedLinks; QString _previewLinks; @@ -172,7 +172,6 @@ WebpageProcessor::WebpageProcessor( : _history(history) , _api(&history->session().mtp()) , _fieldLinksParser(field) -, _previewState(Data::PreviewState::Allowed) , _timer([=] { if (!ShowWebPagePreview(_previewData) || _previewLinks.isEmpty()) { @@ -198,12 +197,7 @@ WebpageProcessor::WebpageProcessor( _fieldLinksParser.list().changes( ) | rpl::start_with_next([=](QStringList &&parsed) { - if (_previewState == Data::PreviewState::EmptyOnEdit - && _parsedLinks != parsed) { - _previewState = Data::PreviewState::Allowed; - } _parsedLinks = std::move(parsed); - checkPreview(); }, _lifetime); } @@ -212,23 +206,23 @@ rpl::producer<> WebpageProcessor::paintRequests() const { return _paintRequests.events(); } -Data::PreviewState WebpageProcessor::state() const { - return _previewState; +Data::WebPageDraft WebpageProcessor::draft() const { + return _previewDraft; } -void WebpageProcessor::setState(Data::PreviewState value) { - _previewState = value; +void WebpageProcessor::setAllowed(bool allowed) { + _previewDraft.removed = !allowed; } -void WebpageProcessor::refreshState( - Data::PreviewState value, +void WebpageProcessor::refreshDraft( + Data::WebPageDraft draft, bool disable) { // Save links from _field to _parsedLinks without generating preview. - _previewState = Data::PreviewState::Cancelled; + _previewDraft = { .removed = true }; _fieldLinksParser.setDisabled(disable); _fieldLinksParser.parseNow(); _parsedLinks = _fieldLinksParser.list().current(); - _previewState = value; + _previewDraft = draft; checkPreview(); } @@ -275,21 +269,20 @@ void WebpageProcessor::getWebPagePreview() { result.match([=](const MTPDmessageMediaWebPage &d) { const auto page = _history->owner().processWebpage(d.vwebpage()); _previewCache.insert({ links, page->id }); - auto &till = page->pendingTill; - if (till > 0 && till <= base::unixtime::now()) { - till = -1; + if (page->pendingTill > 0 + && page->pendingTill <= base::unixtime::now()) { + page->pendingTill = 0; + page->failed = true; } - if (links == _previewLinks - && _previewState == Data::PreviewState::Allowed) { - _previewData = (page->id && page->pendingTill >= 0) + if (links == _previewLinks && !_previewDraft.removed) { + _previewData = (page->id && !page->failed) ? page.get() : nullptr; updatePreview(); } }, [=](const MTPDmessageMediaEmpty &d) { _previewCache.insert({ links, 0 }); - if (links == _previewLinks - && _previewState == Data::PreviewState::Allowed) { + if (links == _previewLinks && !_previewDraft.removed) { _previewData = nullptr; updatePreview(); } @@ -303,8 +296,7 @@ void WebpageProcessor::getWebPagePreview() { void WebpageProcessor::checkPreview() { const auto previewRestricted = _history->peer && _history->peer->amRestricted(ChatRestriction::EmbedLinks); - if (_previewState != Data::PreviewState::Allowed - || previewRestricted) { + if (_previewDraft.removed || previewRestricted) { cancel(); return; } @@ -1476,7 +1468,7 @@ void ComposeControls::setFieldText( if (_preview) { _preview->cancel(); - _preview->setState(Data::PreviewState::Allowed); + _preview->setAllowed(true); } } @@ -1490,7 +1482,7 @@ void ComposeControls::saveFieldToHistoryLocalDraft() { const auto key = draftKeyCurrent(); _history->setDraft( key, - std::make_unique(_field, id, _preview->state())); + std::make_unique(_field, id, _preview->draft())); } else { _history->clearDraft(draftKeyCurrent()); } @@ -1625,7 +1617,7 @@ void ComposeControls::init() { _header->previewCancelled( ) | rpl::start_with_next([=] { if (_preview) { - _preview->setState(Data::PreviewState::Cancelled); + _preview->setAllowed(false); } _saveDraftText = true; _saveDraftStart = crl::now(); @@ -2009,7 +2001,7 @@ void ComposeControls::fieldChanged() { updateSendButtonType(); _hasSendText = HasSendText(_field); if (!_hasSendText.current() && _preview) { - _preview->setState(Data::PreviewState::Allowed); + _preview->setAllowed(true); } if (updateBotCommandShown() || updateLikeShown()) { updateControlsVisibility(); @@ -2113,7 +2105,7 @@ void ComposeControls::registerDraftSource() { return Storage::MessageDraft{ _header->getDraftReply(), _field->getTextWithTags(), - _preview->state(), + _preview->draft(), }; }; auto draftSource = Storage::MessageDraftSource{ @@ -2179,7 +2171,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { } _header->editMessage({}); _header->replyToMessage({}); - _preview->refreshState(Data::PreviewState::Allowed, false); + _preview->refreshDraft({}, false); _canReplaceMedia = false; _photoEditMedia = nullptr; return; @@ -2194,7 +2186,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; if (_preview) { const auto disablePreview = (editDraft != nullptr); - _preview->refreshState(draft->previewState, disablePreview); + _preview->refreshDraft(draft->webpage, disablePreview); } if (draft == editDraft) { @@ -2215,7 +2207,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { item->fullId()); } _header->editMessage(editingId, _photoEditMedia != nullptr); - _preview->refreshState(_preview->state(), disablePreview); + _preview->refreshDraft(_preview->draft(), disablePreview); return true; } _canReplaceMedia = false; @@ -2892,15 +2884,6 @@ void ComposeControls::editMessage(not_null item) { int(editData.text.size()), Ui::kQFixedMax }; - const auto previewPage = [&]() -> WebPageData* { - if (const auto media = item->media()) { - return media->webpage(); - } - return nullptr; - }(); - const auto previewState = previewPage - ? Data::PreviewState::Allowed - : Data::PreviewState::EmptyOnEdit; const auto key = draftKey(DraftType::Edit); _history->setDraft( key, @@ -2911,7 +2894,7 @@ void ComposeControls::editMessage(not_null item) { .topicRootId = key.topicRootId(), }, cursor, - previewState)); + Data::WebPageDraft::FromItem(item))); applyDraft(); if (updateReplaceMediaButton()) { updateControlsVisibility(); @@ -2997,7 +2980,7 @@ void ComposeControls::replyToMessage(FullReplyTo id) { TextWithTags(), id, MessageCursor(), - Data::PreviewState::Allowed)); + Data::WebPageDraft())); } } else { _header->replyToMessage(id); 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 b98cbca833..f8b644a097 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -43,7 +43,6 @@ namespace Data { struct MessagePosition; struct Draft; class DraftKey; -enum class PreviewState : char; class PhotoMedia; } // namespace Data 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 f89cfa9e1d..6ae91e5743 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -493,7 +493,7 @@ void ShowReplyToChatBox( textWithTags, reply, cursor, - Data::PreviewState::Allowed)); + Data::WebPageDraft())); history->clearLocalEditDraft(topicRootId); history->session().changes().entryUpdated( thread, diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d9a8d36060..4cc65c77aa 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -604,7 +604,7 @@ bool MainWidget::shareUrl( textWithTags, FullReplyTo{ .topicRootId = topicRootId }, cursor, - Data::PreviewState::Allowed)); + Data::WebPageDraft())); history->clearLocalEditDraft(topicRootId); history->session().changes().entryUpdated( thread, diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index c7c7704447..599b38e5c9 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -575,7 +575,7 @@ chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true r chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; -chatInvite#3033e855 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector color:flags.10?int = ChatInvite; +chatInvite#cde0ec40 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector color:int = ChatInvite; chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; @@ -589,7 +589,7 @@ inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet; -stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; +stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true text_color:flags.9?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; messages.stickerSet#6e153f16 set:StickerSet packs:Vector keywords:Vector documents:Vector = messages.StickerSet; messages.stickerSetNotModified#d3f924eb = messages.StickerSet; @@ -705,7 +705,7 @@ botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument currency:string total_amount:long reply_markup:flags.2?ReplyMarkup = BotInlineMessage; -botInlineMessageMediaWebPage#809ad9a6 flags:# invert_media:flags.3?true force_large_media:flags.4?true force_small_media:flags.5?true manual:flags.7?true message:string entities:flags.1?Vector url:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaWebPage#809ad9a6 flags:# invert_media:flags.3?true force_large_media:flags.4?true force_small_media:flags.5?true manual:flags.7?true safe:flags.8?true message:string entities:flags.1?Vector url:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult; botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult; @@ -1586,7 +1586,7 @@ payments.giveawayInfoResults#cd5570 flags:# winner:flags.0?true refunded:flags.1 prepaidGiveaway#b2539d54 id:long months:int quantity:int date:int = PrepaidGiveaway; -boost#53c300c8 flags:# gift:flags.1?true giveaway:flags.2?true unclaimed:flags.3?true id:string user_id:flags.0?long giveaway_msg_id:flags.2?int date:int expires:int used_gift_slug:flags.4?string = Boost; +boost#2a1c8c71 flags:# gift:flags.1?true giveaway:flags.2?true unclaimed:flags.3?true id:string user_id:flags.0?long giveaway_msg_id:flags.2?int date:int expires:int used_gift_slug:flags.4?string multiplier:flags.5?int = Boost; premium.boostsList#86f8613c flags:# count:int boosts:Vector next_offset:flags.0?string users:Vector = premium.BoostsList; @@ -1594,7 +1594,7 @@ myBoost#c448415c flags:# slot:int peer:flags.0?Peer date:int expires:int cooldow premium.myBoosts#9ae228e2 my_boosts:Vector chats:Vector users:Vector = premium.MyBoosts; -premium.boostsStatus#3d5daae6 flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int gift_boosts:flags.3?int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue boost_url:string prepaid_giveaways:flags.3?Vector my_boost_slots:flags.2?Vector = premium.BoostsStatus; +premium.boostsStatus#4959427a flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int gift_boosts:flags.4?int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue boost_url:string prepaid_giveaways:flags.3?Vector my_boost_slots:flags.2?Vector = premium.BoostsStatus; ---functions--- @@ -2168,5 +2168,5 @@ stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; premium.getMyBoosts#be77b4a = premium.MyBoosts; -premium.applyBoost#184bc3b9 flags:# slots:flags.0?Vector peer:InputPeer = Bool; +premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = premium.MyBoosts; premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 78374de821..d444c2a1d9 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -1044,7 +1044,7 @@ void EnumerateDrafts( key, draft->reply, draft->textWithTags, - draft->previewState, + draft->webpage, draft->cursor); } for (const auto &[key, source] : sources) { @@ -1057,7 +1057,7 @@ void EnumerateDrafts( key, draft.reply, draft.textWithTags, - draft.previewState, + draft.webpage, cursor); } } @@ -1122,13 +1122,18 @@ void Account::writeDrafts(not_null history) { auto&&, // key const FullReplyTo &reply, const TextWithTags &text, - Data::PreviewState, + const Data::WebPageDraft &webpage, auto&&) { // cursor size += sizeof(qint64) // key + Serialize::stringSize(text.text) + TextUtilities::SerializeTagsSize(text.tags) + sizeof(qint64) + sizeof(qint64) // messageId - + sizeof(qint32); // previewState + + Serialize::stringSize(webpage.url) + + sizeof(qint32) // webpage.forceLargeMedia + + sizeof(qint32) // webpage.forceSmallMedia + + sizeof(qint32) // webpage.invert + + sizeof(qint32) // webpage.manual + + sizeof(qint32); // webpage.removed }; EnumerateDrafts( map, @@ -1146,7 +1151,7 @@ void Account::writeDrafts(not_null history) { const Data::DraftKey &key, const FullReplyTo &reply, const TextWithTags &text, - Data::PreviewState previewState, + const Data::WebPageDraft &webpage, auto&&) { // cursor data.stream << key.serialize() @@ -1154,7 +1159,12 @@ void Account::writeDrafts(not_null history) { << TextUtilities::SerializeTags(text.tags) << qint64(reply.messageId.peer.value) << qint64(reply.messageId.msg.bare) - << qint32(previewState); + << webpage.url + << qint32(webpage.forceLargeMedia ? 1 : 0) + << qint32(webpage.forceSmallMedia ? 1 : 0) + << qint32(webpage.invert ? 1 : 0) + << qint32(webpage.manual ? 1 : 0) + << qint32(webpage.removed ? 1 : 0); }; EnumerateDrafts( map, @@ -1206,7 +1216,7 @@ void Account::writeDraftCursors(not_null history) { const Data::DraftKey &key, auto&&, // reply auto&&, // text - Data::PreviewState, + auto&&, // webpage const MessageCursor &cursor) { // cursor data.stream << key.serialize() @@ -1370,18 +1380,33 @@ void Account::readDraftsWithCursors(not_null history) { QByteArray textTagsSerialized; qint64 keyValue = 0; qint64 messageIdPeer = 0, messageIdMsg = 0; - qint32 keyValueOld = 0, uncheckedPreviewState = 0; + qint32 keyValueOld = 0; + QString webpageUrl; + qint32 webpageForceLargeMedia = 0; + qint32 webpageForceSmallMedia = 0; + qint32 webpageInvert = 0; + qint32 webpageManual = 0; + qint32 webpageRemoved = 0; if (keysOld) { draft.stream >> keyValueOld; } else { draft.stream >> keyValue; } if (!rich) { + qint32 uncheckedPreviewState = 0; draft.stream >> text.text >> textTagsSerialized >> messageIdMsg >> uncheckedPreviewState; + enum class PreviewState : char { + Allowed, + Cancelled, + EmptyOnEdit, + }; + if (uncheckedPreviewState == int(PreviewState::Cancelled)) { + webpageRemoved = 1; + } messageIdPeer = peerId.value; } else { draft.stream @@ -1389,17 +1414,16 @@ void Account::readDraftsWithCursors(not_null history) { >> textTagsSerialized >> messageIdPeer >> messageIdMsg - >> uncheckedPreviewState; + >> webpageUrl + >> webpageForceLargeMedia + >> webpageForceSmallMedia + >> webpageInvert + >> webpageManual + >> webpageRemoved; } text.tags = TextUtilities::DeserializeTags( textTagsSerialized, text.text.size()); - auto previewState = Data::PreviewState::Allowed; - switch (static_cast(uncheckedPreviewState)) { - case Data::PreviewState::Cancelled: - case Data::PreviewState::EmptyOnEdit: - previewState = Data::PreviewState(uncheckedPreviewState); - } const auto key = keysOld ? Data::DraftKey::FromSerializedOld(keyValueOld) : Data::DraftKey::FromSerialized(keyValue); @@ -1413,7 +1437,14 @@ void Account::readDraftsWithCursors(not_null history) { .topicRootId = key.topicRootId(), }, MessageCursor(), - previewState)); + Data::WebPageDraft{ + .url = webpageUrl, + .forceLargeMedia = (webpageForceLargeMedia == 1), + .forceSmallMedia = (webpageForceSmallMedia == 1), + .invert = (webpageInvert == 1), + .manual = (webpageManual == 1), + .removed = (webpageRemoved == 1), + })); } } if (draft.stream.status() != QDataStream::Ok) { @@ -1478,9 +1509,9 @@ void Account::readDraftsWithCursorsLegacy( msgData, FullReplyTo{ FullMsgId(peerId, MsgId(msgReplyTo)) }, MessageCursor(), - (msgPreviewCancelled - ? Data::PreviewState::Cancelled - : Data::PreviewState::Allowed))); + Data::WebPageDraft{ + .removed = (msgPreviewCancelled == 1), + })); } if (editMsgId) { map.emplace( @@ -1489,9 +1520,9 @@ void Account::readDraftsWithCursorsLegacy( editData, FullReplyTo{ FullMsgId(peerId, editMsgId) }, MessageCursor(), - (editPreviewCancelled - ? Data::PreviewState::Cancelled - : Data::PreviewState::Allowed))); + Data::WebPageDraft{ + .removed = (editPreviewCancelled == 1), + })); } readDraftCursors(peerId, map); history->setDraftsMap(std::move(map)); diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index ebc5ace386..ade41f10c8 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -53,7 +53,7 @@ enum class StartResult : uchar; struct MessageDraft { FullReplyTo reply; TextWithTags textWithTags; - Data::PreviewState previewState = Data::PreviewState::Allowed; + Data::WebPageDraft webpage; }; struct MessageDraftSource { diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index c7cbb8e255..08f27db07d 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -161,7 +161,7 @@ Data::Draft OccupiedDraft(const QString &normalizedName) { + normalizedName }, FullReplyTo(), MessageCursor(), - Data::PreviewState::Allowed + Data::WebPageDraft() }; } diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index b48e630e9e..440703d2fa 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -1073,7 +1073,7 @@ void Manager::notificationActivated( int(reply.text.size()), Ui::kQFixedMax, }, - Data::PreviewState::Allowed); + Data::WebPageDraft()); history->setLocalDraft(std::move(draft)); } window->widget()->showFromTray(); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 3e151c8516..11dae1d945 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -787,7 +787,7 @@ void SessionNavigation::applyBoostChecked( MTP_flags(0), MTPVector(), // slots channel->input - )).done([=](const MTPBool &result) { + )).done([=](const MTPpremium_MyBoosts &result) { done(true); }).fail([=](const MTP::Error &error) { showToast(u"Error: "_q + error.type()); @@ -1556,7 +1556,7 @@ bool SessionController::switchInlineQuery( textWithTags, to.currentReplyTo, cursor, - Data::PreviewState::Allowed); + Data::WebPageDraft()); auto params = Window::SectionShow(); params.reapplyLocalDraft = true; From 8b4216189887a5a2eb95f18c2f1384da0277ff79 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Oct 2023 17:48:15 +0400 Subject: [PATCH 50/99] Allow sending custom webpage previews. --- Telegram/SourceFiles/api/api_common.h | 5 +- Telegram/SourceFiles/api/api_editing.cpp | 30 +++- Telegram/SourceFiles/api/api_editing.h | 5 + Telegram/SourceFiles/apiwrap.cpp | 106 +++++++++---- Telegram/SourceFiles/data/data_histories.cpp | 9 ++ Telegram/SourceFiles/data/data_histories.h | 2 + .../SourceFiles/data/data_media_types.cpp | 9 +- Telegram/SourceFiles/data/data_session.cpp | 20 +-- Telegram/SourceFiles/data/data_session.h | 4 +- Telegram/SourceFiles/data/data_web_page.cpp | 15 +- Telegram/SourceFiles/data/data_web_page.h | 14 +- Telegram/SourceFiles/history/history_item.cpp | 5 + .../history/history_item_edition.cpp | 1 + .../history/history_item_edition.h | 1 + .../SourceFiles/history/history_widget.cpp | 149 ++++++++++-------- Telegram/SourceFiles/history/history_widget.h | 3 +- .../history_view_compose_controls.cpp | 18 +-- .../controls/history_view_compose_controls.h | 3 +- .../controls/history_view_forward_panel.cpp | 83 ++++++++++ .../controls/history_view_forward_panel.h | 7 + .../view/history_view_context_menu.cpp | 3 - .../history/view/history_view_message.cpp | 35 ++-- .../view/history_view_replies_section.cpp | 6 +- .../view/history_view_scheduled_section.cpp | 6 +- .../view/media/history_view_web_page.cpp | 17 +- .../view/media/history_view_web_page.h | 5 +- .../media/stories/media_stories_reply.cpp | 4 +- Telegram/SourceFiles/ui/menu_icons.style | 4 + 28 files changed, 396 insertions(+), 173 deletions(-) diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 08ee59ca1f..c828411425 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_drafts.h" + class History; namespace Data { @@ -22,7 +24,6 @@ struct SendOptions { TimeId scheduled = 0; bool silent = false; bool handleSupportSwitch = false; - bool removeWebPageId = false; bool hideViaBot = false; }; [[nodiscard]] SendOptions DefaultSendWhenOnlineOptions(); @@ -54,7 +55,7 @@ struct MessageToSend { SendAction action; TextWithTags textWithTags; - WebPageId webPageId = 0; + Data::WebPageDraft webPage; }; struct RemoteFileInfo { diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 916e276534..4a69ed31c2 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_media.h" #include "api/api_text_entities.h" #include "ui/boxes/confirm_box.h" +#include "data/data_histories.h" #include "data/data_scheduled_messages.h" #include "data/data_session.h" +#include "data/data_web_page.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" @@ -45,6 +47,7 @@ template mtpRequestId EditMessage( not_null item, const TextWithEntities &textWithEntities, + Data::WebPageDraft webpage, SendOptions options, DoneCallback &&done, FailCallback &&fail, @@ -71,9 +74,15 @@ mtpRequestId EditMessage( | ((media && inputMedia.has_value()) ? MTPmessages_EditMessage::Flag::f_media : emptyFlag) - | (options.removeWebPageId + | (webpage.removed ? MTPmessages_EditMessage::Flag::f_no_webpage : emptyFlag) + | ((!webpage.removed && !webpage.url.isEmpty()) + ? MTPmessages_EditMessage::Flag::f_media + : emptyFlag) + | ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert) + ? MTPmessages_EditMessage::Flag::f_invert_media + : emptyFlag) | (!sentEntities.v.isEmpty() ? MTPmessages_EditMessage::Flag::f_entities : emptyFlag) @@ -89,7 +98,7 @@ mtpRequestId EditMessage( item->history()->peer->input, MTP_int(id), MTP_string(text), - inputMedia.value_or(MTPInputMedia()), + inputMedia.value_or(Data::WebPageForMTP(webpage)), MTPReplyMarkup(), sentEntities, MTP_int(options.scheduled) @@ -133,9 +142,15 @@ mtpRequestId EditMessage( FailCallback &&fail, std::optional inputMedia = std::nullopt) { const auto &text = item->originalText(); + const auto webpage = (!item->media() || !item->media()->webpage()) + ? Data::WebPageDraft{ .removed = true } + : Data::WebPageDraft{ + .id = item->media()->webpage()->id, + }; return EditMessage( item, text, + webpage, options, std::forward(done), std::forward(fail), @@ -216,12 +231,19 @@ mtpRequestId EditCaption( SendOptions options, Fn done, Fn fail) { - return EditMessage(item, caption, options, done, fail); + return EditMessage( + item, + caption, + Data::WebPageDraft(), + options, + done, + fail); } mtpRequestId EditTextMessage( not_null item, const TextWithEntities &caption, + Data::WebPageDraft webpage, SendOptions options, Fn done, Fn fail) { @@ -229,7 +251,7 @@ mtpRequestId EditTextMessage( applyUpdates(); done(id); }; - return EditMessage(item, caption, options, callback, fail); + return EditMessage(item, caption, webpage, options, callback, fail); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h index 7c132124b1..86f106f45e 100644 --- a/Telegram/SourceFiles/api/api_editing.h +++ b/Telegram/SourceFiles/api/api_editing.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryItem; +namespace Data { +struct WebPageDraft; +} // namespace Data + namespace MTP { class Error; } // namespace MTP @@ -48,6 +52,7 @@ mtpRequestId EditCaption( mtpRequestId EditTextMessage( not_null item, const TextWithEntities &caption, + Data::WebPageDraft webpage, SendOptions options, Fn done, Fn fail); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 6914ca752c..d08e4605df 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2159,15 +2159,7 @@ void ApiWrap::saveDraftsToCloud() { history->peer->input, MTP_string(textWithTags.text), entities, - MTP_inputMediaWebPage( - MTP_flags(PageFlag::f_optional - | (cloudDraft->webpage.forceLargeMedia - ? PageFlag::f_force_large_media - : PageFlag()) - | (cloudDraft->webpage.forceSmallMedia - ? PageFlag::f_force_small_media - : PageFlag())), - MTP_string(cloudDraft->webpage.url)) + Data::WebPageForMTP(cloudDraft->webpage) )).done([=](const MTPBool &result, const MTP::Response &response) { const auto requestId = response.requestId; history->finishSavingCloudDraft( @@ -3630,29 +3622,48 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTPstring msgText(MTP_string(sending.text)); auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendMessage::Flags(0); + auto mediaFlags = MTPmessages_SendMedia::Flags(0); if (action.replyTo) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to; + mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } const auto replyHeader = NewMessageReplyHeader(action); MTPMessageMedia media = MTP_messageMediaEmpty(); - if (message.webPageId == CancelledWebPageId) { + if (message.webPage.removed) { sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage; - } else if (message.webPageId) { - auto page = _session->data().webpage(message.webPageId); + } else if (const auto fields = message.webPage; fields.id) { + using PageFlag = MTPDmessageMediaWebPage::Flag; + using PendingFlag = MTPDwebPagePending::Flag; + const auto page = _session->data().webpage(fields.id); media = MTP_messageMediaWebPage( - MTP_flags(0), + MTP_flags(PageFlag() + | (fields.manual ? PageFlag::f_manual : PageFlag()) + | (fields.forceLargeMedia + ? PageFlag::f_force_large_media + : PageFlag()) + | (fields.forceSmallMedia + ? PageFlag::f_force_small_media + : PageFlag())), MTP_webPagePending( - MTP_flags(0), + MTP_flags(page->url.isEmpty() + ? PendingFlag() + : PendingFlag::f_url), MTP_long(page->id), - MTPstring(), // url + MTP_string(page->url), MTP_int(page->pendingTill))); } const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); FillMessagePostFlags(action, peer, flags); + if (message.webPage.id && message.webPage.invert) { + flags |= MessageFlag::InvertMedia; + sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media; + mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media; + } if (silentPost) { sendFlags |= MTPmessages_SendMessage::Flag::f_silent; + mediaFlags |= MTPmessages_SendMedia::Flag::f_silent; } const auto sentEntities = Api::EntitiesToMTP( _session, @@ -3665,6 +3676,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { const auto topicRootId = action.replyTo.topicRootId; if (clearCloudDraft) { sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft; + mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft; history->clearCloudDraft(topicRootId); history->startSavingCloudDraft(topicRootId); } @@ -3676,6 +3688,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { : _session->userPeerId(); if (sendAs) { sendFlags |= MTPmessages_SendMessage::Flag::f_send_as; + mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as; } const auto messagePostAuthor = peer->isBroadcast() ? _session->user()->name() @@ -3683,6 +3696,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date; + mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; } const auto viaBotId = UserId(); lastMessage = history->addNewLocalMessage( @@ -3696,27 +3710,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sending, media, HistoryMessageMarkupData()); - histories.sendPreparedMessage( - history, - action.replyTo, - randomId, - Data::Histories::PrepareMessage( - MTP_flags(sendFlags), - peer->input, - Data::Histories::ReplyToPlaceholder(), - msgText, - MTP_long(randomId), - MTPReplyMarkup(), - sentEntities, - MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()) - ), [=](const MTPUpdates &result, const MTP::Response &response) { + const auto done = [=]( + const MTPUpdates &result, + const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( topicRootId, UnixtimeFromMsgId(response.outerMsgId)); } - }, [=](const MTP::Error &error, const MTP::Response &response) { + }; + const auto fail = [=]( + const MTP::Error &error, + const MTP::Response &response) { if (error.type() == u"MESSAGE_EMPTY"_q) { lastMessage->destroy(); } else { @@ -3727,7 +3732,44 @@ void ApiWrap::sendMessage(MessageToSend &&message) { topicRootId, UnixtimeFromMsgId(response.outerMsgId)); } - }); + }; + if (!message.webPage.removed + && (message.webPage.manual || sending.empty()) + && !message.webPage.url.isEmpty()) { + using PageFlag = MTPDinputMediaWebPage::Flag; + histories.sendPreparedMessage( + history, + action.replyTo, + randomId, + Data::Histories::PrepareMessage( + MTP_flags(mediaFlags), + peer->input, + Data::Histories::ReplyToPlaceholder(), + Data::WebPageForMTP(message.webPage), + msgText, + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTP_int(message.action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) + ), done, fail); + } else { + histories.sendPreparedMessage( + history, + action.replyTo, + randomId, + Data::Histories::PrepareMessage( + MTP_flags(sendFlags), + peer->input, + Data::Histories::ReplyToPlaceholder(), + msgText, + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) + ), done, fail); + } } finishForwarding(action); diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 9c9b9059ac..9b6f556558 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -74,6 +74,15 @@ MTPInputReplyTo ReplyToForMTP( return MTPInputReplyTo(); } +MTPInputMedia WebPageForMTP(const Data::WebPageDraft &draft) { + using Flag = MTPDinputMediaWebPage::Flag; + return MTP_inputMediaWebPage( + MTP_flags(Flag::f_optional + | (draft.forceLargeMedia ? Flag::f_force_large_media : Flag()) + | (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())), + MTP_string(draft.url)); +} + Histories::Histories(not_null owner) : _owner(owner) , _readRequestsTimer([=] { sendReadRequests(); }) { diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index 336dcffc36..3d9472bb05 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -25,10 +25,12 @@ namespace Data { class Session; class Folder; +struct WebPageDraft; [[nodiscard]] MTPInputReplyTo ReplyToForMTP( not_null history, FullReplyTo replyTo); +[[nodiscard]] MTPInputMedia WebPageForMTP(const Data::WebPageDraft &draft); class Histories final { public: diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 769fa0df1b..19bab78ae8 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -1500,10 +1500,13 @@ bool MediaWebPage::replyPreviewLoaded() const { } ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const { - return { .text = options.translated + auto text = options.translated ? parent()->translatedText() - : parent()->originalText() - }; + : parent()->originalText(); + if (text.empty()) { + text = Ui::Text::Colorized(_page->url); + } + return { .text = text }; } TextWithEntities MediaWebPage::notificationText() const { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index a174d2db7a..52999367f6 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3260,6 +3260,7 @@ not_null Session::processWebpage(const MTPWebPage &data) { return processWebpage(data.c_webPage()); case mtpc_webPageEmpty: { const auto result = webpage(data.c_webPageEmpty().vid().v); + result->type = WebPageType::None; if (result->pendingTill > 0) { result->pendingTill = 0; result->failed = 1; @@ -3283,13 +3284,13 @@ not_null Session::processWebpage(const MTPDwebPage &data) { return result; } -not_null Session::processWebpage(const MTPDwebPagePending &data) { +not_null Session::processWebpage( + const MTPDwebPagePending &data) { constexpr auto kDefaultPendingTimeout = 60; const auto result = webpage(data.vid().v); webpageApplyFields( result, - WebPageType::Article, - false, + WebPageType::None, QString(), QString(), QString(), @@ -3301,6 +3302,7 @@ not_null Session::processWebpage(const MTPDwebPagePending &data) { WebPageCollage(), 0, QString(), + false, data.vdate().v ? data.vdate().v : (base::unixtime::now() + kDefaultPendingTimeout)); @@ -3314,7 +3316,6 @@ not_null Session::webpage( return webpage( id, WebPageType::Article, - false, QString(), QString(), siteName, @@ -3325,13 +3326,13 @@ not_null Session::webpage( WebPageCollage(), 0, QString(), + false, TimeId(0)); } not_null Session::webpage( WebPageId id, WebPageType type, - bool hasLargeMedia, const QString &url, const QString &displayUrl, const QString &siteName, @@ -3342,12 +3343,12 @@ not_null Session::webpage( WebPageCollage &&collage, int duration, const QString &author, + bool hasLargeMedia, TimeId pendingTill) { const auto result = webpage(id); webpageApplyFields( result, type, - hasLargeMedia, url, displayUrl, siteName, @@ -3359,6 +3360,7 @@ not_null Session::webpage( std::move(collage), duration, author, + hasLargeMedia, pendingTill); return result; } @@ -3439,7 +3441,6 @@ void Session::webpageApplyFields( webpageApplyFields( page, (story ? WebPageType::Story : ParseWebPageType(data)), - data.is_has_large_media(), qs(data.vurl()), qs(data.vdisplay_url()), siteName, @@ -3459,13 +3460,13 @@ void Session::webpageApplyFields( WebPageCollage(this, data), data.vduration().value_or_empty(), qs(data.vauthor().value_or_empty()), + data.is_has_large_media(), pendingTill); } void Session::webpageApplyFields( not_null page, WebPageType type, - bool hasLargeMedia, const QString &url, const QString &displayUrl, const QString &siteName, @@ -3477,11 +3478,11 @@ void Session::webpageApplyFields( WebPageCollage &&collage, int duration, const QString &author, + bool hasLargeMedia, TimeId pendingTill) { const auto requestPending = (!page->pendingTill && pendingTill > 0); const auto changed = page->applyChanges( type, - hasLargeMedia, url, displayUrl, siteName, @@ -3493,6 +3494,7 @@ void Session::webpageApplyFields( std::move(collage), duration, author, + hasLargeMedia, pendingTill); if (requestPending) { _session->api().requestWebPageDelayed(page); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 5f88f2235a..0b810b642c 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -552,7 +552,6 @@ public: [[nodiscard]] not_null webpage( WebPageId id, WebPageType type, - bool hasLargeMedia, const QString &url, const QString &displayUrl, const QString &siteName, @@ -563,6 +562,7 @@ public: WebPageCollage &&collage, int duration, const QString &author, + bool hasLargeMedia, TimeId pendingTill); [[nodiscard]] not_null game(GameId id); @@ -814,7 +814,6 @@ private: void webpageApplyFields( not_null page, WebPageType type, - bool hasLargeMedia, const QString &url, const QString &displayUrl, const QString &siteName, @@ -826,6 +825,7 @@ private: WebPageCollage &&collage, int duration, const QString &author, + bool hasLargeMedia, TimeId pendingTill); void gameApplyFields( diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index e6809f89c6..2e473f347f 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_channel.h" #include "data/data_document.h" +#include "lang/lang_keys.h" #include "ui/image/image.h" #include "ui/text/text_entity.h" @@ -213,7 +214,6 @@ Main::Session &WebPageData::session() const { bool WebPageData::applyChanges( WebPageType newType, - bool newHasLargeMedia, const QString &newUrl, const QString &newDisplayUrl, const QString &newSiteName, @@ -225,6 +225,7 @@ bool WebPageData::applyChanges( WebPageCollage &&newCollage, int newDuration, const QString &newAuthor, + bool newHasLargeMedia, int newPendingTill) { if (newPendingTill != 0 && (!url.isEmpty() || failed) @@ -255,7 +256,6 @@ bool WebPageData::applyChanges( }(); if (type == newType - && hasLargeMedia == newHasLargeMedia && url == resultUrl && displayUrl == resultDisplayUrl && siteName == resultSiteName @@ -267,6 +267,7 @@ bool WebPageData::applyChanges( && collage.items == newCollage.items && duration == newDuration && author == resultAuthor + && hasLargeMedia == (newHasLargeMedia ? 1 : 0) && pendingTill == newPendingTill) { return false; } @@ -274,7 +275,7 @@ bool WebPageData::applyChanges( _owner->session().api().clearWebPageRequest(this); } type = newType; - hasLargeMedia = newHasLargeMedia; + hasLargeMedia = newHasLargeMedia ? 1 : 0; url = resultUrl; displayUrl = resultDisplayUrl; siteName = resultSiteName; @@ -346,3 +347,11 @@ void WebPageData::ApplyChanges( } session->data().sendWebPageGamePollNotifications(); } + +QString WebPageData::displayedSiteName() const { + return (document && document->isWallPaper()) + ? tr::lng_media_chat_background(tr::now) + : (document && document->isTheme()) + ? tr::lng_media_color_theme(tr::now) + : siteName; +} diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index 8272e9f056..a498a5767c 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -18,6 +18,8 @@ class Session; } // namespace Data enum class WebPageType : uint8 { + None, + Message, Group, @@ -67,7 +69,6 @@ struct WebPageData { bool applyChanges( WebPageType newType, - bool newHasLargeMedia, const QString &newUrl, const QString &newDisplayUrl, const QString &newSiteName, @@ -79,6 +80,7 @@ struct WebPageData { WebPageCollage &&newCollage, int newDuration, const QString &newAuthor, + bool newHasLargeMedia, int newPendingTill); static void ApplyChanges( @@ -86,9 +88,10 @@ struct WebPageData { ChannelData *channel, const MTPmessages_Messages &result); - WebPageId id = 0; - WebPageType type = WebPageType::Article; - bool hasLargeMedia = false; + [[nodiscard]] QString displayedSiteName() const; + + const WebPageId id = 0; + WebPageType type = WebPageType::None; QString url; QString displayUrl; QString siteName; @@ -101,7 +104,8 @@ struct WebPageData { WebPageCollage collage; int duration = 0; TimeId pendingTill = 0; - uint32 version : 31 = 0; + uint32 version : 30 = 0; + uint32 hasLargeMedia : 1 = 0; uint32 failed : 1 = 0; private: diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f2b688748a..fdbf21d523 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1509,6 +1509,11 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { } else { _flags &= ~MessageFlag::HideEdited; } + if (edition.invertMedia) { + _flags |= MessageFlag::InvertMedia; + } else { + _flags &= ~MessageFlag::InvertMedia; + } if (edition.editDate != -1) { //_flags |= MTPDmessage::Flag::f_edit_date; diff --git a/Telegram/SourceFiles/history/history_item_edition.cpp b/Telegram/SourceFiles/history/history_item_edition.cpp index a9bfd1125c..4349a3c937 100644 --- a/Telegram/SourceFiles/history/history_item_edition.cpp +++ b/Telegram/SourceFiles/history/history_item_edition.cpp @@ -29,6 +29,7 @@ HistoryMessageEdition::HistoryMessageEdition( if (const auto mtpReplies = message.vreplies()) { replies = HistoryMessageRepliesData(mtpReplies); } + invertMedia = message.is_invert_media(); const auto period = message.vttl_period(); ttl = (period && period->v > 0) ? (message.vdate().v + period->v) : 0; diff --git a/Telegram/SourceFiles/history/history_item_edition.h b/Telegram/SourceFiles/history/history_item_edition.h index b24bf88365..b8f577d807 100644 --- a/Telegram/SourceFiles/history/history_item_edition.h +++ b/Telegram/SourceFiles/history/history_item_edition.h @@ -30,6 +30,7 @@ struct HistoryMessageEdition { bool useSameMarkup = false; bool useSameReactions = false; bool savePreviousMedia = false; + bool invertMedia = false; TextWithEntities textWithEntities; HistoryMessageMarkupData replyMarkup; HistoryMessageRepliesData replies; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 30d653198a..927055b6e1 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2149,6 +2149,7 @@ void HistoryWidget::showHistory( _photoEditMedia = nullptr; updateReplaceMediaButton(); _previewData = nullptr; + _previewDraft = {}; _previewCache.clear(); _fieldBarCancel->hide(); @@ -3761,12 +3762,6 @@ void HistoryWidget::saveEditMsg() { cancelEdit(); return; } - const auto webPageId = _previewDraft.removed - ? CancelledWebPageId - : (_previewData && !_previewData->failed) - ? _previewData->id - : WebPageId(); - const auto textWithTags = _field->getTextWithAppliedMarkdown(); const auto prepareFlags = Ui::ItemTextOptions( _history, @@ -3833,10 +3828,10 @@ void HistoryWidget::saveEditMsg() { }; auto options = Api::SendOptions(); - options.removeWebPageId = (webPageId == CancelledWebPageId); _saveEditMsgRequestId = Api::EditTextMessage( item, sending, + _previewDraft, options, done, fail); @@ -3920,15 +3915,9 @@ void HistoryWidget::send(Api::SendOptions options) { _cornerButtons.clearReplyReturns(); } - const auto webPageId = _previewDraft.removed - ? CancelledWebPageId - : (_previewData && !_previewData->failed) - ? _previewData->id - : WebPageId(); - auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = _field->getTextWithAppliedMarkdown(); - message.webPageId = webPageId; + message.webPage = _previewDraft; const auto ignoreSlowmodeCountdown = (options.scheduled != 0); if (showSendMessageError( @@ -4338,23 +4327,23 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) { void HistoryWidget::updateOverStates(QPoint pos) { const auto isReadyToForward = readyToForward(); const auto skip = isReadyToForward ? 0 : st::historyReplySkip; - const auto replyEditForwardInfoRect = QRect( + const auto detailsRect = QRect( skip, _field->y() - st::historySendPadding - st::historyReplyHeight, width() - skip - _fieldBarCancel->width(), st::historyReplyHeight); - auto inReplyEditForward = (_editMsgId || replyTo() || isReadyToForward) - && replyEditForwardInfoRect.contains(pos); - auto inPhotoEdit = inReplyEditForward + const auto hasWebPage = _previewData && !_previewData->failed; + const auto inDetails = detailsRect.contains(pos) + && (_editMsgId || replyTo() || isReadyToForward || hasWebPage); + const auto inPhotoEdit = inDetails && _photoEditMedia && QRect( - replyEditForwardInfoRect.x(), - (replyEditForwardInfoRect.y() - + (replyEditForwardInfoRect.height() - - st::historyReplyPreview) / 2), + detailsRect.x(), + (detailsRect.y() + + (detailsRect.height() - st::historyReplyPreview) / 2), st::historyReplyPreview, st::historyReplyPreview).contains(pos); - auto inClickable = inReplyEditForward; + const auto inClickable = inDetails; if (_inPhotoEdit != inPhotoEdit) { _inPhotoEdit = inPhotoEdit; if (_photoEditMedia) { @@ -4367,7 +4356,7 @@ void HistoryWidget::updateOverStates(QPoint pos) { _inPhotoEditOver.stop(); } } - _inReplyEditForward = inReplyEditForward && !inPhotoEdit; + _inDetails = inDetails && !inPhotoEdit; if (inClickable != _inClickable) { _inClickable = inClickable; setCursor(_inClickable ? style::cur_pointer : style::cur_default); @@ -4605,6 +4594,9 @@ bool HistoryWidget::showRecordButton() const { && !_voiceRecordBar->isListenState() && !_voiceRecordBar->isRecordingByAnotherBar() && !HasSendText(_field) + && (!_previewData + || _previewData->failed + || _previewData->pendingTill) && !readyToForward() && !_editMsgId; } @@ -6224,42 +6216,43 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { { _history->peer->id, _editMsgId }, _field->getTextWithTags(), crl::guard(_list, [=] { cancelEdit(); })); - } else if (_inReplyEditForward) { - if (isReadyToForward) { - if (e->button() != Qt::LeftButton) { - _forwardPanel->editToNextOption(); - } else { - _forwardPanel->editOptions(controller()->uiShow()); - } - } else if (const auto reply = replyTo()) { - const auto highlight = [=] { - controller()->showPeerHistory( - reply.messageId.peer, - Window::SectionShow::Way::Forward, - reply.messageId.msg); - }; - const auto history = _history; - using namespace HistoryView::Controls; - EditReplyOptions( - controller()->uiShow(), - reply, - highlight, - [=] { ClearDraftReplyTo(history, reply.messageId); }); - } else if (_editMsgId) { - controller()->showPeerHistory( - _peer, - Window::SectionShow::Way::Forward, - _editMsgId); - } else if (_previewData - && !_previewData->failed - && !_previewData->pendingTill) { - //const auto history = _history; - //using namespace HistoryView::Controls; - //EditWebPageOptions( - // controller()->uiShow(), - // _previewData, - // _previewDraft); + } else if (!_inDetails) { + return; + } else if (_previewData + && !_previewData->failed + && !_previewData->pendingTill) { + const auto history = _history; + using namespace HistoryView::Controls; + EditWebPageOptions( + controller()->uiShow(), + _previewData, + _previewDraft, + [=](Data::WebPageDraft draft) { applyPreview(draft); }); + } else if (isReadyToForward) { + if (e->button() != Qt::LeftButton) { + _forwardPanel->editToNextOption(); + } else { + _forwardPanel->editOptions(controller()->uiShow()); } + } else if (const auto reply = replyTo()) { + const auto highlight = [=] { + controller()->showPeerHistory( + reply.messageId.peer, + Window::SectionShow::Way::Forward, + reply.messageId.msg); + }; + const auto history = _history; + using namespace HistoryView::Controls; + EditReplyOptions( + controller()->uiShow(), + reply, + highlight, + [=] { ClearDraftReplyTo(history, reply.messageId); }); + } else if (_editMsgId) { + controller()->showPeerHistory( + _peer, + Window::SectionShow::Way::Forward, + _editMsgId); } } @@ -7073,8 +7066,10 @@ void HistoryWidget::setFieldText( _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; - previewCancel(); - _previewDraft = {}; + if (!_previewDraft.manual) { + previewCancel(); + _previewDraft = {}; + } } void HistoryWidget::clearFieldText( @@ -7424,12 +7419,7 @@ void HistoryWidget::cancelFieldAreaState() { controller()->hideLayer(); _replyForwardPressed = false; if (_previewData && !_previewData->failed) { - _previewDraft = { .removed = true }; - previewCancel(); - - _saveDraftText = true; - _saveDraftStart = crl::now(); - saveDraft(); + applyPreview({ .removed = true }); } else if (_editMsgId) { cancelEdit(); } else if (readyToForward()) { @@ -7441,6 +7431,20 @@ void HistoryWidget::cancelFieldAreaState() { } } +void HistoryWidget::applyPreview(Data::WebPageDraft draft) { + _previewDraft = draft; + if (draft.removed) { + previewCancel(); + } else if (draft.id) { + _previewData = session().data().webpage(draft.id).get(); + requestPreview(); + } + + _saveDraftText = true; + _saveDraftStart = crl::now(); + saveDraft(); +} + void HistoryWidget::previewCancel() { _api.request(base::take(_previewRequest)).cancel(); _previewData = nullptr; @@ -7455,6 +7459,8 @@ void HistoryWidget::checkPreview() { if (_previewDraft.removed || previewRestricted) { previewCancel(); return; + } else if (_previewDraft.manual) { + return; } const auto links = _parsedLinks.join(' '); if (_previewLinks != links) { @@ -7476,6 +7482,8 @@ void HistoryWidget::checkPreview() { }).send(); } else if (i.value()) { _previewData = session().data().webpage(i.value()); + _previewDraft.id = _previewData->id; + _previewDraft.url = _previewData->url; updatePreview(); } else if (_previewData && !_previewData->failed) { previewCancel(); @@ -7518,6 +7526,12 @@ void HistoryWidget::gotPreview( _previewData = (page->id && !page->failed) ? page.get() : nullptr; + if (_previewData) { + _previewDraft.id = _previewData->id; + _previewDraft.url = _previewData->url; + } else { + _previewDraft = {}; + } updatePreview(); } session().data().sendWebPageGamePollNotifications(); @@ -7525,6 +7539,7 @@ void HistoryWidget::gotPreview( _previewCache.insert(links, 0); if (links == _previewLinks && !_previewDraft.removed) { _previewData = nullptr; + _previewDraft = {}; updatePreview(); } } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 832931ef99..1e30935b63 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -406,6 +406,7 @@ private: void startBotCommand(); void hidePinnedMessage(); void cancelFieldAreaState(); + void applyPreview(Data::WebPageDraft draft); void unblockUser(); void sendBotStartCommand(); void joinChannel(); @@ -759,7 +760,7 @@ private: object_ptr _field; base::unique_qptr _fieldDisabled; Ui::Animations::Simple _inPhotoEditOver; - bool _inReplyEditForward = false; + bool _inDetails = false; bool _inPhotoEdit = false; bool _inClickable = 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 bb7c842bde..b5d03eeca6 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -365,7 +365,7 @@ public: [[nodiscard]] rpl::producer scrollToItemRequests() const; [[nodiscard]] rpl::producer<> editPhotoRequests() const; [[nodiscard]] MessageToEdit queryToEdit(); - [[nodiscard]] WebPageId webPageId() const; + [[nodiscard]] Data::WebPageDraft webPageDraft() const; [[nodiscard]] FullReplyTo getDraftReply() const; [[nodiscard]] rpl::producer<> editCancelled() const { @@ -399,6 +399,7 @@ private: struct Preview { WebPageData *data = nullptr; + Data::WebPageDraft draft; Ui::Text::String title; Ui::Text::String description; bool cancelled = false; @@ -958,8 +959,10 @@ bool FieldHeader::hasPreview() const { return ShowWebPagePreview(_preview.data); } -WebPageId FieldHeader::webPageId() const { - return hasPreview() ? _preview.data->id : CancelledWebPageId; +Data::WebPageDraft FieldHeader::webPageDraft() const { + return hasPreview() + ? Data::WebPageDraft{ .id = _preview.data->id } + : Data::WebPageDraft{ .removed = true }; } FullReplyTo FieldHeader::getDraftReply() const { @@ -1027,10 +1030,7 @@ MessageToEdit FieldHeader::queryToEdit() { } return { .fullId = item->fullId(), - .options = { - .scheduled = item->isScheduled() ? item->date() : 0, - .removeWebPageId = !hasPreview(), - }, + .options = { .scheduled = item->isScheduled() ? item->date() : 0 }, }; } @@ -3119,8 +3119,8 @@ void ComposeControls::initForwardProcess() { updateForwarding(); } -WebPageId ComposeControls::webPageId() const { - return _header->webPageId(); +Data::WebPageDraft ComposeControls::webPageDraft() const { + return _header->webPageDraft(); } rpl::producer ComposeControls::scrollRequests() 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 f8b644a097..06d66e69d4 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -44,6 +44,7 @@ struct MessagePosition; struct Draft; class DraftKey; class PhotoMedia; +struct WebPageDraft; } // namespace Data namespace InlineBots { @@ -207,7 +208,7 @@ public: void tryProcessKeyInput(not_null e); [[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const; - [[nodiscard]] WebPageId webPageId() const; + [[nodiscard]] Data::WebPageDraft webPageDraft() const; void setText(const TextWithTags &text); void clear(); void hidePanelsAnimated(); 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 6ae91e5743..eabef48c6b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -547,4 +547,87 @@ void EditReplyOptions( })); } +void EditWebPageOptions( + std::shared_ptr show, + not_null webpage, + Data::WebPageDraft draft, + Fn done) { + show->show(Box([=](not_null box) { + box->setTitle(rpl::single(u"Link Preview"_q)); + + struct State { + rpl::variable result; + Ui::SettingsButton *large = nullptr; + Ui::SettingsButton *small = nullptr; + }; + const auto state = box->lifetime().make_state(State{ + .result = draft, + }); + + state->large = Settings::AddButton( + box->verticalLayout(), + rpl::single(u"Force large media"_q), + st::settingsButton, + { &st::menuIconMakeBig }); + state->large->setClickedCallback([=] { + auto copy = state->result.current(); + copy.forceLargeMedia = true; + copy.forceSmallMedia = false; + state->result = copy; + }); + + state->small = Settings::AddButton( + box->verticalLayout(), + rpl::single(u"Force small media"_q), + st::settingsButton, + { &st::menuIconMakeSmall }); + state->small->setClickedCallback([=] { + auto copy = state->result.current(); + copy.forceSmallMedia = true; + copy.forceLargeMedia = false; + state->result = copy; + }); + + state->result.value( + ) | rpl::start_with_next([=](const Data::WebPageDraft &draft) { + state->large->setColorOverride(draft.forceLargeMedia + ? st::windowActiveTextFg->c + : std::optional()); + state->small->setColorOverride(draft.forceSmallMedia + ? st::windowActiveTextFg->c + : std::optional()); + }, box->lifetime()); + + Settings::AddButton( + box->verticalLayout(), + state->result.value( + ) | rpl::map([=](const Data::WebPageDraft &draft) { + return draft.invert + ? u"Above message"_q + : u"Below message"_q; + }), + st::settingsButton, + { &st::menuIconChangeOrder } + )->setClickedCallback([=] { + auto copy = state->result.current(); + copy.invert = !copy.invert; + state->result = copy; + }); + + box->addButton(tr::lng_settings_save(), [=] { + const auto weak = Ui::MakeWeak(box.get()); + auto result = state->result.current(); + result.manual = true; + done(result); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + })); + +} + } // namespace HistoryView::Controls 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 963d780af6..c881dc06f0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h @@ -20,6 +20,7 @@ class SpoilerAnimation; namespace Data { class Thread; +struct WebPageDraft; } // namespace Data namespace Window { @@ -88,4 +89,10 @@ void EditReplyOptions( Fn highlight, Fn clearOldDraft = nullptr); +void EditWebPageOptions( + std::shared_ptr show, + not_null webpage, + Data::WebPageDraft draft, + Fn done); + } // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 56d9b9b8b2..958611083d 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -539,9 +539,6 @@ bool AddRescheduleAction( if (!item || !item->isScheduled()) { continue; } - if (!item->media() || !item->media()->webpage()) { - options.removeWebPageId = true; - } Api::RescheduleMessage(item, options); // Increase the scheduled date by 1s to keep the order. options.scheduled += 1; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 9b59ae05c9..2c6a081eb2 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2568,25 +2568,38 @@ void Message::updatePressed(QPoint point) { TextForMimeData Message::selectedText(TextSelection selection) const { const auto media = this->media(); auto logEntryOriginalResult = TextForMimeData(); + const auto mediaDisplayed = (media && media->isDisplayed()); + const auto textSelection = (mediaDisplayed && invertMedia()) + ? media->skipSelection(selection) + : selection; + const auto mediaSelection = !invertMedia() + ? skipTextSelection(selection) + : selection; auto textResult = hasVisibleText() - ? text().toTextForMimeData(selection) + ? text().toTextForMimeData(textSelection) : TextForMimeData(); - auto skipped = skipTextSelection(selection); - auto mediaDisplayed = (media && media->isDisplayed()); auto mediaResult = (mediaDisplayed || isHiddenByGroup()) - ? media->selectedText(skipped) + ? media->selectedText(mediaSelection) : TextForMimeData(); if (auto entry = logEntryOriginal()) { - const auto originalSelection = mediaDisplayed - ? media->skipSelection(skipped) - : skipped; + const auto originalSelection = (mediaDisplayed && invertMedia()) + ? skipTextSelection(textSelection) + : mediaDisplayed + ? media->skipSelection(mediaSelection) + : skipTextSelection(selection); logEntryOriginalResult = entry->selectedText(originalSelection); } - auto result = textResult; + auto &first = (mediaDisplayed && invertMedia()) + ? mediaResult + : textResult; + auto &second = (mediaDisplayed && invertMedia()) + ? textResult + : mediaResult; + auto result = first; if (result.empty()) { - result = std::move(mediaResult); - } else if (!mediaResult.empty()) { - result.append(u"\n\n"_q).append(std::move(mediaResult)); + result = std::move(second); + } else if (!second.empty()) { + result.append(u"\n\n"_q).append(std::move(second)); } if (result.empty()) { result = std::move(logEntryOriginalResult); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 49fac6d314..162243feb8 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1170,11 +1170,9 @@ void RepliesWidget::send(Api::SendOptions options) { _cornerButtons.clearReplyReturns(); } - const auto webPageId = _composeControls->webPageId(); - auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); - message.webPageId = webPageId; + message.webPage = _composeControls->webPageDraft(); const auto error = GetErrorTextForSending( _history->peer, @@ -1213,6 +1211,7 @@ void RepliesWidget::edit( return; } const auto textWithTags = _composeControls->getTextWithAppliedMarkdown(); + const auto webpage = _composeControls->webPageDraft(); const auto prepareFlags = Ui::ItemTextOptions( _history, session().user()).flags; @@ -1274,6 +1273,7 @@ void RepliesWidget::edit( *saveEditMsgRequestId = Api::EditTextMessage( item, sending, + webpage, options, crl::guard(this, done), crl::guard(this, fail)); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index d8db92e6df..1ee20e414a 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -585,11 +585,11 @@ void ScheduledWidget::send() { } void ScheduledWidget::send(Api::SendOptions options) { - const auto webPageId = _composeControls->webPageId(); + const auto webPageDraft = _composeControls->webPageDraft(); auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); - message.webPageId = webPageId; + message.webPage = webPageDraft; session().api().sendMessage(std::move(message)); @@ -635,6 +635,7 @@ void ScheduledWidget::edit( return; } const auto textWithTags = _composeControls->getTextWithAppliedMarkdown(); + const auto webpage = _composeControls->webPageDraft(); const auto prepareFlags = Ui::ItemTextOptions( _history, session().user()).flags; @@ -696,6 +697,7 @@ void ScheduledWidget::edit( *saveEditMsgRequestId = Api::EditTextMessage( item, sending, + webpage, options, crl::guard(this, done), crl::guard(this, fail)); 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 e58ca94108..fd9a5b0895 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -308,11 +308,12 @@ QSize WebPage::countOptimalSize() { Ui::WebpageTextDescriptionOptions(), context); } - if (!displayedSiteName().isEmpty()) { + const auto siteName = _data->displayedSiteName(); + if (!siteName.isEmpty()) { _siteNameLines = 1; _siteName.setMarkedText( st::webPageTitleStyle, - Ui::Text::Link(displayedSiteName(), _data->url), + Ui::Text::Link(siteName, _data->url), Ui::WebpageTextTitleOptions()); } if (_title.isEmpty() && !title.isEmpty()) { @@ -868,6 +869,10 @@ TextSelection WebPage::adjustSelection(TextSelection selection, TextSelectType t return { siteNameSelection.from, fromDescriptionSelection(descriptionSelection).to }; } +uint16 WebPage::fullSelectionLength() const { + return _siteName.length() + _title.length() + _description.length(); +} + void WebPage::clickHandlerActiveChanged( const ClickHandlerPtr &p, bool active) { @@ -994,14 +999,6 @@ int WebPage::bottomInfoPadding() const { return result; } -QString WebPage::displayedSiteName() const { - return (_data->document && _data->document->isWallPaper()) - ? tr::lng_media_chat_background(tr::now) - : (_data->document && _data->document->isTheme()) - ? tr::lng_media_color_theme(tr::now) - : _data->siteName; -} - WebPage::~WebPage() { history()->owner().unregisterWebPageView(_data, _parent); if (_photoMedia) { 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 2bc65bdedc..18e7f032a5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -41,9 +41,7 @@ public: [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; - uint16 fullSelectionLength() const override { - return _title.length() + _description.length(); - } + uint16 fullSelectionLength() const override; bool hasTextForCopy() const override { // We do not add _title and _description in FullSelection text copy. return false; @@ -119,7 +117,6 @@ private: [[nodiscard]] int bottomInfoPadding() const; [[nodiscard]] bool isLogEntryOriginal() const; - [[nodiscard]] QString displayedSiteName() const; [[nodiscard]] ClickHandlerPtr replaceAttachLink( const ClickHandlerPtr &link) const; [[nodiscard]] bool asArticle() const; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 4da1201f08..bb3a834a60 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -193,11 +193,11 @@ void ReplyArea::sendReaction(const Data::ReactionId &id) { } void ReplyArea::send(Api::SendOptions options) { - const auto webPageId = _controls->webPageId(); + const auto webPageDraft = _controls->webPageDraft(); auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = _controls->getTextWithAppliedMarkdown(); - message.webPageId = webPageId; + message.webPage = webPageDraft; send(std::move(message), options); } diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 1a840e0f48..59cd81b535 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -202,3 +202,7 @@ mediaSpeedVeryFast: icon {{ "player/speed/audiospeed_menu_1.7", mediaviewMenuFg mediaSpeedVeryFastActive: icon {{ "player/speed/audiospeed_menu_1.7", mediaviewTextLinkFg }}; mediaSpeedSuperFast: icon {{ "player/speed/audiospeed_menu_2.0", mediaviewMenuFg }}; mediaSpeedSuperFastActive: icon {{ "player/speed/audiospeed_menu_2.0", mediaviewTextLinkFg }}; + +menuIconMakeBig: icon {{ "player/player_fullscreen", menuIconColor }}; +menuIconMakeSmall: icon {{ "player/player_minimize", menuIconColor }}; +menuIconChangeOrder: icon {{ "player/player_order", menuIconColor }}; From 20c63b98c74e012352a3f1c53f6e814e44e5ee76 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 21 Oct 2023 09:36:22 +0400 Subject: [PATCH 51/99] Fix build with Xcode. --- Telegram/SourceFiles/apiwrap.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index d08e4605df..fbc6553cfb 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2151,7 +2151,6 @@ void ApiWrap::saveDraftsToCloud() { TextUtilities::ConvertTextTagsToEntities(textWithTags.tags), Api::ConvertOption::SkipLocal); - using PageFlag = MTPDinputMediaWebPage::Flag; history->startSavingCloudDraft(topicRootId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), @@ -3736,7 +3735,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) { if (!message.webPage.removed && (message.webPage.manual || sending.empty()) && !message.webPage.url.isEmpty()) { - using PageFlag = MTPDinputMediaWebPage::Flag; histories.sendPreparedMessage( history, action.replyTo, From 8c28ce4c99ad64a1372688b512cb9e2c8e67f436 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Oct 2023 09:00:09 +0400 Subject: [PATCH 52/99] [stats] Fix build on layer 166. --- Telegram/SourceFiles/api/api_statistics.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index 79de3c2cde..c3286f667c 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -501,9 +501,9 @@ rpl::producer Boosts::request() { return lifetime; } - _api.request(MTPstories_GetBoostsStatus( + _api.request(MTPpremium_GetBoostsStatus( _peer->input - )).done([=](const MTPstories_BoostsStatus &result) { + )).done([=](const MTPpremium_BoostsStatus &result) { const auto &data = result.data(); const auto hasPremium = !!data.vpremium_audience(); const auto premiumMemberCount = hasPremium @@ -553,28 +553,29 @@ void Boosts::requestBoosts( } constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); constexpr auto kTlLimit = tl::make_int(kLimit); - _requestId = _api.request(MTPstories_GetBoostersList( + _requestId = _api.request(MTPpremium_GetBoostsList( + MTP_flags(0), _peer->input, MTP_string(token.next), token.next.isEmpty() ? kTlFirstSlice : kTlLimit - )).done([=](const MTPstories_BoostersList &result) { + )).done([=](const MTPpremium_BoostsList &result) { _requestId = 0; const auto &data = result.data(); _peer->owner().processUsers(data.vusers()); auto list = std::vector(); - list.reserve(data.vboosters().v.size()); - for (const auto &boost : data.vboosters().v) { + list.reserve(data.vboosts().v.size()); + for (const auto &boost : data.vboosts().v) { list.push_back({ - boost.data().vuser_id().v, + boost.data().vuser_id().value_or_empty(), QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v), }); } done(Data::BoostsListSlice{ .list = std::move(list), .total = data.vcount().v, - .allLoaded = (data.vcount().v == data.vboosters().v.size()), + .allLoaded = (data.vcount().v == data.vboosts().v.size()), .token = Data::BoostsListSlice::OffsetToken{ data.vnext_offset() ? qs(*data.vnext_offset()) From 4709e11e465b75ce866719650b06e14a9e239020 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Oct 2023 14:58:40 +0400 Subject: [PATCH 53/99] Support two-color quote outlines. --- .../history/history_item_components.cpp | 22 +- .../history/view/history_view_element.cpp | 23 +- .../history/view/history_view_element.h | 28 ++- .../history/view/history_view_message.cpp | 34 +-- .../history/view/history_view_message.h | 4 - .../history/view/history_view_view_button.cpp | 7 +- .../view/media/history_view_document.cpp | 2 +- .../media/history_view_extended_preview.cpp | 2 +- .../history/view/media/history_view_game.cpp | 5 +- .../history/view/media/history_view_gif.cpp | 2 +- .../view/media/history_view_giveaway.cpp | 5 +- .../view/media/history_view_media_grouped.cpp | 2 +- .../history/view/media/history_view_photo.cpp | 2 +- .../view/media/history_view_web_page.cpp | 9 +- Telegram/SourceFiles/ui/chat/chat_style.cpp | 222 +++++++++++++----- Telegram/SourceFiles/ui/chat/chat_style.h | 59 ++++- Telegram/SourceFiles/ui/chat/chat_theme.cpp | 2 +- Telegram/lib_ui | 2 +- 18 files changed, 290 insertions(+), 142 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 9968347b4c..a5efe9ba88 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -682,15 +682,19 @@ void HistoryMessageReply::paint( const auto hasQuote = !_fields.quote.empty(); const auto selected = context.selected(); const auto colorIndexPlusOne = context.outbg ? 0 : _colorIndexPlusOne; + const auto twoColored = colorIndexPlusOne + && Ui::ColorIndexTwoColored(colorIndexPlusOne - 1); const auto cache = !inBubble ? (hasQuote - ? st->serviceQuoteCache() - : st->serviceReplyCache()).get() + ? st->serviceQuoteCache(twoColored) + : st->serviceReplyCache(twoColored)).get() : colorIndexPlusOne ? (hasQuote ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() - : (hasQuote ? stm->quoteCache : stm->replyCache).get(); + : (hasQuote + ? (twoColored ? stm->quoteCacheTwo : stm->quoteCache) + : (twoColored ? stm->replyCacheTwo : stm->replyCache)).get(); const auto "eSt = hasQuote ? st::messageTextStyle.blockquote : st::messageQuoteStyle; @@ -763,12 +767,10 @@ void HistoryMessageReply::paint( if (w > textLeft + st::historyReplyPadding.right()) { w -= textLeft + st::historyReplyPadding.right(); p.setPen(!inBubble - ? st->msgImgReplyBarColor() + ? st->msgImgReplyBarColor()->c : colorIndexPlusOne - ? HistoryView::FromNameFg( - context, - colorIndexPlusOne - 1) - : stm->msgServiceFg); + ? FromNameFg(context, colorIndexPlusOne - 1) + : stm->msgServiceFg->c); _name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); if (originalVia && w > _name.maxWidth() + st::msgServiceFont->spacew) { p.setFont(st::msgServiceFont); @@ -802,7 +804,7 @@ void HistoryMessageReply::paint( auto copy = std::optional(); if (inBubble && _colorIndexPlusOne) { copy.emplace(*replyToTextPalette); - owned.emplace(cache->outline); + owned.emplace(cache->icon); copy->linkFg = owned->color(); replyToTextPalette = &*copy; } @@ -821,7 +823,7 @@ void HistoryMessageReply::paint( } } else { p.setFont(st::msgDateFont); - p.setPen(cache->outline); + p.setPen(cache->icon); p.drawTextLeft( x + textLeft, (y + (_height - st::msgDateFont->height) / 2), diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 58e1676b03..e490fc0ff9 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -467,6 +467,7 @@ Element::Element( | (IsItemScheduledUntilOnline(data) ? Flag::ScheduledUntilOnline : Flag())) +, _colorIndex(data->computeColorIndex()) , _context(delegate->elementContext()) { history()->owner().registerItemView(this); refreshMedia(replacing); @@ -490,6 +491,10 @@ not_null Element::history() const { return _data->history(); } +uint8 Element::colorIndex() const { + return _colorIndex; +} + QDateTime Element::dateTime() const { return _dateTime; } @@ -557,8 +562,8 @@ void Element::prepareCustomEmojiPaint( } clearCustomEmojiRepaint(); p.setInactive(context.paused); - if (!_heavyCustomEmoji) { - _heavyCustomEmoji = true; + if (!(_flags & Flag::HeavyCustomEmoji)) { + _flags |= Flag::HeavyCustomEmoji; history()->owner().registerHeavyViewPart(const_cast(this)); } } @@ -572,8 +577,8 @@ void Element::prepareCustomEmojiPaint( } clearCustomEmojiRepaint(); p.setInactive(context.paused); - if (!_heavyCustomEmoji) { - _heavyCustomEmoji = true; + if (!(_flags & Flag::HeavyCustomEmoji)) { + _flags |= Flag::HeavyCustomEmoji; history()->owner().registerHeavyViewPart(const_cast(this)); } } @@ -1411,7 +1416,7 @@ auto Element::verticalRepaintRange() const -> VerticalRepaintRange { } bool Element::hasHeavyPart() const { - return _heavyCustomEmoji; + return (_flags & Flag::HeavyCustomEmoji); } void Element::checkHeavyPart() { @@ -1445,8 +1450,8 @@ void Element::unloadHeavyPart() { if (_media) { _media->unloadHeavyPart(); } - if (_heavyCustomEmoji) { - _heavyCustomEmoji = false; + if (_flags & Flag::HeavyCustomEmoji) { + _flags &= ~Flag::HeavyCustomEmoji; _text.unloadPersistentAnimation(); if (const auto reply = data()->Get()) { reply->unloadPersistentAnimation(); @@ -1623,8 +1628,8 @@ Element::~Element() { // Delete media while owner still exists. clearSpecialOnlyEmoji(); base::take(_media); - if (_heavyCustomEmoji) { - _heavyCustomEmoji = false; + if (_flags & Flag::HeavyCustomEmoji) { + _flags &= ~Flag::HeavyCustomEmoji; _text.unloadPersistentAnimation(); checkHeavyPart(); } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 6c16cef305..d570093c12 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -277,18 +277,19 @@ class Element , public base::has_weak_ptr { public: enum class Flag : uint16 { - ServiceMessage = 0x0001, - NeedsResize = 0x0002, - AttachedToPrevious = 0x0004, - AttachedToNext = 0x0008, + ServiceMessage = 0x0001, + NeedsResize = 0x0002, + AttachedToPrevious = 0x0004, + AttachedToNext = 0x0008, BubbleAttachedToPrevious = 0x0010, - BubbleAttachedToNext = 0x0020, - HiddenByGroup = 0x0040, - SpecialOnlyEmoji = 0x0080, - CustomEmojiRepainting = 0x0100, - ScheduledUntilOnline = 0x0200, - TopicRootReply = 0x0400, - MediaOverriden = 0x0800, + BubbleAttachedToNext = 0x0020, + HiddenByGroup = 0x0040, + SpecialOnlyEmoji = 0x0080, + CustomEmojiRepainting = 0x0100, + ScheduledUntilOnline = 0x0200, + TopicRootReply = 0x0400, + MediaOverriden = 0x0800, + HeavyCustomEmoji = 0x1000, }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -306,6 +307,7 @@ public: [[nodiscard]] Context context() const; void refreshDataId(); + [[nodiscard]] uint8 colorIndex() const; [[nodiscard]] QDateTime dateTime() const; [[nodiscard]] int y() const; @@ -592,9 +594,11 @@ private: int _indexInBlock = -1; mutable Flags _flags = Flag(0); - mutable bool _heavyCustomEmoji = false; + uint8 _colorIndex = 0; Context _context = Context(); }; +constexpr auto size = sizeof(Element); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 2c6a081eb2..b89a6385aa 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -366,12 +366,6 @@ QString FastReplyText() { } // namespace -style::color FromNameFg( - const Ui::ChatPaintContext &context, - uint8 colorIndex) { - return Ui::FromNameFg(context.st, context.selected(), colorIndex); -} - struct Message::CommentsButton { std::unique_ptr ripple; std::vector userpics; @@ -1358,8 +1352,8 @@ void Message::paintFromName( Assert(from || info); const auto st = context.st; const auto nameFg = !context.outbg - ? FromNameFg(context, from ? from->colorIndex() : info->colorIndex) - : stm->msgServiceFg; + ? FromNameFg(context, colorIndex()) + : stm->msgServiceFg->c; const auto nameText = [&] { if (from) { validateFromNameText(from); @@ -1374,11 +1368,8 @@ void Message::paintFromName( const auto x = availableLeft + std::min(availableWidth - statusWidth, nameText->maxWidth()); const auto y = trect.top(); - const auto color = QColor( - nameFg->c.red(), - nameFg->c.green(), - nameFg->c.blue(), - nameFg->c.alpha() * 115 / 255); + auto color = nameFg; + color.setAlpha(115); const auto user = from->asUser(); const auto id = user ? user->emojiStatusId() : 0; if (_fromNameStatus->id != id) { @@ -1630,7 +1621,7 @@ void Message::paintText( .availableWidth = trect.width(), .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->quoteCache.get(), + .blockquote = context.quoteCache(colorIndex()), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, @@ -3064,14 +3055,13 @@ void Message::updateViewButtonExistence() { } else if (_viewButton) { return; } - auto repainter = [=] { repaint(); }; - const auto index = item->computeColorIndex(); - _viewButton = sponsored - ? std::make_unique( - sponsored, - index, - std::move(repainter)) - : std::make_unique(media, index, std::move(repainter)); + auto make = [=](auto &&from) { + return std::make_unique( + std::forward(from), + colorIndex(), + [=] { repaint(); }); + }; + _viewButton = sponsored ? make(sponsored) : make(media); } void Message::initLogEntryOriginal() { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 5556660079..33c1b66074 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -56,10 +56,6 @@ struct BottomRippleMask { int shift = 0; }; -[[nodiscard]] style::color FromNameFg( - const Ui::ChatPaintContext &context, - uint8 colorIndex); - class Message final : public Element { public: Message( diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index 448ec0624d..afffc0b2b4 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -235,8 +235,9 @@ void ViewButton::draw( const auto stm = context.messageStyle(); const auto selected = context.selected(); + const auto twoColored = Ui::ColorIndexTwoColored(_inner->colorIndex); const auto cache = context.outbg - ? stm->replyCache.get() + ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() : context.st->coloredReplyCache(selected, _inner->colorIndex).get(); const auto radius = st::historyPagePreview.radius; @@ -249,7 +250,7 @@ void ViewButton::draw( p.setBrush(cache->bg); p.drawRoundedRect(r, radius, radius); - p.setPen(cache->outline); + p.setPen(cache->icon); _inner->text.drawElided( p, r.left(), @@ -266,7 +267,7 @@ void ViewButton::draw( r.left() + r.width() - icon.width() - padding, r.top() + padding, r.width(), - cache->outline); + cache->icon); } if (_inner->lastWidth != r.width()) { _inner->lastWidth = r.width(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 7184a7fc0b..071b6bb3df 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -749,7 +749,7 @@ void Document::draw( .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->quoteCache.get(), + .blockquote = context.quoteCache(parent()->colorIndex()), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, 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 a4695e4a1c..6c259748d7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -236,7 +236,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->quoteCache.get(), + .blockquote = context.quoteCache(parent()->colorIndex()), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 80204eb5d0..70e92a6e91 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -220,15 +220,16 @@ void Game::draw(Painter &p, const PaintContext &context) const { auto paintw = inner.width(); const auto selected = context.selected(); + const auto twoColored = Ui::ColorIndexTwoColored(_colorIndex); const auto cache = context.outbg - ? stm->replyCache.get() + ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() : st->coloredReplyCache(selected, _colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); auto lineHeight = UnitedLineHeight(); if (_titleLines) { - p.setPen(cache->outline); + p.setPen(cache->icon); p.setTextPalette(context.outbg ? stm->semiboldPalette : st->coloredTextPalette(selected, _colorIndex)); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index f30a8d550c..b30035efac 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -721,7 +721,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->quoteCache.get(), + .blockquote = context.quoteCache(parent()->colorIndex()), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 47ecb030db..07a96f934c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -347,8 +347,9 @@ void Giveaway::paintChannels( const auto ratio = style::DevicePixelRatio(); const auto stm = context.messageStyle(); const auto selected = context.selected(); + const auto twoColored = Ui::ColorIndexTwoColored(_colorIndex); const auto cache = context.outbg - ? stm->replyCache.get() + ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() : context.st->coloredReplyCache(selected, _colorIndex).get(); if (_channelCorners[0].isNull() || _channelBg != cache->bg) { _channelBg = cache->bg; @@ -357,7 +358,7 @@ void Giveaway::paintChannels( style::colorizeImage(image, cache->bg, &image); } } - p.setPen(cache->outline); + p.setPen(cache->icon); const auto padding = st::chatGiveawayChannelPadding; for (const auto &channel : _channels) { const auto &thumbnail = channel.thumbnail; 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 fc7263386f..79cbcf869f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -368,7 +368,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->quoteCache.get(), + .blockquote = context.quoteCache(parent()->colorIndex()), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index a83991bf6c..9d09faf179 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -406,7 +406,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { .availableWidth = captionw, .palette = &stm->textPalette, .pre = stm->preCache.get(), - .blockquote = stm->quoteCache.get(), + .blockquote = context.quoteCache(parent()->colorIndex()), .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, 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 fd9a5b0895..2bce7b939c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -546,8 +546,9 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); const auto selected = context.selected(); + const auto twoColored = Ui::ColorIndexTwoColored(_colorIndex); const auto cache = context.outbg - ? stm->replyCache.get() + ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() : st->coloredReplyCache(selected, _colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); @@ -601,7 +602,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { paintw -= pw + st::webPagePhotoDelta; } if (_siteNameLines) { - p.setPen(cache->outline); + p.setPen(cache->icon); p.setTextPalette(context.outbg ? stm->semiboldPalette : st->coloredTextPalette(selected, _colorIndex)); @@ -703,10 +704,10 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { if (_openButtonWidth) { p.setFont(st::semiboldFont); - p.setPen(cache->outline); + p.setPen(cache->icon); const auto end = inner.y() + inner.height() + _st.padding.bottom(); const auto line = st::historyPageButtonLine; - auto color = cache->outline; + auto color = cache->icon; color.setAlphaF(color.alphaF() * 0.3); p.fillRect(inner.x(), end, inner.width(), line, color); const auto top = end + st::historyPageButtonPadding.top(); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 8e241d12f1..5f817b44c8 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -31,16 +31,16 @@ void EnsureCorners( void EnsureBlockquoteCache( std::unique_ptr &cache, - const style::color &color) { + Fn values) { if (cache) { return; } cache = std::make_unique(); - cache->bg = color->c; - cache->bg.setAlphaF(0.12); - cache->outline = color->c; - cache->outline.setAlphaF(0.9); - cache->icon = cache->outline; + const auto &colors = values(); + cache->bg = colors.bg; + cache->outline1 = colors.outline1; + cache->outline2 = colors.outline2; + cache->icon = colors.name; } void EnsurePreCache( @@ -56,11 +56,12 @@ void EnsurePreCache( if (!bg) { cache->bg.setAlphaF(0.12); } - cache->outline = color->c; - cache->outline.setAlphaF(0.9); + cache->outline1 = color->c; + cache->outline1.setAlphaF(0.9); + cache->outline2 = cache->outline1; cache->header = color->c; cache->header.setAlphaF(0.25); - cache->icon = cache->outline; + cache->icon = cache->outline1; cache->icon.setAlphaF(0.6); } @@ -74,6 +75,15 @@ not_null ChatPaintContext::imageStyle() const { return &st->imageStyle(selected()); } +not_null ChatPaintContext::quoteCache( + uint8 colorIndex) const { + return !outbg + ? st->coloredQuoteCache(selected(), colorIndex).get() + : ColorIndexTwoColored(colorIndex) + ? messageStyle()->quoteCacheTwo.get() + : messageStyle()->quoteCache.get(); +} + int HistoryServiceMsgRadius() { static const auto result = [] { const auto minMessageHeight = st::msgServicePadding.top() @@ -99,6 +109,87 @@ int HistoryServiceMsgInvertedShrink() { return result; } +ColorIndexValues ComputeColorIndexValues( + not_null st, + bool selected, + uint8 colorIndex) { + if (colorIndex < kSimpleColorIndexCount) { + const style::color list[] = { + st->historyPeer1NameFg(), + st->historyPeer2NameFg(), + st->historyPeer3NameFg(), + st->historyPeer4NameFg(), + st->historyPeer5NameFg(), + st->historyPeer6NameFg(), + st->historyPeer7NameFg(), + st->historyPeer8NameFg(), + }; + const style::color listSelected[] = { + st->historyPeer1NameFgSelected(), + st->historyPeer2NameFgSelected(), + st->historyPeer3NameFgSelected(), + st->historyPeer4NameFgSelected(), + st->historyPeer5NameFgSelected(), + st->historyPeer6NameFgSelected(), + st->historyPeer7NameFgSelected(), + st->historyPeer8NameFgSelected(), + }; + const auto paletteIndex = ColorIndexToPaletteIndex(colorIndex); + auto result = ColorIndexValues{ + .name = (selected ? listSelected : list)[paletteIndex]->c, + }; + result.bg = result.name; + result.bg.setAlphaF(0.12); + result.outline1 = result.name; + result.outline1.setAlphaF(0.9); + result.outline2 = result.outline1; + return result; + } + struct Pair { + QColor outline1; + QColor outline2; + }; + const Pair list[] = { + { QColor(0xE1, 0x50, 0x52), QColor(0xF9, 0xAE, 0x63) }, // Red + { QColor(0xE0, 0x80, 0x2B), QColor(0xFA, 0xC5, 0x34) }, // Orange + { QColor(0xA0, 0x5F, 0xF3), QColor(0xF4, 0x8F, 0xFF) }, // Violet + { QColor(0x27, 0xA9, 0x10), QColor(0xA7, 0xDC, 0x57) }, // Green + { QColor(0x27, 0xAC, 0xCE), QColor(0x82, 0xE8, 0xD6) }, // Cyan + { QColor(0x33, 0x91, 0xD4), QColor(0x7D, 0xD3, 0xF0) }, // Blue + { QColor(0xD1, 0x48, 0x72), QColor(0xFF, 0xBE, 0xA0) }, // Pink + }; + const auto &pair = list[colorIndex - kSimpleColorIndexCount]; + auto bg = pair.outline1; + bg.setAlphaF(0.12); + return { + .name = st->dark() ? pair.outline2 : pair.outline1, + .bg = bg, + .outline1 = pair.outline1, + .outline2 = pair.outline2, + }; +} + +bool ColorIndexTwoColored(uint8 colorIndex) { + return (colorIndex >= kSimpleColorIndexCount); +} + +ColorIndexValues SimpleColorIndexValues(QColor color, bool twoColored) { + auto bg = color; + bg.setAlphaF(0.12); + auto outline1 = color; + outline1.setAlphaF(0.9); + auto outline2 = outline1; + if (twoColored) { + outline2.setAlphaF(0.3); + } + return { + .name = color, + .bg = bg, + .outline1 = outline1, + .outline2 = outline2, + }; +} + ChatStyle::ChatStyle() { finalize(); make(_historyPsaForwardPalette, st::historyPsaForwardPalette); @@ -453,6 +544,8 @@ ChatStyle::ChatStyle() { &MessageImageStyle::historyVideoMessageMute, st::historyVideoMessageMute, st::historyVideoMessageMuteSelected); + + updateDarkValue(); } ChatStyle::ChatStyle(not_null isolated) @@ -464,6 +557,13 @@ void ChatStyle::apply(not_null theme) { applyCustomPalette(theme->palette()); } +void ChatStyle::updateDarkValue() { + const auto withBg = [&](const QColor &color) { + return CountContrast(windowBg()->c, color); + }; + _dark = (withBg({ 0, 0, 0 }) < withBg({ 255, 255, 255 })); +} + void ChatStyle::applyCustomPalette(const style::palette *palette) { assignPalette(palette ? palette : style::main_palette::get().get()); if (palette) { @@ -547,10 +647,13 @@ void ChatStyle::assignPalette(not_null palette) { = stm.semiboldPalette.linkAlwaysActive = (stm.textPalette.linkFg->c == stm.historyTextFg->c); } - - for (auto &palette : _coloredTextPalettes) { - palette.inited = false; + for (auto &values : _coloredValues) { + values.reset(); } + for (auto &palette : _coloredTextPalettes) { + palette.linkFg.reset(); + } + updateDarkValue(); _paletteChanged.fire({}); } @@ -584,20 +687,24 @@ const MessageStyle &ChatStyle::messageStyle(bool outbg, bool selected) const { BubbleRadiusLarge(), result.msgBg, &result.msgShadow); + const auto &replyBar = result.msgReplyBarColor->c; EnsureBlockquoteCache( result.replyCache, - result.msgReplyBarColor); + [&] { return SimpleColorIndexValues(replyBar, false); }); + EnsureBlockquoteCache( + result.replyCacheTwo, + [&] { return SimpleColorIndexValues(replyBar, true); }); if (!result.quoteCache) { result.quoteCache = std::make_unique( *result.replyCache); } + if (!result.quoteCacheTwo) { + result.quoteCacheTwo = std::make_unique( + *result.replyCacheTwo); + } const auto preBgOverride = [&] { - const auto withBg = [&](const QColor &color) { - return CountContrast(windowBg()->c, color); - }; - const auto dark = (withBg({ 0, 0, 0 }) < withBg({ 255, 255, 255 })); - return dark ? QColor(0, 0, 0, 192) : std::optional(); + return _dark ? QColor(0, 0, 0, 192) : std::optional(); }; EnsurePreCache( result.preCache, @@ -634,14 +741,37 @@ const MessageImageStyle &ChatStyle::imageStyle(bool selected) const { return result; } -not_null ChatStyle::serviceQuoteCache() const { - EnsureBlockquoteCache(_serviceQuoteCache, msgServiceFg()); - return _serviceQuoteCache.get(); +not_null ChatStyle::serviceQuoteCache( + bool twoColored) const { + const auto index = (twoColored ? 1 : 0); + const auto &service = msgServiceFg()->c; + EnsureBlockquoteCache( + _serviceQuoteCache[index], + [&] { return SimpleColorIndexValues(service, twoColored); }); + return _serviceQuoteCache[index].get(); } -not_null ChatStyle::serviceReplyCache() const { - EnsureBlockquoteCache(_serviceReplyCache, msgServiceFg()); - return _serviceReplyCache.get(); +not_null ChatStyle::serviceReplyCache( + bool twoColored) const { + const auto index = (twoColored ? 1 : 0); + const auto &service = msgServiceFg()->c; + EnsureBlockquoteCache( + _serviceReplyCache[index], + [&] { return SimpleColorIndexValues(service, twoColored); }); + return _serviceReplyCache[index].get(); +} + +const ColorIndexValues &ChatStyle::coloredValues( + bool selected, + uint8 colorIndex) const { + Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); + + const auto shift = (selected ? kColorIndexCount : 0); + auto &result = _coloredValues[shift + colorIndex]; + if (!result) { + result.emplace(ComputeColorIndexValues(this, selected, colorIndex)); + } + return *result; } const style::TextPalette &ChatStyle::coloredTextPalette( @@ -651,14 +781,14 @@ const style::TextPalette &ChatStyle::coloredTextPalette( const auto shift = (selected ? kColorIndexCount : 0); auto &result = _coloredTextPalettes[shift + colorIndex]; - if (!result.inited) { - result.inited = true; + if (!result.linkFg) { + result.linkFg.emplace(FromNameFg(this, selected, colorIndex)); make( result.data, (selected ? st::inReplyTextPaletteSelected : st::inReplyTextPalette)); - result.data.linkFg = FromNameFg(this, selected, colorIndex); + result.data.linkFg = result.linkFg->color(); result.data.selectLinkFg = result.data.linkFg; } return result.data; @@ -684,7 +814,9 @@ not_null ChatStyle::coloredCache( const auto shift = (selected ? kColorIndexCount : 0); auto &cache = caches[shift + colorIndex]; - EnsureBlockquoteCache(cache, FromNameFg(this, selected, colorIndex)); + EnsureBlockquoteCache(cache, [&] { + return coloredValues(selected, colorIndex); + }); return cache.get(); } @@ -816,47 +948,21 @@ void ChatStyle::make( } uint8 DecideColorIndex(uint64 id) { - return id % kColorIndexCount; + return id % kSimpleColorIndexCount; } uint8 ColorIndexToPaletteIndex(uint8 colorIndex) { Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); const int8 map[] = { 0, 7, 4, 1, 6, 3, 5 }; - return map[colorIndex]; + return map[colorIndex % kSimpleColorIndexCount]; } -style::color FromNameFg( +QColor FromNameFg( not_null st, bool selected, uint8 colorIndex) { - Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); - - if (selected) { - const style::color colors[] = { - st->historyPeer1NameFgSelected(), - st->historyPeer2NameFgSelected(), - st->historyPeer3NameFgSelected(), - st->historyPeer4NameFgSelected(), - st->historyPeer5NameFgSelected(), - st->historyPeer6NameFgSelected(), - st->historyPeer7NameFgSelected(), - st->historyPeer8NameFgSelected(), - }; - return colors[ColorIndexToPaletteIndex(colorIndex)]; - } else { - const style::color colors[] = { - st->historyPeer1NameFg(), - st->historyPeer2NameFg(), - st->historyPeer3NameFg(), - st->historyPeer4NameFg(), - st->historyPeer5NameFg(), - st->historyPeer6NameFg(), - st->historyPeer7NameFg(), - st->historyPeer8NameFg(), - }; - return colors[ColorIndexToPaletteIndex(colorIndex)]; - } + return st->coloredValues(selected, colorIndex).name; } void FillComplexOverlayRect( diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 8482d5936d..6d7367b44d 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -27,7 +27,8 @@ class ChatTheme; class ChatStyle; struct BubblePattern; -inline constexpr auto kColorIndexCount = uint8(7); +inline constexpr auto kColorIndexCount = uint8(14); +inline constexpr auto kSimpleColorIndexCount = uint8(7); struct MessageStyle { CornersPixmaps msgBgCornersSmall; @@ -79,7 +80,9 @@ struct MessageStyle { style::icon historyTranscribeIcon = { Qt::Uninitialized }; style::icon historyTranscribeHide = { Qt::Uninitialized }; std::unique_ptr quoteCache; + std::unique_ptr quoteCacheTwo; std::unique_ptr replyCache; + std::unique_ptr replyCacheTwo; std::unique_ptr preCache; }; @@ -116,8 +119,6 @@ struct ChatPaintContext { QRect viewport; QRect clip; TextSelection selection; - bool outbg = false; - bool paused = false; crl::time now = 0; void translate(int x, int y) { @@ -133,6 +134,8 @@ struct ChatPaintContext { } [[nodiscard]] not_null messageStyle() const; [[nodiscard]] not_null imageStyle() const; + [[nodiscard]] not_null quoteCache( + uint8 colorIndex) const; [[nodiscard]] ChatPaintContext translated(int x, int y) const { auto result = *this; @@ -157,12 +160,27 @@ struct ChatPaintContext { }; SkipDrawingParts skipDrawingParts = SkipDrawingParts::None; + bool outbg = false; + bool paused = false; + }; [[nodiscard]] int HistoryServiceMsgRadius(); [[nodiscard]] int HistoryServiceMsgInvertedRadius(); [[nodiscard]] int HistoryServiceMsgInvertedShrink(); +struct ColorIndexValues { + QColor name; + QColor bg; + QColor outline1; + QColor outline2; +}; +[[nodiscard]] ColorIndexValues ComputeColorIndexValues( + not_null st, + bool selected, + uint8 colorIndex); +[[nodiscard]] bool ColorIndexTwoColored(uint8 colorIndex); + class ChatStyle final : public style::palette { public: ChatStyle(); @@ -172,6 +190,10 @@ public: void applyCustomPalette(const style::palette *palette); void applyAdjustedServiceBg(QColor serviceBg); + [[nodiscard]] bool dark() const { + return _dark; + } + [[nodiscard]] std::span highlightColors() const; [[nodiscard]] rpl::producer<> paletteChanged() const { @@ -202,10 +224,13 @@ public: bool selected) const; [[nodiscard]] const MessageImageStyle &imageStyle(bool selected) const; - [[nodiscard]] auto serviceQuoteCache() const + [[nodiscard]] auto serviceQuoteCache(bool twoColored) const -> not_null; - [[nodiscard]] auto serviceReplyCache() const + [[nodiscard]] auto serviceReplyCache(bool twoColored) const -> not_null; + [[nodiscard]] const ColorIndexValues &coloredValues( + bool selected, + uint8 colorIndex) const; [[nodiscard]] not_null coloredQuoteCache( bool selected, uint8 colorIndex) const; @@ -307,11 +332,12 @@ private: kColorIndexCount * 2>; struct ColoredPalette { + std::optional linkFg; style::TextPalette data; - bool inited = false; }; void assignPalette(not_null palette); + void updateDarkValue(); [[nodiscard]] not_null coloredCache( ColoredQuotePaintCaches &caches, @@ -368,8 +394,15 @@ private: int(CachedCornerRadius::kCount)]; mutable std::vector _highlightColors; - mutable std::unique_ptr _serviceQuoteCache; - mutable std::unique_ptr _serviceReplyCache; + mutable std::array< + std::unique_ptr, + 2> _serviceQuoteCache; + mutable std::array< + std::unique_ptr, + 2> _serviceReplyCache; + mutable std::array< + std::optional, + 2 * kColorIndexCount> _coloredValues; mutable ColoredQuotePaintCaches _coloredQuoteCaches; mutable ColoredQuotePaintCaches _coloredReplyCaches; mutable std::array< @@ -403,6 +436,8 @@ private: style::icon _historyPollChoiceRight = { Qt::Uninitialized }; style::icon _historyPollChoiceWrong = { Qt::Uninitialized }; + bool _dark = false; + rpl::event_stream<> _paletteChanged; rpl::lifetime _defaultPaletteChangeLifetime; @@ -412,11 +447,17 @@ private: [[nodiscard]] uint8 DecideColorIndex(uint64 id); [[nodiscard]] uint8 ColorIndexToPaletteIndex(uint8 colorIndex); -[[nodiscard]] style::color FromNameFg( +[[nodiscard]] QColor FromNameFg( not_null st, bool selected, uint8 colorIndex); +[[nodiscard]] inline QColor FromNameFg( + const ChatPaintContext &context, + uint8 colorIndex) { + return FromNameFg(context.st, context.selected(), colorIndex); +} + void FillComplexOverlayRect( QPainter &p, QRect rect, diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.cpp b/Telegram/SourceFiles/ui/chat/chat_theme.cpp index 856b75b4a0..1022f8e56b 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_theme.cpp @@ -489,8 +489,8 @@ ChatPaintContext ChatTheme::preparePaintContext( .bubblesPattern = _bubblesBackgroundPattern.get(), .viewport = viewport, .clip = clip, - .paused = paused, .now = now, + .paused = paused, }; } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 4d1a5686a7..71d24af3a8 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 4d1a5686a7620b163639492e1b81101b677d2472 +Subproject commit 71d24af3a840d15a6cc0a3a8a1e9cbe77a604739 From 60fb5fdaf0e5925e944c032f19d83620e1bcc609 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Oct 2023 21:29:18 +0400 Subject: [PATCH 54/99] Update color index caches on palette change. --- Telegram/SourceFiles/data/data_changes.h | 55 ++++++++++--------- Telegram/SourceFiles/data/data_peer.cpp | 14 +++++ Telegram/SourceFiles/data/data_peer.h | 4 ++ Telegram/SourceFiles/data/data_session.cpp | 23 ++++++++ Telegram/SourceFiles/data/data_session.h | 5 ++ Telegram/SourceFiles/data/data_types.h | 3 + .../history/history_inner_widget.cpp | 4 ++ Telegram/SourceFiles/history/history_item.cpp | 18 +++++- Telegram/SourceFiles/history/history_item.h | 11 ++-- .../history/history_item_components.cpp | 22 ++++---- .../history/history_item_components.h | 1 - .../history/view/history_view_element.cpp | 3 +- .../history/view/history_view_element.h | 1 - .../history/view/history_view_list_widget.cpp | 5 ++ .../history/view/media/history_view_game.cpp | 8 +-- .../history/view/media/history_view_game.h | 3 +- .../view/media/history_view_giveaway.cpp | 8 +-- .../view/media/history_view_giveaway.h | 1 - .../view/media/history_view_web_page.cpp | 8 +-- .../view/media/history_view_web_page.h | 3 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 8 +-- Telegram/SourceFiles/ui/chat/chat_style.cpp | 12 +++- 22 files changed, 148 insertions(+), 72 deletions(-) diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index a7f52b98bd..9de11be5b5 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -72,41 +72,42 @@ struct PeerUpdate { Usernames = (1ULL << 12), TranslationDisabled = (1ULL << 13), Color = (1ULL << 14), + BackgroundEmoji = (1ULL << 15), // For users - CanShareContact = (1ULL << 15), - IsContact = (1ULL << 16), - PhoneNumber = (1ULL << 17), - OnlineStatus = (1ULL << 18), - BotCommands = (1ULL << 19), - BotCanBeInvited = (1ULL << 20), - BotStartToken = (1ULL << 21), - CommonChats = (1ULL << 22), - HasCalls = (1ULL << 23), - SupportInfo = (1ULL << 24), - IsBot = (1ULL << 25), - EmojiStatus = (1ULL << 26), - StoriesState = (1ULL << 27), + CanShareContact = (1ULL << 16), + IsContact = (1ULL << 17), + PhoneNumber = (1ULL << 18), + OnlineStatus = (1ULL << 19), + BotCommands = (1ULL << 20), + BotCanBeInvited = (1ULL << 21), + BotStartToken = (1ULL << 22), + CommonChats = (1ULL << 23), + HasCalls = (1ULL << 24), + SupportInfo = (1ULL << 25), + IsBot = (1ULL << 26), + EmojiStatus = (1ULL << 27), + StoriesState = (1ULL << 28), // For chats and channels - InviteLinks = (1ULL << 28), - Members = (1ULL << 29), - Admins = (1ULL << 30), - BannedUsers = (1ULL << 31), - Rights = (1ULL << 32), - PendingRequests = (1ULL << 33), - Reactions = (1ULL << 34), + InviteLinks = (1ULL << 29), + Members = (1ULL << 30), + Admins = (1ULL << 31), + BannedUsers = (1ULL << 32), + Rights = (1ULL << 33), + PendingRequests = (1ULL << 34), + Reactions = (1ULL << 35), // For channels - ChannelAmIn = (1ULL << 35), - StickersSet = (1ULL << 36), - ChannelLinkedChat = (1ULL << 37), - ChannelLocation = (1ULL << 38), - Slowmode = (1ULL << 39), - GroupCall = (1ULL << 40), + ChannelAmIn = (1ULL << 36), + StickersSet = (1ULL << 37), + ChannelLinkedChat = (1ULL << 38), + ChannelLocation = (1ULL << 39), + Slowmode = (1ULL << 40), + GroupCall = (1ULL << 41), // For iteration - LastUsedBit = (1ULL << 40), + LastUsedBit = (1ULL << 41), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index e58c941edd..d89b23984d 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -627,6 +627,13 @@ bool PeerData::changeColorIndex( : clearColorIndex(); } +bool PeerData::changeBackgroundEmoji( + const tl::conditional &cloudBackgroundEmoji) { + return changeBackgroundEmoji(cloudBackgroundEmoji + ? cloudBackgroundEmoji->v + : DocumentId()); +} + void PeerData::fillNames() { _nameWords.clear(); _nameFirstLetters.clear(); @@ -862,6 +869,13 @@ bool PeerData::clearColorIndex() { return true; } +bool PeerData::changeBackgroundEmoji(uint64 id) { + if (_backgroundEmojiId == id) { + return false; + } + _backgroundEmojiId = id; + return true; +} bool PeerData::isSelf() const { if (const auto user = asUser()) { return (user->flags() & UserDataFlag::Self); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 643dae0417..c0d84e7186 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -169,6 +169,7 @@ public: } bool changeColorIndex(uint8 index); bool clearColorIndex(); + bool changeBackgroundEmoji(uint64 id); [[nodiscard]] bool isUser() const { return peerIsUser(id); @@ -360,6 +361,8 @@ public: void setSettings(const MTPPeerSettings &data); bool changeColorIndex(const tl::conditional &cloudColorIndex); + bool changeBackgroundEmoji( + const tl::conditional &cloudBackgroundEmoji); enum class BlockStatus : char { Unknown, @@ -452,6 +455,7 @@ private: base::flat_set _nameWords; // for filtering base::flat_set _nameFirstLetters; + uint64 _backgroundEmojiId = 0; crl::time _lastFullUpdate = 0; QString _name; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 52999367f6..3f8b7796fb 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -705,8 +705,18 @@ not_null Session::processUser(const MTPUser &data) { if (canShareThisContact != result->canShareThisContactFast()) { flags |= UpdateFlag::CanShareContact; } + + auto decorationsUpdated = false; if (result->changeColorIndex(data.vcolor())) { flags |= UpdateFlag::Color; + decorationsUpdated = true; + } + if (result->changeBackgroundEmoji(data.vbackground_emoji_id())) { + flags |= UpdateFlag::BackgroundEmoji; + decorationsUpdated = true; + } + if (decorationsUpdated && result->isMinimalLoaded()) { + _peerDecorationsUpdated.fire_copy(result); } }); @@ -982,8 +992,17 @@ not_null Session::processChat(const MTPChat &data) { if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) { flags |= UpdateFlag::GroupCall; } + auto decorationsUpdated = false; if (result->changeColorIndex(data.vcolor())) { flags |= UpdateFlag::Color; + decorationsUpdated = true; + } + if (result->changeBackgroundEmoji(data.vbackground_emoji_id())) { + flags |= UpdateFlag::BackgroundEmoji; + decorationsUpdated = true; + } + if (decorationsUpdated && result->isMinimalLoaded()) { + _peerDecorationsUpdated.fire_copy(result); } }, [&](const MTPDchannelForbidden &data) { const auto channel = result->asChannel(); @@ -4515,6 +4534,10 @@ auto Session::webViewResultSent() const -> rpl::producer { return _webViewResultSent.events(); } +rpl::producer> Session::peerDecorationsUpdated() const { + return _peerDecorationsUpdated.events(); +} + void Session::clearLocalStorage() { _cache->close(); _cache->clear(); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 0b810b642c..9ecb5c45cf 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -726,6 +726,9 @@ public: void webViewResultSent(WebViewResultSent &&sent); [[nodiscard]] rpl::producer webViewResultSent() const; + [[nodiscard]] auto peerDecorationsUpdated() const + -> rpl::producer>; + void clearLocalStorage(); private: @@ -1011,6 +1014,8 @@ private: rpl::event_stream _webViewResultSent; + rpl::event_stream> _peerDecorationsUpdated; + Groups _groups; const std::unique_ptr _chatsFilters; std::unique_ptr _scheduledMessages; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 611a912638..8029c5eeb1 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -306,6 +306,9 @@ enum class MessageFlag : uint64 { StoryItem = (1ULL << 38), InHighlightProcess = (1ULL << 39), + + // If not set then we need to refresh _displayFrom value. + DisplayFromChecked = (1ULL << 40), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 04f7c55182..6f103704cf 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -544,6 +544,10 @@ HistoryInner::HistoryInner( PremiumPreview::InfiniteReactions); }, lifetime()); + session().data().peerDecorationsUpdated( + ) | rpl::start_with_next([=] { + update(); + }, lifetime()); session().data().itemRemoved( ) | rpl::start_with_next( [this](auto item) { itemRemoved(item); }, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index fdbf21d523..7aa8b24941 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1126,7 +1126,7 @@ HistoryItem *HistoryItem::lookupDiscussionPostOriginal() const { forwarded->savedFromMsgId); } -PeerData *HistoryItem::displayFrom() const { +PeerData *HistoryItem::computeDisplayFrom() const { if (const auto sender = discussionPostOriginalSender()) { return sender; } else if (const auto sponsored = Get()) { @@ -1134,14 +1134,24 @@ PeerData *HistoryItem::displayFrom() const { return nullptr; } } else if (const auto forwarded = Get()) { - if (_history->peer->isSelf() || _history->peer->isRepliesChat() || forwarded->imported) { + if (_history->peer->isSelf() + || _history->peer->isRepliesChat() + || forwarded->imported) { return forwarded->originalSender; } } return author().get(); } -uint8 HistoryItem::computeColorIndex() const { +PeerData *HistoryItem::displayFrom() const { + if (!(_flags & MessageFlag::DisplayFromChecked)) { + _flags |= MessageFlag::DisplayFromChecked; + _displayFrom = computeDisplayFrom(); + } + return _displayFrom; +} + +uint8 HistoryItem::colorIndex() const { if (const auto from = displayFrom()) { return from->colorIndex(); } else if (const auto info = hiddenSenderInfo()) { @@ -1615,6 +1625,7 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) { createServiceFromMtp(message); applyServiceDateEdition(message); finishEditionToEmpty(); + _flags &= ~MessageFlag::DisplayFromChecked; } else if (isService()) { if (const auto reply = Get()) { reply->clearData(this); @@ -1624,6 +1635,7 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) { createServiceFromMtp(message); applyServiceDateEdition(message); finishEdition(-1); + _flags &= ~MessageFlag::DisplayFromChecked; } } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 55b5f14b72..a5206bdcc0 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -501,7 +501,7 @@ public: [[nodiscard]] bool isDiscussionPost() const; [[nodiscard]] HistoryItem *lookupDiscussionPostOriginal() const; [[nodiscard]] PeerData *displayFrom() const; - [[nodiscard]] uint8 computeColorIndex() const; + [[nodiscard]] uint8 colorIndex() const; [[nodiscard]] std::unique_ptr createView( not_null delegate, @@ -584,8 +584,8 @@ private: bool used); void setSelfDestruct(HistorySelfDestructType type, MTPint mtpTTLvalue); - TextWithEntities fromLinkText() const; - ClickHandlerPtr fromLink() const; + [[nodiscard]] TextWithEntities fromLinkText() const; + [[nodiscard]] ClickHandlerPtr fromLink() const; void setGroupId(MessageGroupId groupId); @@ -624,9 +624,12 @@ private: [[nodiscard]] PreparedServiceText prepareCallScheduledText( TimeId scheduleDate); + [[nodiscard]] PeerData *computeDisplayFrom() const; + const not_null _history; const not_null _from; - MessageFlags _flags = 0; + mutable PeerData *_displayFrom = nullptr; + mutable MessageFlags _flags = 0; TextWithEntities _text; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index a5efe9ba88..c8b3acece7 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -402,11 +402,7 @@ bool HistoryMessageReply::updateData( } } - if (resolvedMessage) { - _colorIndexPlusOne = resolvedMessage->computeColorIndex() + 1; - } else if (resolvedStory) { - _colorIndexPlusOne = resolvedStory->peer()->colorIndex() + 1; - } else { + if (!resolvedMessage && !resolvedStory) { _unavailable = 1; } @@ -422,7 +418,6 @@ bool HistoryMessageReply::updateData( if (_fields.messageId || _fields.storyId) { _unavailable = 1; } - _colorIndexPlusOne = 0; spoiler = nullptr; } if (force) { @@ -681,14 +676,19 @@ void HistoryMessageReply::paint( const auto rect = QRect(x, y, w, _height); const auto hasQuote = !_fields.quote.empty(); const auto selected = context.selected(); - const auto colorIndexPlusOne = context.outbg ? 0 : _colorIndexPlusOne; + const auto colorIndexPlusOne = resolvedMessage + ? (resolvedMessage->colorIndex() + 1) + : resolvedStory + ? (resolvedStory->peer()->colorIndex() + 1) + : 0; + const auto useColorIndex = colorIndexPlusOne && !context.outbg; const auto twoColored = colorIndexPlusOne && Ui::ColorIndexTwoColored(colorIndexPlusOne - 1); const auto cache = !inBubble ? (hasQuote ? st->serviceQuoteCache(twoColored) : st->serviceReplyCache(twoColored)).get() - : colorIndexPlusOne + : useColorIndex ? (hasQuote ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() @@ -768,7 +768,7 @@ void HistoryMessageReply::paint( w -= textLeft + st::historyReplyPadding.right(); p.setPen(!inBubble ? st->msgImgReplyBarColor()->c - : colorIndexPlusOne + : useColorIndex ? FromNameFg(context, colorIndexPlusOne - 1) : stm->msgServiceFg->c); _name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); @@ -786,7 +786,7 @@ void HistoryMessageReply::paint( y + st::historyReplyPadding.top() + st::msgServiceNameFont->height); auto replyToTextPalette = &(!inBubble ? st->imgReplyTextPalette() - : colorIndexPlusOne + : useColorIndex ? st->coloredTextPalette(selected, colorIndexPlusOne - 1) : stm->replyTextPalette); if (_fields.storyId) { @@ -802,7 +802,7 @@ void HistoryMessageReply::paint( } auto owned = std::optional(); auto copy = std::optional(); - if (inBubble && _colorIndexPlusOne) { + if (inBubble && colorIndexPlusOne) { copy.emplace(*replyToTextPalette); owned.emplace(cache->icon); copy->linkFg = owned->color(); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 507fa46da8..79b78d6d7a 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -346,7 +346,6 @@ private: mutable int _minHeight = 0; mutable int _height = 0; mutable int _nameVersion = 0; - uint8 _colorIndexPlusOne : 6 = 0; uint8 _unavailable : 1 = 0; }; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index e490fc0ff9..1f3acc5433 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -467,7 +467,6 @@ Element::Element( | (IsItemScheduledUntilOnline(data) ? Flag::ScheduledUntilOnline : Flag())) -, _colorIndex(data->computeColorIndex()) , _context(delegate->elementContext()) { history()->owner().registerItemView(this); refreshMedia(replacing); @@ -492,7 +491,7 @@ not_null Element::history() const { } uint8 Element::colorIndex() const { - return _colorIndex; + return data()->colorIndex(); } QDateTime Element::dateTime() const { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index d570093c12..3cc0903ffb 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -594,7 +594,6 @@ private: int _indexInBlock = -1; mutable Flags _flags = Flag(0); - uint8 _colorIndex = 0; Context _context = Context(); }; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index f2ed79c395..e3e979285e 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -353,6 +353,11 @@ ListWidget::ListWidget( update(); }, lifetime()); + session().data().peerDecorationsUpdated( + ) | rpl::start_with_next([=] { + update(); + }, lifetime()); + session().data().itemRemoved( ) | rpl::start_with_next([=](not_null item) { itemRemoved(item); diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 70e92a6e91..9f09e28db9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -34,7 +34,6 @@ Game::Game( : Media(parent) , _st(st::historyPagePreview) , _data(data) -, _colorIndex(parent->data()->computeColorIndex()) , _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { if (!consumed.text.isEmpty()) { @@ -219,11 +218,12 @@ void Game::draw(Painter &p, const PaintContext &context) const { auto tshift = inner.top(); auto paintw = inner.width(); + const auto colorIndex = parent()->colorIndex(); const auto selected = context.selected(); - const auto twoColored = Ui::ColorIndexTwoColored(_colorIndex); + const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() - : st->coloredReplyCache(selected, _colorIndex).get(); + : st->coloredReplyCache(selected, colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); @@ -232,7 +232,7 @@ void Game::draw(Painter &p, const PaintContext &context) const { p.setPen(cache->icon); p.setTextPalette(context.outbg ? stm->semiboldPalette - : st->coloredTextPalette(selected, _colorIndex)); + : st->coloredTextPalette(selected, colorIndex)); auto endskip = 0; if (_title.hasSkipBlock()) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.h b/Telegram/SourceFiles/history/view/media/history_view_game.h index d942064fd2..9cec01fdc5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.h +++ b/Telegram/SourceFiles/history/view/media/history_view_game.h @@ -105,8 +105,7 @@ private: int _gameTagWidth = 0; int _descriptionLines = 0; - uint32 _titleLines : 24 = 0; - uint32 _colorIndex : 8 = 0; + int _titleLines = 0; Ui::Text::String _title; Ui::Text::String _description; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 07a96f934c..9079ac6e01 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -61,8 +61,7 @@ Giveaway::Giveaway( , _participants(st::msgMinWidth) , _countries(st::msgMinWidth) , _winnersTitle(st::msgMinWidth) -, _winners(st::msgMinWidth) -, _colorIndex(parent->data()->computeColorIndex()) { +, _winners(st::msgMinWidth) { fillFromData(giveaway); } @@ -347,10 +346,11 @@ void Giveaway::paintChannels( const auto ratio = style::DevicePixelRatio(); const auto stm = context.messageStyle(); const auto selected = context.selected(); - const auto twoColored = Ui::ColorIndexTwoColored(_colorIndex); + const auto colorIndex = parent()->colorIndex(); + const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() - : context.st->coloredReplyCache(selected, _colorIndex).get(); + : context.st->coloredReplyCache(selected, colorIndex).get(); if (_channelCorners[0].isNull() || _channelBg != cache->bg) { _channelBg = cache->bg; _channelCorners = Images::CornersMask(size / 2); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index 437c94c5c2..6529ad4971 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -115,7 +115,6 @@ private: int _countriesWidth = 0; int _winnersTitleTop = 0; int _winnersTop = 0; - uint8 _colorIndex : 7 = 0; mutable uint8 _subscribedToThumbnails : 1 = 0; }; 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 2bce7b939c..1bc521a16c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -126,7 +126,6 @@ WebPage::WebPage( : Media(parent) , _st(st::historyPagePreview) , _data(data) -, _colorIndex(parent->data()->computeColorIndex()) , _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) @@ -546,10 +545,11 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); const auto selected = context.selected(); - const auto twoColored = Ui::ColorIndexTwoColored(_colorIndex); + const auto colorIndex = parent()->colorIndex(); + const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() - : st->coloredReplyCache(selected, _colorIndex).get(); + : st->coloredReplyCache(selected, colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); @@ -605,7 +605,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { p.setPen(cache->icon); p.setTextPalette(context.outbg ? stm->semiboldPalette - : st->coloredTextPalette(selected, _colorIndex)); + : st->coloredTextPalette(selected, colorIndex)); auto endskip = 0; if (_siteName.hasSkipBlock()) { 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 18e7f032a5..0946714ff2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -132,8 +132,7 @@ private: int _dataVersion = -1; int _siteNameLines = 0; int _descriptionLines = 0; - uint32 _titleLines : 24 = 0; - uint32 _colorIndex : 7 = 0; + uint32 _titleLines : 31 = 0; uint32 _asArticle : 1 = 0; Ui::Text::String _siteName; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 599b38e5c9..2ae1e04633 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -172,7 +172,7 @@ messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; messageActionSetChatWallPaper#bc44a927 wallpaper:WallPaper = MessageAction; messageActionSetSameChatWallPaper#c0787d6d wallpaper:WallPaper = MessageAction; -messageActionGiftCode#d2cfdb0e flags:# via_giveaway:flags.0?true boost_peer:flags.1?Peer months:int slug:string = MessageAction; +messageActionGiftCode#d2cfdb0e flags:# via_giveaway:flags.0?true unclaimed:flags.2?true boost_peer:flags.1?Peer months:int slug:string = MessageAction; messageActionGiveawayLaunch#332ba9ed = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; @@ -292,7 +292,7 @@ updateChatParticipantAdd#3dda5451 chat_id:long user_id:long inviter_id:long date updateChatParticipantDelete#e32f3d77 chat_id:long user_id:long version:int = Update; updateDcOptions#8e5e9873 dc_options:Vector = Update; updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update; -updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector = Update; +updateServiceNotification#ebe46819 flags:# popup:flags.0?true invert_media:flags.2?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector = Update; updatePrivacy#ee3b272a key:PrivacyKey rules:Vector = Update; updateUserPhone#5492a13 user_id:long phone:string = Update; updateReadHistoryInbox#9c974fdf flags:# folder_id:flags.0?int peer:Peer max_id:int still_unread_count:int pts:int pts_count:int = Update; @@ -686,7 +686,7 @@ messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; messages.savedGifs#84a02a0d hash:long gifs:Vector = messages.SavedGifs; inputBotInlineMessageMediaAuto#3380c786 flags:# invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; -inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; @@ -700,7 +700,7 @@ inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags. inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult; botInlineMessageMediaAuto#764cf810 flags:# invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; -botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 5f817b44c8..17524b4eeb 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -180,7 +180,7 @@ ColorIndexValues SimpleColorIndexValues(QColor color, bool twoColored) { outline1.setAlphaF(0.9); auto outline2 = outline1; if (twoColored) { - outline2.setAlphaF(0.3); + outline2.setAlphaF(0.5); } return { .name = color, @@ -624,7 +624,9 @@ void ChatStyle::assignPalette(not_null palette) { style.msgBgCornersSmall = {}; style.msgBgCornersLarge = {}; style.quoteCache = nullptr; + style.quoteCacheTwo = nullptr; style.replyCache = nullptr; + style.replyCacheTwo = nullptr; style.preCache = nullptr; } for (auto &style : _imageStyles) { @@ -653,6 +655,12 @@ void ChatStyle::assignPalette(not_null palette) { for (auto &palette : _coloredTextPalettes) { palette.linkFg.reset(); } + for (auto &cache : _coloredReplyCaches) { + cache = nullptr; + } + for (auto &cache : _coloredQuoteCaches) { + cache = nullptr; + } updateDarkValue(); _paletteChanged.fire({}); @@ -782,7 +790,7 @@ const style::TextPalette &ChatStyle::coloredTextPalette( const auto shift = (selected ? kColorIndexCount : 0); auto &result = _coloredTextPalettes[shift + colorIndex]; if (!result.linkFg) { - result.linkFg.emplace(FromNameFg(this, selected, colorIndex)); + result.linkFg.emplace(coloredValues(selected, colorIndex).name); make( result.data, (selected From 9c23de7f1a9d3d62488b1085fc46f9a96ae00271 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Oct 2023 11:47:11 +0400 Subject: [PATCH 55/99] Display reply background emoji. --- Telegram/SourceFiles/data/data_peer.cpp | 10 +- Telegram/SourceFiles/data/data_peer.h | 5 +- Telegram/SourceFiles/data/data_session.cpp | 4 +- .../history/history_item_components.cpp | 157 +++++++++++++++++- Telegram/SourceFiles/ui/chat/chat_style.cpp | 19 +++ Telegram/SourceFiles/ui/chat/chat_style.h | 26 +++ 6 files changed, 211 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index d89b23984d..edda209ad5 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -627,9 +627,9 @@ bool PeerData::changeColorIndex( : clearColorIndex(); } -bool PeerData::changeBackgroundEmoji( +bool PeerData::changeBackgroundEmojiId( const tl::conditional &cloudBackgroundEmoji) { - return changeBackgroundEmoji(cloudBackgroundEmoji + return changeBackgroundEmojiId(cloudBackgroundEmoji ? cloudBackgroundEmoji->v : DocumentId()); } @@ -869,7 +869,11 @@ bool PeerData::clearColorIndex() { return true; } -bool PeerData::changeBackgroundEmoji(uint64 id) { +DocumentId PeerData::backgroundEmojiId() const { + return _backgroundEmojiId; +} + +bool PeerData::changeBackgroundEmojiId(DocumentId id) { if (_backgroundEmojiId == id) { return false; } diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index c0d84e7186..33a7727412 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -169,7 +169,8 @@ public: } bool changeColorIndex(uint8 index); bool clearColorIndex(); - bool changeBackgroundEmoji(uint64 id); + [[nodiscard]] DocumentId backgroundEmojiId() const; + bool changeBackgroundEmojiId(DocumentId id); [[nodiscard]] bool isUser() const { return peerIsUser(id); @@ -361,7 +362,7 @@ public: void setSettings(const MTPPeerSettings &data); bool changeColorIndex(const tl::conditional &cloudColorIndex); - bool changeBackgroundEmoji( + bool changeBackgroundEmojiId( const tl::conditional &cloudBackgroundEmoji); enum class BlockStatus : char { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 3f8b7796fb..4c4223895f 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -711,7 +711,7 @@ not_null Session::processUser(const MTPUser &data) { flags |= UpdateFlag::Color; decorationsUpdated = true; } - if (result->changeBackgroundEmoji(data.vbackground_emoji_id())) { + if (result->changeBackgroundEmojiId(data.vbackground_emoji_id())) { flags |= UpdateFlag::BackgroundEmoji; decorationsUpdated = true; } @@ -997,7 +997,7 @@ not_null Session::processChat(const MTPChat &data) { flags |= UpdateFlag::Color; decorationsUpdated = true; } - if (result->changeBackgroundEmoji(data.vbackground_emoji_id())) { + if (result->changeBackgroundEmojiId(data.vbackground_emoji_id())) { flags |= UpdateFlag::BackgroundEmoji; decorationsUpdated = true; } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index c8b3acece7..a21b77b583 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "data/data_user.h" @@ -55,6 +56,127 @@ namespace { const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; +void ValidateBackgroundEmoji( + DocumentId backgroundEmojiId, + not_null data, + not_null cache, + not_null quote, + not_null holder) { + if (data->firstFrameMask.isNull()) { + if (!cache->frames[0].isNull()) { + for (auto &frame : cache->frames) { + frame = QImage(); + } + } + const auto tag = Data::CustomEmojiSizeTag::Isolated; + if (!data->emoji) { + const auto owner = &holder->history()->owner(); + const auto repaint = crl::guard(holder, [=] { + holder->history()->owner().requestViewRepaint(holder); + }); + data->emoji = owner->customEmojiManager().create( + backgroundEmojiId, + repaint, + tag); + } + if (!data->emoji->ready()) { + return; + } + const auto size = Data::FrameSizeFromTag(tag); + data->firstFrameMask = QImage( + QSize(size, size), + QImage::Format_ARGB32_Premultiplied); + data->firstFrameMask.fill(Qt::transparent); + data->firstFrameMask.setDevicePixelRatio(style::DevicePixelRatio()); + auto p = Painter(&data->firstFrameMask); + data->emoji->paint(p, { + .textColor = QColor(255, 255, 255), + .position = QPoint(0, 0), + .internal = { + .forceFirstFrame = true, + }, + }); + p.end(); + + data->emoji = nullptr; + } + if (!cache->frames[0].isNull() && cache->color == quote->icon) { + return; + } + cache->color = quote->icon; + const auto ratio = style::DevicePixelRatio(); + auto colorized = QImage( + data->firstFrameMask.size(), + QImage::Format_ARGB32_Premultiplied); + colorized.setDevicePixelRatio(ratio); + style::colorizeImage( + data->firstFrameMask, + cache->color, + &colorized, + QRect(), // src + QPoint(), // dst + true); // use alpha + const auto make = [&](int size) { + size = style::ConvertScale(size) * ratio; + auto result = colorized.scaled( + size, + size, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + result.setDevicePixelRatio(ratio); + return result; + }; + + constexpr auto kSize1 = 12; + constexpr auto kSize2 = 16; + constexpr auto kSize3 = 20; + cache->frames[0] = make(kSize1); + cache->frames[1] = make(kSize2); + cache->frames[2] = make(kSize3); +} + +void FillBackgroundEmoji( + Painter &p, + const QRect &rect, + bool quote, + const Ui::BackgroundEmojiCache &cache) { + p.setClipRect(rect); + + const auto &frames = cache.frames; + const auto right = rect.x() + rect.width(); + const auto paint = [&](int x, int y, int index, float64 opacity) { + y = style::ConvertScale(y); + if (y >= rect.height()) { + return; + } + p.setOpacity(opacity); + p.drawImage( + right - style::ConvertScale(x + (quote ? 12 : 0)), + rect.y() + y, + frames[index]); + }; + + paint(28, 4, 2, 0.32); + paint(51, 15, 1, 0.32); + paint(64, -2, 0, 0.28); + paint(87, 11, 1, 0.24); + paint(125, -2, 2, 0.16); + + paint(28, 31, 1, 0.24); + paint(72, 33, 2, 0.2); + + paint(46, 52, 1, 0.24); + paint(24, 55, 2, 0.18); + + if (quote) { + paint(4, 23, 1, 0.28); + paint(0, 48, 0, 0.24); + } + + p.setClipping(false); + p.setOpacity(1.); +} + } // namespace void HistoryMessageVia::create( @@ -676,10 +798,18 @@ void HistoryMessageReply::paint( const auto rect = QRect(x, y, w, _height); const auto hasQuote = !_fields.quote.empty(); const auto selected = context.selected(); - const auto colorIndexPlusOne = resolvedMessage - ? (resolvedMessage->colorIndex() + 1) + const auto colorPeer = resolvedMessage + ? resolvedMessage->displayFrom() : resolvedStory - ? (resolvedStory->peer()->colorIndex() + 1) + ? resolvedStory->peer().get() + : nullptr; + const auto backgroundEmojiId = colorPeer + ? colorPeer->backgroundEmojiId() + : DocumentId(); + const auto colorIndexPlusOne = colorPeer + ? (colorPeer->colorIndex() + 1) + : resolvedMessage + ? (resolvedMessage->hiddenSenderInfo()->colorIndex + 1) : 0; const auto useColorIndex = colorIndexPlusOne && !context.outbg; const auto twoColored = colorIndexPlusOne @@ -698,12 +828,33 @@ void HistoryMessageReply::paint( const auto "eSt = hasQuote ? st::messageTextStyle.blockquote : st::messageQuoteStyle; + const auto backgroundEmoji = backgroundEmojiId + ? st->backgroundEmojiData(backgroundEmojiId).get() + : nullptr; + const auto backgroundEmojiCache = backgroundEmoji + ? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex( + selected, + context.outbg, + inBubble, + colorIndexPlusOne)] + : nullptr; const auto rippleColor = cache->bg; if (!inBubble) { cache->bg = QColor(0, 0, 0, 0); } Ui::Text::ValidateQuotePaintCache(*cache, quoteSt); Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt); + if (backgroundEmoji) { + ValidateBackgroundEmoji( + backgroundEmojiId, + backgroundEmoji, + backgroundEmojiCache, + cache, + holder); + if (!backgroundEmojiCache->frames[0].isNull()) { + FillBackgroundEmoji(p, rect, hasQuote, *backgroundEmojiCache); + } + } if (!inBubble) { cache->bg = rippleColor; } diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 17524b4eeb..1ca60e7a38 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/image/image_prepare.h" // ImageRoundRadius +#include "ui/text/text_custom_emoji.h" #include "ui/color_contrast.h" #include "ui/painter.h" #include "ui/ui_utility.h" @@ -190,6 +191,17 @@ ColorIndexValues SimpleColorIndexValues(QColor color, bool twoColored) { }; } +int BackgroundEmojiData::CacheIndex( + bool selected, + bool outbg, + bool inbubble, + uint8 colorIndexPlusOne) { + const auto base = colorIndexPlusOne + ? (colorIndexPlusOne - 1) + : (kColorIndexCount + (!inbubble ? 0 : outbg ? 1 : 2)); + return (base * 2) + (selected ? 1 : 0); +}; + ChatStyle::ChatStyle() { finalize(); make(_historyPsaForwardPalette, st::historyPsaForwardPalette); @@ -553,6 +565,8 @@ ChatStyle::ChatStyle(not_null isolated) assignPalette(isolated); } +ChatStyle::~ChatStyle() = default; + void ChatStyle::apply(not_null theme) { applyCustomPalette(theme->palette()); } @@ -802,6 +816,11 @@ const style::TextPalette &ChatStyle::coloredTextPalette( return result.data; } +not_null ChatStyle::backgroundEmojiData( + uint64 id) const { + return &_backgroundEmojis[id]; +} + not_null ChatStyle::coloredQuoteCache( bool selected, uint8 colorIndex) const { diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 6d7367b44d..8f2b7639de 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -21,6 +21,10 @@ struct TwoIconButton; struct ScrollArea; } // namespace style +namespace Ui::Text { +class CustomEmoji; +} // namespace Ui::Text + namespace Ui { class ChatTheme; @@ -112,6 +116,23 @@ struct ReactionPaintInfo { Fn effectPaint; }; +struct BackgroundEmojiCache { + QColor color; + std::array frames; +}; + +struct BackgroundEmojiData { + std::unique_ptr emoji; + QImage firstFrameMask; + std::array caches; + + [[nodiscard]] static int CacheIndex( + bool selected, + bool outbg, + bool inbubble, + uint8 colorIndexPlusOne); +}; + struct ChatPaintContext { not_null st; const BubblePattern *bubblesPattern = nullptr; @@ -185,6 +206,7 @@ class ChatStyle final : public style::palette { public: ChatStyle(); explicit ChatStyle(not_null isolated); + ~ChatStyle(); void apply(not_null theme); void applyCustomPalette(const style::palette *palette); @@ -242,6 +264,9 @@ public: bool selected, uint8 colorIndex) const; + [[nodiscard]] not_null backgroundEmojiData( + uint64 id) const; + [[nodiscard]] const CornersPixmaps &msgBotKbOverBgAddCornersSmall() const; [[nodiscard]] const CornersPixmaps &msgBotKbOverBgAddCornersLarge() const; [[nodiscard]] const CornersPixmaps &msgSelectOverlayCorners( @@ -408,6 +433,7 @@ private: mutable std::array< ColoredPalette, 2 * kColorIndexCount> _coloredTextPalettes; + mutable base::flat_map _backgroundEmojis; style::TextPalette _historyPsaForwardPalette; style::TextPalette _imgReplyTextPalette; From 91ab82c9dab4df8636927d6a76be928bf0b2283b Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Oct 2023 12:51:35 +0400 Subject: [PATCH 56/99] Fix unread counter in General topic. --- Telegram/SourceFiles/history/history.cpp | 4 ++- Telegram/SourceFiles/history/history_item.cpp | 26 +++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 9d9cbca18b..1ab97d5028 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -282,9 +282,11 @@ Data::Draft *History::createCloudDraft( } else { auto existing = cloudDraft(topicRootId); if (!existing) { + auto reply = fromDraft->reply; + reply.topicRootId = topicRootId; setCloudDraft(std::make_unique( fromDraft->textWithTags, - fromDraft->reply, + reply, fromDraft->cursor, fromDraft->webpage)); existing = cloudDraft(topicRootId); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 7aa8b24941..6fafd5465a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1845,8 +1845,10 @@ void HistoryItem::destroyHistoryEntry() { topic->unreadReactions().erase(id); } } - if (const auto reply = Get()) { - changeReplyToTopCounter(reply, -1); + if (isRegular() && _history->peer->isMegagroup()) { + if (const auto reply = Get()) { + changeReplyToTopCounter(reply, -1); + } } } @@ -1905,21 +1907,19 @@ void HistoryItem::removeFromSharedMediaIndex() { } void HistoryItem::incrementReplyToTopCounter() { - if (const auto reply = Get()) { - changeReplyToTopCounter(reply, 1); + if (isRegular() && _history->peer->isMegagroup()) { + _history->session().changes().messageUpdated( + this, + Data::MessageUpdate::Flag::ReplyToTopAdded); + if (const auto reply = Get()) { + changeReplyToTopCounter(reply, 1); + } } } void HistoryItem::changeReplyToTopCounter( not_null reply, int delta) { - if (!isRegular() || !_history->peer->isMegagroup()) { - return; - } else if (delta > 0) { - _history->session().changes().messageUpdated( - this, - Data::MessageUpdate::Flag::ReplyToTopAdded); - } const auto topId = reply->topMessageId(); if (!topId) { return; @@ -1975,7 +1975,7 @@ void HistoryItem::setRealId(MsgId newId) { if (reply->link()) { reply->setLinkFrom(this); } - changeReplyToTopCounter(reply, 1); + incrementReplyToTopCounter(); } } @@ -2688,7 +2688,7 @@ void HistoryItem::setReplyFields( && !IsServerMsgId(reply->topMessageId()); reply->updateFields(this, replyTo, replyToTop, isForumPost); if (increment) { - changeReplyToTopCounter(reply, 1); + incrementReplyToTopCounter(); } } if (const auto topic = this->topic()) { From 2d3e2b1ef87dcccab90e4fdc5ff9b26005ca4d84 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Oct 2023 13:10:17 +0400 Subject: [PATCH 57/99] Fix drafts in topics. --- Telegram/SourceFiles/data/data_histories.cpp | 7 +++---- Telegram/SourceFiles/history/history.cpp | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 9b6f556558..fb20655a5a 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -46,7 +46,8 @@ MTPInputReplyTo ReplyToForMTP( } } } else if (replyTo.messageId || replyTo.topicRootId) { - const auto external = (replyTo.messageId.peer != history->peer->id); + const auto external = replyTo.messageId + && (replyTo.messageId.peer != history->peer->id); const auto quoteEntities = Api::EntitiesToMTP( &history->session(), replyTo.quote.entities, @@ -61,9 +62,7 @@ MTPInputReplyTo ReplyToForMTP( | (quoteEntities.v.isEmpty() ? Flag() : Flag::f_quote_entities)), - MTP_int(replyTo.messageId - ? replyTo.messageId.msg - : replyTo.topicRootId), + MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0), MTP_int(replyTo.topicRootId), (external ? owner->peer(replyTo.messageId.peer)->input diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 1ab97d5028..e5c03f37dc 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -275,7 +275,7 @@ Data::Draft *History::createCloudDraft( if (Data::DraftIsNull(fromDraft)) { setCloudDraft(std::make_unique( TextWithTags(), - FullReplyTo(), + FullReplyTo{ .topicRootId = topicRootId }, MessageCursor(), Data::WebPageDraft())); cloudDraft(topicRootId)->date = TimeId(0); From aad157cf56026d04df7d926cac5fd5575438f706 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Oct 2023 13:19:45 +0400 Subject: [PATCH 58/99] Open link on webpreview attach click. --- .../view/media/history_view_web_page.cpp | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) 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 1bc521a16c..c89705092d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -822,29 +822,13 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { ClickHandlerPtr WebPage::replaceAttachLink( const ClickHandlerPtr &link) const { - if (!link || !_attach->isReadyForOpen() || !_collage.empty()) { + if (!_attach->isReadyForOpen() + || (_siteName.isEmpty() + && _title.isEmpty() + && _description.isEmpty())) { return link; } - if (_data->document) { - if (_data->document->isWallPaper() || _data->document->isTheme()) { - return _openl; - } - } else if (_data->photo) { - if (_data->type == WebPageType::Profile - || _data->type == WebPageType::Video) { - return _openl; - } else if (_data->type == WebPageType::Photo - || _data->type == WebPageType::Document - || _data->siteName == u"Twitter"_q - || _data->siteName == u"Facebook"_q) { - // leave photo link - } else { - return _openl; - } - } else if (ThemeDocument::ParamsFromUrl(_data->url).has_value()) { - return _openl; - } - return link; + return _openl; } TextSelection WebPage::adjustSelection(TextSelection selection, TextSelectType type) const { From d62fb5786d91bb9941974e8c8b78d9ef9fa53c45 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Oct 2023 13:46:43 +0400 Subject: [PATCH 59/99] Support selecting quote in reply info edit. --- Telegram/CMakeLists.txt | 2 + .../history/history_inner_widget.cpp | 1 + .../SourceFiles/history/history_widget.cpp | 5 + .../history_view_compose_controls.cpp | 7 +- .../controls/history_view_forward_panel.cpp | 119 ----- .../controls/history_view_forward_panel.h | 11 - .../controls/history_view_reply_options.cpp | 496 ++++++++++++++++++ .../controls/history_view_reply_options.h | 32 ++ .../history/view/history_view_element.h | 5 + .../history/view/history_view_message.cpp | 98 +++- .../history/view/history_view_message.h | 5 + .../view/history_view_replies_section.cpp | 1 + .../view/history_view_service_message.cpp | 11 + .../view/history_view_service_message.h | 5 + .../view/media/history_view_document.cpp | 18 + .../view/media/history_view_document.h | 2 + .../history/view/media/history_view_gif.cpp | 5 + .../history/view/media/history_view_gif.h | 2 + .../history/view/media/history_view_media.h | 4 + .../history/view/media/history_view_photo.cpp | 5 + .../history/view/media/history_view_photo.h | 2 + 21 files changed, 704 insertions(+), 132 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_reply_options.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 7b0d52e5f3..d43e91d1f4 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -653,6 +653,8 @@ PRIVATE history/view/controls/history_view_compose_search.h history/view/controls/history_view_forward_panel.cpp history/view/controls/history_view_forward_panel.h + history/view/controls/history_view_reply_options.cpp + history/view/controls/history_view_reply_options.h history/view/controls/history_view_ttl_button.cpp history/view/controls/history_view_ttl_button.h history/view/controls/history_view_voice_record_bar.cpp diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 6f103704cf..c1eb6cf98e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_helpers.h" #include "history/view/controls/history_view_forward_panel.h" +#include "history/view/controls/history_view_reply_options.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_web_page.h" diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 927055b6e1..c3ac82ff95 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -82,6 +82,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_unread_things.h" #include "history/view/controls/history_view_compose_search.h" #include "history/view/controls/history_view_forward_panel.h" +#include "history/view/controls/history_view_reply_options.h" #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_ttl_button.h" #include "history/view/reactions/history_view_reactions_button.h" @@ -6235,6 +6236,9 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { _forwardPanel->editOptions(controller()->uiShow()); } } else if (const auto reply = replyTo()) { + const auto done = [=](FullReplyTo replyTo) { + replyToMessage(replyTo); + }; const auto highlight = [=] { controller()->showPeerHistory( reply.messageId.peer, @@ -6246,6 +6250,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { EditReplyOptions( controller()->uiShow(), reply, + done, highlight, [=] { ClearDraftReplyTo(history, reply.messageId); }); } else if (_editMsgId) { 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 b5d03eeca6..098840e8a2 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -46,9 +46,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/power_saving.h" #include "history/history.h" #include "history/history_item.h" +#include "history/view/controls/history_view_forward_panel.h" +#include "history/view/controls/history_view_reply_options.h" #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_ttl_button.h" -#include "history/view/controls/history_view_forward_panel.h" #include "history/view/history_view_webpage_preview.h" #include "inline_bots/bot_attach_web_view.h" #include "inline_bots/inline_results_widget.h" @@ -624,12 +625,16 @@ void FieldHeader::init() { }; const auto history = _history; const auto topicRootId = _topicRootId; + const auto done = [=](FullReplyTo replyTo) { + replyToMessage(replyTo); + }; const auto clearOldReplyTo = [=, id = reply.messageId] { ClearDraftReplyTo(history, topicRootId, id); }; EditReplyOptions( _show, reply, + done, highlight, clearOldReplyTo); } else { 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 eabef48c6b..30869de205 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -428,125 +428,6 @@ void ClearDraftReplyTo( } } -void ShowReplyToChatBox( - std::shared_ptr show, - FullReplyTo reply, - Fn clearOldDraft) { - class Controller final : public ChooseRecipientBoxController { - public: - using Chosen = not_null; - - Controller(not_null session) - : ChooseRecipientBoxController( - session, - [=](Chosen thread) mutable { _singleChosen.fire_copy(thread); }, - nullptr) { - } - - void rowClicked(not_null row) override final { - ChooseRecipientBoxController::rowClicked(row); - } - - [[nodiscard]] rpl::producer singleChosen() const{ - return _singleChosen.events(); - } - - bool respectSavedMessagesChat() const override { - return false; - } - - private: - void prepareViewHook() override { - delegate()->peerListSetTitle(rpl::single(u"Reply in..."_q)); - } - - rpl::event_stream _singleChosen; - - }; - - struct State { - not_null box; - not_null controller; - base::unique_qptr menu; - }; - const auto session = &show->session(); - const auto state = [&] { - auto controller = std::make_unique(session); - const auto controllerRaw = controller.get(); - auto box = Box(std::move(controller), nullptr); - const auto boxRaw = box.data(); - show->show(std::move(box)); - auto state = State{ boxRaw, controllerRaw }; - return boxRaw->lifetime().make_state(std::move(state)); - }(); - - auto chosen = [=](not_null thread) mutable { - const auto history = thread->owningHistory(); - const auto topicRootId = thread->topicRootId(); - const auto draft = history->localDraft(topicRootId); - const auto textWithTags = draft - ? draft->textWithTags - : TextWithTags(); - const auto cursor = draft ? draft->cursor : MessageCursor(); - reply.topicRootId = topicRootId; - history->setLocalDraft(std::make_unique( - textWithTags, - reply, - cursor, - Data::WebPageDraft())); - history->clearLocalEditDraft(topicRootId); - history->session().changes().entryUpdated( - thread, - Data::EntryUpdate::Flag::LocalDraftSet); - - if (clearOldDraft) { - crl::on_main(&history->session(), clearOldDraft); - } - return true; - }; - auto callback = [=, chosen = std::move(chosen)]( - Controller::Chosen thread) mutable { - const auto weak = Ui::MakeWeak(state->box); - if (!chosen(thread)) { - return; - } else if (const auto strong = weak.data()) { - strong->closeBox(); - } - }; - state->controller->singleChosen( - ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); -} - -void EditReplyOptions( - std::shared_ptr show, - FullReplyTo reply, - Fn highlight, - Fn clearOldDraft) { - show->show(Box([=](not_null box) { - box->setTitle(rpl::single(u"Reply to Message"_q)); - - Settings::AddButton( - box->verticalLayout(), - rpl::single(u"Reply in another chat"_q), - st::settingsButton, - { &st::menuIconReply } - )->setClickedCallback([=] { - ShowReplyToChatBox(show, reply, clearOldDraft); - }); - - Settings::AddButton( - box->verticalLayout(), - rpl::single(u"Show message"_q), - st::settingsButton, - { &st::menuIconShowInChat } - )->setClickedCallback(highlight); - - box->addButton(tr::lng_box_ok(), [=] { - box->closeBox(); - }); - })); -} - void EditWebPageOptions( std::shared_ptr show, not_null webpage, 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 c881dc06f0..7e3d2fac47 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h @@ -78,17 +78,6 @@ void ClearDraftReplyTo( MsgId topicRootId, FullMsgId equalTo); -void ShowReplyToChatBox( - std::shared_ptr show, - FullReplyTo reply, - Fn clearOldDraft = nullptr); - -void EditReplyOptions( - std::shared_ptr show, - FullReplyTo reply, - Fn highlight, - Fn clearOldDraft = nullptr); - void EditWebPageOptions( std::shared_ptr show, not_null webpage, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp new file mode 100644 index 0000000000..7d348beb93 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp @@ -0,0 +1,496 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/controls/history_view_reply_options.h" + +#include "boxes/peer_list_box.h" +#include "boxes/peer_list_controllers.h" +#include "chat_helpers/compose/compose_show.h" +#include "data/data_changes.h" +#include "data/data_drafts.h" +#include "data/data_file_origin.h" +#include "data/data_session.h" +#include "data/data_thread.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_common.h" +#include "ui/chat/chat_style.h" +#include "ui/chat/chat_theme.h" +#include "ui/effects/path_shift_gradient.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/buttons.h" +#include "ui/painter.h" +#include "window/themes/window_theme.h" +#include "window/section_widget.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +#include +#include + +namespace HistoryView::Controls { +namespace { + +class PreviewDelegate final : public DefaultElementDelegate { +public: + PreviewDelegate( + not_null parent, + not_null st, + Fn update); + + bool elementAnimationsPaused() override; + not_null elementPathShiftGradient() override; + Context elementContext() override; + +private: + const not_null _parent; + const std::unique_ptr _pathGradient; + +}; + +[[nodiscard]] std::unique_ptr DefaultThemeOn( + rpl::lifetime &lifetime) { + auto result = std::make_unique(); + + using namespace Window::Theme; + const auto push = [=, raw = result.get()] { + const auto background = Background(); + const auto &paper = background->paper(); + raw->setBackground({ + .prepared = background->prepared(), + .preparedForTiled = background->preparedForTiled(), + .gradientForFill = background->gradientForFill(), + .colorForFill = background->colorForFill(), + .colors = paper.backgroundColors(), + .patternOpacity = paper.patternOpacity(), + .gradientRotation = paper.gradientRotation(), + .isPattern = paper.isPattern(), + .tile = background->tile(), + }); + }; + + push(); + Background()->updates( + ) | rpl::start_with_next([=](const BackgroundUpdate &update) { + if (update.type == BackgroundUpdate::Type::New + || update.type == BackgroundUpdate::Type::Changed) { + push(); + } + }, lifetime); + + return result; +} + +[[nodiscard]] rpl::producer AddQuoteTracker( + not_null box, + std::shared_ptr show, + not_null item, + const TextWithEntities "e) { + struct State { + std::unique_ptr theme; + std::unique_ptr style; + std::unique_ptr delegate; + std::unique_ptr element; + rpl::variable selection; + Ui::PeerUserpicView userpic; + QPoint position; + + base::Timer trippleClickTimer; + TextSelectType selectType = TextSelectType::Letters; + uint16 symbol = 0; + bool afterSymbol = false; + bool textCursor = false; + bool selecting = false; + bool over = false; + uint16 selectionStartSymbol = 0; + bool selectionStartAfterSymbol = false; + }; + + const auto preview = box->addRow( + object_ptr(box), + QMargins(0, 0, 0, st::settingsThemesTopSkip)); + const auto state = preview->lifetime().make_state(); + state->theme = DefaultThemeOn(preview->lifetime()); + + state->style = std::make_unique(); + state->style->apply(state->theme.get()); + + state->delegate = std::make_unique( + box, + state->style.get(), + [=] { preview->update(); }); + + state->element = item->createView(state->delegate.get()); + state->element->initDimensions(); + state->position = QPoint(0, st::msgMargin.bottom()); + + state->selection = state->element->selectionFromQuote(quote); + + const auto session = &show->session(); + session->data().viewRepaintRequest( + ) | rpl::start_with_next([=](not_null view) { + if (view == state->element.get()) { + preview->update(); + } + }, preview->lifetime()); + + state->selection.changes() | rpl::start_with_next([=] { + preview->update(); + }, preview->lifetime()); + + const auto resolveNewSelection = [=] { + const auto make = [](uint16 symbol, bool afterSymbol) { + return uint16(symbol + (afterSymbol ? 1 : 0)); + }; + const auto first = make(state->symbol, state->afterSymbol); + const auto second = make( + state->selectionStartSymbol, + state->selectionStartAfterSymbol); + const auto result = (first <= second) + ? TextSelection{ first, second } + : TextSelection{ second, first }; + return state->element->adjustSelection(result, state->selectType); + }; + const auto startSelection = [=](TextSelectType type) { + if (state->selecting && state->selectType >= type) { + return; + } + state->selecting = true; + state->selectType = type; + state->selectionStartSymbol = state->symbol; + state->selectionStartAfterSymbol = state->afterSymbol; + if (!state->textCursor) { + preview->setCursor(style::cur_text); + } + preview->update(); + }; + preview->setMouseTracking(true); + preview->events() | rpl::start_with_next([=](not_null e) { + const auto type = e->type(); + const auto mouse = static_cast(e.get()); + if (type == QEvent::MouseMove) { + auto resolved = state->element->textState( + mouse->pos() - state->position, + { .flags = Ui::Text::StateRequest::Flag::LookupSymbol }); + state->over = true; + const auto text = (resolved.cursor == CursorState::Text); + if (state->textCursor != text) { + state->textCursor = text; + preview->setCursor((text || state->selecting) + ? style::cur_text + : style::cur_default); + } + if (state->symbol != resolved.symbol + || state->afterSymbol != resolved.afterSymbol) { + state->symbol = resolved.symbol; + state->afterSymbol = resolved.afterSymbol; + if (state->selecting) { + preview->update(); + } + } + } else if (type == QEvent::Leave && state->over) { + state->over = false; + if (state->textCursor) { + state->textCursor = false; + if (!state->selecting) { + preview->setCursor(style::cur_default); + } + } + } else if (type == QEvent::MouseButtonDblClick && state->over) { + startSelection(TextSelectType::Words); + state->trippleClickTimer.callOnce( + QApplication::doubleClickInterval()); + } else if (type == QEvent::MouseButtonPress && state->over) { + startSelection(state->trippleClickTimer.isActive() + ? TextSelectType::Paragraphs + : TextSelectType::Letters); + } else if (type == QEvent::MouseButtonRelease && state->selecting) { + const auto result = resolveNewSelection(); + state->selecting = false; + state->selectType = TextSelectType::Letters; + state->selection = result; + if (!state->textCursor) { + preview->setCursor(style::cur_default); + } + } + }, preview->lifetime()); + + preview->widthValue( + ) | rpl::filter([=](int width) { + return width > st::msgMinWidth; + }) | rpl::start_with_next([=](int width) { + const auto height = state->element->resizeGetHeight(width) + + state->position.y() + + st::msgMargin.top(); + preview->resize(width, height); + }, preview->lifetime()); + + preview->paintRequest() | rpl::start_with_next([=](QRect clip) { + Window::SectionWidget::PaintBackground( + state->theme.get(), + preview, + preview->window()->height(), + 0, + clip); + + auto p = Painter(preview); + auto hq = PainterHighQualityEnabler(p); + p.translate(state->position); + auto context = state->theme->preparePaintContext( + state->style.get(), + preview->rect(), + clip, + !box->window()->isActiveWindow()); + context.outbg = state->element->hasOutLayout(); + context.selection = state->selecting + ? resolveNewSelection() + : state->selection.current(); + state->element->draw(p, context); + if (state->element->displayFromPhoto()) { + auto userpicMinBottomSkip = st::historyPaddingBottom + + st::msgMargin.bottom(); + auto userpicBottom = preview->height() + - state->element->marginBottom() + - state->element->marginTop(); + const auto userpicTop = userpicBottom - st::msgPhotoSize; + if (const auto from = item->displayFrom()) { + from->paintUserpicLeft( + p, + state->userpic, + st::historyPhotoLeft, + userpicTop, + preview->width(), + st::msgPhotoSize); + } else if (const auto info = item->hiddenSenderInfo()) { + if (info->customUserpic.empty()) { + info->emptyUserpic.paintCircle( + p, + st::historyPhotoLeft, + userpicTop, + preview->width(), + st::msgPhotoSize); + } else { + const auto valid = info->paintCustomUserpic( + p, + state->userpic, + st::historyPhotoLeft, + userpicTop, + preview->width(), + st::msgPhotoSize); + if (!valid) { + info->customUserpic.load(session, item->fullId()); + } + } + } else { + Unexpected("Corrupt forwarded information in message."); + } + } + }, preview->lifetime()); + + return state->selection.value( + ) | rpl::map([=](TextSelection selection) { + return state->element->selectedQuote(selection); + }); +} + +PreviewDelegate::PreviewDelegate( + not_null parent, + not_null st, + Fn update) +: _parent(parent) +, _pathGradient(MakePathShiftGradient(st, update)) { +} + +bool PreviewDelegate::elementAnimationsPaused() { + return _parent->window()->isActiveWindow(); +} + +auto PreviewDelegate::elementPathShiftGradient() +-> not_null { + return _pathGradient.get(); +} + +Context PreviewDelegate::elementContext() { + return Context::History; +} + +} // namespace + +void ShowReplyToChatBox( + std::shared_ptr show, + FullReplyTo reply, + Fn clearOldDraft) { + class Controller final : public ChooseRecipientBoxController { + public: + using Chosen = not_null; + + Controller(not_null session) + : ChooseRecipientBoxController( + session, + [=](Chosen thread) mutable { _singleChosen.fire_copy(thread); }, + nullptr) { + } + + void rowClicked(not_null row) override final { + ChooseRecipientBoxController::rowClicked(row); + } + + [[nodiscard]] rpl::producer singleChosen() const{ + return _singleChosen.events(); + } + + bool respectSavedMessagesChat() const override { + return false; + } + + private: + void prepareViewHook() override { + delegate()->peerListSetTitle(rpl::single(u"Reply in..."_q)); + } + + rpl::event_stream _singleChosen; + + }; + + struct State { + not_null box; + not_null controller; + base::unique_qptr menu; + }; + const auto session = &show->session(); + const auto state = [&] { + auto controller = std::make_unique(session); + const auto controllerRaw = controller.get(); + auto box = Box(std::move(controller), nullptr); + const auto boxRaw = box.data(); + show->show(std::move(box)); + auto state = State{ boxRaw, controllerRaw }; + return boxRaw->lifetime().make_state(std::move(state)); + }(); + + auto chosen = [=](not_null thread) mutable { + const auto history = thread->owningHistory(); + const auto topicRootId = thread->topicRootId(); + const auto draft = history->localDraft(topicRootId); + const auto textWithTags = draft + ? draft->textWithTags + : TextWithTags(); + const auto cursor = draft ? draft->cursor : MessageCursor(); + reply.topicRootId = topicRootId; + history->setLocalDraft(std::make_unique( + textWithTags, + reply, + cursor, + Data::WebPageDraft())); + history->clearLocalEditDraft(topicRootId); + history->session().changes().entryUpdated( + thread, + Data::EntryUpdate::Flag::LocalDraftSet); + + if (clearOldDraft) { + crl::on_main(&history->session(), clearOldDraft); + } + return true; + }; + auto callback = [=, chosen = std::move(chosen)]( + Controller::Chosen thread) mutable { + const auto weak = Ui::MakeWeak(state->box); + if (!chosen(thread)) { + return; + } else if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + state->controller->singleChosen( + ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); +} + +void EditReplyOptions( + std::shared_ptr show, + FullReplyTo reply, + Fn done, + Fn highlight, + Fn clearOldDraft) { + const auto session = &show->session(); + const auto item = session->data().message(reply.messageId); + if (!item) { + return; + } + show->show(Box([=](not_null box) { + box->setWidth(st::boxWideWidth); + + struct State { + rpl::variable quote; + }; + const auto state = box->lifetime().make_state(); + state->quote = AddQuoteTracker(box, show, item, reply.quote); + + box->setTitle(reply.quote.empty() + ? rpl::single(u"Reply to Message"_q) + : rpl::single(u"Update Quote"_q)); + + Settings::AddButton( + box->verticalLayout(), + rpl::single(u"Reply in another chat"_q), + st::settingsButton, + { &st::menuIconReply } + )->setClickedCallback([=] { + ShowReplyToChatBox(show, reply, clearOldDraft); + }); + + Settings::AddButton( + box->verticalLayout(), + rpl::single(u"Show message"_q), + st::settingsButton, + { &st::menuIconShowInChat } + )->setClickedCallback(highlight); + + const auto finish = [=](FullReplyTo result) { + const auto weak = Ui::MakeWeak(box); + done(std::move(result)); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + + Settings::AddButton( + box->verticalLayout(), + rpl::single(u"Remove reply"_q), + st::settingsAttentionButtonWithIcon, + { &st::menuIconDeleteAttention } + )->setClickedCallback([=] { + finish({}); + }); + + box->addButton(rpl::single(u"Apply"_q), [=] { + auto result = reply; + result.quote = state->quote.current(); + finish(result); + }); + + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + + session->data().itemRemoved( + ) | rpl::filter([=](not_null removed) { + return removed == item; + }) | rpl::start_with_next([=] { + finish({}); + }, box->lifetime()); + })); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.h b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.h new file mode 100644 index 0000000000..048fd8ebae --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.h @@ -0,0 +1,32 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Window { +class SessionController; +} // namespace Window + +namespace HistoryView::Controls { + +void EditReplyOptions( + std::shared_ptr show, + FullReplyTo reply, + Fn done, + Fn highlight, + Fn clearOldDraft); + +void ShowReplyToChatBox( + std::shared_ptr show, + FullReplyTo reply, + Fn clearOldDraft = nullptr); + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 3cc0903ffb..b514877036 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -395,6 +395,11 @@ public: virtual TextWithEntities selectedQuote( const Ui::Text::String &text, TextSelection selection) const = 0; + virtual TextSelection selectionFromQuote( + const TextWithEntities "e) const = 0; + virtual TextSelection selectionFromQuote( + const Ui::Text::String &text, + const TextWithEntities "e) const = 0; [[nodiscard]] virtual TextSelection adjustSelection( TextSelection selection, TextSelectType type) const; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index b89a6385aa..142020884d 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -56,7 +56,7 @@ namespace { constexpr auto kPlayStatusLimit = 2; const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; -std::optional ExtractController( +[[nodiscard]] std::optional ExtractController( const ClickContext &context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { @@ -65,6 +65,42 @@ std::optional ExtractController( 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); @@ -2674,6 +2710,66 @@ TextWithEntities Message::selectedQuote( return result; } +TextSelection Message::selectionFromQuote( + const TextWithEntities "e) const { + const auto item = data(); + const auto &translated = item->translatedText(); + const auto &original = item->originalText(); + if (&translated != &original || quote.empty()) { + return {}; + } else if (hasVisibleText()) { + return selectionFromQuote(text(), quote); + } else if (const auto media = this->media()) { + if (media->isDisplayed() || isHiddenByGroup()) { + return media->selectionFromQuote(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; + } + const auto &modifications = text.modifications(); + //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 { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 33c1b66074..7506bf355e 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -99,6 +99,11 @@ public: TextWithEntities selectedQuote( const Ui::Text::String &text, TextSelection selection) const override; + TextSelection selectionFromQuote( + const TextWithEntities "e) const override; + TextSelection selectionFromQuote( + const Ui::Text::String &text, + const TextWithEntities "e) const override; TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 162243feb8..faf393999a 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_compose_controls.h" #include "history/view/controls/history_view_forward_panel.h" +#include "history/view/controls/history_view_reply_options.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 983d7ff114..a3d6550c56 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -679,6 +679,17 @@ TextWithEntities Service::selectedQuote( return {}; } +TextSelection Service::selectionFromQuote( + const TextWithEntities "e) const { + return {}; +} + +TextSelection Service::selectionFromQuote( + const Ui::Text::String &text, + const TextWithEntities "e) const { + return {}; +} + TextSelection Service::adjustSelection( TextSelection selection, TextSelectType type) const { diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index d40a69c8bb..c862ce6575 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -47,6 +47,11 @@ public: TextWithEntities selectedQuote( const Ui::Text::String &text, TextSelection selection) const override; + TextSelection selectionFromQuote( + const TextWithEntities "e) const override; + TextSelection selectionFromQuote( + const Ui::Text::String &text, + const TextWithEntities "e) const override; TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 071b6bb3df..5e45b8877d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -1226,6 +1226,24 @@ TextWithEntities Document::selectedQuote(TextSelection selection) const { return {}; } +TextSelection Document::selectionFromQuote( + const TextWithEntities "e) const { + if (const auto captioned = Get()) { + const auto result = parent()->selectionFromQuote( + captioned->caption, + quote); + if (result.empty()) { + return {}; + } else if (const auto voice = Get()) { + return HistoryView::ShiftItemSelection( + result, + voice->transcribeText); + } + return result; + } + return {}; +} + bool Document::uploading() const { return _data->uploading(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index 6084820eb6..31dd9886ab 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -47,6 +47,8 @@ public: TextForMimeData selectedText(TextSelection selection) const override; TextWithEntities selectedQuote(TextSelection selection) const override; + TextSelection selectionFromQuote( + const TextWithEntities "e) const override; bool uploading() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index b30035efac..70a81264d2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1207,6 +1207,11 @@ TextWithEntities Gif::selectedQuote(TextSelection selection) const { return parent()->selectedQuote(_caption, selection); } +TextSelection Gif::selectionFromQuote( + const TextWithEntities "e) const { + return parent()->selectionFromQuote(_caption, quote); +} + bool Gif::fullFeaturedGrouped(RectParts sides) const { return (sides & RectPart::Left) && (sides & RectPart::Right); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index 9a81457ebb..fdba9a0af3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -69,6 +69,8 @@ public: TextForMimeData selectedText(TextSelection selection) const override; TextWithEntities selectedQuote(TextSelection selection) const override; + TextSelection selectionFromQuote( + const TextWithEntities "e) const override; bool uploading() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index ea60e186fc..bda57224e2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -92,6 +92,10 @@ public: TextSelection selection) const { return {}; } + [[nodiscard]] virtual TextSelection selectionFromQuote( + const TextWithEntities "e) const { + return {}; + } [[nodiscard]] virtual bool isDisplayed() const; virtual void updateNeedBubbleState() { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 9d09faf179..81dee1c54c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -1053,6 +1053,11 @@ TextWithEntities Photo::selectedQuote(TextSelection selection) const { return parent()->selectedQuote(_caption, selection); } +TextSelection Photo::selectionFromQuote( + const TextWithEntities "e) const { + return parent()->selectionFromQuote(_caption, quote); +} + void Photo::hideSpoilers() { _caption.setSpoilerRevealed(false, anim::type::instant); if (_spoiler) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 76c1ff6ad5..b76bbe2644 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -58,6 +58,8 @@ public: TextForMimeData selectedText(TextSelection selection) const override; TextWithEntities selectedQuote(TextSelection selection) const override; + TextSelection selectionFromQuote( + const TextWithEntities "e) const override; PhotoData *getPhoto() const override { return _data; From b463c76eca08c240b80587da793584302237aa7e Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Oct 2023 21:39:25 +0400 Subject: [PATCH 60/99] Allow quote selection only in captions. --- .../controls/history_view_reply_options.cpp | 11 ++++++++- .../history/view/history_view_cursor_state.h | 1 + .../history/view/history_view_message.cpp | 24 ++++++++++++++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp index 7d348beb93..bd2898705b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp @@ -176,14 +176,23 @@ private: } preview->update(); }; + const auto media = item->media(); + const auto onlyMessageText = media + && (media->webpage() + || media->game() + || (!media->photo() && !media->document())); preview->setMouseTracking(true); preview->events() | rpl::start_with_next([=](not_null e) { const auto type = e->type(); const auto mouse = static_cast(e.get()); if (type == QEvent::MouseMove) { + auto request = StateRequest{ + .flags = Ui::Text::StateRequest::Flag::LookupSymbol, + .onlyMessageText = onlyMessageText, + }; auto resolved = state->element->textState( mouse->pos() - state->position, - { .flags = Ui::Text::StateRequest::Flag::LookupSymbol }); + request); state->over = true; const auto text = (resolved.cursor == CursorState::Text); if (state->textCursor != text) { diff --git a/Telegram/SourceFiles/history/view/history_view_cursor_state.h b/Telegram/SourceFiles/history/view/history_view_cursor_state.h index d9ae018897..80cec91a9b 100644 --- a/Telegram/SourceFiles/history/view/history_view_cursor_state.h +++ b/Telegram/SourceFiles/history/view/history_view_cursor_state.h @@ -63,6 +63,7 @@ struct StateRequest { result.flags = flags; return result; } + bool onlyMessageText = false; }; enum class InfoDisplayType : char { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 142020884d..c35d18d7a2 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2025,6 +2025,10 @@ TextState Message::textState( const auto media = this->media(); auto result = TextState(item); + const auto minSymbol = (_invertMedia && request.onlyMessageText) + ? visibleMediaTextLength() + : 0; + result.symbol = minSymbol; auto g = countGeometry(); if (g.width() < 1 || isHidden()) { @@ -2158,7 +2162,7 @@ TextState Message::textState( result = bottomInfoResult; } }; - if (!result.symbol && inBubble) { + if (result.symbol <= minSymbol && inBubble) { const auto mediaHeight = mediaDisplayed ? media->height() : 0; const auto mediaLeft = trect.x() - st::msgPadding.left(); const auto mediaTop = (!mediaDisplayed || _invertMedia) @@ -2174,7 +2178,17 @@ TextState Message::textState( result = media->textState( point - QPoint(mediaLeft, mediaTop), request); - if (!_invertMedia) { + if (_invertMedia) { + if (request.onlyMessageText) { + result.symbol = minSymbol; + result.afterSymbol = false; + result.cursor = CursorState::None; + } + } else if (request.onlyMessageText) { + result.symbol = visibleTextLength(); + result.afterSymbol = false; + result.cursor = CursorState::None; + } else { result.symbol += visibleTextLength(); } } else if (getStateText(point, trect, &result, request)) { @@ -2208,6 +2222,11 @@ TextState Message::textState( } } else if (media && media->isDisplayed()) { result = media->textState(point - g.topLeft(), request); + if (request.onlyMessageText) { + result.symbol = 0; + result.afterSymbol = false; + result.cursor = CursorState::None; + } result.symbol += visibleTextLength(); } @@ -2220,7 +2239,6 @@ TextState Message::textState( : 0); if (QRect(g.left(), keyboardTop, g.width(), keyboardHeight).contains(point)) { result.link = keyboard->getLink(point - QPoint(g.left(), keyboardTop)); - return result; } } From 1409d38ac3df589f9d0ec55541f5e70525ce9cb3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 25 Oct 2023 11:00:22 +0400 Subject: [PATCH 61/99] Improve reply options edit design. --- Telegram/Resources/icons/menu/link_above.png | Bin 0 -> 450 bytes .../Resources/icons/menu/link_above@2x.png | Bin 0 -> 725 bytes .../Resources/icons/menu/link_above@3x.png | Bin 0 -> 1029 bytes Telegram/Resources/icons/menu/link_below.png | Bin 0 -> 454 bytes .../Resources/icons/menu/link_below@2x.png | Bin 0 -> 761 bytes .../Resources/icons/menu/link_below@3x.png | Bin 0 -> 1017 bytes .../Resources/icons/menu/link_enlarge.png | Bin 0 -> 582 bytes .../Resources/icons/menu/link_enlarge@2x.png | Bin 0 -> 965 bytes .../Resources/icons/menu/link_enlarge@3x.png | Bin 0 -> 1378 bytes Telegram/Resources/icons/menu/link_shrink.png | Bin 0 -> 542 bytes .../Resources/icons/menu/link_shrink@2x.png | Bin 0 -> 991 bytes .../Resources/icons/menu/link_shrink@3x.png | Bin 0 -> 1324 bytes Telegram/Resources/langs/lang.strings | 19 +++++ .../SourceFiles/data/data_chat_filters.cpp | 18 +---- Telegram/SourceFiles/data/data_drafts.cpp | 19 ++--- Telegram/SourceFiles/data/data_peer.cpp | 34 ++++++++ Telegram/SourceFiles/data/data_peer.h | 6 ++ .../history/history_inner_widget.cpp | 72 ++++++++++------- .../history/history_item_components.cpp | 36 +++++++++ .../history/history_item_components.h | 4 + .../SourceFiles/history/history_widget.cpp | 6 +- .../history_view_compose_controls.cpp | 6 +- .../controls/history_view_reply_options.cpp | 76 ++++++++++++------ Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- Telegram/SourceFiles/ui/menu_icons.style | 5 ++ Telegram/lib_ui | 2 +- 26 files changed, 219 insertions(+), 86 deletions(-) create mode 100644 Telegram/Resources/icons/menu/link_above.png create mode 100644 Telegram/Resources/icons/menu/link_above@2x.png create mode 100644 Telegram/Resources/icons/menu/link_above@3x.png create mode 100644 Telegram/Resources/icons/menu/link_below.png create mode 100644 Telegram/Resources/icons/menu/link_below@2x.png create mode 100644 Telegram/Resources/icons/menu/link_below@3x.png create mode 100644 Telegram/Resources/icons/menu/link_enlarge.png create mode 100644 Telegram/Resources/icons/menu/link_enlarge@2x.png create mode 100644 Telegram/Resources/icons/menu/link_enlarge@3x.png create mode 100644 Telegram/Resources/icons/menu/link_shrink.png create mode 100644 Telegram/Resources/icons/menu/link_shrink@2x.png create mode 100644 Telegram/Resources/icons/menu/link_shrink@3x.png diff --git a/Telegram/Resources/icons/menu/link_above.png b/Telegram/Resources/icons/menu/link_above.png new file mode 100644 index 0000000000000000000000000000000000000000..1f8881dfebec24aa4af0e2cba3b37ac59fd5a39a GIT binary patch literal 450 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfOvls3F~mYJ zIYEN;fj~lXa`d*Gn}2_Q|N8aoNq(UPnzPOG|FN^>-`itoV4!eZr>yMT+UV`~_SNoQ zy7cLpnZ}dV{p)^zTRU;0;K?*b-<_qer)}9%QdRZq<>lp#ztsKa{CIcQT31)MBU0yx z#K|K^Ncl`;4EIb=lNv1UQoOri!%a$oq zr@D$AJPK6({oC8y^?!aahBpYgrKYAdhGr>mdKI;Vst08Qqwo&W#< literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/link_above@2x.png b/Telegram/Resources/icons/menu/link_above@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..61c53d18e377cbabd4911b2117693cf80b01edba GIT binary patch literal 725 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@HZMn+SQniz z&Dnl?`e{|QKDWgJVw|a)BbNq6t_&$yaW}7?UA_JA#;COhsTMNFJU6v0;QYNmMo)Rc z?YFN=tai5S5M-b9P-|b@`h@`U1IJ!}?dq|e>9enS!u|K+iv`}S@ljKrv`e99 z#fAR+t{39V4xHA_^742gyMK<;5m#RGrMKUTZ9ZL5ye~$tfW_hLxBTtV8#bm{%bR6w zm5Mtd^vZAf;j`?*-b;hhHqX56xv7&&mXCc>e}esaah7JK7p)E7tu2;c)_gnlY?|?f zuYM_3E}}o?izvJ^`e7)-!>0OX)9+WgX0ua_IOltPPc(OXSnY6VqQ)`RxqkOg94`^% zKjxw&*y%EFp}-&g!zs)Cfsxyw6gA`6MbbAj}p6}`67?Q#I zHuAi;R-nkc&YMmz+Go8zwP5X*2@^B~J2QmWF1q00B%qjaS&8F6m*JtgJHnNw^4xM0 z5KLlm4Cr=P;Tk#ZmVWj71AFwGC-0qHyjytB<73w8=Pd94+_$s*-OQQO#C}a=TEd{| zz%>C#)w4!LNB>m6YPR@p-u>IRt7~g*EiEPG<*#p($a~@J?7TGS<@ej~fBvkjs+tvD zwpPe;?z!2sXPXDr|8Kl~=1fnRYT*G{Ik|m%_tqZwvZ|@9{8+VD#^A_u2}w!K>8Is4 zCEa=U?Aa0VWn34Z7Tvvj*D&mpsha%t*QNXKujlei*>L;qzWw{t*Yx%EEnBwi#=}F0 z4qdw@X3diK`SWL`)7e6&AAbzd5)J$Q@L^(kx%nNZ8L9#tTefb^&C4_5E?`ccT# z*s)_z)^N2p9Zh0bsn=t#~4V61V5P`nDBgU*y=UcYV7jOW-rz8N=h)7 z;E|S=J|n-Q)oG#r;=1w!M~=9x4NDG{laZO>qsI12(a?h1_u8*tR^3c_8<--ed}}^; z&hKa4*8ThMU%jfTzgp0+Gg@nE(w&68ynKA8l5a*FcRR!uwtDN{y|GQqVFv0C=k>X? z>!p;Hna%X^`e!wF-TwXZlG=xKg*p!~olY^5m6i3{=FKZBFPCmD-f;SUwdq|JbFMja=7dbUar-uR z{(=Y2Ge3!3YTA{=&(D9N#CM0Sot@U=bLY=rzjkd(*&bHcoXkwl2f1d_cKXMJmnG>< zzkToCF^kFk(8Q?u!CvzNx4+oFn-AvhtUeSS{?0Pm&E0+B@ysi4Iyc70$N#ReJ9lTn zYr){Z&oz!F3HtMx?)LNd|0uct{O31sc;?G&J=Wv)_|YS!@6Qcuraq`FEoD95A}%f- p-=&lu)F7h3>cNOi)vaNwXV|do8uL`yN5-Hm=;`X`vd$@?2>>o>z|H^w literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/link_below.png b/Telegram/Resources/icons/menu/link_below.png new file mode 100644 index 0000000000000000000000000000000000000000..315150fa66007626819a4b93a9529b2ed6a181f6 GIT binary patch literal 454 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf%)rydF~maf z?PUKx7YC7Jy*zpff*aFUHZL{a(PqIg_g=K{#zH3X1B`1r_eSh+_CB-|6=3kpE{?FycF7gPgaRM__i$ZONGt5@4vlR z3S7&!b1ydFDR)(wu)_Vrqh4h@k@7SaJ z9zWPw7W@2nUF`KjnfrO>zL)v>-Y$!rc6Jlbs^-$YasJCMuj}Dx3ZJ#C_WonF&J$19 bckW}{cC9z2qsQttC=5Ma{an^LB{Ts5(^a$1 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/link_below@2x.png b/Telegram/Resources/icons/menu/link_below@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f30fe99b492a0ff5e4438bc3ddf656f3bc98f18f GIT binary patch literal 761 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@Hz16gOaCzcW3a9k^YV&}h}5>$UKFt)M|G0Q(PMr>C$GD0OjUL| zU;S#I?`NNPJD%;F_q;Yc&G2sVdCUEuXWyS&o%Zf{dqpQ#2N>1_I(#~sBw4lh-s`Vg zQ@yUqv_5=T@S<$@_uqe0*Q9O!`E#E3M2`;}CO)cp{88e^gFQ{p&p-cMWB1(ixSr&b z7bR9ZW6n+1IZ(PYreSh}i=k`CgZ-flyHD5H#p{R(Uy#V(9<4WBd-~}qrZ2z$ZusE<==iPhxI>ObycfU zzU%noeuIDKdfk*8PNf*x81``g=9}xMuJ+>PgQW>|CT}J=hWjiHYVqE7H}B`^3Dp~0 z7?L(dOklp)bwp2cEz|l(0cuYQEav#BD{}T9Pd1RSIB3F|x%Jk`6rTdQ<|8v`DTGdPqR}FQzHY*n(Nsy5_2mfHl0mtmWogj za$NAnjwQ!ysSp=1$fSDP{1W9vcHi9>wKm{nh6&e19>tkHVJkx>tiGGK`f8S(%q+dx zXSc=Z1t$LBcWe3d)8_Ku=(X3*Z*=re+Zwg7SJAH^s+Dn1;)xs+DXpnp%NSZB#JXAg zK6bDaufP6Uc48zF85Q64l+Kg+AG%W+UoT2lkr|n#6gA`6MbbAj}p6u!37?Q#I zHtM{0mZQi!TT^kpP4Qb!i)n3{5GEw0C?x3U(kxK}EO zOux2BVUhf5vvn+jD%zW!-tJ{SRB-W9=AOrUj~1GKzh`{^=f3>3Ig5+g**-M$Ent4Z zVAX)2_8)GvH!%v)x_a!`u`5?Zd<~Y*2wEx9=`wHLydy{WTb=%=eDGX;`OB(*ms-x9 zIdh@qW1rysW1l`5ong#8z9wvSZhrpdYWJ~8}iJjD8 zaQ8`LzBKjRdRfC&r<~VcfB*iyak!kky!`Uz%dM@gH#{sW-L;;vy}f;}_(8k*{gb}D zd86a`{9}bi_12a^-=o{NZ=aUOGrd+$Z`Dc(4weS7&kJ1^U$n5c?mn8tQ)W=UP-xou z=XdkWi}^gQf_z;)XMCL@()u+c;r{N&9i3S}@7;^r8f9x|CnqJ9#1N)6^62Db z@gunCt@*=0hI0Mu*RMC1cicGb^wV31Rd#RQvW4aNv(0O%Q^ozech|04H_1D~wV=RJ&++QBXUk6T{5W`U=aJ8!jT8TW`0yc9I^^JN z|34W8j`L>Ee*LG9XO8907(Kn6mCaE)(~ci^Hwrt%wxY;t?jFv9Bkk*Zi+1WvVacty zdE#L~!I@j~87o{{vUx%#S}nF`u}CwV>GS;g^9_-A@83Uccg}FK%7p+2&q?Bw7s1lI bQNaUyfu&o^ub%I^49a$%u6{1-oD!Ml4mk+4?JW>++32n8^yWwdhuNdofJ0`pxrh_y^mT0havTae`~Q&O_Jy4H&F z`*%-Coq54|ywJSWC+*+=`-Np?K`Z~K>7Q&en(4DJz~fkH%FdX)?a_ZVEw)DGnn=y{ zTmHSh%to$}QA~wnqJ<3K-njFHGF?>(3=MC;S4Va`dz^0f2s{u_^6ZlGx%)Y0Q@v8n zX1|hWk|JK!Sj0m`1Vs-Xu(W?0MT)yRw?tx2A79KY_>$bQtz;6Ef zu+>XquCwy6KB}=(PddW+{mp5n%_m_8_OxYB-e)5UsEep2ZetR(C!poAd)uBPH zCxV^6R>iKYnD_l#>Y{7u8$z@Ut+vFtTnO9}qBYm=vQ*810EJaM0t78CXz|L6>Xq2Y zt>*Y^HUIpn{{LJ~cfJ%@u(-q@GSHcRy6})~yuhPbhIeCQPup^einn+e=Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFFG)l}R9Fe^Sk0~>K@?^T3sy+j zSP(x;8{*zX!rp=>@Bl($>oo)siS>O14N_si7eq;7RH)w(le zq86P~r_TA#*QZYXbel}i_RPS4mI1TO`}ll5zu)h6yJd`(Hs|wsyJ;IDww>7)%!Fu2~un`}0l n*=(lwr|0!B&(UWF9y9}AY-UF=+Zal<00000NkvXXu0mjfq-dU9 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/link_enlarge@3x.png b/Telegram/Resources/icons/menu/link_enlarge@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..78b3f137be2fcaa8106e68e7bd49fdf4d431e498 GIT binary patch literal 1378 zcmV-o1)chdP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS<%t=H+RA>e5n!7JPO%%s}F13&l zArhA$Arum&0u|Ay1@S*9Bnp3kzkw)(PNF~*fLmX^xMM@L6pU0oj^A5s!VVCwa``uh5so}Sj# zgc&r;KBz1X(E#aFclh zSonRCO;#*~GlaL0y`I1jNWcp@u$)(KZ*MWLD-&X107h{Go~fB%IkAP zfDvGTG$}7ORS+>r6+64gR)mg@j^5th;$owAmh|T4=KlV^TODA=#>P}RAWZ?6ap>ym zY8jOCO62H5TU*=S-kuLDrGjv=v$NC9O$S4;YQkfTP&gl@52vum(FLX}BP+CUQZcJ; zbvhVAaCZY$VkD-=?D({)e~9vxm6g-e(;yu^`RibGkd6V3uIOlI@s(|j0IjB`M#oL` zw79rfSy^dIQ3Aw(&ic8%y%i2kO-=Lj^A=e#ve6Yq>u(Fq&CT1}+pMc6yStlc&E!g^3n<`21$mPC##E#3tOy` zAr=Fx1c((C6|JtW>bnIVsLvHqqRg8vOJP7EZdW8bY;0^);MfBltSG;`yHjzAih~2v za5sya7?IxW>?}C0uCCmyDCg|vTg}MG2qC3A0(KL~x@2NvLeidwhK8*B`*tA0#wUH7 z7%KrIzyN7#m&`e)N9ZPG5zFIt#fWf&cmG{t_?w>B1&0;AWh7$c!o$PErq5uUSLf&F z&(F^@Gc(bFS{otb=E1J@`T5B@J~=sYf0u>T{r$bHa*Oj8aPtU1W902BQ*3Q*C2Q0z z>WD4a*-0+-1~JItiRt;TE<0Fp3J(kn*oxVMTO>R&PaSSno~N*9U%Q1iiRBI}Y-EjC zPO-8^tiHb9k>Mxhf#T10LaK#@1^LG08MBMN*aSPSm}{&spPd;~^T3z0(a}-zD9Hwp z$*;&nVw)&YWRn>4wz07xLo0!-k^Cx$4rrnwf#~^(JYe2VT*3h@Lq!*KFh0IuTw?^U z0>U%WFhOrY_Vsb{^(WjOi# z6q|r*0A%z)BLV*k_Q`y??3{Uxc~Vu^)A`Lspme$fK|0_R)q-1IH#ga7~l07*qoM6N<$f|5>qkpKVy literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/link_shrink.png b/Telegram/Resources/icons/menu/link_shrink.png new file mode 100644 index 0000000000000000000000000000000000000000..48bf8f0350b84caed6229d1fb001c04d17be92ad GIT binary patch literal 542 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfti#jAF~maf z>=eVkrT~GqyJwF%Nn2@b*ksUnu7WksA$5&KQ)~N%%?r0oj4oj0&R^JCa>PZ6ZPV}6 zcom)e5H9oG>n~J({`Y@=<>A>cOa9%ix-9(a`|oYJum7DYwvajZym+f<&&P^Ack@nf z`ct!`YY_um#DoJ!9p^n?ef3nK%%rLf3=F$t^frk(T1;#9h&vE4OZ~`mx0Woqn)kc! zzDqC&aG&>_x7|7CK!C!(1wC%cr!IZDr*QdY$@beDXP-^G{#sNQB&BubH}9Vp^Lrvz zthxSLf#Z?IwTo+87D%=&3|XXcDfGh@p;uq4uDqzQ(G%l-7jbpt__*%rRSjS(C?kZp?QMr)8Ny=eMlr-gPusRHRkv?f2g&Q#Qru z>7G6mX1vd$@?2>|oT;Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFNl8ROR9Fe^Sg~pQ;; zz+^J6o4arKy@ZEbEO+kQIp@rs*_qifnO4s#1OHhD%#{9kJf5AM9f!j~30{nu&*#J8 z@cH?<*Xu3TYVfSDucy=LR;vX>Lmof@5U?6V@KB%6*KW5B(IJ5V7T}ynGL(nR@Aud1 zbv8zgMx$6PHk(bP9neyHO3`dK+wJyXFo^VoLLoF63KD@UYYATwGj;P7coL z#M)2x$;k=R?sB<4RS*g{H#cZoC=_^XI2<02$8bUucftTTV~NFLq93-LAzqCJ+$I`s zw;Pe{4FhVb)vC>An@lFDR7wQl;8+nvKkTJ~q&TfbQB5Qgxx2d~BK#FWlA2nrMpT0^ z;{`dLPCin|jV3h^WTk5-Mjjm)YGg{r&yv`vcF*%S%3= zAB{$oE*Y7j*=)urq3QVeSfuZEyVuv(qGy?Hq@eTj^UKRii^VdXPDMUss8Xqj9u?b2 zK{&DO@9(RSQL2pp$|$TVG1LrUpTcnPRhS`S7GvIIG8ydNDjb@ssDh%=XgnTQguc4E z(mzrhnJTDUF7NK{@(f}1W9tooFpD{yeuF9qt4KDRWqfRBVCxOu(l@13ljhEe`_%32 ztxo#I=Gp{_YOpi(`~5{kogNK>BsE%hO`=e$Ae=m_TL}{ zeLw3*5d#0&uk_j3nL<7mUEh1zApaIIeLw3uhHLtk3#-Ku`|x1wFrU~QXJ4t#H=qEh zel4UtWFMm5EvyB&6~L;)7c?nAy@+n$u<-Qsr1#QOon}?L%D@lJz%NxG2?^=)f^`4@ N002ovPDHLkV1nDxxUT>J literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/link_shrink@3x.png b/Telegram/Resources/icons/menu/link_shrink@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb66a523c8929f2e11c4f1e1b5e65266b4c7f12 GIT binary patch literal 1324 zcmV+{1=IS8P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*ISe5TDvbbK@>+cA`ucK zG$d{$3av_`Lg!KV4$t6NG9^WsSd?#ae zW*;-Nv-f`AZZ$i5=A7T}oHMiYs;Eex)B~vpQV*maNIg)#9;noV-qzOE)z#I~($dt_ zR9UI1#K-ja_xID&)5F8V<>lq=?X8cbolgx74RdpIXJ=|-A(FVUS77gw)Xe; zZ*FeT6Tp!^*(#vlHa0f4x3>=r3=9no)z{aP=JWIO-rnBV*H^01DIoNGtUfJyKgd7#^e`tR&Xj+DfAMUs2h-(BI#` zy}dm%Gb5d3c6N52o}R9(tQ;L3NxAW0Fj?BHq!_5xKphK}}7~+uIw{JUTim zNP*Se-5uFQ+ynu<0t^WNq*s8|C?m$Z6N*&rqt?U6wY9aZRVI_+n}gNC!2xB;{r&yM z#s)9I$~+hzq#&szCdkG8>Sfi}*H)}59UWIg`7?KslToc3Q4<@*Q_}<^&mzI_S z#e4)3NX5y?$@%$t8abrkum}9{@ge;bvN8c;K!&*C2?3}2!2&TUD_bjk0vfeoN~|wk2@VgPf!q z2L*uni|a?{yuaL!YW-YX)`#oLJ_g1#!Npm9KA-pVfvazTLH8F3US#8?c7X*w3ioJO z2Q#|B0#E##SB^leG%tzm?(PO&68m4T^|5}56)}IgkA5TW3*j$fh8;1m=O5g*v*Pt^qM^l6oNZ iKuser(data.vuser_id().v); - user->setAccessHash(data.vaccess_hash().v); - return (PeerData*)user; - }, [&](const MTPDinputPeerChat &data) { - return (PeerData*)owner->chat(data.vchat_id().v); - }, [&](const MTPDinputPeerChannel &data) { - const auto channel = owner->channel(data.vchannel_id().v); - channel->setAccessHash(data.vaccess_hash().v); - return (PeerData*)channel; - }, [&](const MTPDinputPeerSelf &data) { - return (PeerData*)owner->session().user(); - }, [&](const auto &data) { - return (PeerData*)nullptr; - }); + const MTPInputPeer &input) { + const auto peer = Data::PeerFromInputMTP(owner, input); return peer ? owner->history(peer).get() : nullptr; }) | ranges::views::filter([](History *history) { return history != nullptr; diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 50cad31fc5..adf92511b1 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -81,12 +81,10 @@ void ApplyPeerCloudDraft( session, draft.ventities().value_or_empty())) }; - const auto reply = draft.vreply_to() - ? ReplyFieldsFromMTP(history, *draft.vreply_to()) - : ReplyFields(); - const auto replyPeerId = reply.externalPeerId - ? reply.externalPeerId - : peerId; + auto replyTo = draft.vreply_to() + ? ReplyToFromMTP(history, *draft.vreply_to()) + : FullReplyTo(); + replyTo.topicRootId = topicRootId; auto webpage = WebPageDraft{ .invert = draft.is_invert_media(), .removed = draft.is_no_webpage(), @@ -106,14 +104,7 @@ void ApplyPeerCloudDraft( } auto cloudDraft = std::make_unique( textWithTags, - FullReplyTo{ - .messageId = FullMsgId(replyPeerId, reply.messageId), - .quote = reply.quote, - .storyId = (reply.storyId - ? FullStoryId{ replyPeerId, reply.storyId } - : FullStoryId()), - .topicRootId = topicRootId, - }, + replyTo, MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax), std::move(webpage)); cloudDraft->date = date; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index edda209ad5..780b6c5711 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -126,6 +126,40 @@ AllowedReactions Parse(const MTPChatReactions &value) { }); } +PeerData *PeerFromInputMTP( + not_null owner, + const MTPInputPeer &input) { + return input.match([&](const MTPDinputPeerUser &data) { + const auto user = owner->user(data.vuser_id().v); + user->setAccessHash(data.vaccess_hash().v); + return (PeerData*)user; + }, [&](const MTPDinputPeerChat &data) { + return (PeerData*)owner->chat(data.vchat_id().v); + }, [&](const MTPDinputPeerChannel &data) { + const auto channel = owner->channel(data.vchannel_id().v); + channel->setAccessHash(data.vaccess_hash().v); + return (PeerData*)channel; + }, [&](const MTPDinputPeerSelf &data) { + return (PeerData*)owner->session().user(); + }, [&](const auto &data) { + return (PeerData*)nullptr; + }); +} + +UserData *UserFromInputMTP( + not_null owner, + const MTPInputUser &input) { + return input.match([&](const MTPDinputUser &data) { + const auto user = owner->user(data.vuser_id().v); + user->setAccessHash(data.vaccess_hash().v); + return user.get(); + }, [&](const MTPDinputUserSelf &data) { + return owner->session().user().get(); + }, [](const auto &data) { + return (UserData*)nullptr; + }); +} + } // namespace Data PeerClickHandler::PeerClickHandler(not_null peer) diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 33a7727412..894045fdd5 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -116,6 +116,12 @@ bool operator<(const AllowedReactions &a, const AllowedReactions &b); bool operator==(const AllowedReactions &a, const AllowedReactions &b); [[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value); +[[nodiscard]] PeerData *PeerFromInputMTP( + not_null owner, + const MTPInputPeer &input); +[[nodiscard]] UserData *UserFromInputMTP( + not_null owner, + const MTPInputUser &input); } // namespace Data diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c1eb6cf98e..c6440fce36 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2216,35 +2216,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { return; } const auto itemId = item->fullId(); - const auto canSendReply = [&] { - const auto peer = item->history()->peer; - const auto topic = item->topic(); - return topic - ? Data::CanSendAnything(topic) - : (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; - }(); - if (canReply) { - const auto quote = selectedQuote(item); - _menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { - if (canSendReply) { - _widget->replyToMessage({ itemId, quote }); - } else { - HistoryView::Controls::ShowReplyToChatBox( - controller->uiShow(), - { itemId, quote }); - } - }, &st::menuIconReply); - } const auto repliesCount = item->repliesCount(); const auto withReplies = (repliesCount > 0); const auto topicRootId = item->history()->isForum() @@ -2408,6 +2379,45 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } }; + const auto addReplyAction = [&](HistoryItem *item) { + if (!item) { + return; + } + const auto canSendReply = [&] { + const auto peer = item->history()->peer; + const auto topic = item->topic(); + return topic + ? Data::CanSendAnything(topic) + : (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; + }(); + if (canReply) { + const auto itemId = item->fullId(); + const auto quote = selectedQuote(item); + const auto text = quote.empty() + ? tr::lng_context_reply_msg(tr::now) + : tr::lng_context_quote_and_reply(tr::now); + _menu->addAction(text, [=] { + if (canSendReply) { + _widget->replyToMessage({ itemId, quote }); + } else { + HistoryView::Controls::ShowReplyToChatBox( + controller->uiShow(), + { itemId, quote }); + } + }, &st::menuIconReply); + } + }; + const auto lnkPhoto = link ? reinterpret_cast( link->property(kPhotoLinkMediaProperty).toULongLong()) @@ -2419,6 +2429,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (lnkPhoto || lnkDocument) { const auto item = _dragStateItem; const auto itemId = item ? item->fullId() : FullMsgId(); + addReplyAction(item); + if (isUponSelected > 0) { const auto selectedText = getSelectedText(); if (!hasCopyRestrictionForSelected() @@ -2529,6 +2541,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { : QString(); if (isUponSelected > 0) { + addReplyAction(item); const auto selectedText = getSelectedText(); if (!hasCopyRestrictionForSelected() && !selectedText.empty()) { _menu->addAction( @@ -2551,6 +2564,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } addItemActions(item, item); } else { + addReplyAction(item); addItemActions(item, albumPartItem); if (item && !isUponSelected) { const auto media = (view ? view->media() : nullptr); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index a21b77b583..1528269e73 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -435,6 +435,42 @@ ReplyFields ReplyFieldsFromMTP( }); } +FullReplyTo ReplyToFromMTP( + not_null history, + const MTPInputReplyTo &reply) { + return reply.match([&](const MTPDinputReplyToMessage &data) { + auto result = FullReplyTo{ + .messageId = { history->peer->id, data.vreply_to_msg_id().v }, + }; + if (const auto peer = data.vreply_to_peer_id()) { + const auto parsed = Data::PeerFromInputMTP( + &history->owner(), + *peer); + if (!parsed) { + return FullReplyTo(); + } + result.messageId.peer = parsed->id; + } + result.topicRootId = data.vtop_msg_id().value_or_empty(); + result.quote = TextWithEntities{ + qs(data.vquote_text().value_or_empty()), + Api::EntitiesFromMTP( + &history->session(), + data.vquote_entities().value_or_empty()), + }; + return result; + }, [&](const MTPDinputReplyToStory &data) { + if (const auto parsed = Data::UserFromInputMTP( + &history->owner(), + data.vuser_id())) { + return FullReplyTo{ + .storyId = { parsed->id, data.vstory_id().v }, + }; + } + return FullReplyTo(); + }); +} + HistoryMessageReply::HistoryMessageReply() = default; HistoryMessageReply &HistoryMessageReply::operator=( diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 79b78d6d7a..c3bf23770c 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -245,6 +245,10 @@ struct ReplyFields { not_null history, const MTPMessageReplyHeader &reply); +[[nodiscard]] FullReplyTo ReplyToFromMTP( + not_null history, + const MTPInputReplyTo &reply); + struct HistoryMessageReply : public RuntimeComponent { HistoryMessageReply(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index c3ac82ff95..836c368921 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6237,7 +6237,11 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } } else if (const auto reply = replyTo()) { const auto done = [=](FullReplyTo replyTo) { - replyToMessage(replyTo); + if (replyTo) { + replyToMessage(replyTo); + } else { + cancelReply(); + } }; const auto highlight = [=] { controller()->showPeerHistory( 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 098840e8a2..c94e0fa100 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -626,7 +626,11 @@ void FieldHeader::init() { const auto history = _history; const auto topicRootId = _topicRootId; const auto done = [=](FullReplyTo replyTo) { - replyToMessage(replyTo); + if (replyTo) { + replyToMessage(replyTo); + } else { + _replyCancelled.fire({}); + } }; const auto clearOldReplyTo = [=, id = reply.messageId] { ClearDraftReplyTo(history, topicRootId, id); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp index bd2898705b..0eb6f8aeb8 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp @@ -118,9 +118,7 @@ private: bool selectionStartAfterSymbol = false; }; - const auto preview = box->addRow( - object_ptr(box), - QMargins(0, 0, 0, st::settingsThemesTopSkip)); + const auto preview = box->addRow(object_ptr(box), {}); const auto state = preview->lifetime().make_state(); state->theme = DefaultThemeOn(preview->lifetime()); @@ -246,14 +244,17 @@ private: preview->resize(width, height); }, preview->lifetime()); - preview->paintRequest() | rpl::start_with_next([=](QRect clip) { + box->setAttribute(Qt::WA_OpaquePaintEvent, false); + box->paintRequest() | rpl::start_with_next([=](QRect clip) { Window::SectionWidget::PaintBackground( state->theme.get(), - preview, - preview->window()->height(), + box, + box->window()->height(), 0, clip); + }, box->lifetime()); + preview->paintRequest() | rpl::start_with_next([=](QRect clip) { auto p = Painter(preview); auto hq = PainterHighQualityEnabler(p); p.translate(state->position); @@ -366,7 +367,7 @@ void ShowReplyToChatBox( private: void prepareViewHook() override { - delegate()->peerListSetTitle(rpl::single(u"Reply in..."_q)); + delegate()->peerListSetTitle(tr::lng_reply_in_another_title()); } rpl::event_stream _singleChosen; @@ -382,7 +383,10 @@ void ShowReplyToChatBox( const auto state = [&] { auto controller = std::make_unique(session); const auto controllerRaw = controller.get(); - auto box = Box(std::move(controller), nullptr); + auto box = Box(std::move(controller), [=]( + not_null box) { + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }); const auto boxRaw = box.data(); show->show(std::move(box)); auto state = State{ boxRaw, controllerRaw }; @@ -440,28 +444,31 @@ void EditReplyOptions( show->show(Box([=](not_null box) { box->setWidth(st::boxWideWidth); - struct State { - rpl::variable quote; + const auto bottom = box->setPinnedToBottomContent( + object_ptr(box)); + const auto addSkip = [&] { + const auto skip = bottom->add(object_ptr( + bottom, + st::settingsPrivacySkipTop)); + skip->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(skip).fillRect(clip, st::boxBg); + }, skip->lifetime()); }; - const auto state = box->lifetime().make_state(); - state->quote = AddQuoteTracker(box, show, item, reply.quote); - box->setTitle(reply.quote.empty() - ? rpl::single(u"Reply to Message"_q) - : rpl::single(u"Update Quote"_q)); + addSkip(); Settings::AddButton( - box->verticalLayout(), - rpl::single(u"Reply in another chat"_q), + bottom, + tr::lng_reply_in_another_chat(), st::settingsButton, - { &st::menuIconReply } + { &st::menuIconReplace } )->setClickedCallback([=] { ShowReplyToChatBox(show, reply, clearOldDraft); }); Settings::AddButton( - box->verticalLayout(), - rpl::single(u"Show message"_q), + bottom, + tr::lng_reply_show_in_chat(), st::settingsButton, { &st::menuIconShowInChat } )->setClickedCallback(highlight); @@ -475,15 +482,38 @@ void EditReplyOptions( }; Settings::AddButton( - box->verticalLayout(), - rpl::single(u"Remove reply"_q), + bottom, + tr::lng_reply_remove(), st::settingsAttentionButtonWithIcon, { &st::menuIconDeleteAttention } )->setClickedCallback([=] { finish({}); }); - box->addButton(rpl::single(u"Apply"_q), [=] { + if (!item->originalText().empty()) { + addSkip(); + Settings::AddDividerText( + bottom, + tr::lng_reply_about_quote()); + } + + struct State { + rpl::variable quote; + }; + const auto state = box->lifetime().make_state(); + state->quote = AddQuoteTracker(box, show, item, reply.quote); + + box->setTitle(reply.quote.empty() + ? tr::lng_reply_options_header() + : tr::lng_reply_options_quote()); + + auto save = state->quote.value( + ) | rpl::map([=](const TextWithEntities "e) { + return quote.empty() + ? tr::lng_settings_save() + : tr::lng_reply_quote_selected(); + }) | rpl::flatten_latest(); + box->addButton(std::move(save), [=] { auto result = reply; result.quote = state->quote.current(); finish(result); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 2ae1e04633..8a0ff53bfd 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -761,7 +761,7 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#db074fa8 flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?MessageReplyHeader message:string entities:flags.3?Vector media:flags.5?MessageMedia date:int = DraftMessage; +draftMessage#3fccf7ef flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 59cd81b535..f8885acb64 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -140,6 +140,11 @@ menuIconPremium: icon {{ "menu/premium", menuIconColor }}; menuIconIpAddress: icon {{ "menu/ip_address", menuIconColor }}; menuIconAddress: icon {{ "menu/payment_address", menuIconColor }}; menuIconShowAll: icon {{ "menu/all_media", menuIconColor }}; +menuIconReplace: icon {{ "chat/input_replace", menuIconColor }}; +menuIconAbove: icon {{ "menu/link_above", menuIconColor }}; +menuIconBelow: icon {{ "menu/link_below", menuIconColor }}; +menuIconEnlarge: icon {{ "menu/link_enlarge", menuIconColor }}; +menuIconShrink: icon {{ "menu/link_shrink", menuIconColor }}; menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }}; menuIconTTLAnyTextPosition: point(11px, 22px); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 71d24af3a8..c36559a679 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 71d24af3a840d15a6cc0a3a8a1e9cbe77a604739 +Subproject commit c36559a6797f02d8a56a414ac91f9c6fd08b5270 From 17578be4b94b4344b567329a50de1503e377a0ae Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 25 Oct 2023 21:23:54 +0400 Subject: [PATCH 62/99] Edit reply / webpage options together. --- Telegram/CMakeLists.txt | 6 +- Telegram/SourceFiles/data/data_drafts.cpp | 1 + Telegram/SourceFiles/data/data_web_page.cpp | 27 + Telegram/SourceFiles/data/data_web_page.h | 1 + .../history/history_inner_widget.cpp | 2 +- .../SourceFiles/history/history_widget.cpp | 376 +++----- Telegram/SourceFiles/history/history_widget.h | 29 +- .../history_view_compose_controls.cpp | 405 ++------- .../controls/history_view_compose_controls.h | 12 +- .../controls/history_view_draft_options.cpp | 846 ++++++++++++++++++ ...options.h => history_view_draft_options.h} | 11 +- .../controls/history_view_reply_options.cpp | 535 ----------- .../history_view_webpage_processor.cpp | 327 +++++++ .../controls/history_view_webpage_processor.h | 100 +++ .../view/history_view_replies_section.cpp | 2 +- .../view/media/history_view_web_page.cpp | 25 +- Telegram/lib_ui | 2 +- 17 files changed, 1525 insertions(+), 1182 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp rename Telegram/SourceFiles/history/view/controls/{history_view_reply_options.h => history_view_draft_options.h} (80%) delete mode 100644 Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d43e91d1f4..32aa477b4f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -651,16 +651,18 @@ PRIVATE history/view/controls/history_view_compose_controls.h history/view/controls/history_view_compose_search.cpp history/view/controls/history_view_compose_search.h + history/view/controls/history_view_draft_options.cpp + history/view/controls/history_view_draft_options.h history/view/controls/history_view_forward_panel.cpp history/view/controls/history_view_forward_panel.h - history/view/controls/history_view_reply_options.cpp - history/view/controls/history_view_reply_options.h history/view/controls/history_view_ttl_button.cpp history/view/controls/history_view_ttl_button.h history/view/controls/history_view_voice_record_bar.cpp history/view/controls/history_view_voice_record_bar.h history/view/controls/history_view_voice_record_button.cpp history/view/controls/history_view_voice_record_button.h + history/view/controls/history_view_webpage_processor.cpp + history/view/controls/history_view_webpage_processor.h history/view/media/history_view_call.cpp history/view/media/history_view_call.h history/view/media/history_view_contact.cpp diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index adf92511b1..6f386e582c 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -37,6 +37,7 @@ WebPageDraft WebPageDraft::FromItem(not_null item) { .forceSmallMedia = !!(previewFlags & PageFlag::ForceSmallMedia), .invert = item->invertMedia(), .manual = !!(previewFlags & PageFlag::Manual), + .removed = !previewPage, }; } diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 2e473f347f..ec0f964545 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -355,3 +355,30 @@ QString WebPageData::displayedSiteName() const { ? tr::lng_media_color_theme(tr::now) : siteName; } + +bool WebPageData::computeDefaultSmallMedia() const { + if (!collage.items.empty()) { + return false; + } else if (siteName.isEmpty() + && title.isEmpty() + && description.empty() + && author.isEmpty()) { + return false; + } else if (!document + && photo + && type != WebPageType::Photo + && type != WebPageType::Document + && type != WebPageType::Story + && type != WebPageType::Video) { + if (type == WebPageType::Profile) { + return true; + } else if (siteName == u"Twitter"_q + || siteName == u"Facebook"_q + || type == WebPageType::ArticleWithIV) { + return false; + } else { + return true; + } + } + return false; +} diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index a498a5767c..9aea4a0f58 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -89,6 +89,7 @@ struct WebPageData { const MTPmessages_Messages &result); [[nodiscard]] QString displayedSiteName() const; + [[nodiscard]] bool computeDefaultSmallMedia() const; const WebPageId id = 0; WebPageType type = WebPageType::None; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c6440fce36..e0944e7a85 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_helpers.h" #include "history/view/controls/history_view_forward_panel.h" -#include "history/view/controls/history_view_reply_options.h" +#include "history/view/controls/history_view_draft_options.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_web_page.h" diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 836c368921..9a595520b4 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -82,9 +82,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_unread_things.h" #include "history/view/controls/history_view_compose_search.h" #include "history/view/controls/history_view_forward_panel.h" -#include "history/view/controls/history_view_reply_options.h" +#include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_ttl_button.h" +#include "history/view/controls/history_view_webpage_processor.h" #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_service_message.h" @@ -206,7 +207,6 @@ HistoryWidget::HistoryWidget( , _api(&controller->session().mtp()) , _updateEditTimeLeftDisplay([=] { updateField(); }) , _fieldBarCancel(this, st::historyReplyCancel) -, _previewTimer([=] { requestPreview(); }) , _topBar(this, controller) , _scroll( this, @@ -440,12 +440,6 @@ HistoryWidget::HistoryWidget( if (_supportAutocomplete) { supportInitAutocomplete(); } - _fieldLinksParser = std::make_unique(_field); - _fieldLinksParser->list().changes( - ) | rpl::start_with_next([=](QStringList &&parsed) { - _parsedLinks = std::move(parsed); - checkPreview(); - }, lifetime()); _field->rawTextEdit()->installEventFilter(this); _field->rawTextEdit()->installEventFilter(_fieldAutocomplete); _field->setMimeDataHook([=]( @@ -566,13 +560,6 @@ HistoryWidget::HistoryWidget( }); }, lifetime()); - session().data().webPageUpdates( - ) | rpl::filter([=](not_null page) { - return (_previewData == page.get()); - }) | rpl::start_with_next([=] { - updatePreview(); - }, lifetime()); - session().data().channelDifferenceTooLong( ) | rpl::filter([=](not_null channel) { return _peer == channel.get(); @@ -723,9 +710,9 @@ HistoryWidget::HistoryWidget( return update.flags; }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) { if (flags & PeerUpdateFlag::Rights) { - checkPreview(); updateStickersByEmoji(); updateFieldPlaceholder(); + _preview->checkNow(false); } if (flags & PeerUpdateFlag::Migration) { handlePeerMigration(); @@ -1600,7 +1587,9 @@ void HistoryWidget::fieldChanged() { updateSendButtonType(); if (!HasSendText(_field)) { - _previewDraft = {}; + if (_preview) { + _preview->apply({}); + } _fieldIsEmpty = true; } else if (_fieldIsEmpty) { _fieldIsEmpty = false; @@ -1664,14 +1653,14 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { .messageId = FullMsgId(_history->peer->id, _editMsgId), .topicRootId = topicRootId, }, - _previewDraft, + _preview->draft(), _saveEditMsgRequestId)); } else { if (_replyTo || !_field->empty()) { _history->setLocalDraft(std::make_unique( _field, _replyTo, - _previewDraft)); + _preview->draft())); } else { _history->clearLocalDraft(topicRootId); } @@ -1897,6 +1886,9 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { _processingReplyItem = _replyEditMsg = nullptr; _processingReplyTo = _replyTo = FullReplyTo(); setEditMsgId(0); + if (_preview) { + _preview->apply({}); + } if (fieldWillBeHiddenAfterEdit) { updateControlsVisibility(); updateControlsGeometry(); @@ -1930,18 +1922,9 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { processReply(); } - // Save links from _field to _parsedLinks without generating preview. - _previewDraft = { .removed = true }; - if (_editMsgId) { - _fieldLinksParser->setDisabled(!_replyEditMsg - || (_replyEditMsg->media() - && !_replyEditMsg->media()->webpage())); + if (_preview) { + _preview->apply(draft->webpage, !_editMsgId); } - _fieldLinksParser->parseNow(); - _parsedLinks = _fieldLinksParser->list().current(); - _previewDraft = draft->webpage; - checkPreview(); - return true; } @@ -2149,9 +2132,6 @@ void HistoryWidget::showHistory( _canReplaceMedia = false; _photoEditMedia = nullptr; updateReplaceMediaButton(); - _previewData = nullptr; - _previewDraft = {}; - _previewCache.clear(); _fieldBarCancel->hide(); _membersDropdownShowTimer.cancel(); @@ -2409,10 +2389,45 @@ void HistoryWidget::setHistory(History *history) { _history = history; _migrated = _history ? _history->migrateFrom() : nullptr; registerDraftSource(); + if (_history) { + setupPreview(); + } else { + _previewDrawPreview = nullptr; + _preview = nullptr; + } } refreshAttachBotsMenu(); } +void HistoryWidget::setupPreview() { + Expects(_history != nullptr); + + using namespace HistoryView::Controls; + _preview = std::make_unique(_history, _field); + _preview->repaintRequests() | rpl::start_with_next([=] { + updateField(); + }, _preview->lifetime()); + + _preview->parsedValue( + ) | rpl::start_with_next([=](WebpageParsed value) { + _previewTitle.setText( + st::msgNameStyle, + value.title, + Ui::NameTextOptions()); + _previewDescription.setText( + st::defaultTextStyle, + value.description, + Ui::DialogTextOptions()); + const auto changed = (!_previewDrawPreview != !value.drawPreview); + _previewDrawPreview = value.drawPreview; + if (changed) { + updateControlsGeometry(); + updateControlsVisibility(); + } + updateField(); + }, _preview->lifetime()); +} + void HistoryWidget::injectSponsoredMessages() const { session().data().sponsoredMessages().inject( _history, @@ -2468,7 +2483,7 @@ void HistoryWidget::registerDraftSource() { ? FullReplyTo{ FullMsgId(peerId, editMsgId) } : _replyTo), _field->getTextWithTags(), - _previewDraft, + _preview->draft(), }; }; auto draftSource = Storage::MessageDraftSource{ @@ -2486,9 +2501,6 @@ void HistoryWidget::registerDraftSource() { void HistoryWidget::setEditMsgId(MsgId msgId) { unregisterDraftSources(); _editMsgId = msgId; - if (_fieldLinksParser && !_editMsgId) { - _fieldLinksParser->setDisabled(false); - } if (!msgId) { _canReplaceMedia = false; } @@ -2906,7 +2918,7 @@ void HistoryWidget::updateControlsVisibility() { if (_editMsgId || _replyTo || readyToForward() - || (_previewData && !_previewData->failed) + || _previewDrawPreview || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); @@ -3832,7 +3844,7 @@ void HistoryWidget::saveEditMsg() { _saveEditMsgRequestId = Api::EditTextMessage( item, sending, - _previewDraft, + _preview->draft(), options, done, fail); @@ -3918,7 +3930,7 @@ void HistoryWidget::send(Api::SendOptions options) { auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = _field->getTextWithAppliedMarkdown(); - message.webPage = _previewDraft; + message.webPage = _preview->draft(); const auto ignoreSlowmodeCountdown = (options.scheduled != 0); if (showSendMessageError( @@ -3935,9 +3947,6 @@ void HistoryWidget::send(Api::SendOptions options) { hideSelectorControlsAnimated(); - if (_previewData && _previewData->pendingTill) { - previewCancel(); - } setInnerFocus(); if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) { @@ -4333,7 +4342,7 @@ void HistoryWidget::updateOverStates(QPoint pos) { _field->y() - st::historySendPadding - st::historyReplyHeight, width() - skip - _fieldBarCancel->width(), st::historyReplyHeight); - const auto hasWebPage = _previewData && !_previewData->failed; + const auto hasWebPage = !!_previewDrawPreview; const auto inDetails = detailsRect.contains(pos) && (_editMsgId || replyTo() || isReadyToForward || hasWebPage); const auto inPhotoEdit = inDetails @@ -4595,9 +4604,7 @@ bool HistoryWidget::showRecordButton() const { && !_voiceRecordBar->isListenState() && !_voiceRecordBar->isRecordingByAnotherBar() && !HasSendText(_field) - && (!_previewData - || _previewData->failed - || _previewData->pendingTill) + && !_previewDrawPreview && !readyToForward() && !_editMsgId; } @@ -4781,7 +4788,7 @@ void HistoryWidget::toggleKeyboard(bool manual) { _kbReplyTo = nullptr; if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !_editMsgId && !_replyTo) { _fieldBarCancel->hide(); @@ -5771,7 +5778,7 @@ void HistoryWidget::updateHistoryGeometry( if (_editMsgId || replyTo() || readyToForward() - || (_previewData && !_previewData->failed)) { + || _previewDrawPreview) { newScrollHeight -= st::historyReplyHeight; } if (_kbShown) { @@ -6074,7 +6081,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _kbShown = false; _kbReplyTo = nullptr; if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !_replyTo) { _fieldBarCancel->hide(); updateMouseTracking(); @@ -6092,7 +6099,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _kbShown = false; _kbReplyTo = nullptr; if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !_replyTo && !_editMsgId) { _fieldBarCancel->hide(); @@ -6139,7 +6146,7 @@ int HistoryWidget::computeMaxFieldHeight() const { - ((_editMsgId || replyTo() || readyToForward() - || (_previewData && !_previewData->failed)) + || _previewDrawPreview) ? st::historyReplyHeight : 0) - (2 * st::historySendPadding) @@ -6219,44 +6226,21 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { crl::guard(_list, [=] { cancelEdit(); })); } else if (!_inDetails) { return; - } else if (_previewData - && !_previewData->failed - && !_previewData->pendingTill) { - const auto history = _history; - using namespace HistoryView::Controls; - EditWebPageOptions( - controller()->uiShow(), - _previewData, - _previewDraft, - [=](Data::WebPageDraft draft) { applyPreview(draft); }); + } else if (_previewDrawPreview) { + editDraftOptions(); } else if (isReadyToForward) { if (e->button() != Qt::LeftButton) { _forwardPanel->editToNextOption(); } else { _forwardPanel->editOptions(controller()->uiShow()); } - } else if (const auto reply = replyTo()) { - const auto done = [=](FullReplyTo replyTo) { - if (replyTo) { - replyToMessage(replyTo); - } else { - cancelReply(); - } - }; - const auto highlight = [=] { - controller()->showPeerHistory( - reply.messageId.peer, - Window::SectionShow::Way::Forward, - reply.messageId.msg); - }; - const auto history = _history; - using namespace HistoryView::Controls; - EditReplyOptions( - controller()->uiShow(), - reply, - done, - highlight, - [=] { ClearDraftReplyTo(history, reply.messageId); }); + } else if (_replyTo) { + editDraftOptions(); + } else if (_kbReplyTo) { + controller()->showPeerHistory( + _kbReplyTo->history()->peer->id, + Window::SectionShow::Way::Forward, + _kbReplyTo->id); } else if (_editMsgId) { controller()->showPeerHistory( _peer, @@ -6265,6 +6249,42 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } } +void HistoryWidget::editDraftOptions() { + Expects(_history != nullptr); + + const auto history = _history; + const auto reply = _replyTo; + const auto webpage = _preview->draft(); + + const auto done = [=]( + FullReplyTo replyTo, + Data::WebPageDraft webpage) { + if (replyTo) { + replyToMessage(replyTo); + } else { + cancelReply(); + } + if (_preview->draft() != webpage) { + _preview->apply(webpage); + } + }; + const auto highlight = [=] { + controller()->showPeerHistory( + reply.messageId.peer, + Window::SectionShow::Way::Forward, + reply.messageId.msg); + }; + + using namespace HistoryView::Controls; + EditDraftOptions( + controller()->uiShow(), + history, + Data::Draft(_field, reply, _preview->draft()), + done, + highlight, + [=] { ClearDraftReplyTo(history, reply.messageId); }); +} + void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (!_history) return; @@ -7075,9 +7095,8 @@ void HistoryWidget::setFieldText( _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; - if (!_previewDraft.manual) { - previewCancel(); - _previewDraft = {}; + if (_preview) { + _preview->checkNow(false); } } @@ -7239,7 +7258,7 @@ void HistoryWidget::editMessage(not_null item) { _history->setLocalDraft(std::make_unique( _field, _replyTo, - _previewDraft)); + _preview->draft())); } else { _history->clearLocalDraft({}); } @@ -7259,13 +7278,6 @@ void HistoryWidget::editMessage(not_null item) { previewDraft)); applyDraft(); - _previewData = previewDraft.id - ? session().data().webpage(previewDraft.id).get() - : nullptr; - if (_previewData) { - updatePreview(); - } - updateBotKeyboard(); if (fieldOrDisabledShown()) { @@ -7332,7 +7344,7 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { _processingReplyTo = _replyTo = FullReplyTo(); mouseMoveEvent(0); if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !_kbReplyTo) { _fieldBarCancel->hide(); updateMouseTracking(); @@ -7405,7 +7417,7 @@ void HistoryWidget::cancelEdit() { mouseMoveEvent(nullptr); if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !replyTo()) { _fieldBarCancel->hide(); updateMouseTracking(); @@ -7427,8 +7439,8 @@ void HistoryWidget::cancelEdit() { void HistoryWidget::cancelFieldAreaState() { controller()->hideLayer(); _replyForwardPressed = false; - if (_previewData && !_previewData->failed) { - applyPreview({ .removed = true }); + if (_previewDrawPreview) { + _preview->apply({ .removed = true }); } else if (_editMsgId) { cancelEdit(); } else if (readyToForward()) { @@ -7440,165 +7452,6 @@ void HistoryWidget::cancelFieldAreaState() { } } -void HistoryWidget::applyPreview(Data::WebPageDraft draft) { - _previewDraft = draft; - if (draft.removed) { - previewCancel(); - } else if (draft.id) { - _previewData = session().data().webpage(draft.id).get(); - requestPreview(); - } - - _saveDraftText = true; - _saveDraftStart = crl::now(); - saveDraft(); -} - -void HistoryWidget::previewCancel() { - _api.request(base::take(_previewRequest)).cancel(); - _previewData = nullptr; - _previewLinks.clear(); - updatePreview(); -} - -void HistoryWidget::checkPreview() { - const auto previewRestricted = [&] { - return _peer && _peer->amRestricted(ChatRestriction::EmbedLinks); - }(); - if (_previewDraft.removed || previewRestricted) { - previewCancel(); - return; - } else if (_previewDraft.manual) { - return; - } - const auto links = _parsedLinks.join(' '); - if (_previewLinks != links) { - _api.request(base::take(_previewRequest)).cancel(); - _previewLinks = links; - if (_previewLinks.isEmpty()) { - if (_previewData && !_previewData->failed) { - previewCancel(); - } - } else { - const auto i = _previewCache.constFind(links); - if (i == _previewCache.cend()) { - _previewRequest = _api.request(MTPmessages_GetWebPagePreview( - MTP_flags(0), - MTP_string(links), - MTPVector() - )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) { - gotPreview(links, result, requestId); - }).send(); - } else if (i.value()) { - _previewData = session().data().webpage(i.value()); - _previewDraft.id = _previewData->id; - _previewDraft.url = _previewData->url; - updatePreview(); - } else if (_previewData && !_previewData->failed) { - previewCancel(); - } - } - } -} - -void HistoryWidget::requestPreview() { - if (!_previewData || _previewData->failed || _previewLinks.isEmpty()) { - return; - } - const auto links = _previewLinks; - _previewRequest = _api.request(MTPmessages_GetWebPagePreview( - MTP_flags(0), - MTP_string(links), - MTPVector() - )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) { - gotPreview(links, result, requestId); - }).send(); -} - -void HistoryWidget::gotPreview( - QString links, - const MTPMessageMedia &result, - mtpRequestId req) { - if (req == _previewRequest) { - _previewRequest = 0; - } - if (result.type() == mtpc_messageMediaWebPage) { - const auto &data = result.c_messageMediaWebPage().vwebpage(); - const auto page = session().data().processWebpage(data); - _previewCache.insert(links, page->id); - if (page->pendingTill > 0 - && page->pendingTill <= base::unixtime::now()) { - page->pendingTill = 0; - page->failed = true; - } - if (links == _previewLinks && !_previewDraft.removed) { - _previewData = (page->id && !page->failed) - ? page.get() - : nullptr; - if (_previewData) { - _previewDraft.id = _previewData->id; - _previewDraft.url = _previewData->url; - } else { - _previewDraft = {}; - } - updatePreview(); - } - session().data().sendWebPageGamePollNotifications(); - } else if (result.type() == mtpc_messageMediaEmpty) { - _previewCache.insert(links, 0); - if (links == _previewLinks && !_previewDraft.removed) { - _previewData = nullptr; - _previewDraft = {}; - updatePreview(); - } - } -} - -void HistoryWidget::updatePreview() { - _previewTimer.cancel(); - if (_previewData && !_previewData->failed) { - _fieldBarCancel->show(); - updateMouseTracking(); - if (_previewData->pendingTill) { - _previewTitle.setText( - st::msgNameStyle, - tr::lng_preview_loading(tr::now), - Ui::NameTextOptions()); - auto linkText = QStringView(_previewLinks).split(' ').at(0).toString(); - _previewDescription.setText( - st::defaultTextStyle, - linkText, - Ui::DialogTextOptions()); - - const auto timeout = (_previewData->pendingTill - base::unixtime::now()); - _previewTimer.callOnce(std::max(timeout, 0) * crl::time(1000)); - } else { - auto preview = - HistoryView::TitleAndDescriptionFromWebPage(_previewData); - if (preview.title.isEmpty()) { - if (_previewData->document) { - preview.title = tr::lng_attach_file(tr::now); - } else if (_previewData->photo) { - preview.title = tr::lng_attach_photo(tr::now); - } - } - _previewTitle.setText( - st::msgNameStyle, - preview.title, - Ui::NameTextOptions()); - _previewDescription.setText( - st::defaultTextStyle, - preview.description, - Ui::DialogTextOptions()); - } - } else if (!readyToForward() && !replyTo() && !_editMsgId) { - _fieldBarCancel->hide(); - updateMouseTracking(); - } - updateControlsGeometry(); - update(); -} - void HistoryWidget::fullInfoUpdated() { auto refresh = false; if (_list) { @@ -7960,12 +7813,11 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { } else if (hasForward) { backy -= st::historyReplyHeight; backh += st::historyReplyHeight; - } else if (_previewData && !_previewData->failed) { + } else if (_previewDrawPreview) { backy -= st::historyReplyHeight; backh += st::historyReplyHeight; } - auto drawWebPagePreview = (_previewData && !_previewData->failed) - && !_replyForwardPressed; + auto drawWebPagePreview = _previewDrawPreview && !_replyForwardPressed; p.setInactive( controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any)); p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg); @@ -8072,7 +7924,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { backy + (st::historyReplyHeight - st::historyReplyPreview) / 2, st::historyReplyPreview, st::historyReplyPreview); - if (HistoryView::DrawWebPageDataPreview(p, _previewData, _peer, to)) { + if (_previewDrawPreview(p, to)) { previewLeft += st::historyReplyPreview + st::msgReplyBarSkip; } p.setPen(st::historyReplyNameFg); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1e30935b63..a78875e22a 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -94,13 +94,15 @@ class Element; class PinnedTracker; class TranslateBar; class ComposeSearch; -namespace Controls { +} // namespace HistoryView + +namespace HistoryView::Controls { class RecordLock; class VoiceRecordBar; class ForwardPanel; class TTLButton; -} // namespace Controls -} // namespace HistoryView +class WebpageProcessor; +} // namespace HistoryView::Controls class BotKeyboard; class HistoryInner; @@ -200,9 +202,6 @@ public: [[nodiscard]] QVector replyReturns() const; void setReplyReturns(PeerId peer, QVector replyReturns); - void updatePreview(); - void previewCancel(); - void escape(); void sendBotCommand(const Bot::SendCommandRequest &request); @@ -406,7 +405,6 @@ private: void startBotCommand(); void hidePinnedMessage(); void cancelFieldAreaState(); - void applyPreview(Data::WebPageDraft draft); void unblockUser(); void sendBotStartCommand(); void joinChannel(); @@ -540,9 +538,9 @@ private: void saveEditMsg(); - void checkPreview(); - void requestPreview(); - void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req); + void setupPreview(); + void editDraftOptions(); + void messagesReceived(not_null peer, const MTPmessages_Messages &messages, int requestId); void messagesFailed(const MTP::Error &error, int requestId); void addMessagesToFront(not_null peer, const QVector &messages); @@ -674,16 +672,10 @@ private: mtpRequestId _saveEditMsgRequestId = 0; - QStringList _parsedLinks; - QString _previewLinks; - WebPageData *_previewData = nullptr; - typedef QMap PreviewCache; - PreviewCache _previewCache; - mtpRequestId _previewRequest = 0; + std::unique_ptr _preview; + Fn _previewDrawPreview; Ui::Text::String _previewTitle; Ui::Text::String _previewDescription; - base::Timer _previewTimer; - Data::WebPageDraft _previewDraft; bool _replyForwardPressed = false; @@ -725,7 +717,6 @@ private: const object_ptr _fieldAutocomplete; object_ptr _supportAutocomplete; - std::unique_ptr _fieldLinksParser; UserData *_inlineBot = nullptr; QString _inlineBotUsername; 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 c94e0fa100..9150c5e509 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -47,9 +47,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/controls/history_view_forward_panel.h" -#include "history/view/controls/history_view_reply_options.h" +#include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_ttl_button.h" +#include "history/view/controls/history_view_webpage_processor.h" #include "history/view/history_view_webpage_preview.h" #include "inline_bots/bot_attach_web_view.h" #include "inline_bots/inline_results_widget.h" @@ -120,222 +121,6 @@ WebPageText ProcessWebPageData(WebPageData *page) { } // namespace -class WebpageProcessor final { -public: - WebpageProcessor( - not_null history, - not_null field); - - void cancel(); - void checkPreview(); - - [[nodiscard]] Data::WebPageDraft draft() const; - void setAllowed(bool allowed); - void refreshDraft(Data::WebPageDraft draft, bool disable); - - [[nodiscard]] rpl::producer<> paintRequests() const; - [[nodiscard]] rpl::producer titleChanges() const; - [[nodiscard]] rpl::producer descriptionChanges() const; - [[nodiscard]] rpl::producer pageDataChanges() const; - -private: - void updatePreview(); - void getWebPagePreview(); - - const not_null _history; - MTP::Sender _api; - MessageLinksParser _fieldLinksParser; - - Data::WebPageDraft _previewDraft; - - QStringList _parsedLinks; - QString _previewLinks; - - WebPageData *_previewData = nullptr; - std::map _previewCache; - - mtpRequestId _previewRequest = 0; - - rpl::event_stream<> _paintRequests; - rpl::event_stream _titleChanges; - rpl::event_stream _descriptionChanges; - rpl::event_stream _pageDataChanges; - - base::Timer _timer; - - rpl::lifetime _lifetime; - -}; - -WebpageProcessor::WebpageProcessor( - not_null history, - not_null field) -: _history(history) -, _api(&history->session().mtp()) -, _fieldLinksParser(field) -, _timer([=] { - if (!ShowWebPagePreview(_previewData) - || _previewLinks.isEmpty()) { - return; - } - getWebPagePreview(); -}) { - - _history->session().downloaderTaskFinished( - ) | rpl::filter([=] { - return _previewData - && (_previewData->document || _previewData->photo); - }) | rpl::start_with_next([=] { - _paintRequests.fire({}); - }, _lifetime); - - _history->owner().webPageUpdates( - ) | rpl::filter([=](not_null page) { - return (_previewData == page.get()); - }) | rpl::start_with_next([=] { - updatePreview(); - }, _lifetime); - - _fieldLinksParser.list().changes( - ) | rpl::start_with_next([=](QStringList &&parsed) { - _parsedLinks = std::move(parsed); - checkPreview(); - }, _lifetime); -} - -rpl::producer<> WebpageProcessor::paintRequests() const { - return _paintRequests.events(); -} - -Data::WebPageDraft WebpageProcessor::draft() const { - return _previewDraft; -} - -void WebpageProcessor::setAllowed(bool allowed) { - _previewDraft.removed = !allowed; -} - -void WebpageProcessor::refreshDraft( - Data::WebPageDraft draft, - bool disable) { - // Save links from _field to _parsedLinks without generating preview. - _previewDraft = { .removed = true }; - _fieldLinksParser.setDisabled(disable); - _fieldLinksParser.parseNow(); - _parsedLinks = _fieldLinksParser.list().current(); - _previewDraft = draft; - checkPreview(); -} - -void WebpageProcessor::cancel() { - _api.request(base::take(_previewRequest)).cancel(); - _previewData = nullptr; - _previewLinks.clear(); - updatePreview(); -} - -void WebpageProcessor::updatePreview() { - _timer.cancel(); - auto t = QString(); - auto d = QString(); - if (ShowWebPagePreview(_previewData)) { - if (const auto till = _previewData->pendingTill) { - t = tr::lng_preview_loading(tr::now); - d = QStringView(_previewLinks).split(' ').at(0).toString(); - - const auto timeout = till - base::unixtime::now(); - _timer.callOnce( - std::max(timeout, 0) * crl::time(1000)); - } else { - const auto preview = ProcessWebPageData(_previewData); - t = preview.title; - d = preview.description; - } - } - _titleChanges.fire_copy(t); - _descriptionChanges.fire_copy(d); - _pageDataChanges.fire_copy(_previewData); - _paintRequests.fire({}); -} - -void WebpageProcessor::getWebPagePreview() { - const auto links = _previewLinks; - _previewRequest = _api.request( - MTPmessages_GetWebPagePreview( - MTP_flags(0), - MTP_string(links), - MTPVector() - )).done([=](const MTPMessageMedia &result) { - _previewRequest = 0; - result.match([=](const MTPDmessageMediaWebPage &d) { - const auto page = _history->owner().processWebpage(d.vwebpage()); - _previewCache.insert({ links, page->id }); - if (page->pendingTill > 0 - && page->pendingTill <= base::unixtime::now()) { - page->pendingTill = 0; - page->failed = true; - } - if (links == _previewLinks && !_previewDraft.removed) { - _previewData = (page->id && !page->failed) - ? page.get() - : nullptr; - updatePreview(); - } - }, [=](const MTPDmessageMediaEmpty &d) { - _previewCache.insert({ links, 0 }); - if (links == _previewLinks && !_previewDraft.removed) { - _previewData = nullptr; - updatePreview(); - } - }, [](const auto &d) { - }); - }).fail([=] { - _previewRequest = 0; - }).send(); -} - -void WebpageProcessor::checkPreview() { - const auto previewRestricted = _history->peer - && _history->peer->amRestricted(ChatRestriction::EmbedLinks); - if (_previewDraft.removed || previewRestricted) { - cancel(); - return; - } - const auto newLinks = _parsedLinks.join(' '); - if (_previewLinks == newLinks) { - return; - } - _api.request(base::take(_previewRequest)).cancel(); - _previewLinks = newLinks; - if (_previewLinks.isEmpty()) { - if (ShowWebPagePreview(_previewData)) { - cancel(); - } - } else { - const auto i = _previewCache.find(_previewLinks); - if (i == _previewCache.end()) { - getWebPagePreview(); - } else if (i->second) { - _previewData = _history->owner().webpage(i->second); - updatePreview(); - } else if (ShowWebPagePreview(_previewData)) { - cancel(); - } - } -} - -rpl::producer WebpageProcessor::titleChanges() const { - return _titleChanges.events(); -} - -rpl::producer WebpageProcessor::descriptionChanges() const { - return _descriptionChanges.events(); -} - -rpl::producer WebpageProcessor::pageDataChanges() const { - return _pageDataChanges.events(); -} - class FieldHeader final : public Ui::RpWidget { public: FieldHeader( @@ -350,10 +135,7 @@ public: void updateForwarding( Data::Thread *thread, Data::ResolvedForwardDraft items); - void previewRequested( - rpl::producer title, - rpl::producer description, - rpl::producer page); + void previewReady(rpl::producer parsed); void previewUnregister(); [[nodiscard]] bool isDisplayed() const; @@ -366,7 +148,6 @@ public: [[nodiscard]] rpl::producer scrollToItemRequests() const; [[nodiscard]] rpl::producer<> editPhotoRequests() const; [[nodiscard]] MessageToEdit queryToEdit(); - [[nodiscard]] Data::WebPageDraft webPageDraft() const; [[nodiscard]] FullReplyTo getDraftReply() const; [[nodiscard]] rpl::producer<> editCancelled() const { @@ -399,18 +180,14 @@ private: bool hasPreview() const; struct Preview { - WebPageData *data = nullptr; - Data::WebPageDraft draft; + Controls::WebpageParsed parsed; Ui::Text::String title; Ui::Text::String description; - bool cancelled = false; }; const std::shared_ptr _show; History *_history = nullptr; MsgId _topicRootId = 0; - rpl::variable _title; - rpl::variable _description; Preview _preview; rpl::event_stream<> _editCancelled; @@ -493,7 +270,7 @@ void FieldHeader::init() { st::historyReplyIcon.paint(p, position, width()); } - (ShowWebPagePreview(_preview.data) && !*leftIconPressed) + (_preview.parsed && !*leftIconPressed) ? paintWebPage( p, _history ? _history->peer : _data->session().user()) @@ -548,22 +325,6 @@ void FieldHeader::init() { update(); }); - _title.value( - ) | rpl::start_with_next([=](const auto &t) { - _preview.title.setText( - st::msgNameStyle, - t, - Ui::NameTextOptions()); - }, lifetime()); - - _description.value( - ) | rpl::start_with_next([=](const auto &d) { - _preview.description.setText( - st::messageTextStyle, - d, - Ui::DialogTextOptions()); - }, lifetime()); - setMouseTracking(true); events( ) | rpl::filter([=](not_null event) { @@ -619,28 +380,32 @@ void FieldHeader::init() { if (!isEditingMessage() && readyToForward()) { _forwardPanel->editOptions(_show); } else if (!isEditingMessage() && reply) { - using namespace Controls; - const auto highlight = [=] { - _scrollToItemRequests.fire_copy(reply.messageId); - }; - const auto history = _history; - const auto topicRootId = _topicRootId; - const auto done = [=](FullReplyTo replyTo) { - if (replyTo) { - replyToMessage(replyTo); - } else { - _replyCancelled.fire({}); - } - }; - const auto clearOldReplyTo = [=, id = reply.messageId] { - ClearDraftReplyTo(history, topicRootId, id); - }; - EditReplyOptions( - _show, - reply, - done, - highlight, - clearOldReplyTo); + //using namespace Controls; + //const auto highlight = [=] { + // _scrollToItemRequests.fire_copy(reply.messageId); + //}; + //const auto history = _history; + //const auto topicRootId = _topicRootId; + //const auto done = [=]( + // FullReplyTo replyTo, + // Data::WebPageDraft webpage) { + // if (replyTo) { + // replyToMessage(replyTo); + // } else { + // _replyCancelled.fire({}); + // } + + //}; + //const auto clearOldReplyTo = [=, id = reply.messageId] { + // ClearDraftReplyTo(history, topicRootId, id); + //}; + //EditDraftOptions( + // _show, + // _history, + // Data::Draft( reply, + // done, + // highlight, + // clearOldReplyTo); } else { auto id = isEditingMessage() ? _editMsgId.current() @@ -683,17 +448,17 @@ void FieldHeader::setShownMessage(HistoryItem *item) { _shownMessage = item; if (item) { updateShownMessageText(); - if (item->fullId() == _editMsgId.current()) { - _preview = {}; - if (const auto media = item->media()) { - if (const auto page = media->webpage()) { - const auto preview = ProcessWebPageData(page); - _title = preview.title; - _description = preview.description; - _preview.data = page; - } - } - } + //if (item->fullId() == _editMsgId.current()) { + // _preview = {}; + // if (const auto media = item->media()) { + // if (const auto page = media->webpage()) { + // const auto preview = ProcessWebPageData(page); + // _title = preview.title; + // _description = preview.description; + // _preview.data = page; + // } + // } + //} } else { _shownMessageText.clear(); resolveMessageData(); @@ -737,34 +502,22 @@ void FieldHeader::resolveMessageData() { _data->session().api().requestMessageData(peer, itemId, callback); } -void FieldHeader::previewRequested( - rpl::producer title, - rpl::producer description, - rpl::producer page) { +void FieldHeader::previewReady( + rpl::producer parsed) { _previewLifetime.destroy(); std::move( - title - ) | rpl::filter([=] { - return !_preview.cancelled; - }) | rpl::start_with_next([=](const QString &t) { - _title = t; - }, _previewLifetime); - - std::move( - description - ) | rpl::filter([=] { - return !_preview.cancelled; - }) | rpl::start_with_next([=](const QString &d) { - _description = d; - }, _previewLifetime); - - std::move( - page - ) | rpl::filter([=] { - return !_preview.cancelled; - }) | rpl::start_with_next([=](WebPageData *p) { - _preview.data = p; + parsed + ) | rpl::start_with_next([=](Controls::WebpageParsed parsed) { + _preview.parsed = std::move(parsed); + _preview.title.setText( + st::msgNameStyle, + _preview.parsed.title, + Ui::NameTextOptions()); + _preview.description.setText( + st::messageTextStyle, + _preview.parsed.description, + Ui::DialogTextOptions()); updateVisible(); }, _previewLifetime); } @@ -774,7 +527,7 @@ void FieldHeader::previewUnregister() { } void FieldHeader::paintWebPage(Painter &p, not_null context) { - Expects(ShowWebPagePreview(_preview.data)); + Expects(!!_preview.parsed); const auto textTop = st::msgReplyPadding.top(); auto previewLeft = st::historyReplySkip + st::msgReplyBarSkip; @@ -784,7 +537,7 @@ void FieldHeader::paintWebPage(Painter &p, not_null context) { (st::historyReplyHeight - st::historyReplyPreview) / 2, st::historyReplyPreview, st::historyReplyPreview); - if (HistoryView::DrawWebPageDataPreview(p, _preview.data, context, to)) { + if (_preview.parsed.drawPreview(p, to)) { previewLeft += st::historyReplyPreview + st::msgReplyBarSkip; } const auto elidedWidth = width() @@ -965,13 +718,7 @@ FullReplyTo FieldHeader::replyingToMessage() const { } bool FieldHeader::hasPreview() const { - return ShowWebPagePreview(_preview.data); -} - -Data::WebPageDraft FieldHeader::webPageDraft() const { - return hasPreview() - ? Data::WebPageDraft{ .id = _preview.data->id } - : Data::WebPageDraft{ .removed = true }; + return !!_preview.parsed; } FullReplyTo FieldHeader::getDraftReply() const { @@ -1476,8 +1223,7 @@ void ComposeControls::setFieldText( | TextUpdateEvent::SendTyping; if (_preview) { - _preview->cancel(); - _preview->setAllowed(true); + _preview->checkNow(false); } } @@ -1626,7 +1372,7 @@ void ComposeControls::init() { _header->previewCancelled( ) | rpl::start_with_next([=] { if (_preview) { - _preview->setAllowed(false); + _preview->apply({ .removed = true }); } _saveDraftText = true; _saveDraftStart = crl::now(); @@ -2010,7 +1756,7 @@ void ComposeControls::fieldChanged() { updateSendButtonType(); _hasSendText = HasSendText(_field); if (!_hasSendText.current() && _preview) { - _preview->setAllowed(true); + _preview->apply({}); } if (updateBotCommandShown() || updateLikeShown()) { updateControlsVisibility(); @@ -2180,7 +1926,9 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { } _header->editMessage({}); _header->replyToMessage({}); - _preview->refreshDraft({}, false); + if (_preview) { + _preview->apply({}); + } _canReplaceMedia = false; _photoEditMedia = nullptr; return; @@ -2194,15 +1942,13 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { draft->cursor.applyTo(_field); _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; if (_preview) { - const auto disablePreview = (editDraft != nullptr); - _preview->refreshDraft(draft->webpage, disablePreview); + _preview->apply(draft->webpage, draft != editDraft); } if (draft == editDraft) { const auto resolve = [=] { if (const auto item = _history->owner().message(editingId)) { const auto media = item->media(); - const auto disablePreview = media && !media->webpage(); _canReplaceMedia = media && media->allowsEditMedia(); _photoEditMedia = (_canReplaceMedia && _regularWindow @@ -2216,7 +1962,11 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { item->fullId()); } _header->editMessage(editingId, _photoEditMedia != nullptr); - _preview->refreshDraft(_preview->draft(), disablePreview); + if (_preview) { + _preview->apply( + Data::WebPageDraft::FromItem(item), + false); + } return true; } _canReplaceMedia = false; @@ -3070,9 +2820,11 @@ void ComposeControls::initWebpageProcess() { return; } - _preview = std::make_unique(_history, _field); + _preview = std::make_unique( + _history, + _field); - _preview->paintRequests( + _preview->repaintRequests( ) | rpl::start_with_next(crl::guard(_header.get(), [=] { _header->update(); }), _historyLifetime); @@ -3088,7 +2840,7 @@ void ComposeControls::initWebpageProcess() { return update.flags; }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) { if (flags & Data::PeerUpdate::Flag::Rights) { - _preview->checkPreview(); + _preview->checkNow(false); updateStickersByEmoji(); updateFieldPlaceholder(); } @@ -3106,10 +2858,7 @@ void ComposeControls::initWebpageProcess() { } }, _historyLifetime); - _header->previewRequested( - _preview->titleChanges(), - _preview->descriptionChanges(), - _preview->pageDataChanges()); + _header->previewReady(_preview->parsedValue()); } void ComposeControls::initForwardProcess() { @@ -3129,7 +2878,7 @@ void ComposeControls::initForwardProcess() { } Data::WebPageDraft ComposeControls::webPageDraft() const { - return _header->webPageDraft(); + return _preview ? _preview->draft() : Data::WebPageDraft(); } rpl::producer ComposeControls::scrollRequests() 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 06d66e69d4..d6a132af26 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -79,15 +79,15 @@ namespace Api { enum class SendProgressType; } // namespace Api -namespace HistoryView { - -namespace Controls { +namespace HistoryView::Controls { class VoiceRecordBar; class TTLButton; -} // namespace Controls +class WebpageProcessor; +} // namespace HistoryView::Controls + +namespace HistoryView { class FieldHeader; -class WebpageProcessor; enum class ComposeControlsMode { Normal, @@ -422,7 +422,7 @@ private: std::shared_ptr _photoEditMedia; bool _canReplaceMedia = false; - std::unique_ptr _preview; + std::unique_ptr _preview; Fn _raiseEmojiSuggestions; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp new file mode 100644 index 0000000000..1147395aa7 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -0,0 +1,846 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/controls/history_view_draft_options.h" + +#include "base/unixtime.h" +#include "boxes/peer_list_box.h" +#include "boxes/peer_list_controllers.h" +#include "chat_helpers/compose/compose_show.h" +#include "data/data_changes.h" +#include "data/data_drafts.h" +#include "data/data_file_origin.h" +#include "data/data_session.h" +#include "data/data_thread.h" +#include "data/data_user.h" +#include "data/data_web_page.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_common.h" +#include "ui/chat/chat_style.h" +#include "ui/chat/chat_theme.h" +#include "ui/effects/path_shift_gradient.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" +#include "ui/painter.h" +#include "window/themes/window_theme.h" +#include "window/section_widget.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +#include +#include + +namespace HistoryView::Controls { +namespace { + +enum class Section { + Reply, + Link, +}; + +class PreviewDelegate final : public DefaultElementDelegate { +public: + PreviewDelegate( + not_null parent, + not_null st, + Fn update); + + bool elementAnimationsPaused() override; + not_null elementPathShiftGradient() override; + Context elementContext() override; + +private: + const not_null _parent; + const std::unique_ptr _pathGradient; + +}; + +[[nodiscard]] std::unique_ptr DefaultThemeOn( + rpl::lifetime &lifetime) { + auto result = std::make_unique(); + + using namespace Window::Theme; + const auto push = [=, raw = result.get()] { + const auto background = Background(); + const auto &paper = background->paper(); + raw->setBackground({ + .prepared = background->prepared(), + .preparedForTiled = background->preparedForTiled(), + .gradientForFill = background->gradientForFill(), + .colorForFill = background->colorForFill(), + .colors = paper.backgroundColors(), + .patternOpacity = paper.patternOpacity(), + .gradientRotation = paper.gradientRotation(), + .isPattern = paper.isPattern(), + .tile = background->tile(), + }); + }; + + push(); + Background()->updates( + ) | rpl::start_with_next([=](const BackgroundUpdate &update) { + if (update.type == BackgroundUpdate::Type::New + || update.type == BackgroundUpdate::Type::Changed) { + push(); + } + }, lifetime); + + return result; +} + +class PreviewWrap final : public Ui::RpWidget { +public: + PreviewWrap( + not_null box, + not_null history); + ~PreviewWrap(); + + [[nodiscard]] rpl::producer showQuoteSelector( + not_null item, + const TextWithEntities "e); + [[nodiscard]] rpl::producer showLinkSelector( + const TextWithTags &message, + Data::WebPageDraft webpage); + +private: + void paintEvent(QPaintEvent *e) override; + void leaveEventHook(QEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + + void initElement(); + void startSelection(TextSelectType type); + [[nodiscard]] TextSelection resolveNewSelection() const; + + const not_null _box; + const not_null _history; + const std::unique_ptr _theme; + const std::unique_ptr _style; + const std::unique_ptr _delegate; + + Section _section = Section::Reply; + HistoryItem *_draftItem = nullptr; + std::unique_ptr _element; + rpl::variable _selection; + Ui::PeerUserpicView _userpic; + rpl::lifetime _elementLifetime; + + QPoint _position; + + base::Timer _trippleClickTimer; + TextSelectType _selectType = TextSelectType::Letters; + uint16 _symbol = 0; + uint16 _selectionStartSymbol = 0; + bool _onlyMessageText = false; + bool _afterSymbol = false; + bool _selectionStartAfterSymbol = false; + bool _over = false; + bool _textCursor = false; + bool _linkCursor = false; + bool _selecting = false; + +}; + +PreviewWrap::PreviewWrap( + not_null box, + not_null history) +: RpWidget(box) +, _box(box) +, _history(history) +, _theme(DefaultThemeOn(lifetime())) +, _style(std::make_unique()) +, _delegate(std::make_unique( + box, + _style.get(), + [=] { update(); })) +, _position(0, st::msgMargin.bottom()) { + _style->apply(_theme.get()); + + const auto session = &_history->session(); + session->data().viewRepaintRequest( + ) | rpl::start_with_next([=](not_null view) { + if (view == _element.get()) { + update(); + } + }, lifetime()); + + _selection.changes() | rpl::start_with_next([=] { + update(); + }, lifetime()); + + _box->setAttribute(Qt::WA_OpaquePaintEvent, false); + + _box->paintRequest() | rpl::start_with_next([=](QRect clip) { + const auto geometry = Ui::MapFrom(_box, this, rect()); + const auto fill = geometry.intersected(clip); + if (!fill.isEmpty()) { + auto p = QPainter(_box); + p.setClipRect(fill); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(_box->width(), _box->window()->height()), + fill); + } + }, lifetime()); + + setMouseTracking(true); +} + +PreviewWrap::~PreviewWrap() { + _selection.reset(TextSelection()); + _elementLifetime.destroy(); + _element = nullptr; + if (_draftItem) { + _draftItem->destroy(); + } +} + +rpl::producer PreviewWrap::showQuoteSelector( + not_null item, + const TextWithEntities "e) { + auto element = item->createView(_delegate.get()); + _selection.reset(element->selectionFromQuote(quote)); + _element = std::move(element); + + if (const auto was = base::take(_draftItem)) { + was->destroy(); + } + + const auto media = item->media(); + _onlyMessageText = media + && (media->webpage() + || media->game() + || (!media->photo() && !media->document())); + _section = Section::Reply; + + initElement(); + + return _selection.value( + ) | rpl::map([=](TextSelection selection) { + return _element->selectedQuote(selection); + }); +} + +rpl::producer PreviewWrap::showLinkSelector( + const TextWithTags &message, + Data::WebPageDraft webpage) { + _selection.reset(TextSelection()); + + _element = nullptr; + if (const auto was = base::take(_draftItem)) { + was->destroy(); + } + using Flag = MTPDmessageMediaWebPage::Flag; + _draftItem = _history->addNewLocalMessage( + _history->nextNonHistoryEntryId(), + (MessageFlag::FakeHistoryItem + | MessageFlag::Outgoing + | (webpage.invert ? MessageFlag::InvertMedia : MessageFlag())), + UserId(), // via + FullReplyTo(), + base::unixtime::now(), // date + _history->session().userPeerId(), + QString(), // postAuthor + TextWithEntities{ + message.text, + TextUtilities::ConvertTextTagsToEntities(message.tags), + }, + MTP_messageMediaWebPage( + MTP_flags(Flag() + | (webpage.forceLargeMedia + ? Flag::f_force_large_media + : Flag()) + | (webpage.forceSmallMedia + ? Flag::f_force_small_media + : Flag())), + MTP_webPagePending( + MTP_flags(webpage.url.isEmpty() + ? MTPDwebPagePending::Flag() + : MTPDwebPagePending::Flag::f_url), + MTP_long(webpage.id), + MTP_string(webpage.url), + MTP_int(0))), + HistoryMessageMarkupData(), + uint64(0)); // groupedId + _element = _draftItem->createView(_delegate.get()); + _selectType = TextSelectType::Letters; + _symbol = _selectionStartSymbol = 0; + _afterSymbol = _selectionStartAfterSymbol = false; + _section = Section::Link; + + initElement(); + + return rpl::never(); +} + +void PreviewWrap::paintEvent(QPaintEvent *e) { + if (!_element) { + return; + } + + auto p = Painter(this); + auto hq = PainterHighQualityEnabler(p); + + auto context = _theme->preparePaintContext( + _style.get(), + rect(), + e->rect(), + !window()->isActiveWindow()); + context.outbg = _element->hasOutLayout(); + context.selection = _selecting + ? resolveNewSelection() + : _selection.current(); + + p.translate(_position); + _element->draw(p, context); + + if (_element->displayFromPhoto()) { + auto userpicMinBottomSkip = st::historyPaddingBottom + + st::msgMargin.bottom(); + auto userpicBottom = height() + - _element->marginBottom() + - _element->marginTop(); + const auto item = _element->data(); + const auto userpicTop = userpicBottom - st::msgPhotoSize; + if (const auto from = item->displayFrom()) { + from->paintUserpicLeft( + p, + _userpic, + st::historyPhotoLeft, + userpicTop, + width(), + st::msgPhotoSize); + } else if (const auto info = item->hiddenSenderInfo()) { + if (info->customUserpic.empty()) { + info->emptyUserpic.paintCircle( + p, + st::historyPhotoLeft, + userpicTop, + width(), + st::msgPhotoSize); + } else { + const auto valid = info->paintCustomUserpic( + p, + _userpic, + st::historyPhotoLeft, + userpicTop, + width(), + st::msgPhotoSize); + if (!valid) { + info->customUserpic.load( + &item->history()->session(), + item->fullId()); + } + } + } else { + Unexpected("Corrupt forwarded information in message."); + } + } +} + +void PreviewWrap::leaveEventHook(QEvent *e) { + if (!_element || !_over) { + return; + } + _over = false; + _textCursor = false; + _linkCursor = false; + if (!_selecting) { + setCursor(style::cur_default); + } +} + +void PreviewWrap::mouseMoveEvent(QMouseEvent *e) { + if (!_element) { + return; + } + using Flag = Ui::Text::StateRequest::Flag; + auto request = StateRequest{ + .flags = (_section == Section::Reply + ? Flag::LookupSymbol + : Flag::LookupLink), + .onlyMessageText = (_section == Section::Link || _onlyMessageText), + }; + auto resolved = _element->textState( + e->pos() - _position, + request); + _over = true; + const auto text = (_section == Section::Reply) + && (resolved.cursor == CursorState::Text); + const auto link = (_section == Section::Link) && resolved.link; + if (_textCursor != text || _linkCursor != link) { + _textCursor = text; + _linkCursor = link; + setCursor((text || _selecting) + ? style::cur_text + : link + ? style::cur_pointer + : style::cur_default); + } + if (_symbol != resolved.symbol + || _afterSymbol != resolved.afterSymbol) { + _symbol = resolved.symbol; + _afterSymbol = resolved.afterSymbol; + if (_selecting) { + update(); + } + } +} + +void PreviewWrap::mousePressEvent(QMouseEvent *e) { + if (!_over) { + return; + } else if (_section == Section::Reply) { + startSelection(_trippleClickTimer.isActive() + ? TextSelectType::Paragraphs + : TextSelectType::Letters); + } +} + +void PreviewWrap::mouseReleaseEvent(QMouseEvent *e) { + if (!_selecting) { + return; + } else if (_section == Section::Reply) { + const auto result = resolveNewSelection(); + _selecting = false; + _selectType = TextSelectType::Letters; + if (!_textCursor) { + setCursor(style::cur_default); + } + _selection = result; + } +} + +void PreviewWrap::mouseDoubleClickEvent(QMouseEvent *e) { + if (!_over) { + return; + } else if (_section == Section::Reply) { + startSelection(TextSelectType::Words); + _trippleClickTimer.callOnce(QApplication::doubleClickInterval()); + } +} + +void PreviewWrap::initElement() { + _elementLifetime.destroy(); + + if (!_element) { + return; + } + _element->initDimensions(); + + widthValue( + ) | rpl::filter([=](int width) { + return width > st::msgMinWidth; + }) | rpl::start_with_next([=](int width) { + const auto height = _position.y() + + _element->resizeGetHeight(width) + + st::msgMargin.top(); + resize(width, height); + }, _elementLifetime); +} + +TextSelection PreviewWrap::resolveNewSelection() const { + if (_section != Section::Reply) { + return TextSelection(); + } + const auto make = [](uint16 symbol, bool afterSymbol) { + return uint16(symbol + (afterSymbol ? 1 : 0)); + }; + const auto first = make(_symbol, _afterSymbol); + const auto second = make( + _selectionStartSymbol, + _selectionStartAfterSymbol); + const auto result = (first <= second) + ? TextSelection{ first, second } + : TextSelection{ second, first }; + return _element->adjustSelection(result, _selectType); +} + +void PreviewWrap::startSelection(TextSelectType type) { + if (_selecting && _selectType >= type) { + return; + } + _selecting = true; + _selectType = type; + _selectionStartSymbol = _symbol; + _selectionStartAfterSymbol = _afterSymbol; + if (!_textCursor) { + setCursor(style::cur_text); + } + update(); +} + +PreviewDelegate::PreviewDelegate( + not_null parent, + not_null st, + Fn update) +: _parent(parent) +, _pathGradient(MakePathShiftGradient(st, update)) { +} + +bool PreviewDelegate::elementAnimationsPaused() { + return _parent->window()->isActiveWindow(); +} + +auto PreviewDelegate::elementPathShiftGradient() +-> not_null { + return _pathGradient.get(); +} + +Context PreviewDelegate::elementContext() { + return Context::History; +} + +} // namespace + +void ShowReplyToChatBox( + std::shared_ptr show, + FullReplyTo reply, + Fn clearOldDraft) { + class Controller final : public ChooseRecipientBoxController { + public: + using Chosen = not_null; + + Controller(not_null session) + : ChooseRecipientBoxController( + session, + [=](Chosen thread) mutable { _singleChosen.fire_copy(thread); }, + nullptr) { + } + + void rowClicked(not_null row) override final { + ChooseRecipientBoxController::rowClicked(row); + } + + [[nodiscard]] rpl::producer singleChosen() const{ + return _singleChosen.events(); + } + + bool respectSavedMessagesChat() const override { + return false; + } + + private: + void prepareViewHook() override { + delegate()->peerListSetTitle(tr::lng_reply_in_another_title()); + } + + rpl::event_stream _singleChosen; + + }; + + struct State { + not_null box; + not_null controller; + base::unique_qptr menu; + }; + const auto session = &show->session(); + const auto state = [&] { + auto controller = std::make_unique(session); + const auto controllerRaw = controller.get(); + auto box = Box(std::move(controller), [=]( + not_null box) { + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }); + const auto boxRaw = box.data(); + show->show(std::move(box)); + auto state = State{ boxRaw, controllerRaw }; + return boxRaw->lifetime().make_state(std::move(state)); + }(); + + auto chosen = [=](not_null thread) mutable { + const auto history = thread->owningHistory(); + const auto topicRootId = thread->topicRootId(); + const auto draft = history->localDraft(topicRootId); + const auto textWithTags = draft + ? draft->textWithTags + : TextWithTags(); + const auto cursor = draft ? draft->cursor : MessageCursor(); + reply.topicRootId = topicRootId; + history->setLocalDraft(std::make_unique( + textWithTags, + reply, + cursor, + Data::WebPageDraft())); + history->clearLocalEditDraft(topicRootId); + history->session().changes().entryUpdated( + thread, + Data::EntryUpdate::Flag::LocalDraftSet); + + if (clearOldDraft) { + crl::on_main(&history->session(), clearOldDraft); + } + return true; + }; + auto callback = [=, chosen = std::move(chosen)]( + Controller::Chosen thread) mutable { + const auto weak = Ui::MakeWeak(state->box); + if (!chosen(thread)) { + return; + } else if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + state->controller->singleChosen( + ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); +} + +void EditDraftOptions( + std::shared_ptr show, + not_null history, + Data::Draft draft, + Fn done, + Fn highlight, + Fn clearOldDraft) { + const auto session = &show->session(); + const auto replyItem = session->data().message(draft.reply.messageId); + const auto previewDataRaw = draft.webpage.id + ? session->data().webpage(draft.webpage.id).get() + : nullptr; + const auto previewData = (previewDataRaw + && !previewDataRaw->pendingTill + && !previewDataRaw->failed) + ? previewDataRaw + : nullptr; + if (!replyItem && !previewData) { + return; + } + show->show(Box([=](not_null box) { + box->setWidth(st::boxWideWidth); + + struct State { + rpl::variable
shown; + rpl::lifetime shownLifetime; + rpl::variable quote; + Data::WebPageDraft webpage; + Ui::SettingsSlider *tabs = nullptr; + PreviewWrap *wrap = nullptr; + }; + const auto state = box->lifetime().make_state(); + state->quote = draft.reply.quote; + state->webpage = draft.webpage; + state->shown = previewData ? Section::Link : Section::Reply; + if (replyItem && previewData) { + box->setNoContentMargin(true); + state->tabs = box->setPinnedToTopContent( + object_ptr( + box.get(), + st::defaultTabsSlider)); + state->tabs->resizeToWidth(st::boxWideWidth); + state->tabs->move(0, 0); + state->tabs->setRippleTopRoundRadius(st::boxRadius); + state->tabs->setSections({ + tr::lng_reply_header_short(tr::now), + tr::lng_link_header_short(tr::now), + }); + state->tabs->setActiveSectionFast(1); + state->tabs->sectionActivated( + ) | rpl::start_with_next([=](int section) { + state->shown = section ? Section::Link : Section::Reply; + }, box->lifetime()); + } else { + box->setTitle(previewData + ? tr::lng_link_options_header() + : draft.reply.quote.empty() + ? tr::lng_reply_options_header() + : tr::lng_reply_options_quote()); + } + + const auto bottom = box->setPinnedToBottomContent( + object_ptr(box)); + const auto addSkip = [=] { + const auto skip = bottom->add(object_ptr( + bottom, + st::settingsPrivacySkipTop)); + skip->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(skip).fillRect(clip, st::boxBg); + }, skip->lifetime()); + }; + + const auto resolveReply = [=] { + auto result = draft.reply; + result.quote = state->quote.current(); + return result; + }; + const auto finish = [=]( + FullReplyTo result, + Data::WebPageDraft webpage) { + const auto weak = Ui::MakeWeak(box); + done(std::move(result), std::move(webpage)); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + const auto setupReplyActions = [=] { + addSkip(); + + 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); + + Settings::AddButton( + bottom, + tr::lng_reply_remove(), + st::settingsAttentionButtonWithIcon, + { &st::menuIconDeleteAttention } + )->setClickedCallback([=] { + finish({}, state->webpage); + }); + + if (!replyItem->originalText().empty()) { + addSkip(); + Settings::AddDividerText( + bottom, + tr::lng_reply_about_quote()); + } + }; + const auto setupLinkActions = [=] { + addSkip(); + + if (!draft.textWithTags.empty()) { + Settings::AddButton( + bottom, + (state->webpage.invert + ? tr::lng_link_move_down() + : tr::lng_link_move_up()), + st::settingsButton, + { state->webpage.invert + ? &st::menuIconBelow + : &st::menuIconAbove } + )->setClickedCallback([=] { + state->webpage.invert = !state->webpage.invert; + state->webpage.manual = true; + state->shown.force_assign(Section::Link); + }); + } + + if (previewData->hasLargeMedia) { + const auto small = state->webpage.forceSmallMedia + || (!state->webpage.forceLargeMedia + && previewData->computeDefaultSmallMedia()); + Settings::AddButton( + bottom, + (small + ? tr::lng_link_enlarge_photo() + : tr::lng_link_shrink_photo()), + st::settingsButton, + { small ? &st::menuIconEnlarge : &st::menuIconShrink } + )->setClickedCallback([=] { + if (small) { + state->webpage.forceSmallMedia = false; + state->webpage.forceLargeMedia = true; + } else { + state->webpage.forceLargeMedia = false; + state->webpage.forceSmallMedia = true; + } + state->webpage.manual = true; + state->shown.force_assign(Section::Link); + }); + } + + Settings::AddButton( + bottom, + tr::lng_link_remove(), + st::settingsAttentionButtonWithIcon, + { &st::menuIconDeleteAttention } + )->setClickedCallback([=] { + finish(resolveReply(), { .removed = true }); + }); + + if (true) { + addSkip(); + Settings::AddDividerText( + bottom, + tr::lng_link_about_choose()); + } + }; + + state->wrap = box->addRow( + object_ptr(box, history), + {}); + state->shown.value() | rpl::start_with_next([=](Section shown) { + bottom->clear(); + state->shownLifetime.destroy(); + if (shown == Section::Reply) { + state->quote = state->wrap->showQuoteSelector( + replyItem, + state->quote.current()); + setupReplyActions(); + } else { + state->wrap->showLinkSelector( + draft.textWithTags, + state->webpage + ) | rpl::start_with_next([=](QString url) { + }, state->shownLifetime); + setupLinkActions(); + } + }, box->lifetime()); + + auto save = rpl::combine( + state->quote.value(), + state->shown.value() + ) | rpl::map([=](const TextWithEntities "e, Section shown) { + return (quote.empty() || shown != Section::Reply) + ? tr::lng_settings_save() + : tr::lng_reply_quote_selected(); + }) | rpl::flatten_latest(); + box->addButton(std::move(save), [=] { + finish(resolveReply(), state->webpage); + }); + + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + + if (replyItem) { + session->data().itemRemoved( + ) | rpl::filter([=](not_null removed) { + return removed == replyItem; + }) | rpl::start_with_next([=] { + if (previewData) { + state->tabs = nullptr; + box->setPinnedToTopContent( + object_ptr(nullptr)); + box->setNoContentMargin(false); + box->setTitle(state->quote.current().empty() + ? tr::lng_reply_options_header() + : tr::lng_reply_options_quote()); + state->shown = Section::Link; + } else { + box->closeBox(); + } + }, box->lifetime()); + } + })); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.h b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h similarity index 80% rename from Telegram/SourceFiles/history/view/controls/history_view_reply_options.h rename to Telegram/SourceFiles/history/view/controls/history_view_draft_options.h index 048fd8ebae..6e3def8cc3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h @@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_drafts.h" + +class History; + namespace ChatHelpers { class Show; } // namespace ChatHelpers @@ -17,10 +21,11 @@ class SessionController; namespace HistoryView::Controls { -void EditReplyOptions( +void EditDraftOptions( std::shared_ptr show, - FullReplyTo reply, - Fn done, + not_null history, + Data::Draft draft, + Fn done, Fn highlight, Fn clearOldDraft); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp deleted file mode 100644 index 0eb6f8aeb8..0000000000 --- a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp +++ /dev/null @@ -1,535 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include "history/view/controls/history_view_reply_options.h" - -#include "boxes/peer_list_box.h" -#include "boxes/peer_list_controllers.h" -#include "chat_helpers/compose/compose_show.h" -#include "data/data_changes.h" -#include "data/data_drafts.h" -#include "data/data_file_origin.h" -#include "data/data_session.h" -#include "data/data_thread.h" -#include "history/history.h" -#include "history/history_item.h" -#include "history/history_item_components.h" -#include "history/view/history_view_element.h" -#include "history/view/history_view_cursor_state.h" -#include "lang/lang_keys.h" -#include "main/main_session.h" -#include "settings/settings_common.h" -#include "ui/chat/chat_style.h" -#include "ui/chat/chat_theme.h" -#include "ui/effects/path_shift_gradient.h" -#include "ui/layers/generic_box.h" -#include "ui/widgets/buttons.h" -#include "ui/painter.h" -#include "window/themes/window_theme.h" -#include "window/section_widget.h" -#include "window/window_session_controller.h" -#include "styles/style_chat.h" -#include "styles/style_layers.h" -#include "styles/style_menu_icons.h" -#include "styles/style_settings.h" - -#include -#include - -namespace HistoryView::Controls { -namespace { - -class PreviewDelegate final : public DefaultElementDelegate { -public: - PreviewDelegate( - not_null parent, - not_null st, - Fn update); - - bool elementAnimationsPaused() override; - not_null elementPathShiftGradient() override; - Context elementContext() override; - -private: - const not_null _parent; - const std::unique_ptr _pathGradient; - -}; - -[[nodiscard]] std::unique_ptr DefaultThemeOn( - rpl::lifetime &lifetime) { - auto result = std::make_unique(); - - using namespace Window::Theme; - const auto push = [=, raw = result.get()] { - const auto background = Background(); - const auto &paper = background->paper(); - raw->setBackground({ - .prepared = background->prepared(), - .preparedForTiled = background->preparedForTiled(), - .gradientForFill = background->gradientForFill(), - .colorForFill = background->colorForFill(), - .colors = paper.backgroundColors(), - .patternOpacity = paper.patternOpacity(), - .gradientRotation = paper.gradientRotation(), - .isPattern = paper.isPattern(), - .tile = background->tile(), - }); - }; - - push(); - Background()->updates( - ) | rpl::start_with_next([=](const BackgroundUpdate &update) { - if (update.type == BackgroundUpdate::Type::New - || update.type == BackgroundUpdate::Type::Changed) { - push(); - } - }, lifetime); - - return result; -} - -[[nodiscard]] rpl::producer AddQuoteTracker( - not_null box, - std::shared_ptr show, - not_null item, - const TextWithEntities "e) { - struct State { - std::unique_ptr theme; - std::unique_ptr style; - std::unique_ptr delegate; - std::unique_ptr element; - rpl::variable selection; - Ui::PeerUserpicView userpic; - QPoint position; - - base::Timer trippleClickTimer; - TextSelectType selectType = TextSelectType::Letters; - uint16 symbol = 0; - bool afterSymbol = false; - bool textCursor = false; - bool selecting = false; - bool over = false; - uint16 selectionStartSymbol = 0; - bool selectionStartAfterSymbol = false; - }; - - const auto preview = box->addRow(object_ptr(box), {}); - const auto state = preview->lifetime().make_state(); - state->theme = DefaultThemeOn(preview->lifetime()); - - state->style = std::make_unique(); - state->style->apply(state->theme.get()); - - state->delegate = std::make_unique( - box, - state->style.get(), - [=] { preview->update(); }); - - state->element = item->createView(state->delegate.get()); - state->element->initDimensions(); - state->position = QPoint(0, st::msgMargin.bottom()); - - state->selection = state->element->selectionFromQuote(quote); - - const auto session = &show->session(); - session->data().viewRepaintRequest( - ) | rpl::start_with_next([=](not_null view) { - if (view == state->element.get()) { - preview->update(); - } - }, preview->lifetime()); - - state->selection.changes() | rpl::start_with_next([=] { - preview->update(); - }, preview->lifetime()); - - const auto resolveNewSelection = [=] { - const auto make = [](uint16 symbol, bool afterSymbol) { - return uint16(symbol + (afterSymbol ? 1 : 0)); - }; - const auto first = make(state->symbol, state->afterSymbol); - const auto second = make( - state->selectionStartSymbol, - state->selectionStartAfterSymbol); - const auto result = (first <= second) - ? TextSelection{ first, second } - : TextSelection{ second, first }; - return state->element->adjustSelection(result, state->selectType); - }; - const auto startSelection = [=](TextSelectType type) { - if (state->selecting && state->selectType >= type) { - return; - } - state->selecting = true; - state->selectType = type; - state->selectionStartSymbol = state->symbol; - state->selectionStartAfterSymbol = state->afterSymbol; - if (!state->textCursor) { - preview->setCursor(style::cur_text); - } - preview->update(); - }; - const auto media = item->media(); - const auto onlyMessageText = media - && (media->webpage() - || media->game() - || (!media->photo() && !media->document())); - preview->setMouseTracking(true); - preview->events() | rpl::start_with_next([=](not_null e) { - const auto type = e->type(); - const auto mouse = static_cast(e.get()); - if (type == QEvent::MouseMove) { - auto request = StateRequest{ - .flags = Ui::Text::StateRequest::Flag::LookupSymbol, - .onlyMessageText = onlyMessageText, - }; - auto resolved = state->element->textState( - mouse->pos() - state->position, - request); - state->over = true; - const auto text = (resolved.cursor == CursorState::Text); - if (state->textCursor != text) { - state->textCursor = text; - preview->setCursor((text || state->selecting) - ? style::cur_text - : style::cur_default); - } - if (state->symbol != resolved.symbol - || state->afterSymbol != resolved.afterSymbol) { - state->symbol = resolved.symbol; - state->afterSymbol = resolved.afterSymbol; - if (state->selecting) { - preview->update(); - } - } - } else if (type == QEvent::Leave && state->over) { - state->over = false; - if (state->textCursor) { - state->textCursor = false; - if (!state->selecting) { - preview->setCursor(style::cur_default); - } - } - } else if (type == QEvent::MouseButtonDblClick && state->over) { - startSelection(TextSelectType::Words); - state->trippleClickTimer.callOnce( - QApplication::doubleClickInterval()); - } else if (type == QEvent::MouseButtonPress && state->over) { - startSelection(state->trippleClickTimer.isActive() - ? TextSelectType::Paragraphs - : TextSelectType::Letters); - } else if (type == QEvent::MouseButtonRelease && state->selecting) { - const auto result = resolveNewSelection(); - state->selecting = false; - state->selectType = TextSelectType::Letters; - state->selection = result; - if (!state->textCursor) { - preview->setCursor(style::cur_default); - } - } - }, preview->lifetime()); - - preview->widthValue( - ) | rpl::filter([=](int width) { - return width > st::msgMinWidth; - }) | rpl::start_with_next([=](int width) { - const auto height = state->element->resizeGetHeight(width) - + state->position.y() - + st::msgMargin.top(); - preview->resize(width, height); - }, preview->lifetime()); - - box->setAttribute(Qt::WA_OpaquePaintEvent, false); - box->paintRequest() | rpl::start_with_next([=](QRect clip) { - Window::SectionWidget::PaintBackground( - state->theme.get(), - box, - box->window()->height(), - 0, - clip); - }, box->lifetime()); - - preview->paintRequest() | rpl::start_with_next([=](QRect clip) { - auto p = Painter(preview); - auto hq = PainterHighQualityEnabler(p); - p.translate(state->position); - auto context = state->theme->preparePaintContext( - state->style.get(), - preview->rect(), - clip, - !box->window()->isActiveWindow()); - context.outbg = state->element->hasOutLayout(); - context.selection = state->selecting - ? resolveNewSelection() - : state->selection.current(); - state->element->draw(p, context); - if (state->element->displayFromPhoto()) { - auto userpicMinBottomSkip = st::historyPaddingBottom - + st::msgMargin.bottom(); - auto userpicBottom = preview->height() - - state->element->marginBottom() - - state->element->marginTop(); - const auto userpicTop = userpicBottom - st::msgPhotoSize; - if (const auto from = item->displayFrom()) { - from->paintUserpicLeft( - p, - state->userpic, - st::historyPhotoLeft, - userpicTop, - preview->width(), - st::msgPhotoSize); - } else if (const auto info = item->hiddenSenderInfo()) { - if (info->customUserpic.empty()) { - info->emptyUserpic.paintCircle( - p, - st::historyPhotoLeft, - userpicTop, - preview->width(), - st::msgPhotoSize); - } else { - const auto valid = info->paintCustomUserpic( - p, - state->userpic, - st::historyPhotoLeft, - userpicTop, - preview->width(), - st::msgPhotoSize); - if (!valid) { - info->customUserpic.load(session, item->fullId()); - } - } - } else { - Unexpected("Corrupt forwarded information in message."); - } - } - }, preview->lifetime()); - - return state->selection.value( - ) | rpl::map([=](TextSelection selection) { - return state->element->selectedQuote(selection); - }); -} - -PreviewDelegate::PreviewDelegate( - not_null parent, - not_null st, - Fn update) -: _parent(parent) -, _pathGradient(MakePathShiftGradient(st, update)) { -} - -bool PreviewDelegate::elementAnimationsPaused() { - return _parent->window()->isActiveWindow(); -} - -auto PreviewDelegate::elementPathShiftGradient() --> not_null { - return _pathGradient.get(); -} - -Context PreviewDelegate::elementContext() { - return Context::History; -} - -} // namespace - -void ShowReplyToChatBox( - std::shared_ptr show, - FullReplyTo reply, - Fn clearOldDraft) { - class Controller final : public ChooseRecipientBoxController { - public: - using Chosen = not_null; - - Controller(not_null session) - : ChooseRecipientBoxController( - session, - [=](Chosen thread) mutable { _singleChosen.fire_copy(thread); }, - nullptr) { - } - - void rowClicked(not_null row) override final { - ChooseRecipientBoxController::rowClicked(row); - } - - [[nodiscard]] rpl::producer singleChosen() const{ - return _singleChosen.events(); - } - - bool respectSavedMessagesChat() const override { - return false; - } - - private: - void prepareViewHook() override { - delegate()->peerListSetTitle(tr::lng_reply_in_another_title()); - } - - rpl::event_stream _singleChosen; - - }; - - struct State { - not_null box; - not_null controller; - base::unique_qptr menu; - }; - const auto session = &show->session(); - const auto state = [&] { - auto controller = std::make_unique(session); - const auto controllerRaw = controller.get(); - auto box = Box(std::move(controller), [=]( - not_null box) { - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); - }); - const auto boxRaw = box.data(); - show->show(std::move(box)); - auto state = State{ boxRaw, controllerRaw }; - return boxRaw->lifetime().make_state(std::move(state)); - }(); - - auto chosen = [=](not_null thread) mutable { - const auto history = thread->owningHistory(); - const auto topicRootId = thread->topicRootId(); - const auto draft = history->localDraft(topicRootId); - const auto textWithTags = draft - ? draft->textWithTags - : TextWithTags(); - const auto cursor = draft ? draft->cursor : MessageCursor(); - reply.topicRootId = topicRootId; - history->setLocalDraft(std::make_unique( - textWithTags, - reply, - cursor, - Data::WebPageDraft())); - history->clearLocalEditDraft(topicRootId); - history->session().changes().entryUpdated( - thread, - Data::EntryUpdate::Flag::LocalDraftSet); - - if (clearOldDraft) { - crl::on_main(&history->session(), clearOldDraft); - } - return true; - }; - auto callback = [=, chosen = std::move(chosen)]( - Controller::Chosen thread) mutable { - const auto weak = Ui::MakeWeak(state->box); - if (!chosen(thread)) { - return; - } else if (const auto strong = weak.data()) { - strong->closeBox(); - } - }; - state->controller->singleChosen( - ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); -} - -void EditReplyOptions( - std::shared_ptr show, - FullReplyTo reply, - Fn done, - Fn highlight, - Fn clearOldDraft) { - const auto session = &show->session(); - const auto item = session->data().message(reply.messageId); - if (!item) { - return; - } - show->show(Box([=](not_null box) { - box->setWidth(st::boxWideWidth); - - const auto bottom = box->setPinnedToBottomContent( - object_ptr(box)); - const auto addSkip = [&] { - const auto skip = bottom->add(object_ptr( - bottom, - st::settingsPrivacySkipTop)); - skip->paintRequest() | rpl::start_with_next([=](QRect clip) { - QPainter(skip).fillRect(clip, st::boxBg); - }, skip->lifetime()); - }; - - addSkip(); - - Settings::AddButton( - bottom, - tr::lng_reply_in_another_chat(), - st::settingsButton, - { &st::menuIconReplace } - )->setClickedCallback([=] { - ShowReplyToChatBox(show, reply, clearOldDraft); - }); - - Settings::AddButton( - bottom, - tr::lng_reply_show_in_chat(), - st::settingsButton, - { &st::menuIconShowInChat } - )->setClickedCallback(highlight); - - const auto finish = [=](FullReplyTo result) { - const auto weak = Ui::MakeWeak(box); - done(std::move(result)); - if (const auto strong = weak.data()) { - strong->closeBox(); - } - }; - - Settings::AddButton( - bottom, - tr::lng_reply_remove(), - st::settingsAttentionButtonWithIcon, - { &st::menuIconDeleteAttention } - )->setClickedCallback([=] { - finish({}); - }); - - if (!item->originalText().empty()) { - addSkip(); - Settings::AddDividerText( - bottom, - tr::lng_reply_about_quote()); - } - - struct State { - rpl::variable quote; - }; - const auto state = box->lifetime().make_state(); - state->quote = AddQuoteTracker(box, show, item, reply.quote); - - box->setTitle(reply.quote.empty() - ? tr::lng_reply_options_header() - : tr::lng_reply_options_quote()); - - auto save = state->quote.value( - ) | rpl::map([=](const TextWithEntities "e) { - return quote.empty() - ? tr::lng_settings_save() - : tr::lng_reply_quote_selected(); - }) | rpl::flatten_latest(); - box->addButton(std::move(save), [=] { - auto result = reply; - result.quote = state->quote.current(); - finish(result); - }); - - box->addButton(tr::lng_cancel(), [=] { - box->closeBox(); - }); - - session->data().itemRemoved( - ) | rpl::filter([=](not_null removed) { - return removed == item; - }) | rpl::start_with_next([=] { - finish({}); - }, box->lifetime()); - })); -} - -} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp new file mode 100644 index 0000000000..909e3508e3 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp @@ -0,0 +1,327 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/controls/history_view_webpage_processor.h" + +#include "base/unixtime.h" +#include "data/data_chat_participant_status.h" +#include "data/data_file_origin.h" +#include "data/data_session.h" +#include "data/data_web_page.h" +#include "history/history.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" + +namespace HistoryView::Controls { + +WebPageText TitleAndDescriptionFromWebPage(not_null d) { + QString resultTitle, resultDescription; + const auto document = d->document; + const auto author = d->author; + const auto siteName = d->siteName; + const auto title = d->title; + const auto description = d->description; + const auto filenameOrUrl = [&] { + return ((document && !document->filename().isEmpty()) + ? document->filename() + : d->url); + }; + const auto authorOrFilename = [&] { + return (author.isEmpty() + ? filenameOrUrl() + : author); + }; + const auto descriptionOrAuthor = [&] { + return (description.text.isEmpty() + ? authorOrFilename() + : description.text); + }; + if (siteName.isEmpty()) { + if (title.isEmpty()) { + if (description.text.isEmpty()) { + resultTitle = author; + resultDescription = filenameOrUrl(); + } else { + resultTitle = description.text; + resultDescription = authorOrFilename(); + } + } else { + resultTitle = title; + resultDescription = descriptionOrAuthor(); + } + } else { + resultTitle = siteName; + resultDescription = title.isEmpty() + ? descriptionOrAuthor() + : title; + } + return { resultTitle, resultDescription }; +} + +bool DrawWebPageDataPreview( + QPainter &p, + not_null webpage, + not_null context, + QRect to) { + const auto document = webpage->document; + const auto photo = webpage->photo; + if ((!photo || photo->isNull()) + && (!document + || !document->hasThumbnail() + || document->isPatternWallPaper())) { + return false; + } + + const auto preview = photo + ? photo->getReplyPreview(Data::FileOrigin(), context, false) + : document->getReplyPreview(Data::FileOrigin(), context, false); + if (preview) { + const auto w = preview->width(); + const auto h = preview->height(); + if (w == h) { + p.drawPixmap(to.x(), to.y(), preview->pix()); + } else { + const auto from = (w > h) + ? QRect((w - h) / 2, 0, h, h) + : QRect(0, (h - w) / 2, w, w); + p.drawPixmap(to, preview->pix(), from); + } + } + return true; +} + +[[nodiscard]] bool ShowWebPagePreview(WebPageData *page) { + return page && !page->failed; +} + +WebPageText ProcessWebPageData(WebPageData *page) { + auto previewText = TitleAndDescriptionFromWebPage(page); + if (previewText.title.isEmpty()) { + if (page->document) { + previewText.title = tr::lng_attach_file(tr::now); + } else if (page->photo) { + previewText.title = tr::lng_attach_photo(tr::now); + } + } + return previewText; +} + +WebpageProcessor::WebpageProcessor( + not_null history, + not_null field) +: _history(history) +, _api(&history->session().mtp()) +, _parser(field) +, _timer([=] { + if (!ShowWebPagePreview(_data) || _link.isEmpty()) { + return; + } + request(); +}) { + _history->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return _data && (_data->document || _data->photo); + }) | rpl::start_with_next([=] { + _repaintRequests.fire({}); + }, _lifetime); + + _history->owner().webPageUpdates( + ) | rpl::filter([=](not_null page) { + return (_data == page.get()); + }) | rpl::start_with_next([=] { + updateFromData(); + }, _lifetime); + + _parser.list().changes( + ) | rpl::start_with_next([=](QStringList &&parsed) { + _parsedLinks = std::move(parsed); + checkPreview(); + }, _lifetime); +} + +rpl::producer<> WebpageProcessor::repaintRequests() const { + return _repaintRequests.events(); +} + +Data::WebPageDraft WebpageProcessor::draft() const { + return _draft; +} + +void WebpageProcessor::apply(Data::WebPageDraft draft, bool reparse) { + _api.request(base::take(_requestId)).cancel(); + if (draft.removed) { + _draft = draft; + _data = nullptr; + _links = QStringList(); + _link = QString(); + _parsed = WebpageParsed(); + updateFromData(); + } else if (draft.manual && draft.id && !draft.url.isEmpty()) { + _draft = draft; + _parsedLinks = QStringList(); + _links = QStringList(); + _link = _draft.url; + const auto page = _history->owner().webpage(draft.id); + if (page->url == draft.url) { + _data = page; + updateFromData(); + } else { + request(); + } + } else if (!draft.manual && !_draft.manual) { + _draft = draft; + checkNow(reparse); + } +} + +void WebpageProcessor::updateFromData() { + _timer.cancel(); + auto parsed = WebpageParsed(); + if (ShowWebPagePreview(_data)) { + if (const auto till = _data->pendingTill) { + parsed.drawPreview = [](QPainter &p, QRect to) { + return false; + }; + parsed.title = tr::lng_preview_loading(tr::now); + parsed.description = _link; + + const auto timeout = till - base::unixtime::now(); + _timer.callOnce( + std::max(timeout, 0) * crl::time(1000)); + } else { + const auto webpage = _data; + const auto context = _history->peer; + const auto preview = ProcessWebPageData(_data); + parsed.title = preview.title; + parsed.description = preview.description; + parsed.drawPreview = [=](QPainter &p, QRect to) { + return DrawWebPageDataPreview(p, webpage, context, to); + }; + } + } + _parsed = std::move(parsed); + _repaintRequests.fire({}); +} + +void WebpageProcessor::request() { + const auto link = _link; + const auto done = [=](const MTPDmessageMediaWebPage &data) { + const auto page = _history->owner().processWebpage(data.vwebpage()); + if (page->pendingTill > 0 + && page->pendingTill < base::unixtime::now()) { + page->pendingTill = 0; + page->failed = true; + } + _cache.emplace(link, page->failed ? nullptr : page.get()); + if (_link == link && !_draft.removed && !_draft.manual) { + _data = (page->id && !page->failed) + ? page.get() + : nullptr; + _draft.id = page->id; + _draft.url = page->url; + updateFromData(); + } + }; + const auto fail = [=] { + _cache.emplace(link, nullptr); + if (_link == link && !_draft.removed && !_draft.manual) { + _links = QStringList(); + checkPreview(); + } + }; + _requestId = _api.request( + MTPmessages_GetWebPagePreview( + MTP_flags(0), + MTP_string(_link), + MTPVector() + )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + } + result.match([=](const MTPDmessageMediaWebPage &data) { + done(data); + }, [&](const auto &d) { + fail(); + }); + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + } + fail(); + }).send(); +} + +void WebpageProcessor::checkNow(bool force) { + _parser.parseNow(); + if (force) { + _link = QString(); + _links = QStringList(); + if (_parsedLinks.isEmpty()) { + _data = nullptr; + updateFromData(); + return; + } + } + checkPreview(); +} + +void WebpageProcessor::checkPreview() { + const auto previewRestricted = _history->peer + && _history->peer->amRestricted(ChatRestriction::EmbedLinks); + if (_draft.removed) { + return; + } else if (previewRestricted) { + apply({ .removed = true }); + _draft.removed = false; + return; + } else if (_draft.manual) { + return; + } else if (_links == _parsedLinks) { + return; + } + _links = _parsedLinks; + + auto page = (WebPageData*)nullptr; + auto chosen = QString(); + for (const auto &link : _links) { + const auto i = _cache.find(link); + if (i == end(_cache)) { + chosen = link; + break; + } else if (i->second) { + if (i->second->failed) { + i->second = nullptr; + } else { + chosen = link; + page = i->second; + break; + } + } + } + if (_link != chosen) { + _link = chosen; + _api.request(base::take(_requestId)).cancel(); + if (!page && !_link.isEmpty()) { + request(); + } + } + if (page) { + _data = page; + _draft.id = _data->id; + _draft.url = _data->url; + } else { + _data = nullptr; + _draft = {}; + } + updateFromData(); +} + +rpl::producer WebpageProcessor::parsedValue() const { + return _parsed.value(); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h new file mode 100644 index 0000000000..e51c7c8386 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h @@ -0,0 +1,100 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/weak_ptr.h" +#include "data/data_drafts.h" +#include "chat_helpers/message_field.h" +#include "mtproto/sender.h" + +class History; + +namespace Ui { +class InputField; +} // namespace Ui + +namespace HistoryView::Controls { + +struct WebPageText { + QString title; + QString description; +}; + +[[nodiscard]] WebPageText TitleAndDescriptionFromWebPage( + not_null data); + +bool DrawWebPageDataPreview( + QPainter &p, + not_null webpage, + not_null context, + QRect to); + +[[nodiscard]] bool ShowWebPagePreview(WebPageData *page); +[[nodiscard]] WebPageText ProcessWebPageData(WebPageData *page); + +struct WebpageParsed { + Fn drawPreview; + QString title; + QString description; + + explicit operator bool() const { + return drawPreview != nullptr; + } +}; + +class WebpageProcessor final : public base::has_weak_ptr { +public: + WebpageProcessor( + not_null history, + not_null field); + + void checkNow(bool force); + + // If editing a message without a preview we don't want to show + // parsed preview until links set is changed in the message. + // + // If writing a new message we want to parse links immediately, + // unless preview was removed in the draft or manual. + void apply(Data::WebPageDraft draft, bool reparse = true); + [[nodiscard]] Data::WebPageDraft draft() const; + + [[nodiscard]] rpl::producer<> repaintRequests() const; + [[nodiscard]] rpl::producer parsedValue() const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + void updateFromData(); + void checkPreview(); + void request(); + + const not_null _history; + MTP::Sender _api; + MessageLinksParser _parser; + + QStringList _parsedLinks; + QStringList _links; + QString _link; + WebPageData *_data = nullptr; + base::flat_map _cache; + Data::WebPageDraft _draft; + + mtpRequestId _requestId = 0; + + rpl::event_stream<> _repaintRequests; + rpl::variable _parsed; + + base::Timer _timer; + + rpl::lifetime _lifetime; + +}; + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index faf393999a..1048b98b9a 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_compose_controls.h" #include "history/view/controls/history_view_forward_panel.h" -#include "history/view/controls/history_view_reply_options.h" +#include "history/view/controls/history_view_draft_options.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" 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 c89705092d..7e9ff2b351 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -244,31 +244,8 @@ QSize WebPage::countOptimalSize() { _asArticle = 0; } else if (_data->photo && (_flags & Flag::ForceSmallMedia)) { _asArticle = 1; - } else if (!_collage.empty()) { - _asArticle = 0; - } else if (!_data->document - && _data->photo - && _data->type != WebPageType::Photo - && _data->type != WebPageType::Document - && _data->type != WebPageType::Story - && _data->type != WebPageType::Video) { - if (_data->type == WebPageType::Profile) { - _asArticle = 1; - } else if (_data->siteName == u"Twitter"_q - || _data->siteName == u"Facebook"_q - || _data->type == WebPageType::ArticleWithIV) { - _asArticle = 0; - } else { - _asArticle = 1; - } - if (_asArticle - && _data->description.text.isEmpty() - && title.isEmpty() - && _data->siteName.isEmpty()) { - _asArticle = 0; - } } else { - _asArticle = 0; + _asArticle = _data->computeDefaultSmallMedia(); } // init attach diff --git a/Telegram/lib_ui b/Telegram/lib_ui index c36559a679..b05f7eb915 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit c36559a6797f02d8a56a414ac91f9c6fd08b5270 +Subproject commit b05f7eb915a86f67249904061d70f293066de618 From 041ec1157f9c94e020b478c085b7d1fd4dd70784 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 25 Oct 2023 23:25:55 +0400 Subject: [PATCH 63/99] Respect invert_media in service notifications. --- Telegram/SourceFiles/api/api_updates.cpp | 5 ++++- Telegram/SourceFiles/data/data_session.cpp | 20 +++++++++++++------- Telegram/SourceFiles/data/data_session.h | 6 ++++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 78b286db07..064f410c5b 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -2071,7 +2071,10 @@ void Updates::feedUpdate(const MTPUpdate &update) { windows.front()->window().show(Ui::MakeInformBox(text)); } } else { - session().data().serviceNotification(text, d.vmedia()); + session().data().serviceNotification( + text, + d.vmedia(), + d.is_invert_media()); session().api().authorizations().reload(); } } break; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 4c4223895f..162db84c2b 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4347,7 +4347,8 @@ auto Session::dialogsRowReplacements() const void Session::serviceNotification( const TextWithEntities &message, - const MTPMessageMedia &media) { + const MTPMessageMedia &media, + bool invertMedia) { const auto date = base::unixtime::now(); if (!peerLoaded(PeerData::kServiceNotificationsId)) { processUser(MTP_user( @@ -4375,22 +4376,27 @@ void Session::serviceNotification( MTPlong())); // background_emoji_id } const auto history = this->history(PeerData::kServiceNotificationsId); + const auto insert = [=] { + insertCheckedServiceNotification(message, media, date, invertMedia); + }; if (!history->folderKnown()) { - histories().requestDialogEntry(history, [=] { - insertCheckedServiceNotification(message, media, date); - }); + histories().requestDialogEntry(history, insert); } else { - insertCheckedServiceNotification(message, media, date); + insert(); } } void Session::insertCheckedServiceNotification( const TextWithEntities &message, const MTPMessageMedia &media, - TimeId date) { + TimeId date, + bool invertMedia) { const auto flags = MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id - | MTPDmessage::Flag::f_media; + | MTPDmessage::Flag::f_media + | (invertMedia + ? MTPDmessage::Flag::f_invert_media + : MTPDmessage::Flag()); const auto localFlags = MessageFlag::ClientSideUnread | MessageFlag::Local; auto sending = TextWithEntities(), left = message; diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 9ecb5c45cf..c470e3a8b3 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -705,7 +705,8 @@ public: void serviceNotification( const TextWithEntities &message, - const MTPMessageMedia &media = MTP_messageMediaEmpty()); + const MTPMessageMedia &media = MTP_messageMediaEmpty(), + bool invertMedia = false); void setMimeForwardIds(MessageIdsList &&list); MessageIdsList takeMimeForwardIds(); @@ -851,7 +852,8 @@ private: void insertCheckedServiceNotification( const TextWithEntities &message, const MTPMessageMedia &media, - TimeId date); + TimeId date, + bool invertMedia); void setWallpapers(const QVector &data, uint64 hash); void highlightProcessDone(uint64 processId); From c035ec6917c5dcf8e847f0dc883b64c6a66ac6cc Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Oct 2023 09:26:45 +0400 Subject: [PATCH 64/99] Allow sending just webpage preview. --- Telegram/SourceFiles/apiwrap.cpp | 39 ++++++++++++------- .../chat_helpers/message_field.cpp | 25 +++++++----- .../SourceFiles/chat_helpers/message_field.h | 36 ++++++++--------- Telegram/SourceFiles/data/data_histories.cpp | 6 ++- Telegram/SourceFiles/data/data_histories.h | 4 +- .../SourceFiles/history/history_widget.cpp | 3 ++ .../history_view_compose_controls.cpp | 9 +++-- .../history_view_webpage_processor.cpp | 6 +++ Telegram/lib_ui | 2 +- 9 files changed, 81 insertions(+), 49 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index fbc6553cfb..932990d620 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3607,7 +3607,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) { auto &histories = history->owner().histories(); - while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { + const auto exactWebPage = !message.webPage.url.isEmpty(); + auto isFirst = true; + while (TextUtilities::CutPart(sending, left, MaxMessageSize) + || (isFirst && exactWebPage)) { + TextUtilities::Trim(left); + const auto isLast = left.empty(); + auto newId = FullMsgId( peer->id, _session->data().nextLocalMessageId()); @@ -3627,17 +3633,23 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to; mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } + const auto ignoreWebPage = message.webPage.removed + || (exactWebPage && !isLast); + const auto manualWebPage = exactWebPage + && !ignoreWebPage + && (message.webPage.manual || (isLast && !isFirst)); const auto replyHeader = NewMessageReplyHeader(action); MTPMessageMedia media = MTP_messageMediaEmpty(); - if (message.webPage.removed) { + if (ignoreWebPage) { sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage; - } else if (const auto fields = message.webPage; fields.id) { + } else if (exactWebPage) { using PageFlag = MTPDmessageMediaWebPage::Flag; using PendingFlag = MTPDwebPagePending::Flag; + const auto &fields = message.webPage; const auto page = _session->data().webpage(fields.id); media = MTP_messageMediaWebPage( MTP_flags(PageFlag() - | (fields.manual ? PageFlag::f_manual : PageFlag()) + | (manualWebPage ? PageFlag::f_manual : PageFlag()) | (fields.forceLargeMedia ? PageFlag::f_force_large_media : PageFlag()) @@ -3645,17 +3657,15 @@ void ApiWrap::sendMessage(MessageToSend &&message) { ? PageFlag::f_force_small_media : PageFlag())), MTP_webPagePending( - MTP_flags(page->url.isEmpty() - ? PendingFlag() - : PendingFlag::f_url), - MTP_long(page->id), - MTP_string(page->url), + MTP_flags(PendingFlag::f_url), + MTP_long(fields.id), + MTP_string(fields.url), MTP_int(page->pendingTill))); } const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); FillMessagePostFlags(action, peer, flags); - if (message.webPage.id && message.webPage.invert) { + if (exactWebPage && !ignoreWebPage && message.webPage.invert) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media; mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media; @@ -3732,9 +3742,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) { UnixtimeFromMsgId(response.outerMsgId)); } }; - if (!message.webPage.removed - && (message.webPage.manual || sending.empty()) - && !message.webPage.url.isEmpty()) { + if (exactWebPage + && !ignoreWebPage + && (manualWebPage || sending.empty())) { histories.sendPreparedMessage( history, action.replyTo, @@ -3743,7 +3753,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTP_flags(mediaFlags), peer->input, Data::Histories::ReplyToPlaceholder(), - Data::WebPageForMTP(message.webPage), + Data::WebPageForMTP(message.webPage, true), msgText, MTP_long(randomId), MTPReplyMarkup(), @@ -3768,6 +3778,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()) ), done, fail); } + isFirst = false; } finishForwarding(action); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index b988a9b3f8..0ef3bceca6 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -601,6 +601,12 @@ MessageLinksParser::MessageLinksParser(not_null field) _lifetime = _field->changes( ) | rpl::start_with_next([=] { const auto length = _field->getTextWithTags().text.size(); + if (!length) { + _lastLength = 0; + _timer.cancel(); + parse(); + return; + } const auto timeout = (std::abs(length - _lastLength) > 2) ? 0 : kParseLinksTimeout; @@ -652,6 +658,7 @@ void MessageLinksParser::parse() { const auto &tags = textWithTags.tags; const auto &markdownTags = _field->getMarkdownTags(); if (_disabled || text.isEmpty()) { + _ranges = {}; _list = QStringList(); return; } @@ -663,7 +670,7 @@ void MessageLinksParser::parse() { || (tag == Ui::InputField::kTagSpoiler); }; - auto ranges = QVector(); + _ranges.clear(); auto tag = tags.begin(); const auto tagsEnd = tags.end(); @@ -672,7 +679,7 @@ void MessageLinksParser::parse() { if (Ui::InputField::IsValidMarkdownLink(tag->id) && !TextUtilities::IsMentionLink(tag->id)) { - ranges.push_back({ tag->offset, tag->length, tag->id }); + _ranges.push_back({ tag->offset, tag->length, tag->id }); } ++tag; }; @@ -782,20 +789,18 @@ void MessageLinksParser::parse() { processTagsBefore(domainOffset); if (!hasTagsIntersection(range.start + range.length)) { if (markdownTagsAllow(range.start, range.length)) { - ranges.push_back(range); + _ranges.push_back(range); } } offset = matchOffset = p - start; } processTagsBefore(Ui::kQFixedMax); - apply(text, ranges); + applyRanges(text); } -void MessageLinksParser::apply( - const QString &text, - const QVector &ranges) { - const auto count = int(ranges.size()); +void MessageLinksParser::applyRanges(const QString &text) { + const auto count = int(_ranges.size()); const auto current = _list.current(); const auto computeLink = [&](const LinkRange &range) { return range.custom.isEmpty() @@ -807,7 +812,7 @@ void MessageLinksParser::apply( return true; } for (auto i = 0; i != count; ++i) { - if (computeLink(ranges[i]) != current[i]) { + if (computeLink(_ranges[i]) != current[i]) { return true; } } @@ -818,7 +823,7 @@ void MessageLinksParser::apply( } auto parsed = QStringList(); parsed.reserve(count); - for (const auto &range : ranges) { + for (const auto &range : _ranges) { parsed.push_back(computeLink(range).toString()); } _list = std::move(parsed); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 9c4ba031f8..d52d1d6706 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -96,38 +96,38 @@ AutocompleteQuery ParseMentionHashtagBotCommandQuery( not_null field, ChatHelpers::ComposeFeatures features); -class MessageLinksParser : private QObject { +class MessageLinksParser final : private QObject { public: MessageLinksParser(not_null field); void parseNow(); void setDisabled(bool disabled); - [[nodiscard]] const rpl::variable &list() const; + struct LinkRange { + int start = 0; + int length = 0; + QString custom; -protected: - bool eventFilter(QObject *object, QEvent *event) override; + friend inline auto operator<=>( + const LinkRange&, + const LinkRange&) = default; + friend inline bool operator==( + const LinkRange&, + const LinkRange&) = default; + }; + + [[nodiscard]] const rpl::variable &list() const; + [[nodiscard]] const std::vector &ranges() const; private: - struct LinkRange { - int start; - int length; - QString custom; - }; - friend inline bool operator==(const LinkRange &a, const LinkRange &b) { - return (a.start == b.start) - && (a.length == b.length) - && (a.custom == b.custom); - } - friend inline bool operator!=(const LinkRange &a, const LinkRange &b) { - return !(a == b); - } + bool eventFilter(QObject *object, QEvent *event) override; void parse(); - void apply(const QString &text, const QVector &ranges); + void applyRanges(const QString &text); not_null _field; rpl::variable _list; + std::vector _ranges; int _lastLength = 0; bool _disabled = false; base::Timer _timer; diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index fb20655a5a..b1ba12e13b 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -73,10 +73,12 @@ MTPInputReplyTo ReplyToForMTP( return MTPInputReplyTo(); } -MTPInputMedia WebPageForMTP(const Data::WebPageDraft &draft) { +MTPInputMedia WebPageForMTP( + const Data::WebPageDraft &draft, + bool required) { using Flag = MTPDinputMediaWebPage::Flag; return MTP_inputMediaWebPage( - MTP_flags(Flag::f_optional + MTP_flags((required ? Flag() : Flag::f_optional) | (draft.forceLargeMedia ? Flag::f_force_large_media : Flag()) | (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())), MTP_string(draft.url)); diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index 3d9472bb05..dd2900a41c 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -30,7 +30,9 @@ struct WebPageDraft; [[nodiscard]] MTPInputReplyTo ReplyToForMTP( not_null history, FullReplyTo replyTo); -[[nodiscard]] MTPInputMedia WebPageForMTP(const Data::WebPageDraft &draft); +[[nodiscard]] MTPInputMedia WebPageForMTP( + const Data::WebPageDraft &draft, + bool required = false); class Histories final { public: diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9a595520b4..6d4985d974 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -3941,6 +3941,9 @@ void HistoryWidget::send(Api::SendOptions options) { session().api().sendMessage(std::move(message)); clearFieldText(); + if (_preview) { + _preview->apply({ .removed = true }); + } _saveDraftText = true; _saveDraftStart = crl::now(); saveDraft(); 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 9150c5e509..c55046a1f1 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -530,7 +530,7 @@ void FieldHeader::paintWebPage(Painter &p, not_null context) { Expects(!!_preview.parsed); const auto textTop = st::msgReplyPadding.top(); - auto previewLeft = st::historyReplySkip + st::msgReplyBarSkip; + auto previewLeft = st::historyReplySkip; const QRect to( previewLeft, @@ -1204,6 +1204,9 @@ void ComposeControls::clear() { {}, saveTextDraft ? TextUpdateEvent::SaveDraft : TextUpdateEvent()); cancelReplyMessage(); + if (_preview) { + _preview->apply({ .removed = true }); + } } void ComposeControls::setText(const TextWithTags &textWithTags) { @@ -1755,8 +1758,8 @@ void ComposeControls::fieldChanged() { && (_textUpdateEvents & TextUpdateEvent::SendTyping)); updateSendButtonType(); _hasSendText = HasSendText(_field); - if (!_hasSendText.current() && _preview) { - _preview->apply({}); + if (!_hasSendText.current() && _preview && !_preview->draft().manual) { + _preview->apply({ .removed = true }); } if (updateBotCommandShown() || updateLikeShown()) { updateControlsVisibility(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp index 909e3508e3..a3b402b299 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp @@ -155,6 +155,9 @@ void WebpageProcessor::apply(Data::WebPageDraft draft, bool reparse) { _api.request(base::take(_requestId)).cancel(); if (draft.removed) { _draft = draft; + if (_parsedLinks.empty()) { + _draft.removed = false; + } _data = nullptr; _links = QStringList(); _link = QString(); @@ -272,6 +275,9 @@ void WebpageProcessor::checkNow(bool force) { void WebpageProcessor::checkPreview() { const auto previewRestricted = _history->peer && _history->peer->amRestricted(ChatRestriction::EmbedLinks); + if (_parsedLinks.empty()) { + _draft.removed = false; + } if (_draft.removed) { return; } else if (previewRestricted) { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index b05f7eb915..383b5b8f7e 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit b05f7eb915a86f67249904061d70f293066de618 +Subproject commit 383b5b8f7e629475e5f22445167aaa7669c5cdd6 From 3b91e2dee4cc4f2def95e466aeffbe4bcc7ea976 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Oct 2023 10:26:06 +0400 Subject: [PATCH 65/99] Improve editing messages with webpage previews. --- Telegram/SourceFiles/api/api_editing.cpp | 4 +- Telegram/SourceFiles/apiwrap.cpp | 4 +- .../SourceFiles/history/history_widget.cpp | 39 ++++++++++++++----- .../history_view_compose_controls.cpp | 5 +-- .../controls/history_view_draft_options.cpp | 1 + .../history_view_webpage_processor.cpp | 21 ++++++++-- .../controls/history_view_webpage_processor.h | 1 + 7 files changed, 54 insertions(+), 21 deletions(-) diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 4a69ed31c2..005effad99 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -68,7 +68,7 @@ mtpRequestId EditMessage( const auto emptyFlag = MTPmessages_EditMessage::Flag(0); const auto flags = emptyFlag - | (!text.isEmpty() || media + | ((!text.isEmpty() || media) ? MTPmessages_EditMessage::Flag::f_message : emptyFlag) | ((media && inputMedia.has_value()) @@ -98,7 +98,7 @@ mtpRequestId EditMessage( item->history()->peer->input, MTP_int(id), MTP_string(text), - inputMedia.value_or(Data::WebPageForMTP(webpage)), + inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())), MTPReplyMarkup(), sentEntities, MTP_int(options.scheduled) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 932990d620..b1fb115724 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2158,7 +2158,9 @@ void ApiWrap::saveDraftsToCloud() { history->peer->input, MTP_string(textWithTags.text), entities, - Data::WebPageForMTP(cloudDraft->webpage) + Data::WebPageForMTP( + cloudDraft->webpage, + textWithTags.text.isEmpty()) )).done([=](const MTPBool &result, const MTP::Response &response) { const auto requestId = response.requestId; history->finishSavingCloudDraft( diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 6d4985d974..fbdb5138e8 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1587,9 +1587,6 @@ void HistoryWidget::fieldChanged() { updateSendButtonType(); if (!HasSendText(_field)) { - if (_preview) { - _preview->apply({}); - } _fieldIsEmpty = true; } else if (_fieldIsEmpty) { _fieldIsEmpty = false; @@ -1887,7 +1884,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { _processingReplyTo = _replyTo = FullReplyTo(); setEditMsgId(0); if (_preview) { - _preview->apply({}); + _preview->apply({ .removed = true }); } if (fieldWillBeHiddenAfterEdit) { updateControlsVisibility(); @@ -1923,7 +1920,17 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { } if (_preview) { - _preview->apply(draft->webpage, !_editMsgId); + _preview->setDisabled(_editMsgId + && _replyEditMsg + && _replyEditMsg->media() + && !_replyEditMsg->media()->webpage()); + if (!_editMsgId) { + _preview->apply(draft->webpage, true); + } else if (!_replyEditMsg + || !_replyEditMsg->media() + || _replyEditMsg->media()->webpage()) { + _preview->apply(draft->webpage, false); + } } return true; } @@ -2503,6 +2510,9 @@ void HistoryWidget::setEditMsgId(MsgId msgId) { _editMsgId = msgId; if (!msgId) { _canReplaceMedia = false; + if (_preview) { + _preview->setDisabled(false); + } } if (_history) { refreshSendAsToggle(); @@ -3775,6 +3785,7 @@ void HistoryWidget::saveEditMsg() { cancelEdit(); return; } + const auto webPageDraft = _preview->draft(); const auto textWithTags = _field->getTextWithAppliedMarkdown(); const auto prepareFlags = Ui::ItemTextOptions( _history, @@ -3785,8 +3796,12 @@ void HistoryWidget::saveEditMsg() { TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; TextUtilities::PrepareForSending(left, prepareFlags); + const auto media = item->media(); if (!TextUtilities::CutPart(sending, left, MaxMessageSize) - && (!item->media() || !item->media()->allowsEditCaption())) { + && (webPageDraft.removed + || webPageDraft.url.isEmpty() + || !webPageDraft.manual) + && (!media || !media->allowsEditCaption())) { const auto suggestModerateActions = false; controller()->show( Box(item, suggestModerateActions)); @@ -3844,7 +3859,7 @@ void HistoryWidget::saveEditMsg() { _saveEditMsgRequestId = Api::EditTextMessage( item, sending, - _preview->draft(), + webPageDraft, options, done, fail); @@ -6267,9 +6282,7 @@ void HistoryWidget::editDraftOptions() { } else { cancelReply(); } - if (_preview->draft() != webpage) { - _preview->apply(webpage); - } + _preview->apply(webpage); }; const auto highlight = [=] { controller()->showPeerHistory( @@ -7693,6 +7706,12 @@ void HistoryWidget::messageDataReceived( } else if (_editMsgId == msgId || (_replyTo.messageId == FullMsgId(peer->id, msgId))) { updateReplyEditTexts(true); + if (_editMsgId == msgId) { + _preview->setDisabled(_editMsgId + && _replyEditMsg + && _replyEditMsg->media() + && !_replyEditMsg->media()->webpage()); + } } } 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 c55046a1f1..c203735c8f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1758,9 +1758,6 @@ void ComposeControls::fieldChanged() { && (_textUpdateEvents & TextUpdateEvent::SendTyping)); updateSendButtonType(); _hasSendText = HasSendText(_field); - if (!_hasSendText.current() && _preview && !_preview->draft().manual) { - _preview->apply({ .removed = true }); - } if (updateBotCommandShown() || updateLikeShown()) { updateControlsVisibility(); updateControlsGeometry(_wrap->size()); @@ -1930,7 +1927,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _header->editMessage({}); _header->replyToMessage({}); if (_preview) { - _preview->apply({}); + _preview->apply({ .removed = true }); } _canReplaceMedia = false; _photoEditMedia = nullptr; 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 1147395aa7..60e9d1bb0a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -252,6 +252,7 @@ rpl::producer PreviewWrap::showLinkSelector( _history->nextNonHistoryEntryId(), (MessageFlag::FakeHistoryItem | MessageFlag::Outgoing + | MessageFlag::HasFromId | (webpage.invert ? MessageFlag::InvertMedia : MessageFlag())), UserId(), // via FullReplyTo(), diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp index a3b402b299..c75accfb2b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp @@ -163,13 +163,15 @@ void WebpageProcessor::apply(Data::WebPageDraft draft, bool reparse) { _link = QString(); _parsed = WebpageParsed(); updateFromData(); - } else if (draft.manual && draft.id && !draft.url.isEmpty()) { + } else if (draft.manual && !draft.url.isEmpty()) { _draft = draft; _parsedLinks = QStringList(); _links = QStringList(); _link = _draft.url; - const auto page = _history->owner().webpage(draft.id); - if (page->url == draft.url) { + const auto page = draft.id + ? _history->owner().webpage(draft.id).get() + : nullptr; + if (page && page->url == draft.url) { _data = page; updateFromData(); } else { @@ -220,7 +222,9 @@ void WebpageProcessor::request() { page->failed = true; } _cache.emplace(link, page->failed ? nullptr : page.get()); - if (_link == link && !_draft.removed && !_draft.manual) { + if (_link == link + && !_draft.removed + && (!_draft.manual || _draft.url == link)) { _data = (page->id && !page->failed) ? page.get() : nullptr; @@ -258,6 +262,15 @@ void WebpageProcessor::request() { }).send(); } +void WebpageProcessor::setDisabled(bool disabled) { + _parser.setDisabled(disabled); + if (disabled) { + apply({ .removed = true }); + } else { + checkNow(false); + } +} + void WebpageProcessor::checkNow(bool force) { _parser.parseNow(); if (force) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h index e51c7c8386..2c356eedce 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h @@ -53,6 +53,7 @@ public: not_null history, not_null field); + void setDisabled(bool disabled); void checkNow(bool force); // If editing a message without a preview we don't want to show From a197ed9e95a94aed4ef402d718ff9ec312946586 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Oct 2023 11:30:36 +0400 Subject: [PATCH 66/99] Allow choosing the link for the preview. --- .../chat_helpers/message_field.cpp | 8 +- .../SourceFiles/chat_helpers/message_field.h | 39 +- .../history/history_inner_widget.cpp | 3 +- .../SourceFiles/history/history_widget.cpp | 23 +- .../controls/history_view_draft_options.cpp | 614 +++++++++++------- .../controls/history_view_draft_options.h | 23 +- .../history_view_webpage_processor.cpp | 185 ++++-- .../controls/history_view_webpage_processor.h | 42 +- .../history/view/history_view_cursor_state.h | 1 + .../history/view/history_view_message.cpp | 1 + 10 files changed, 588 insertions(+), 351 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 0ef3bceca6..9c5b377511 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -648,10 +648,6 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) { return QObject::eventFilter(object, event); } -const rpl::variable &MessageLinksParser::list() const { - return _list; -} - void MessageLinksParser::parse() { const auto &textWithTags = _field->getTextWithTags(); const auto &text = textWithTags.text; @@ -781,7 +777,7 @@ void MessageLinksParser::parse() { continue; } } - const auto range = LinkRange { + const auto range = MessageLinkRange{ int(domainOffset), static_cast(p - start - domainOffset), QString() @@ -802,7 +798,7 @@ void MessageLinksParser::parse() { void MessageLinksParser::applyRanges(const QString &text) { const auto count = int(_ranges.size()); const auto current = _list.current(); - const auto computeLink = [&](const LinkRange &range) { + const auto computeLink = [&](const MessageLinkRange &range) { return range.custom.isEmpty() ? base::StringViewMid(text, range.start, range.length) : QStringView(range.custom); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index d52d1d6706..a78abd94c2 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/widgets/fields/input_field.h" +#include "base/qt/qt_compare.h" #include "base/timer.h" #include "chat_helpers/compose/compose_features.h" +#include "ui/widgets/fields/input_field.h" #ifndef TDESKTOP_DISABLE_SPELLCHECK #include "boxes/dictionaries_manager.h" @@ -96,6 +97,19 @@ AutocompleteQuery ParseMentionHashtagBotCommandQuery( not_null field, ChatHelpers::ComposeFeatures features); +struct MessageLinkRange { + int start = 0; + int length = 0; + QString custom; + + friend inline auto operator<=>( + const MessageLinkRange&, + const MessageLinkRange&) = default; + friend inline bool operator==( + const MessageLinkRange&, + const MessageLinkRange&) = default; +}; + class MessageLinksParser final : private QObject { public: MessageLinksParser(not_null field); @@ -103,21 +117,12 @@ public: void parseNow(); void setDisabled(bool disabled); - struct LinkRange { - int start = 0; - int length = 0; - QString custom; - - friend inline auto operator<=>( - const LinkRange&, - const LinkRange&) = default; - friend inline bool operator==( - const LinkRange&, - const LinkRange&) = default; - }; - - [[nodiscard]] const rpl::variable &list() const; - [[nodiscard]] const std::vector &ranges() const; + [[nodiscard]] const rpl::variable &list() const { + return _list; + } + [[nodiscard]] const std::vector &ranges() const { + return _ranges; + } private: bool eventFilter(QObject *object, QEvent *event) override; @@ -127,7 +132,7 @@ private: not_null _field; rpl::variable _list; - std::vector _ranges; + std::vector _ranges; int _lastLength = 0; bool _disabled = false; base::Timer _timer; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index e0944e7a85..2da2c97e41 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2403,9 +2403,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (canReply) { const auto itemId = item->fullId(); const auto quote = selectedQuote(item); - const auto text = quote.empty() + auto text = quote.empty() ? tr::lng_context_reply_msg(tr::now) : tr::lng_context_quote_and_reply(tr::now); + text.replace('&', u"&&"_q); _menu->addAction(text, [=] { if (canSendReply) { _widget->replyToMessage({ itemId, quote }); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index fbdb5138e8..7f2f9077af 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6284,21 +6284,26 @@ void HistoryWidget::editDraftOptions() { } _preview->apply(webpage); }; + const auto replyToId = reply.messageId; const auto highlight = [=] { controller()->showPeerHistory( - reply.messageId.peer, + replyToId.peer, Window::SectionShow::Way::Forward, - reply.messageId.msg); + replyToId.msg); }; using namespace HistoryView::Controls; - EditDraftOptions( - controller()->uiShow(), - history, - Data::Draft(_field, reply, _preview->draft()), - done, - highlight, - [=] { ClearDraftReplyTo(history, reply.messageId); }); + EditDraftOptions({ + .show = controller()->uiShow(), + .history = history, + .draft = Data::Draft(_field, reply, _preview->draft()), + .usedLink = _preview->link(), + .links = _preview->links(), + .resolver = _preview->resolver(), + .done = done, + .highlight = highlight, + .clearOldDraft = [=] { ClearDraftReplyTo(history, replyToId); }, + }); } void HistoryWidget::keyPressEvent(QKeyEvent *e) { 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 60e9d1bb0a..bee32e72b5 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -18,11 +18,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_thread.h" #include "data/data_user.h" #include "data/data_web_page.h" +#include "history/view/controls/history_view_webpage_processor.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" -#include "history/view/history_view_element.h" -#include "history/view/history_view_cursor_state.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_common.h" @@ -102,6 +103,28 @@ private: return result; } +[[nodiscard]] TextWithEntities HighlightParsedLinks( + TextWithEntities text, + const std::vector &links) { + auto i = text.entities.begin(); + for (const auto &range : links) { + if (range.custom.isEmpty()) { + while (i != text.entities.end()) { + if (i->offset() > range.start) { + break; + } + ++i; + } + i = text.entities.insert( + i, + EntityInText(EntityType::Url, range.start, range.length)); + ++i; + } + } + return text; +} + + class PreviewWrap final : public Ui::RpWidget { public: PreviewWrap( @@ -114,7 +137,9 @@ public: const TextWithEntities "e); [[nodiscard]] rpl::producer showLinkSelector( const TextWithTags &message, - Data::WebPageDraft webpage); + Data::WebPageDraft webpage, + const std::vector &links, + const QString &usedLink); private: void paintEvent(QPaintEvent *e) override; @@ -125,6 +150,10 @@ private: void mouseDoubleClickEvent(QMouseEvent *e) override; void initElement(); + void highlightUsedLink( + const TextWithTags &message, + const QString &usedLink, + const std::vector &links); void startSelection(TextSelectType type); [[nodiscard]] TextSelection resolveNewSelection() const; @@ -138,12 +167,15 @@ private: HistoryItem *_draftItem = nullptr; std::unique_ptr _element; rpl::variable _selection; + rpl::event_stream _chosenUrl; Ui::PeerUserpicView _userpic; rpl::lifetime _elementLifetime; QPoint _position; base::Timer _trippleClickTimer; + ClickHandlerPtr _link; + ClickHandlerPtr _pressedLink; TextSelectType _selectType = TextSelectType::Letters; uint16 _symbol = 0; uint16 _selectionStartSymbol = 0; @@ -219,6 +251,8 @@ rpl::producer PreviewWrap::showQuoteSelector( _selection.reset(element->selectionFromQuote(quote)); _element = std::move(element); + _link = _pressedLink = nullptr; + if (const auto was = base::take(_draftItem)) { was->destroy(); } @@ -240,7 +274,9 @@ rpl::producer PreviewWrap::showQuoteSelector( rpl::producer PreviewWrap::showLinkSelector( const TextWithTags &message, - Data::WebPageDraft webpage) { + Data::WebPageDraft webpage, + const std::vector &links, + const QString &usedLink) { _selection.reset(TextSelection()); _element = nullptr; @@ -259,10 +295,10 @@ rpl::producer PreviewWrap::showLinkSelector( base::unixtime::now(), // date _history->session().userPeerId(), QString(), // postAuthor - TextWithEntities{ + HighlightParsedLinks({ message.text, TextUtilities::ConvertTextTagsToEntities(message.tags), - }, + }, links), MTP_messageMediaWebPage( MTP_flags(Flag() | (webpage.forceLargeMedia @@ -287,8 +323,50 @@ rpl::producer PreviewWrap::showLinkSelector( _section = Section::Link; initElement(); + highlightUsedLink(message, usedLink, links); - return rpl::never(); + return _chosenUrl.events(); +} + +void PreviewWrap::highlightUsedLink( + const TextWithTags &message, + const QString &usedLink, + const std::vector &links) { + auto selection = TextSelection(); + const auto view = QStringView(message.text); + for (const auto &range : links) { + auto text = view.mid(range.start, range.length); + if (range.custom == usedLink + || (range.custom.isEmpty() + && range.length == usedLink.size() + && text == usedLink)) { + selection = { + uint16(range.start), + uint16(range.start + range.length), + }; + const auto skip = [](QChar ch) { + return ch.isSpace() || Ui::Text::IsNewline(ch); + }; + while (!text.isEmpty() && skip(text.front())) { + text = text.mid(1); + ++selection.from; + } + while (!text.isEmpty() && skip(text.back())) { + text = text.mid(0, text.size() - 1); + --selection.to; + } + const auto basic = _element->textState(QPoint(0, 0), { + .flags = Ui::Text::StateRequest::Flag::LookupSymbol, + .onlyMessageText = true, + }); + if (basic.symbol > 0) { + selection.from += basic.symbol; + selection.to += basic.symbol; + } + break; + } + } + _selection = selection; } void PreviewWrap::paintEvent(QPaintEvent *e) { @@ -385,7 +463,10 @@ void PreviewWrap::mouseMoveEvent(QMouseEvent *e) { _over = true; const auto text = (_section == Section::Reply) && (resolved.cursor == CursorState::Text); - const auto link = (_section == Section::Link) && resolved.link; + _link = (_section == Section::Link && resolved.overMessageText) + ? resolved.link + : nullptr; + const auto link = (_link != nullptr) || (_pressedLink != nullptr); if (_textCursor != text || _linkCursor != link) { _textCursor = text; _linkCursor = link; @@ -412,13 +493,16 @@ void PreviewWrap::mousePressEvent(QMouseEvent *e) { startSelection(_trippleClickTimer.isActive() ? TextSelectType::Paragraphs : TextSelectType::Letters); + } else { + _pressedLink = _link; } } void PreviewWrap::mouseReleaseEvent(QMouseEvent *e) { - if (!_selecting) { - return; - } else if (_section == Section::Reply) { + if (_section == Section::Reply) { + if (!_selecting) { + return; + } const auto result = resolveNewSelection(); _selecting = false; _selectType = TextSelectType::Letters; @@ -426,6 +510,12 @@ void PreviewWrap::mouseReleaseEvent(QMouseEvent *e) { setCursor(style::cur_default); } _selection = result; + } else if (base::take(_pressedLink) == _link && _link) { + if (const auto url = _link->url(); !url.isEmpty()) { + _chosenUrl.fire_copy(url); + } + } else if (!_link) { + setCursor(style::cur_default); } } @@ -509,6 +599,276 @@ Context PreviewDelegate::elementContext() { return Context::History; } +void AddFilledSkip(not_null container) { + const auto skip = container->add(object_ptr( + container, + st::settingsPrivacySkipTop)); + skip->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(skip).fillRect(clip, st::boxBg); + }, skip->lifetime()); +}; + +void DraftOptionsBox( + not_null box, + EditDraftOptionsArgs &&args, + HistoryItem *replyItem, + WebPageData *previewData) { + box->setWidth(st::boxWideWidth); + + const auto &draft = args.draft; + struct State { + rpl::variable
shown; + rpl::lifetime shownLifetime; + rpl::variable quote; + Data::WebPageDraft webpage; + WebPageData *preview = nullptr; + QString link; + Ui::SettingsSlider *tabs = nullptr; + PreviewWrap *wrap = nullptr; + rpl::lifetime resolveLifetime; + }; + const auto state = box->lifetime().make_state(); + state->quote = draft.reply.quote; + state->webpage = draft.webpage; + state->preview = previewData; + state->shown = previewData ? Section::Link : Section::Reply; + if (replyItem && previewData) { + box->setNoContentMargin(true); + state->tabs = box->setPinnedToTopContent( + object_ptr( + box.get(), + st::defaultTabsSlider)); + state->tabs->resizeToWidth(st::boxWideWidth); + state->tabs->move(0, 0); + state->tabs->setRippleTopRoundRadius(st::boxRadius); + state->tabs->setSections({ + tr::lng_reply_header_short(tr::now), + tr::lng_link_header_short(tr::now), + }); + state->tabs->setActiveSectionFast(1); + state->tabs->sectionActivated( + ) | rpl::start_with_next([=](int section) { + state->shown = section ? Section::Link : Section::Reply; + }, box->lifetime()); + } else { + box->setTitle(previewData + ? tr::lng_link_options_header() + : draft.reply.quote.empty() + ? tr::lng_reply_options_header() + : tr::lng_reply_options_quote()); + } + + const auto bottom = box->setPinnedToBottomContent( + object_ptr(box)); + + const auto &done = args.done; + const auto &show = args.show; + const auto &highlight = args.highlight; + const auto &clearOldDraft = args.clearOldDraft; + const auto resolveReply = [=] { + auto result = draft.reply; + result.quote = state->quote.current(); + return result; + }; + const auto finish = [=]( + FullReplyTo result, + Data::WebPageDraft webpage) { + const auto weak = Ui::MakeWeak(box); + done(std::move(result), std::move(webpage)); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + const auto setupReplyActions = [=] { + AddFilledSkip(bottom); + + 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); + + Settings::AddButton( + bottom, + tr::lng_reply_remove(), + st::settingsAttentionButtonWithIcon, + { &st::menuIconDeleteAttention } + )->setClickedCallback([=] { + finish({}, state->webpage); + }); + + if (!replyItem->originalText().empty()) { + AddFilledSkip(bottom); + Settings::AddDividerText( + bottom, + tr::lng_reply_about_quote()); + } + }; + const auto setupLinkActions = [=] { + AddFilledSkip(bottom); + + if (!draft.textWithTags.empty()) { + Settings::AddButton( + bottom, + (state->webpage.invert + ? tr::lng_link_move_down() + : tr::lng_link_move_up()), + st::settingsButton, + { state->webpage.invert + ? &st::menuIconBelow + : &st::menuIconAbove } + )->setClickedCallback([=] { + state->webpage.invert = !state->webpage.invert; + state->webpage.manual = true; + state->shown.force_assign(Section::Link); + }); + } + + if (state->preview->hasLargeMedia) { + const auto small = state->webpage.forceSmallMedia + || (!state->webpage.forceLargeMedia + && state->preview->computeDefaultSmallMedia()); + Settings::AddButton( + bottom, + (small + ? tr::lng_link_enlarge_photo() + : tr::lng_link_shrink_photo()), + st::settingsButton, + { small ? &st::menuIconEnlarge : &st::menuIconShrink } + )->setClickedCallback([=] { + if (small) { + state->webpage.forceSmallMedia = false; + state->webpage.forceLargeMedia = true; + } else { + state->webpage.forceLargeMedia = false; + state->webpage.forceSmallMedia = true; + } + state->webpage.manual = true; + state->shown.force_assign(Section::Link); + }); + } + + Settings::AddButton( + bottom, + tr::lng_link_remove(), + st::settingsAttentionButtonWithIcon, + { &st::menuIconDeleteAttention } + )->setClickedCallback([=] { + finish(resolveReply(), { .removed = true }); + }); + + if (args.links.size() > 1) { + AddFilledSkip(bottom); + Settings::AddDividerText( + bottom, + tr::lng_link_about_choose()); + } + }; + + const auto &resolver = args.resolver; + const auto performSwitch = [=](const QString &link, WebPageData *page) { + if (page) { + state->preview = page; + state->webpage.id = page->id; + state->webpage.url = page->url; + state->webpage.manual = true; + state->link = link; + state->shown.force_assign(Section::Link); + } else { + show->showToast(u"Could not generate preview for this link."_q); + } + }; + const auto switchTo = [=](const QString &link) { + if (link == state->link) { + return; + } + if (const auto value = resolver->lookup(link)) { + performSwitch(link, *value); + } else { + resolver->request(link); + state->resolveLifetime = resolver->resolved( + ) | rpl::start_with_next([=](const QString &resolved) { + if (resolved == link) { + state->resolveLifetime.destroy(); + performSwitch( + link, + resolver->lookup(link).value_or(nullptr)); + } + }); + } + }; + + state->wrap = box->addRow( + object_ptr(box, args.history), + {}); + const auto &linkRanges = args.links; + state->shown.value() | rpl::start_with_next([=](Section shown) { + bottom->clear(); + state->shownLifetime.destroy(); + if (shown == Section::Reply) { + state->quote = state->wrap->showQuoteSelector( + replyItem, + state->quote.current()); + setupReplyActions(); + } else { + state->wrap->showLinkSelector( + draft.textWithTags, + state->webpage, + linkRanges, + state->link + ) | rpl::start_with_next([=](QString link) { + switchTo(link); + }, state->shownLifetime); + setupLinkActions(); + } + }, box->lifetime()); + + auto save = rpl::combine( + state->quote.value(), + state->shown.value() + ) | rpl::map([=](const TextWithEntities "e, Section shown) { + return (quote.empty() || shown != Section::Reply) + ? tr::lng_settings_save() + : tr::lng_reply_quote_selected(); + }) | rpl::flatten_latest(); + box->addButton(std::move(save), [=] { + finish(resolveReply(), state->webpage); + }); + + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + + if (replyItem) { + args.show->session().data().itemRemoved( + ) | rpl::filter([=](not_null removed) { + return removed == replyItem; + }) | rpl::start_with_next([=] { + if (previewData) { + state->tabs = nullptr; + box->setPinnedToTopContent( + object_ptr(nullptr)); + box->setNoContentMargin(false); + box->setTitle(state->quote.current().empty() + ? tr::lng_reply_options_header() + : tr::lng_reply_options_quote()); + state->shown = Section::Link; + } else { + box->closeBox(); + } + }, box->lifetime()); + } +} } // namespace void ShowReplyToChatBox( @@ -603,14 +963,9 @@ void ShowReplyToChatBox( ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); } -void EditDraftOptions( - std::shared_ptr show, - not_null history, - Data::Draft draft, - Fn done, - Fn highlight, - Fn clearOldDraft) { - const auto session = &show->session(); +void EditDraftOptions(EditDraftOptionsArgs &&args) { + const auto &draft = args.draft; + const auto session = &args.show->session(); const auto replyItem = session->data().message(draft.reply.messageId); const auto previewDataRaw = draft.webpage.id ? session->data().webpage(draft.webpage.id).get() @@ -623,225 +978,8 @@ void EditDraftOptions( if (!replyItem && !previewData) { return; } - show->show(Box([=](not_null box) { - box->setWidth(st::boxWideWidth); - - struct State { - rpl::variable
shown; - rpl::lifetime shownLifetime; - rpl::variable quote; - Data::WebPageDraft webpage; - Ui::SettingsSlider *tabs = nullptr; - PreviewWrap *wrap = nullptr; - }; - const auto state = box->lifetime().make_state(); - state->quote = draft.reply.quote; - state->webpage = draft.webpage; - state->shown = previewData ? Section::Link : Section::Reply; - if (replyItem && previewData) { - box->setNoContentMargin(true); - state->tabs = box->setPinnedToTopContent( - object_ptr( - box.get(), - st::defaultTabsSlider)); - state->tabs->resizeToWidth(st::boxWideWidth); - state->tabs->move(0, 0); - state->tabs->setRippleTopRoundRadius(st::boxRadius); - state->tabs->setSections({ - tr::lng_reply_header_short(tr::now), - tr::lng_link_header_short(tr::now), - }); - state->tabs->setActiveSectionFast(1); - state->tabs->sectionActivated( - ) | rpl::start_with_next([=](int section) { - state->shown = section ? Section::Link : Section::Reply; - }, box->lifetime()); - } else { - box->setTitle(previewData - ? tr::lng_link_options_header() - : draft.reply.quote.empty() - ? tr::lng_reply_options_header() - : tr::lng_reply_options_quote()); - } - - const auto bottom = box->setPinnedToBottomContent( - object_ptr(box)); - const auto addSkip = [=] { - const auto skip = bottom->add(object_ptr( - bottom, - st::settingsPrivacySkipTop)); - skip->paintRequest() | rpl::start_with_next([=](QRect clip) { - QPainter(skip).fillRect(clip, st::boxBg); - }, skip->lifetime()); - }; - - const auto resolveReply = [=] { - auto result = draft.reply; - result.quote = state->quote.current(); - return result; - }; - const auto finish = [=]( - FullReplyTo result, - Data::WebPageDraft webpage) { - const auto weak = Ui::MakeWeak(box); - done(std::move(result), std::move(webpage)); - if (const auto strong = weak.data()) { - strong->closeBox(); - } - }; - const auto setupReplyActions = [=] { - addSkip(); - - 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); - - Settings::AddButton( - bottom, - tr::lng_reply_remove(), - st::settingsAttentionButtonWithIcon, - { &st::menuIconDeleteAttention } - )->setClickedCallback([=] { - finish({}, state->webpage); - }); - - if (!replyItem->originalText().empty()) { - addSkip(); - Settings::AddDividerText( - bottom, - tr::lng_reply_about_quote()); - } - }; - const auto setupLinkActions = [=] { - addSkip(); - - if (!draft.textWithTags.empty()) { - Settings::AddButton( - bottom, - (state->webpage.invert - ? tr::lng_link_move_down() - : tr::lng_link_move_up()), - st::settingsButton, - { state->webpage.invert - ? &st::menuIconBelow - : &st::menuIconAbove } - )->setClickedCallback([=] { - state->webpage.invert = !state->webpage.invert; - state->webpage.manual = true; - state->shown.force_assign(Section::Link); - }); - } - - if (previewData->hasLargeMedia) { - const auto small = state->webpage.forceSmallMedia - || (!state->webpage.forceLargeMedia - && previewData->computeDefaultSmallMedia()); - Settings::AddButton( - bottom, - (small - ? tr::lng_link_enlarge_photo() - : tr::lng_link_shrink_photo()), - st::settingsButton, - { small ? &st::menuIconEnlarge : &st::menuIconShrink } - )->setClickedCallback([=] { - if (small) { - state->webpage.forceSmallMedia = false; - state->webpage.forceLargeMedia = true; - } else { - state->webpage.forceLargeMedia = false; - state->webpage.forceSmallMedia = true; - } - state->webpage.manual = true; - state->shown.force_assign(Section::Link); - }); - } - - Settings::AddButton( - bottom, - tr::lng_link_remove(), - st::settingsAttentionButtonWithIcon, - { &st::menuIconDeleteAttention } - )->setClickedCallback([=] { - finish(resolveReply(), { .removed = true }); - }); - - if (true) { - addSkip(); - Settings::AddDividerText( - bottom, - tr::lng_link_about_choose()); - } - }; - - state->wrap = box->addRow( - object_ptr(box, history), - {}); - state->shown.value() | rpl::start_with_next([=](Section shown) { - bottom->clear(); - state->shownLifetime.destroy(); - if (shown == Section::Reply) { - state->quote = state->wrap->showQuoteSelector( - replyItem, - state->quote.current()); - setupReplyActions(); - } else { - state->wrap->showLinkSelector( - draft.textWithTags, - state->webpage - ) | rpl::start_with_next([=](QString url) { - }, state->shownLifetime); - setupLinkActions(); - } - }, box->lifetime()); - - auto save = rpl::combine( - state->quote.value(), - state->shown.value() - ) | rpl::map([=](const TextWithEntities "e, Section shown) { - return (quote.empty() || shown != Section::Reply) - ? tr::lng_settings_save() - : tr::lng_reply_quote_selected(); - }) | rpl::flatten_latest(); - box->addButton(std::move(save), [=] { - finish(resolveReply(), state->webpage); - }); - - box->addButton(tr::lng_cancel(), [=] { - box->closeBox(); - }); - - if (replyItem) { - session->data().itemRemoved( - ) | rpl::filter([=](not_null removed) { - return removed == replyItem; - }) | rpl::start_with_next([=] { - if (previewData) { - state->tabs = nullptr; - box->setPinnedToTopContent( - object_ptr(nullptr)); - box->setNoContentMargin(false); - box->setTitle(state->quote.current().empty() - ? tr::lng_reply_options_header() - : tr::lng_reply_options_quote()); - state->shown = Section::Link; - } else { - box->closeBox(); - } - }, box->lifetime()); - } - })); + args.show->show( + Box(DraftOptionsBox, std::move(args), replyItem, previewData)); } } // namespace HistoryView::Controls 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 6e3def8cc3..798e1fa0cc 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_drafts.h" class History; +struct MessageLinkRange; namespace ChatHelpers { class Show; @@ -21,13 +22,21 @@ class SessionController; namespace HistoryView::Controls { -void EditDraftOptions( - std::shared_ptr show, - not_null history, - Data::Draft draft, - Fn done, - Fn highlight, - Fn clearOldDraft); +class WebpageResolver; + +struct EditDraftOptionsArgs { + std::shared_ptr show; + not_null history; + Data::Draft draft; + QString usedLink; + std::vector links; + std::shared_ptr resolver; + Fn done; + Fn highlight; + Fn clearOldDraft; +}; + +void EditDraftOptions(EditDraftOptionsArgs &&args); void ShowReplyToChatBox( std::shared_ptr show, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp index c75accfb2b..50abe57bd5 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp @@ -110,17 +110,88 @@ WebPageText ProcessWebPageData(WebPageData *page) { return previewText; } +WebpageResolver::WebpageResolver(not_null session) +: _session(session) +, _api(&session->mtp()) { +} + +std::optional WebpageResolver::lookup( + const QString &link) const { + const auto i = _cache.find(link); + return (i == end(_cache)) + ? std::optional() + : (i->second && !i->second->failed) + ? i->second + : nullptr; +} + +QString WebpageResolver::find(not_null page) const { + for (const auto &[link, cached] : _cache) { + if (cached == page) { + return link; + } + } + return QString(); +} + +void WebpageResolver::request(const QString &link) { + if (_requestLink == link) { + return; + } + const auto done = [=](const MTPDmessageMediaWebPage &data) { + const auto page = _session->data().processWebpage(data.vwebpage()); + if (page->pendingTill > 0 + && page->pendingTill < base::unixtime::now()) { + page->pendingTill = 0; + page->failed = true; + } + _cache.emplace(link, page->failed ? nullptr : page.get()); + _resolved.fire_copy(link); + }; + const auto fail = [=] { + _cache.emplace(link, nullptr); + _resolved.fire_copy(link); + }; + _requestLink = link; + _requestId = _api.request( + MTPmessages_GetWebPagePreview( + MTP_flags(0), + MTP_string(link), + MTPVector() + )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + } + result.match([=](const MTPDmessageMediaWebPage &data) { + done(data); + }, [&](const auto &d) { + fail(); + }); + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + } + fail(); + }).send(); +} + +void WebpageResolver::cancel(const QString &link) { + if (_requestLink == link) { + _api.request(base::take(_requestId)).cancel(); + } +} + WebpageProcessor::WebpageProcessor( not_null history, not_null field) : _history(history) -, _api(&history->session().mtp()) +, _resolver(std::make_shared(&history->session())) , _parser(field) , _timer([=] { if (!ShowWebPagePreview(_data) || _link.isEmpty()) { return; } - request(); + _resolver->request(_link); }) { _history->session().downloaderTaskFinished( ) | rpl::filter([=] { @@ -141,6 +212,23 @@ WebpageProcessor::WebpageProcessor( _parsedLinks = std::move(parsed); checkPreview(); }, _lifetime); + + _resolver->resolved() | rpl::start_with_next([=](QString link) { + if (_link != link + || _draft.removed + || (_draft.manual && _draft.url != link)) { + return; + } + _data = _resolver->lookup(link).value_or(nullptr); + if (_data) { + _draft.id = _data->id; + _draft.url = _data->url; + updateFromData(); + } else { + _links = QStringList(); + checkPreview(); + } + }, _lifetime); } rpl::producer<> WebpageProcessor::repaintRequests() const { @@ -151,8 +239,20 @@ Data::WebPageDraft WebpageProcessor::draft() const { return _draft; } +std::shared_ptr WebpageProcessor::resolver() const { + return _resolver; +} + +const std::vector &WebpageProcessor::links() const { + return _parser.ranges(); +} + +QString WebpageProcessor::link() const { + return _link; +} + void WebpageProcessor::apply(Data::WebPageDraft draft, bool reparse) { - _api.request(base::take(_requestId)).cancel(); + const auto was = _link; if (draft.removed) { _draft = draft; if (_parsedLinks.empty()) { @@ -173,14 +273,21 @@ void WebpageProcessor::apply(Data::WebPageDraft draft, bool reparse) { : nullptr; if (page && page->url == draft.url) { _data = page; + if (const auto link = _resolver->find(page); !link.isEmpty()) { + _link = link; + } updateFromData(); } else { - request(); + _resolver->request(_link); + return; } } else if (!draft.manual && !_draft.manual) { _draft = draft; checkNow(reparse); } + if (_link != was) { + _resolver->cancel(was); + } } void WebpageProcessor::updateFromData() { @@ -212,56 +319,6 @@ void WebpageProcessor::updateFromData() { _repaintRequests.fire({}); } -void WebpageProcessor::request() { - const auto link = _link; - const auto done = [=](const MTPDmessageMediaWebPage &data) { - const auto page = _history->owner().processWebpage(data.vwebpage()); - if (page->pendingTill > 0 - && page->pendingTill < base::unixtime::now()) { - page->pendingTill = 0; - page->failed = true; - } - _cache.emplace(link, page->failed ? nullptr : page.get()); - if (_link == link - && !_draft.removed - && (!_draft.manual || _draft.url == link)) { - _data = (page->id && !page->failed) - ? page.get() - : nullptr; - _draft.id = page->id; - _draft.url = page->url; - updateFromData(); - } - }; - const auto fail = [=] { - _cache.emplace(link, nullptr); - if (_link == link && !_draft.removed && !_draft.manual) { - _links = QStringList(); - checkPreview(); - } - }; - _requestId = _api.request( - MTPmessages_GetWebPagePreview( - MTP_flags(0), - MTP_string(_link), - MTPVector() - )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) { - if (_requestId == requestId) { - _requestId = 0; - } - result.match([=](const MTPDmessageMediaWebPage &data) { - done(data); - }, [&](const auto &d) { - fail(); - }); - }).fail([=](const MTP::Error &error, mtpRequestId requestId) { - if (_requestId == requestId) { - _requestId = 0; - } - fail(); - }).send(); -} - void WebpageProcessor::setDisabled(bool disabled) { _parser.setDisabled(disabled); if (disabled) { @@ -307,25 +364,21 @@ void WebpageProcessor::checkPreview() { auto page = (WebPageData*)nullptr; auto chosen = QString(); for (const auto &link : _links) { - const auto i = _cache.find(link); - if (i == end(_cache)) { + const auto value = _resolver->lookup(link); + if (!value) { chosen = link; break; - } else if (i->second) { - if (i->second->failed) { - i->second = nullptr; - } else { - chosen = link; - page = i->second; - break; - } + } else if (*value) { + chosen = link; + page = *value; + break; } } if (_link != chosen) { + _resolver->cancel(_link); _link = chosen; - _api.request(base::take(_requestId)).cancel(); if (!page && !_link.isEmpty()) { - request(); + _resolver->request(_link); } } if (page) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h index 2c356eedce..f27d1ad6cd 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h @@ -7,13 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "base/weak_ptr.h" #include "data/data_drafts.h" #include "chat_helpers/message_field.h" #include "mtproto/sender.h" class History; +namespace Main { +class Session; +} // namespace Main + namespace Ui { class InputField; } // namespace Ui @@ -47,7 +50,33 @@ struct WebpageParsed { } }; -class WebpageProcessor final : public base::has_weak_ptr { +class WebpageResolver final { +public: + explicit WebpageResolver(not_null session); + + [[nodiscard]] std::optional lookup( + const QString &link) const; + [[nodiscard]] rpl::producer resolved() const { + return _resolved.events(); + } + + [[nodiscard]] QString find(not_null page) const; + + void request(const QString &link); + void cancel(const QString &link); + +private: + const not_null _session; + MTP::Sender _api; + base::flat_map _cache; + rpl::event_stream _resolved; + + QString _requestLink; + mtpRequestId _requestId = 0; + +}; + +class WebpageProcessor final { public: WebpageProcessor( not_null history, @@ -63,6 +92,9 @@ public: // unless preview was removed in the draft or manual. void apply(Data::WebPageDraft draft, bool reparse = true); [[nodiscard]] Data::WebPageDraft draft() const; + [[nodiscard]] std::shared_ptr resolver() const; + [[nodiscard]] const std::vector &links() const; + [[nodiscard]] QString link() const; [[nodiscard]] rpl::producer<> repaintRequests() const; [[nodiscard]] rpl::producer parsedValue() const; @@ -74,21 +106,17 @@ public: private: void updateFromData(); void checkPreview(); - void request(); const not_null _history; - MTP::Sender _api; + const std::shared_ptr _resolver; MessageLinksParser _parser; QStringList _parsedLinks; QStringList _links; QString _link; WebPageData *_data = nullptr; - base::flat_map _cache; Data::WebPageDraft _draft; - mtpRequestId _requestId = 0; - rpl::event_stream<> _repaintRequests; rpl::variable _parsed; diff --git a/Telegram/SourceFiles/history/view/history_view_cursor_state.h b/Telegram/SourceFiles/history/view/history_view_cursor_state.h index 80cec91a9b..82fcda9547 100644 --- a/Telegram/SourceFiles/history/view/history_view_cursor_state.h +++ b/Telegram/SourceFiles/history/view/history_view_cursor_state.h @@ -49,6 +49,7 @@ struct TextState { FullMsgId itemId; CursorState cursor = CursorState::None; ClickHandlerPtr link; + bool overMessageText = false; bool afterSymbol = false; bool customTooltip = false; uint16 symbol = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index c35d18d7a2..042cfb21bb 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2195,6 +2195,7 @@ TextState Message::textState( if (_invertMedia) { result.symbol += visibleMediaTextLength(); } + result.overMessageText = true; checkBottomInfoState(); return result; } else if (point.y() >= trect.y() + trect.height()) { From cc8408d11c0de9e252476d88a444936786a1729b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Oct 2023 18:44:49 +0400 Subject: [PATCH 67/99] Fix draft options edit, add to topics/replies. --- .../SourceFiles/history/history_widget.cpp | 10 +- .../history_view_compose_controls.cpp | 105 +++++++++++------- .../controls/history_view_compose_controls.h | 1 + .../controls/history_view_draft_options.cpp | 8 +- .../view/history_view_context_menu.cpp | 12 +- .../history/view/history_view_context_menu.h | 1 + .../history/view/history_view_element.cpp | 2 +- .../history/view/history_view_list_widget.cpp | 4 + .../history/view/history_view_message.cpp | 99 ++++++++++------- Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- 10 files changed, 147 insertions(+), 97 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 7f2f9077af..a916c48921 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6246,6 +6246,11 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { return; } else if (_previewDrawPreview) { editDraftOptions(); + } else if (_editMsgId) { + controller()->showPeerHistory( + _peer, + Window::SectionShow::Way::Forward, + _editMsgId); } else if (isReadyToForward) { if (e->button() != Qt::LeftButton) { _forwardPanel->editToNextOption(); @@ -6259,11 +6264,6 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { _kbReplyTo->history()->peer->id, Window::SectionShow::Way::Forward, _kbReplyTo->id); - } else if (_editMsgId) { - controller()->showPeerHistory( - _peer, - Window::SectionShow::Way::Forward, - _editMsgId); } } 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 c203735c8f..a54b671921 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -147,6 +147,7 @@ public: [[nodiscard]] rpl::producer editMsgIdValue() const; [[nodiscard]] rpl::producer scrollToItemRequests() const; [[nodiscard]] rpl::producer<> editPhotoRequests() const; + [[nodiscard]] rpl::producer<> editOptionsRequests() const; [[nodiscard]] MessageToEdit queryToEdit(); [[nodiscard]] FullReplyTo getDraftReply() const; @@ -221,6 +222,7 @@ private: rpl::event_stream _visibleChanged; rpl::event_stream _scrollToItemRequests; + rpl::event_stream<> _editOptionsRequests; rpl::event_stream<> _editPhotoRequests; }; @@ -333,7 +335,8 @@ void FieldHeader::init() { return (ranges::contains(kMouseEvents, type) || leaving) && (isEditingMessage() || readyToForward() - || replyingToMessage()); + || replyingToMessage() + || _preview.parsed); }) | rpl::start_with_next([=](not_null event) { const auto updateOver = [&](bool inClickable, bool inPhotoEdit) { if (_inClickable != inClickable) { @@ -377,40 +380,14 @@ void FieldHeader::init() { _editPhotoRequests.fire({}); } else if (isLeftButton && inPreviewRect) { const auto reply = replyingToMessage(); - if (!isEditingMessage() && readyToForward()) { + if (_preview.parsed) { + _editOptionsRequests.fire({}); + } else if (isEditingMessage()) { + _scrollToItemRequests.fire(_editMsgId.current()); + } else if (readyToForward()) { _forwardPanel->editOptions(_show); - } else if (!isEditingMessage() && reply) { - //using namespace Controls; - //const auto highlight = [=] { - // _scrollToItemRequests.fire_copy(reply.messageId); - //}; - //const auto history = _history; - //const auto topicRootId = _topicRootId; - //const auto done = [=]( - // FullReplyTo replyTo, - // Data::WebPageDraft webpage) { - // if (replyTo) { - // replyToMessage(replyTo); - // } else { - // _replyCancelled.fire({}); - // } - - //}; - //const auto clearOldReplyTo = [=, id = reply.messageId] { - // ClearDraftReplyTo(history, topicRootId, id); - //}; - //EditDraftOptions( - // _show, - // _history, - // Data::Draft( reply, - // done, - // highlight, - // clearOldReplyTo); - } else { - auto id = isEditingMessage() - ? _editMsgId.current() - : replyingToMessage().messageId; - _scrollToItemRequests.fire(std::move(id)); + } else if (reply) { + _editOptionsRequests.fire({}); } } } else if (type == QEvent::MouseButtonRelease) { @@ -429,9 +406,12 @@ void FieldHeader::updateShownMessageText() { .session = &_data->session(), .customEmojiRepaint = [=] { customEmojiRepaint(); }, }; + const auto reply = replyingToMessage(); _shownMessageText.setMarkedText( st::messageTextStyle, - _shownMessage->inReplyText(), + ((isEditingMessage() || reply.quote.empty()) + ? _shownMessage->inReplyText() + : reply.quote), Ui::DialogTextOptions(), context); } @@ -779,6 +759,10 @@ rpl::producer<> FieldHeader::editPhotoRequests() const { return _editPhotoRequests.events(); } +rpl::producer<> FieldHeader::editOptionsRequests() const { + return _editOptionsRequests.events(); +} + MessageToEdit FieldHeader::queryToEdit() { const auto item = _data->message(_editMsgId.current()); if (!isEditingMessage() || !item) { @@ -1372,6 +1356,41 @@ void ComposeControls::init() { crl::guard(_wrap.get(), [=] { cancelEditMessage(); })); }, _wrap->lifetime()); + _header->editOptionsRequests( + ) | rpl::start_with_next([=] { + const auto history = _history; + const auto reply = _header->replyingToMessage(); + const auto webpage = _preview->draft(); + + const auto done = [=]( + FullReplyTo replyTo, + Data::WebPageDraft webpage) { + if (replyTo) { + replyToMessage(replyTo); + } else { + cancelReplyMessage(); + } + _preview->apply(webpage); + }; + const auto replyToId = reply.messageId; + const auto highlight = crl::guard(_wrap.get(), [=] { + _scrollToItemRequests.fire_copy(replyToId); + }); + + using namespace HistoryView::Controls; + EditDraftOptions({ + .show = _show, + .history = history, + .draft = Data::Draft(_field, reply, _preview->draft()), + .usedLink = _preview->link(), + .links = _preview->links(), + .resolver = _preview->resolver(), + .done = done, + .highlight = highlight, + .clearOldDraft = [=] { ClearDraftReplyTo(history, replyToId); }, + }); + }, _wrap->lifetime()); + _header->previewCancelled( ) | rpl::start_with_next([=] { if (_preview) { @@ -2882,13 +2901,15 @@ Data::WebPageDraft ComposeControls::webPageDraft() const { } rpl::producer ComposeControls::scrollRequests() const { - return _header->scrollToItemRequests( - ) | rpl::map([=](FullMsgId id) -> Data::MessagePosition { - if (const auto item = session().data().message(id)) { - return item->position(); - } - return {}; - }); + 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 {}; + }); } 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 d6a132af26..18a76132ef 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -357,6 +357,7 @@ private: const std::unique_ptr _wrap; const std::unique_ptr _writeRestricted; + rpl::event_stream _scrollToItemRequests; 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 bee32e72b5..8d01071578 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -247,10 +247,9 @@ PreviewWrap::~PreviewWrap() { rpl::producer PreviewWrap::showQuoteSelector( not_null item, const TextWithEntities "e) { - auto element = item->createView(_delegate.get()); - _selection.reset(element->selectionFromQuote(quote)); - _element = std::move(element); + _selection.reset(TextSelection()); + _element = item->createView(_delegate.get()); _link = _pressedLink = nullptr; if (const auto was = base::take(_draftItem)) { @@ -266,6 +265,7 @@ rpl::producer PreviewWrap::showQuoteSelector( initElement(); + _selection = _element->selectionFromQuote(quote); return _selection.value( ) | rpl::map([=](TextSelection selection) { return _element->selectedQuote(selection); @@ -596,7 +596,7 @@ auto PreviewDelegate::elementPathShiftGradient() } Context PreviewDelegate::elementContext() { - return Context::History; + return Context::Replies; } void AddFilledSkip(not_null container) { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 958611083d..8bad63c021 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -605,13 +605,18 @@ bool AddReplyToMessageAction( return false; } + const auto "e = request.quote; + auto text = quote.empty() + ? tr::lng_context_reply_msg(tr::now) + : tr::lng_context_quote_and_reply(tr::now); + text.replace('&', u"&&"_q); const auto owner = &item->history()->owner(); const auto itemId = item->fullId(); - menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { + menu->addAction(text, [=] { if (!item) { return; } else { - list->replyToMessageRequestNotify({ itemId }); + list->replyToMessageRequestNotify({ itemId, quote }); } }, &st::menuIconReply); return true; @@ -936,7 +941,6 @@ void AddTopMessageActions( not_null menu, const ContextMenuRequest &request, not_null list) { - AddReplyToMessageAction(menu, request, list); AddGoToMessageAction(menu, request, list); AddViewRepliesAction(menu, request, list); AddEditMessageAction(menu, request, list); @@ -1008,6 +1012,8 @@ base::unique_qptr FillContextMenu( list, st::popupMenuWithIcons); + AddReplyToMessageAction(result, request, list); + if (request.overSelection && !list->hasCopyRestrictionForSelected() && !list->getSelectedText().empty()) { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index d534d9c248..64040ae413 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -47,6 +47,7 @@ struct ContextMenuRequest { HistoryItem *item = nullptr; SelectedItems selectedItems; TextForMimeData selectedText; + TextWithEntities quote; 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 1f3acc5433..61900f20fa 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -802,7 +802,7 @@ auto Element::contextDependentServiceText() -> TextWithLinks { if (!info) { return {}; } - if (_delegate->elementContext() == Context::Replies) { + if (_context == Context::Replies) { if (info->created()) { return { { tr::lng_action_topic_created_inside(tr::now) } }; } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index e3e979285e..2938fffdd3 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2576,6 +2576,7 @@ 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() @@ -2602,6 +2603,9 @@ 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(); request.selectedText = _selectedText; request.selectedItems = collectSelectedItems(); const auto hasSelection = !request.selectedItems.empty() diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 042cfb21bb..f215e2bdd6 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -94,7 +94,7 @@ const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; const auto i = ranges::find(left, cut); if (i != left.end()) { left.erase(i); - } else if (!ranges::contains(allowed, cut.type())) { + } else if (ranges::contains(allowed, cut.type())) { return false; } } @@ -2615,7 +2615,8 @@ TextForMimeData Message::selectedText(TextSelection selection) const { const auto media = this->media(); auto logEntryOriginalResult = TextForMimeData(); const auto mediaDisplayed = (media && media->isDisplayed()); - const auto textSelection = (mediaDisplayed && invertMedia()) + const auto mediaBefore = mediaDisplayed && invertMedia(); + const auto textSelection = mediaBefore ? media->skipSelection(selection) : selection; const auto mediaSelection = !invertMedia() @@ -2628,19 +2629,15 @@ TextForMimeData Message::selectedText(TextSelection selection) const { ? media->selectedText(mediaSelection) : TextForMimeData(); if (auto entry = logEntryOriginal()) { - const auto originalSelection = (mediaDisplayed && invertMedia()) + const auto originalSelection = mediaBefore ? skipTextSelection(textSelection) : mediaDisplayed ? media->skipSelection(mediaSelection) : skipTextSelection(selection); logEntryOriginalResult = entry->selectedText(originalSelection); } - auto &first = (mediaDisplayed && invertMedia()) - ? mediaResult - : textResult; - auto &second = (mediaDisplayed && invertMedia()) - ? textResult - : mediaResult; + auto &first = mediaBefore ? mediaResult : textResult; + auto &second = mediaBefore ? textResult : mediaResult; auto result = first; if (result.empty()) { result = std::move(second); @@ -2664,7 +2661,13 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const { || selection == FullSelection) { return {}; } else if (hasVisibleText()) { - return selectedQuote(text(), selection); + const auto media = this->media(); + const auto mediaDisplayed = media && media->isDisplayed(); + const auto mediaBefore = mediaDisplayed && invertMedia(); + const auto textSelection = mediaBefore + ? media->skipSelection(selection) + : selection; + return selectedQuote(text(), textSelection); } else if (const auto media = this->media()) { if (media->isDisplayed() || isHiddenByGroup()) { return media->selectedQuote(selection); @@ -2737,7 +2740,11 @@ TextSelection Message::selectionFromQuote( if (&translated != &original || quote.empty()) { return {}; } else if (hasVisibleText()) { - return selectionFromQuote(text(), quote); + const auto media = this->media(); + const auto mediaDisplayed = media && media->isDisplayed(); + const auto mediaBefore = mediaDisplayed && invertMedia(); + const auto result = selectionFromQuote(text(), quote); + return mediaBefore ? media->unskipSelection(result) : result; } else if (const auto media = this->media()) { if (media->isDisplayed() || isHiddenByGroup()) { return media->selectionFromQuote(quote); @@ -2793,43 +2800,53 @@ TextSelection Message::adjustSelection( TextSelection selection, TextSelectType type) const { const auto media = this->media(); - - auto result = hasVisibleText() - ? text().adjustSelection(selection, type) + const auto mediaDisplayed = media && media->isDisplayed(); + const auto mediaBefore = mediaDisplayed && invertMedia(); + const auto textSelection = mediaBefore + ? media->skipSelection(selection) : selection; - auto beforeMediaLength = visibleTextLength(); - if (selection.to <= beforeMediaLength) { - return result; - } - auto mediaDisplayed = media && media->isDisplayed(); + auto textAdjusted = hasVisibleText() + ? text().adjustSelection(textSelection, type) + : textSelection; + auto textResult = mediaBefore + ? media->unskipSelection(textAdjusted) + : textAdjusted; + auto mediaResult = TextSelection(); + auto mediaSelection = mediaBefore + ? selection + : skipTextSelection(selection); if (mediaDisplayed) { - auto mediaSelection = unskipTextSelection( - media->adjustSelection(skipTextSelection(selection), type)); - if (selection.from >= beforeMediaLength) { - result = mediaSelection; - } else { - result.to = mediaSelection.to; - } - } - auto beforeEntryLength = beforeMediaLength + visibleMediaTextLength(); - if (selection.to <= beforeEntryLength) { - return result; + auto mediaAdjusted = media->adjustSelection(mediaSelection, type); + mediaResult = mediaBefore + ? mediaAdjusted + : unskipTextSelection(mediaAdjusted); } + auto entryResult = TextSelection(); if (const auto entry = logEntryOriginal()) { - auto entrySelection = mediaDisplayed - ? media->skipSelection(skipTextSelection(selection)) - : skipTextSelection(selection); - auto logEntryOriginalSelection = entry->adjustSelection(entrySelection, type); + auto entrySelection = !mediaDisplayed + ? skipTextSelection(selection) + : mediaBefore + ? skipTextSelection(textSelection) + : media->skipSelection(mediaSelection); + auto entryAdjusted = entry->adjustSelection(entrySelection, type); + entryResult = unskipTextSelection(entryAdjusted); if (mediaDisplayed) { - logEntryOriginalSelection = media->unskipSelection(logEntryOriginalSelection); - } - logEntryOriginalSelection = unskipTextSelection(logEntryOriginalSelection); - if (selection.from >= beforeEntryLength) { - result = logEntryOriginalSelection; - } else { - result.to = logEntryOriginalSelection.to; + entryResult = media->unskipSelection(entryResult); } } + auto result = textResult; + if (!mediaResult.empty()) { + result = result.empty() ? mediaResult : TextSelection{ + std::min(result.from, mediaResult.from), + std::max(result.to, mediaResult.to), + }; + } + if (!entryResult.empty()) { + result = result.empty() ? entryResult : TextSelection{ + std::min(result.from, entryResult.from), + std::max(result.to, entryResult.to), + }; + } return result; } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 8a0ff53bfd..41afd10252 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1264,7 +1264,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#6eebcabd flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector = MessageReplyHeader; +messageReplyHeader#6eebcabd flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector = MessageReplyHeader; messageReplyStoryHeader#9c98bfc1 user_id:long story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; From 5d335341ab50b8bf5e460075244c2c1caf8947ab Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Oct 2023 23:40:53 +0400 Subject: [PATCH 68/99] Support server-side colors by index, up to three. --- .../boxes/background_preview_box.cpp | 3 +- .../boxes/reactions_settings_box.cpp | 3 +- Telegram/SourceFiles/data/data_peer.cpp | 4 +- .../history/history_item_components.cpp | 13 +- .../controls/history_view_draft_options.cpp | 3 +- .../history/view/history_view_view_button.cpp | 7 +- .../history/view/media/history_view_game.cpp | 3 +- .../view/media/history_view_giveaway.cpp | 6 +- .../view/media/history_view_web_page.cpp | 3 +- Telegram/SourceFiles/main/main_app_config.cpp | 146 +++++++++- Telegram/SourceFiles/main/main_app_config.h | 18 ++ Telegram/SourceFiles/main/main_session.cpp | 5 + Telegram/SourceFiles/main/main_session.h | 9 + .../media/stories/media_stories_reactions.cpp | 2 +- .../settings/settings_privacy_controllers.cpp | 4 +- .../support/support_autocomplete.cpp | 3 +- Telegram/SourceFiles/ui/chat/chat.style | 1 + Telegram/SourceFiles/ui/chat/chat_style.cpp | 274 ++++++++++-------- Telegram/SourceFiles/ui/chat/chat_style.h | 47 ++- .../window/window_session_controller.cpp | 2 +- Telegram/lib_ui | 2 +- 21 files changed, 391 insertions(+), 167 deletions(-) diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index c83af953d3..072dc34fb0 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -175,7 +175,8 @@ BackgroundPreviewBox::BackgroundPreviewBox( , _controller(controller) , _forPeer(args.forPeer) , _fromMessageId(args.fromMessageId) -, _chatStyle(std::make_unique()) +, _chatStyle(std::make_unique( + controller->session().colorIndicesValue())) , _serviceHistory(_controller->session().data().history( PeerData::kServiceNotificationsId)) , _service(nullptr) diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index 952aa0c49c..2bbb1b4a75 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -133,7 +133,8 @@ void AddMessage( state->delegate = std::make_unique( controller, crl::guard(widget, [=] { widget->update(); })); - state->style = std::make_unique(); + state->style = std::make_unique( + controller->session().colorIndicesValue()); state->style->apply(controller->defaultChatTheme().get()); state->icons.lifetimes = std::vector(2); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 780b6c5711..4c03f5d4d3 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -889,7 +889,7 @@ bool PeerData::changeColorIndex(uint8 index) { if (_colorIndexCloud && _colorIndex == index) { return false; } - _colorIndexCloud = true; + _colorIndexCloud = 1; _colorIndex = index; return true; } @@ -898,7 +898,7 @@ bool PeerData::clearColorIndex() { if (!_colorIndexCloud) { return false; } - _colorIndexCloud = false; + _colorIndexCloud = 0; _colorIndex = Data::DecideColorIndex(id); return true; } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 1528269e73..5cf6ecb4c2 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -848,19 +848,20 @@ void HistoryMessageReply::paint( ? (resolvedMessage->hiddenSenderInfo()->colorIndex + 1) : 0; const auto useColorIndex = colorIndexPlusOne && !context.outbg; - const auto twoColored = colorIndexPlusOne - && Ui::ColorIndexTwoColored(colorIndexPlusOne - 1); + const auto colorPattern = colorIndexPlusOne + ? st->colorPatternIndex(colorIndexPlusOne - 1) + : 0; const auto cache = !inBubble ? (hasQuote - ? st->serviceQuoteCache(twoColored) - : st->serviceReplyCache(twoColored)).get() + ? st->serviceQuoteCache(colorPattern) + : st->serviceReplyCache(colorPattern)).get() : useColorIndex ? (hasQuote ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() : (hasQuote - ? (twoColored ? stm->quoteCacheTwo : stm->quoteCache) - : (twoColored ? stm->replyCacheTwo : stm->replyCache)).get(); + ? stm->quoteCache[colorPattern] + : stm->replyCache[colorPattern]).get(); const auto "eSt = hasQuote ? st::messageTextStyle.blockquote : st::messageQuoteStyle; 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 8d01071578..0b2fad3d74 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -196,7 +196,8 @@ PreviewWrap::PreviewWrap( , _box(box) , _history(history) , _theme(DefaultThemeOn(lifetime())) -, _style(std::make_unique()) +, _style(std::make_unique( + history->session().colorIndicesValue())) , _delegate(std::make_unique( box, _style.get(), diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index afffc0b2b4..3d91427612 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -232,13 +232,12 @@ void ViewButton::draw( Painter &p, const QRect &r, const Ui::ChatPaintContext &context) { + const auto st = context.st; const auto stm = context.messageStyle(); - const auto selected = context.selected(); - const auto twoColored = Ui::ColorIndexTwoColored(_inner->colorIndex); const auto cache = context.outbg - ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() - : context.st->coloredReplyCache(selected, _inner->colorIndex).get(); + ? stm->replyCache[st->colorPatternIndex(_inner->colorIndex)].get() + : st->coloredReplyCache(selected, _inner->colorIndex).get(); const auto radius = st::historyPagePreview.radius; if (_inner->ripple && !_inner->ripple->empty()) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 9f09e28db9..a91f8350d0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -220,9 +220,8 @@ void Game::draw(Painter &p, const PaintContext &context) const { const auto colorIndex = parent()->colorIndex(); const auto selected = context.selected(); - const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg - ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() + ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() : st->coloredReplyCache(selected, colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 9079ac6e01..311e9bd882 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -344,13 +344,13 @@ void Giveaway::paintChannels( const auto size = _channels[0].geometry.height(); const auto ratio = style::DevicePixelRatio(); + const auto st = context.st; const auto stm = context.messageStyle(); const auto selected = context.selected(); const auto colorIndex = parent()->colorIndex(); - const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg - ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() - : context.st->coloredReplyCache(selected, colorIndex).get(); + ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() + : st->coloredReplyCache(selected, colorIndex).get(); if (_channelCorners[0].isNull() || _channelBg != cache->bg) { _channelBg = cache->bg; _channelCorners = Images::CornersMask(size / 2); 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 7e9ff2b351..f91e7ad553 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -523,9 +523,8 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { const auto selected = context.selected(); const auto colorIndex = parent()->colorIndex(); - const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg - ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() + ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() : st->coloredReplyCache(selected, colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index b2556600ba..3b4b788a11 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "main/main_app_config.h" -#include "main/main_account.h" -#include "base/call_delayed.h" #include "apiwrap.h" +#include "base/call_delayed.h" +#include "main/main_account.h" +#include "ui/chat/chat_style.h" namespace Main { namespace { @@ -27,6 +28,8 @@ AppConfig::AppConfig(not_null account) : _account(account) { }, _lifetime); } +AppConfig::~AppConfig() = default; + void AppConfig::start() { _account->mtpMainSessionValue( ) | rpl::start_with_next([=](not_null instance) { @@ -58,6 +61,7 @@ void AppConfig::refresh() { _data.emplace_or_assign(qs(data.vkey()), data.vvalue()); }); } + parseColorIndices(); DEBUG_LOG(("getAppConfig result handled.")); _refreshed.fire({}); }, [](const MTPDhelp_appConfigNotModified &) {}); @@ -171,6 +175,27 @@ std::vector> AppConfig::getStringMapArray( }); } +std::vector AppConfig::getIntArray( + const QString &key, + std::vector &&fallback) const { + return getValue(key, [&](const MTPJSONValue &value) { + return value.match([&](const MTPDjsonArray &data) { + auto result = std::vector(); + result.reserve(data.vvalue().v.size()); + for (const auto &entry : data.vvalue().v) { + if (entry.type() != mtpc_jsonNumber) { + return std::move(fallback); + } + result.push_back( + int(base::SafeRound(entry.c_jsonNumber().vvalue().v))); + } + return result; + }, [&](const auto &data) { + return std::move(fallback); + }); + }); +} + bool AppConfig::suggestionCurrent(const QString &key) const { return !_dismissedSuggestions.contains(key) && ranges::contains( @@ -199,4 +224,121 @@ void AppConfig::dismissSuggestion(const QString &key) { )).send(); } +void AppConfig::parseColorIndices() { + constexpr auto parseColor = [](const MTPJSONValue &color) { + if (color.type() != mtpc_jsonString) { + LOG(("API Error: Bad type for color element.")); + return uint32(); + } + const auto value = color.c_jsonString().vvalue().v; + if (value.size() != 6) { + LOG(("API Error: Bad length for color element: %1" + ).arg(qs(value))); + return uint32(); + } + const auto hex = [](char ch) { + return (ch >= 'a' && ch <= 'f') + ? (ch - 'a' + 10) + : (ch >= 'A' && ch <= 'F') + ? (ch - 'A' + 10) + : (ch >= '0' && ch <= '9') + ? (ch - '0') + : 0; + }; + auto result = (uint32(1) << 24); + for (auto i = 0; i != 6; ++i) { + result |= (uint32(hex(value[i])) << ((5 - i) * 4)); + } + return result; + }; + + struct ParsedColor { + uint8 colorIndex = Ui::kColorIndexCount; + std::array colors; + + explicit operator bool() const { + return colorIndex < Ui::kColorIndexCount; + } + }; + constexpr auto parseColors = [](const MTPJSONObjectValue &element) { + const auto &data = element.data(); + if (data.vvalue().type() != mtpc_jsonArray) { + LOG(("API Error: Bad value for peer_colors element.")); + return ParsedColor(); + } + const auto &list = data.vvalue().c_jsonArray().vvalue().v; + if (list.empty() || list.size() > Ui::kColorPatternsCount) { + LOG(("API Error: Bad count for peer_colors element: %1" + ).arg(list.size())); + return ParsedColor(); + } + const auto index = data.vkey().v.toInt(); + if (index < Ui::kSimpleColorIndexCount + || index >= Ui::kColorIndexCount) { + LOG(("API Error: Bad index for peer_colors element: %1" + ).arg(qs(data.vkey().v))); + return ParsedColor(); + } + auto result = ParsedColor{ .colorIndex = uint8(index) }; + auto fill = result.colors.data(); + for (const auto &color : list) { + *fill++ = parseColor(color); + } + return result; + }; + constexpr auto checkColorsObjectType = [](const MTPJSONValue &value) { + if (value.type() != mtpc_jsonObject) { + if (value.type() != mtpc_jsonArray + || !value.c_jsonArray().vvalue().v.empty()) { + LOG(("API Error: Bad value for [dark_]peer_colors.")); + } + return false; + } + return true; + }; + + auto colors = std::make_shared< + std::array>(); + getValue(u"peer_colors"_q, [&](const MTPJSONValue &value) { + if (!checkColorsObjectType(value)) { + return; + } + for (const auto &element : value.c_jsonObject().vvalue().v) { + if (const auto parsed = parseColors(element)) { + auto &fields = (*colors)[parsed.colorIndex]; + fields.dark = fields.light = parsed.colors; + } + } + }); + getValue(u"dark_peer_colors"_q, [&](const MTPJSONValue &value) { + if (!checkColorsObjectType(value)) { + return; + } + for (const auto &element : value.c_jsonObject().vvalue().v) { + if (const auto parsed = parseColors(element)) { + (*colors)[parsed.colorIndex].dark = parsed.colors; + } + } + }); + + if (!_colorIndicesCurrent) { + _colorIndicesCurrent = std::make_unique( + Ui::ColorIndicesCompressed{ std::move(colors) }); + _colorIndicesChanged.fire({}); + } else if (*_colorIndicesCurrent->colors != *colors) { + _colorIndicesCurrent->colors = std::move(colors); + _colorIndicesChanged.fire({}); + } +} + +auto AppConfig::colorIndicesValue() const +-> rpl::producer { + return rpl::single(_colorIndicesCurrent + ? *_colorIndicesCurrent + : Ui::ColorIndicesCompressed() + ) | rpl::then(_colorIndicesChanged.events() | rpl::map([=] { + return *_colorIndicesCurrent; + })); +} + } // namespace Main diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index b0273b8dec..04c00dcfe5 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" #include "base/algorithm.h" +namespace Ui { +struct ColorIndicesCompressed; +} // namespace Ui + namespace Main { class Account; @@ -17,6 +21,7 @@ class Account; class AppConfig final { public: explicit AppConfig(not_null account); + ~AppConfig(); void start(); @@ -30,6 +35,8 @@ public: return getString(key, fallback); } else if constexpr (std::is_same_v>) { return getStringArray(key, std::move(fallback)); + } else if constexpr (std::is_same_v>) { + return getIntArray(key, std::move(fallback)); } else if constexpr (std::is_same_v< Type, std::vector>>) { @@ -47,10 +54,14 @@ public: const QString &key) const; void dismissSuggestion(const QString &key); + [[nodiscard]] auto colorIndicesValue() const + -> rpl::producer; + void refresh(); private: void refreshDelayed(); + void parseColorIndices(); template [[nodiscard]] auto getValue( @@ -72,6 +83,9 @@ private: [[nodiscard]] std::vector> getStringMapArray( const QString &key, std::vector> &&fallback) const; + [[nodiscard]] std::vector getIntArray( + const QString &key, + std::vector &&fallback) const; const not_null _account; std::optional _api; @@ -80,6 +94,10 @@ private: base::flat_map _data; rpl::event_stream<> _refreshed; base::flat_set _dismissedSuggestions; + + rpl::event_stream<> _colorIndicesChanged; + std::unique_ptr _colorIndicesCurrent; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index a235f729f0..84f48106a2 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -476,4 +476,9 @@ Window::SessionController *Session::tryResolveWindow() const { return _windows.front(); } +auto Session::colorIndicesValue() const +-> rpl::producer { + return _account->appConfig().colorIndicesValue(); +} + } // namespace Main diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 05dbb9ce58..342c7ced62 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -57,6 +57,10 @@ namespace InlineBots { class AttachWebView; } // namespace InlineBots +namespace Ui { +struct ColorIndicesCompressed; +} // namespace Ui + namespace Main { class Account; @@ -187,6 +191,9 @@ public: [[nodiscard]] Support::Helper &supportHelper() const; [[nodiscard]] Support::Templates &supportTemplates() const; + [[nodiscard]] auto colorIndicesValue() const + -> rpl::producer; + private: static constexpr auto kDefaultSaveDelay = crl::time(1000); @@ -227,6 +234,8 @@ private: QByteArray _tmpPassword; TimeId _tmpPasswordValidUntil = 0; + rpl::event_stream _colorIndicesChanges; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 2d9e017d57..fd4a0ea8ac 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -157,7 +157,7 @@ ReactionView::ReactionView( const Data::SuggestedReaction &reaction) : RpWidget(parent) , _data(reaction) -, _chatStyle(std::make_unique()) +, _chatStyle(std::make_unique(session->colorIndicesValue())) , _pathGradient( std::make_unique( st::shadowFg, diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index f34691241e..aa1e37d09d 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -839,7 +839,9 @@ ForwardsPrivacyController::ForwardsPrivacyController( not_null controller) : SimpleElementDelegate(controller, [] {}) , _controller(controller) -, _chatStyle(std::make_unique()) { +, _chatStyle( + std::make_unique( + controller->session().colorIndicesValue())) { _chatStyle->apply(controller->defaultChatTheme().get()); } diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index aaf9fec54f..75d42760dd 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -506,7 +506,8 @@ ConfirmContactBox::ConfirmContactBox( const Contact &data, Fn submit) : SimpleElementDelegate(controller, [=] { update(); }) -, _chatStyle(std::make_unique()) +, _chatStyle(std::make_unique( + history->session().colorIndicesValue())) , _comment(GenerateCommentItem(this, history, data)) , _contact(GenerateContactItem(this, history, data)) , _submit(submit) { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 7050667ae3..fd5affd421 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -50,6 +50,7 @@ messageQuoteStyle: QuoteStyle(defaultQuoteStyle) { padding: margins(10px, 2px, 4px, 2px); verticalSkip: 4px; outline: 3px; + outlineShift: 2px; radius: 5px; } messageTextStyle: TextStyle(defaultTextStyle) { diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 1ca60e7a38..b0b23e1d33 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -39,8 +39,7 @@ void EnsureBlockquoteCache( cache = std::make_unique(); const auto &colors = values(); cache->bg = colors.bg; - cache->outline1 = colors.outline1; - cache->outline2 = colors.outline2; + cache->outlines = colors.outlines; cache->icon = colors.name; } @@ -55,15 +54,15 @@ void EnsurePreCache( const auto bg = bgOverride(); cache->bg = bg.value_or(color->c); if (!bg) { - cache->bg.setAlphaF(0.12); + cache->bg.setAlpha(0.12 * 255); } - cache->outline1 = color->c; - cache->outline1.setAlphaF(0.9); - cache->outline2 = cache->outline1; + cache->outlines[0] = color->c; + cache->outlines[0].setAlpha(0.9 * 255); + cache->outlines[1] = cache->outlines[2] = QColor(0, 0, 0, 0); cache->header = color->c; - cache->header.setAlphaF(0.25); - cache->icon = cache->outline1; - cache->icon.setAlphaF(0.6); + cache->header.setAlpha(0.25 * 255); + cache->icon = cache->outlines[0]; + cache->icon.setAlpha(0.6 * 255); } } // namespace @@ -80,9 +79,8 @@ not_null ChatPaintContext::quoteCache( uint8 colorIndex) const { return !outbg ? st->coloredQuoteCache(selected(), colorIndex).get() - : ColorIndexTwoColored(colorIndex) - ? messageStyle()->quoteCacheTwo.get() - : messageStyle()->quoteCache.get(); + : messageStyle()->quoteCache[ + st->colorPatternIndex(colorIndex)].get(); } int HistoryServiceMsgRadius() { @@ -110,85 +108,28 @@ int HistoryServiceMsgInvertedShrink() { return result; } -ColorIndexValues ComputeColorIndexValues( - not_null st, - bool selected, - uint8 colorIndex) { - if (colorIndex < kSimpleColorIndexCount) { - const style::color list[] = { - st->historyPeer1NameFg(), - st->historyPeer2NameFg(), - st->historyPeer3NameFg(), - st->historyPeer4NameFg(), - st->historyPeer5NameFg(), - st->historyPeer6NameFg(), - st->historyPeer7NameFg(), - st->historyPeer8NameFg(), - }; - const style::color listSelected[] = { - st->historyPeer1NameFgSelected(), - st->historyPeer2NameFgSelected(), - st->historyPeer3NameFgSelected(), - st->historyPeer4NameFgSelected(), - st->historyPeer5NameFgSelected(), - st->historyPeer6NameFgSelected(), - st->historyPeer7NameFgSelected(), - st->historyPeer8NameFgSelected(), - }; - const auto paletteIndex = ColorIndexToPaletteIndex(colorIndex); - auto result = ColorIndexValues{ - .name = (selected ? listSelected : list)[paletteIndex]->c, - }; - result.bg = result.name; - result.bg.setAlphaF(0.12); - result.outline1 = result.name; - result.outline1.setAlphaF(0.9); - result.outline2 = result.outline1; - return result; - } - struct Pair { - QColor outline1; - QColor outline2; - }; - const Pair list[] = { - { QColor(0xE1, 0x50, 0x52), QColor(0xF9, 0xAE, 0x63) }, // Red - { QColor(0xE0, 0x80, 0x2B), QColor(0xFA, 0xC5, 0x34) }, // Orange - { QColor(0xA0, 0x5F, 0xF3), QColor(0xF4, 0x8F, 0xFF) }, // Violet - { QColor(0x27, 0xA9, 0x10), QColor(0xA7, 0xDC, 0x57) }, // Green - { QColor(0x27, 0xAC, 0xCE), QColor(0x82, 0xE8, 0xD6) }, // Cyan - { QColor(0x33, 0x91, 0xD4), QColor(0x7D, 0xD3, 0xF0) }, // Blue - { QColor(0xD1, 0x48, 0x72), QColor(0xFF, 0xBE, 0xA0) }, // Pink - }; - const auto &pair = list[colorIndex - kSimpleColorIndexCount]; - auto bg = pair.outline1; - bg.setAlphaF(0.12); - return { - .name = st->dark() ? pair.outline2 : pair.outline1, - .bg = bg, - .outline1 = pair.outline1, - .outline2 = pair.outline2, - }; -} - -bool ColorIndexTwoColored(uint8 colorIndex) { - return (colorIndex >= kSimpleColorIndexCount); -} - -ColorIndexValues SimpleColorIndexValues(QColor color, bool twoColored) { +ColorIndexValues SimpleColorIndexValues(QColor color, int patternIndex) { auto bg = color; - bg.setAlphaF(0.12); - auto outline1 = color; - outline1.setAlphaF(0.9); - auto outline2 = outline1; - if (twoColored) { - outline2.setAlphaF(0.5); - } - return { + bg.setAlpha(0.12 * 255); + auto result = ColorIndexValues{ .name = color, .bg = bg, - .outline1 = outline1, - .outline2 = outline2, }; + result.outlines[0] = color; + result.outlines[0].setAlpha(0.9 * 255); + if (patternIndex > 1) { + result.outlines[1] = result.outlines[0]; + result.outlines[1].setAlpha(0.3 * 255); + result.outlines[2] = result.outlines[0]; + result.outlines[2].setAlpha(0.6 * 255); + } else if (patternIndex > 0) { + result.outlines[1] = result.outlines[0]; + result.outlines[1].setAlpha(0.5 * 255); + result.outlines[2] = QColor(0, 0, 0, 0); + } else { + result.outlines[1] = result.outlines[2] = QColor(0, 0, 0, 0); + } + return result; } int BackgroundEmojiData::CacheIndex( @@ -202,7 +143,15 @@ int BackgroundEmojiData::CacheIndex( return (base * 2) + (selected ? 1 : 0); }; -ChatStyle::ChatStyle() { +ChatStyle::ChatStyle(rpl::producer colorIndices) { + if (colorIndices) { + _colorIndicesLifetime = std::move( + colorIndices + ) | rpl::start_with_next([=](ColorIndicesCompressed &&indices) { + _colorIndices = std::move(indices); + }); + } + finalize(); make(_historyPsaForwardPalette, st::historyPsaForwardPalette); make(_imgReplyTextPalette, st::imgReplyTextPalette); @@ -561,7 +510,7 @@ ChatStyle::ChatStyle() { } ChatStyle::ChatStyle(not_null isolated) -: ChatStyle() { +: ChatStyle(rpl::producer()) { assignPalette(isolated); } @@ -631,17 +580,41 @@ std::span ChatStyle::highlightColors() const { return _highlightColors; } +void ChatStyle::clearColorIndexCaches() { + for (auto &style : _messageStyles) { + for (auto &cache : style.quoteCache) { + cache = nullptr; + } + for (auto &cache : style.replyCache) { + cache = nullptr; + } + } + for (auto &values : _coloredValues) { + values.reset(); + } + for (auto &palette : _coloredTextPalettes) { + palette.linkFg.reset(); + } + for (auto &cache : _coloredReplyCaches) { + cache = nullptr; + } + for (auto &cache : _coloredQuoteCaches) { + cache = nullptr; + } +} + void ChatStyle::assignPalette(not_null palette) { *static_cast(this) = *palette; style::internal::resetIcons(); + + clearColorIndexCaches(); for (auto &style : _messageStyles) { style.msgBgCornersSmall = {}; style.msgBgCornersLarge = {}; - style.quoteCache = nullptr; - style.quoteCacheTwo = nullptr; - style.replyCache = nullptr; - style.replyCacheTwo = nullptr; style.preCache = nullptr; + style.textPalette.linkAlwaysActive + = style.semiboldPalette.linkAlwaysActive + = (style.textPalette.linkFg->c == style.historyTextFg->c); } for (auto &style : _imageStyles) { style.msgDateImgBgCorners = {}; @@ -657,24 +630,6 @@ void ChatStyle::assignPalette(not_null palette) { for (auto &corners : _msgSelectOverlayCorners) { corners = {}; } - - for (auto &stm : _messageStyles) { - stm.textPalette.linkAlwaysActive - = stm.semiboldPalette.linkAlwaysActive - = (stm.textPalette.linkFg->c == stm.historyTextFg->c); - } - for (auto &values : _coloredValues) { - values.reset(); - } - for (auto &palette : _coloredTextPalettes) { - palette.linkFg.reset(); - } - for (auto &cache : _coloredReplyCaches) { - cache = nullptr; - } - for (auto &cache : _coloredQuoteCaches) { - cache = nullptr; - } updateDarkValue(); _paletteChanged.fire({}); @@ -710,19 +665,14 @@ const MessageStyle &ChatStyle::messageStyle(bool outbg, bool selected) const { result.msgBg, &result.msgShadow); const auto &replyBar = result.msgReplyBarColor->c; - EnsureBlockquoteCache( - result.replyCache, - [&] { return SimpleColorIndexValues(replyBar, false); }); - EnsureBlockquoteCache( - result.replyCacheTwo, - [&] { return SimpleColorIndexValues(replyBar, true); }); - if (!result.quoteCache) { - result.quoteCache = std::make_unique( - *result.replyCache); - } - if (!result.quoteCacheTwo) { - result.quoteCacheTwo = std::make_unique( - *result.replyCacheTwo); + for (auto i = 0; i != kColorPatternsCount; ++i) { + EnsureBlockquoteCache( + result.replyCache[i], + [&] { return SimpleColorIndexValues(replyBar, i); }); + if (!result.quoteCache[i]) { + result.quoteCache[i] = std::make_unique( + *result.replyCache[i]); + } } const auto preBgOverride = [&] { @@ -763,6 +713,80 @@ const MessageImageStyle &ChatStyle::imageStyle(bool selected) const { return result; } +int ChatStyle::colorPatternIndex(uint8 colorIndex) const { + Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); + + if (!_colorIndices.colors + || colorIndex < kSimpleColorIndexCount) { + return 0; + } + auto &data = (*_colorIndices.colors)[colorIndex]; + auto &colors = _dark ? data.dark : data.light; + return colors[2] ? 2 : colors[1] ? 1 : 0; +} + +ColorIndexValues ChatStyle::computeColorIndexValues( + bool selected, + uint8 colorIndex) const { + if (!_colorIndices.colors) { + colorIndex %= kSimpleColorIndexCount; + } + if (colorIndex < kSimpleColorIndexCount) { + const auto list = std::array{ + &historyPeer1NameFg(), + &historyPeer2NameFg(), + &historyPeer3NameFg(), + &historyPeer4NameFg(), + &historyPeer5NameFg(), + &historyPeer6NameFg(), + &historyPeer7NameFg(), + &historyPeer8NameFg(), + }; + const auto listSelected = std::array{ + &historyPeer1NameFgSelected(), + &historyPeer2NameFgSelected(), + &historyPeer3NameFgSelected(), + &historyPeer4NameFgSelected(), + &historyPeer5NameFgSelected(), + &historyPeer6NameFgSelected(), + &historyPeer7NameFgSelected(), + &historyPeer8NameFgSelected(), + }; + const auto paletteIndex = ColorIndexToPaletteIndex(colorIndex); + auto result = ColorIndexValues{ + .name = (*(selected ? listSelected : list)[paletteIndex])->c, + }; + result.bg = result.name; + result.bg.setAlphaF(0.12); + result.outlines[0] = result.name; + result.outlines[0].setAlphaF(0.9); + result.outlines[1] = result.outlines[2] = QColor(0, 0, 0, 0); + return result; + } + auto &data = (*_colorIndices.colors)[colorIndex]; + auto &colors = _dark ? data.dark : data.light; + if (!colors[0]) { + return computeColorIndexValues( + selected, + colorIndex % kSimpleColorIndexCount); + } + const auto color = [&](int index) { + const auto v = colors[index]; + return v + ? QColor((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF) + : QColor(0, 0, 0, 0); + }; + auto result = ColorIndexValues{ + .outlines = { color(0), color(1), color(2) } + }; + result.bg = result.outlines[0]; + result.bg.setAlpha(0.12 * 255); + result.name = (_dark && colorPatternIndex(colorIndex) == 1) + ? result.outlines[1] + : result.outlines[0]; + return result; +} + not_null ChatStyle::serviceQuoteCache( bool twoColored) const { const auto index = (twoColored ? 1 : 0); @@ -791,7 +815,7 @@ const ColorIndexValues &ChatStyle::coloredValues( const auto shift = (selected ? kColorIndexCount : 0); auto &result = _coloredValues[shift + colorIndex]; if (!result) { - result.emplace(ComputeColorIndexValues(this, selected, colorIndex)); + result.emplace(computeColorIndexValues(selected, colorIndex)); } return *result; } diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 8f2b7639de..c60993df6a 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -31,7 +31,8 @@ class ChatTheme; class ChatStyle; struct BubblePattern; -inline constexpr auto kColorIndexCount = uint8(14); +inline constexpr auto kColorPatternsCount = Text::kMaxQuoteOutlines; +inline constexpr auto kColorIndexCount = uint8(1 << 6); inline constexpr auto kSimpleColorIndexCount = uint8(7); struct MessageStyle { @@ -83,10 +84,12 @@ struct MessageStyle { style::icon historyPollChoiceRight = { Qt::Uninitialized }; style::icon historyTranscribeIcon = { Qt::Uninitialized }; style::icon historyTranscribeHide = { Qt::Uninitialized }; - std::unique_ptr quoteCache; - std::unique_ptr quoteCacheTwo; - std::unique_ptr replyCache; - std::unique_ptr replyCacheTwo; + std::array< + std::unique_ptr, + kColorPatternsCount> quoteCache; + std::array< + std::unique_ptr, + kColorPatternsCount> replyCache; std::unique_ptr preCache; }; @@ -190,22 +193,31 @@ struct ChatPaintContext { [[nodiscard]] int HistoryServiceMsgInvertedRadius(); [[nodiscard]] int HistoryServiceMsgInvertedShrink(); +struct ColorIndexData { + std::array light = {}; + std::array dark = {}; + + friend inline bool operator==( + const ColorIndexData&, + const ColorIndexData&) = default; +}; + +struct ColorIndicesCompressed { + std::shared_ptr> colors; +}; + struct ColorIndexValues { + std::array outlines; QColor name; QColor bg; - QColor outline1; - QColor outline2; }; -[[nodiscard]] ColorIndexValues ComputeColorIndexValues( - not_null st, - bool selected, - uint8 colorIndex); -[[nodiscard]] bool ColorIndexTwoColored(uint8 colorIndex); class ChatStyle final : public style::palette { public: - ChatStyle(); + explicit ChatStyle(rpl::producer colorIndices); explicit ChatStyle(not_null isolated); + ChatStyle(const ChatStyle &other) = delete; + ChatStyle &operator=(const ChatStyle &other) = delete; ~ChatStyle(); void apply(not_null theme); @@ -246,6 +258,11 @@ public: bool selected) const; [[nodiscard]] const MessageImageStyle &imageStyle(bool selected) const; + [[nodiscard]] int colorPatternIndex(uint8 colorIndex) const; + [[nodiscard]] ColorIndexValues computeColorIndexValues( + bool selected, + uint8 colorIndex) const; + [[nodiscard]] auto serviceQuoteCache(bool twoColored) const -> not_null; [[nodiscard]] auto serviceReplyCache(bool twoColored) const @@ -362,6 +379,7 @@ private: }; void assignPalette(not_null palette); + void clearColorIndexCaches(); void updateDarkValue(); [[nodiscard]] not_null coloredCache( @@ -462,11 +480,14 @@ private: style::icon _historyPollChoiceRight = { Qt::Uninitialized }; style::icon _historyPollChoiceWrong = { Qt::Uninitialized }; + ColorIndicesCompressed _colorIndices; + bool _dark = false; rpl::event_stream<> _paletteChanged; rpl::lifetime _defaultPaletteChangeLifetime; + rpl::lifetime _colorIndicesLifetime; }; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 11dae1d945..681604d939 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1088,7 +1088,7 @@ SessionController::SessionController( , _invitePeekTimer([=] { checkInvitePeek(); }) , _activeChatsFilter(session->data().chatsFilters().defaultId()) , _defaultChatTheme(std::make_shared()) -, _chatStyle(std::make_unique()) +, _chatStyle(std::make_unique(session->colorIndicesValue())) , _cachedReactionIconFactory(std::make_unique()) , _giftPremiumValidator(this) { init(); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 383b5b8f7e..611224c52f 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 383b5b8f7e629475e5f22445167aaa7669c5cdd6 +Subproject commit 611224c52f3192f616018fc7a2c5930667531084 From 9561026cd426ae3246bf8c7da023891ec7dff622 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 27 Oct 2023 12:23:48 +0400 Subject: [PATCH 69/99] Add change color button with color sample. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 33 ++- .../boxes/background_preview_box.cpp | 2 +- .../boxes/peers/edit_peer_color_box.cpp | 200 ++++++++++++++++++ .../boxes/peers/edit_peer_color_box.h | 29 +++ .../boxes/peers/edit_peer_info_box.cpp | 113 ++++------ Telegram/SourceFiles/settings/settings.style | 9 + .../SourceFiles/settings/settings_chat.cpp | 74 +++---- .../settings/settings_global_ttl.cpp | 2 +- 9 files changed, 355 insertions(+), 109 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp create mode 100644 Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 32aa477b4f..95ac565a69 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -198,6 +198,8 @@ PRIVATE boxes/peers/edit_participant_box.h boxes/peers/edit_participants_box.cpp boxes/peers/edit_participants_box.h + boxes/peers/edit_peer_color_box.cpp + boxes/peers/edit_peer_color_box.h boxes/peers/edit_peer_common.h boxes/peers/edit_peer_info_box.cpp boxes/peers/edit_peer_info_box.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f9a075de2a..6f530972c1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -402,6 +402,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dlg_search_from" = "From: {user}"; "lng_settings_save" = "Save"; +"lng_settings_apply" = "Apply"; "lng_username_title" = "Username"; "lng_username_description1" = "You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number."; @@ -770,11 +771,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_clear_payment_info_clear" = "Clear"; "lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data."; +"lng_settings_theme_settings" = "Theme settings"; +"lng_settings_theme_name_color" = "Your name color"; "lng_settings_auto_night_mode" = "Auto-Night mode"; -"lng_settings_auto_night_enabled" = "Match the system settings"; +"lng_settings_auto_night_mode_off" = "Off"; +"lng_settings_auto_night_mode_on" = "System"; "lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first."; "lng_settings_auto_night_disable" = "Disable"; +"lng_settings_color_title" = "Color preview"; +"lng_settings_color_reply" = "Reply to your message"; +"lng_settings_color_reply_channel" = "Reply to your channel message"; +"lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color."; +"lng_settings_color_text_channel" = "The name of the channel and replies to its messages will be shown in the selected color."; +"lng_settings_color_link_name" = "Telegram"; +"lng_settings_color_link_title" = "Link Preview"; +"lng_settings_color_link_description" = "Your selected color will also tint the link preview."; +"lng_settings_color_about" = "You can choose a color to tint your name, the links you send, and replies to your messages."; +"lng_settings_color_about_channel" = "You can choose a color to tint your channel's name, the links it sends, and replies to its messages."; +"lng_settings_color_emoji" = "Add icons to replies"; +"lng_settings_color_emoji_off" = "Off"; +"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them."; +"lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them."; +"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name."; + "lng_suggest_hide_new_title" = "Hide new chats?"; "lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?"; "lng_suggest_hide_new_to_settings" = "Go to Settings"; @@ -802,7 +822,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!"; "lng_background_text2" = "I can't even take you seriously right now."; "lng_background_bad_link" = "This background link appears to be invalid."; -"lng_background_apply" = "Apply"; "lng_background_share" = "Share"; "lng_background_link_copied" = "Link copied to clipboard"; "lng_background_blur" = "Blurred"; @@ -2035,6 +2054,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?"; "lng_boost_now_replace" = "Replace"; +"lng_boost_channel_title_color" = "Enable colors"; +"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color."; +"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color."; +"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:"; +"lng_boost_channel_ask_button" = "Copy Link"; +"lng_boost_channel_or" = "or"; +"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}"; +"lng_boost_channel_gifting_link" = "Get boosts >"; + "lng_giveaway_new_title" = "Boosts via Gifts"; "lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers."; "lng_giveaway_create_option" = "Create Giveaway"; @@ -2675,6 +2703,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_bot_title" = "Edit bot"; "lng_edit_sign_messages" = "Sign messages"; "lng_edit_group" = "Edit group"; +"lng_edit_channel_color" = "Change name color"; "lng_edit_self_title" = "Edit your name"; "lng_confirm_contact_data" = "New Contact"; "lng_add_contact" = "Create"; diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 072dc34fb0..a920aa76a5 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -435,7 +435,7 @@ void BackgroundPreviewBox::rebuildButtons(bool dark) { clearButtons(); addButton(_forPeer ? tr::lng_background_apply_button() - : tr::lng_background_apply(), [=] { apply(); }); + : tr::lng_settings_apply(), [=] { apply(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); if (!_forPeer && _paper.hasShareUrl()) { addLeftButton(tr::lng_background_share(), [=] { share(); }); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp new file mode 100644 index 0000000000..f15ae47cb6 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -0,0 +1,200 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/peers/edit_peer_color_box.h" + +#include "data/data_changes.h" +#include "data/data_peer.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_common.h" +#include "ui/chat/chat_style.h" +#include "ui/widgets/buttons.h" +#include "ui/painter.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" +#include "styles/style_widgets.h" + +namespace { + +using namespace Settings; + +class ColorSample final : public Ui::RpWidget { +public: + ColorSample( + not_null parent, + std::shared_ptr st, + rpl::producer colorIndex, + const QString &name); + + int naturalWidth() const override; + +private: + void paintEvent(QPaintEvent *e) override; + + std::shared_ptr _st; + Ui::Text::String _name; + uint8 _index = 0; + +}; + +ColorSample::ColorSample( + not_null parent, + std::shared_ptr st, + rpl::producer colorIndex, + const QString &name) +: RpWidget(parent) +, _st(st) +, _name(st::semiboldTextStyle, name) { + std::move( + colorIndex + ) | rpl::start_with_next([=](uint8 index) { + _index = index; + update(); + }, lifetime()); +} + +void ColorSample::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + auto hq = PainterHighQualityEnabler(p); + const auto colors = _st->coloredValues(false, _index); + if (!colors.outlines[1].alpha()) { + const auto radius = height() / 2; + p.setPen(Qt::NoPen); + p.setBrush(colors.bg); + p.drawRoundedRect(rect(), radius, radius); + + const auto padding = st::settingsColorSamplePadding; + p.setPen(colors.name); + p.setBrush(Qt::NoBrush); + p.setFont(st::semiboldFont); + _name.drawLeftElided( + p, + padding.left(), + padding.top(), + width() - padding.left() - padding.right(), + width(), + 1, + style::al_top); + } else { + const auto size = width(); + const auto half = size / 2.; + p.translate(size / 2., size / 2.); + p.rotate(-45.); + p.setPen(Qt::NoPen); + p.setClipRect(-size, -size, 3 * size, size); + p.setBrush(colors.outlines[0]); + p.drawEllipse(-half, -half, size, size); + p.setClipRect(-size, 0, 3 * size, size); + p.setBrush(colors.outlines[1]); + p.drawEllipse(-half, -half, size, size); + if (colors.outlines[2].alpha()) { + const auto center = st::settingsColorSampleCenter; + const auto radius = st::settingsColorSampleCenterRadius; + p.setClipping(false); + p.setBrush(colors.outlines[2]); + p.drawRoundedRect( + QRectF(-center / 2., -center / 2., center, center), + radius, + radius); + } + } +} + +int ColorSample::naturalWidth() const { + if (_st->colorPatternIndex(_index)) { + return st::settingsColorSampleSize; + } + const auto padding = st::settingsColorSamplePadding; + return std::max( + padding.left() + _name.maxWidth() + padding.right(), + padding.top() + st::semiboldFont->height + padding.bottom()); +} + +} // namespace + +void EditPeerColorBox( + not_null box, + std::shared_ptr show, + not_null peer, + std::shared_ptr st) { + +} + +void AddPeerColorButton( + not_null container, + std::shared_ptr show, + not_null peer) { + const auto button = AddButton( + container, + (peer->isSelf() + ? tr::lng_settings_theme_name_color() + : tr::lng_edit_channel_color()), + st::settingsColorButton, + { &st::menuIconChangeColors }); + + auto colorIndexValue = peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::Color + ) | rpl::map([=] { + return peer->colorIndex(); + }); + const auto name = peer->shortName(); + const auto st = std::make_shared( + peer->session().colorIndicesValue()); + const auto sample = Ui::CreateChild( + button.get(), + st, + rpl::duplicate(colorIndexValue), + name); + sample->show(); + + rpl::combine( + button->widthValue(), + tr::lng_settings_theme_name_color(), + rpl::duplicate(colorIndexValue) + ) | rpl::start_with_next([=]( + int width, + const QString &button, + int colorIndex) { + const auto sampleSize = st::settingsColorSampleSize; + const auto available = width + - st::settingsButton.padding.left() + - (st::settingsColorButton.padding.right() - sampleSize) + - st::settingsButton.style.font->width(button) + - st::settingsButtonRightSkip; + if (st->colorPatternIndex(colorIndex)) { + sample->resize(sampleSize, sampleSize); + } else { + const auto padding = st::settingsColorSamplePadding; + const auto wantedHeight = padding.top() + + st::semiboldFont->height + + padding.bottom(); + const auto wantedWidth = sample->naturalWidth(); + sample->resize(std::min(wantedWidth, available), wantedHeight); + } + sample->update(); + }, sample->lifetime()); + + rpl::combine( + button->sizeValue(), + sample->sizeValue(), + std::move(colorIndexValue) + ) | rpl::start_with_next([=](QSize outer, QSize inner, int colorIndex) { + const auto right = st::settingsColorButton.padding.right() + - st::settingsColorSampleSkip + - st::settingsColorSampleSize + - (st->colorPatternIndex(colorIndex) + ? 0 + : st::settingsColorSamplePadding.right()); + sample->move( + outer.width() - right - inner.width(), + (outer.height() - inner.height()) / 2); + }, sample->lifetime()); + + sample->setAttribute(Qt::WA_TransparentForMouseEvents); +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h new file mode 100644 index 0000000000..b69e9e04fc --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h @@ -0,0 +1,29 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Ui { +class GenericBox; +class ChatStyle; +class VerticalLayout; +} // namespace Ui + +void EditPeerColorBox( + not_null box, + std::shared_ptr show, + not_null peer, + std::shared_ptr st = nullptr); + +void AddPeerColorButton( + not_null container, + std::shared_ptr show, + not_null peer); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 3e95dcc1f6..0012d8a0f5 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -13,8 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "boxes/add_contact_box.h" #include "ui/boxes/confirm_box.h" -#include "boxes/peer_list_controllers.h" #include "boxes/peers/edit_participants_box.h" +#include "boxes/peers/edit_peer_color_box.h" #include "boxes/peers/edit_peer_common.h" #include "boxes/peers/edit_peer_type_box.h" #include "boxes/peers/edit_peer_history_visibility_box.h" @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_linked_chat_box.h" #include "boxes/peers/edit_peer_requests_box.h" #include "boxes/peers/edit_peer_reactions.h" +#include "boxes/peer_list_controllers.h" #include "boxes/stickers_box.h" #include "boxes/username_box.h" #include "ui/boxes/single_choice_box.h" @@ -302,6 +303,7 @@ private: void fillLinkedChatButton(); //void fillInviteLinkButton(); void fillForumButton(); + void fillColorIndexButton(); void fillSignaturesButton(); void fillHistoryVisibilityButton(); void fillManageSection(); @@ -905,6 +907,13 @@ void Controller::refreshForumToggleLocked() { _controls.forumToggle->setToggleLocked(locked); } +void Controller::fillColorIndexButton() { + Expects(_controls.buttonsLayout != nullptr); + + const auto show = _navigation->uiShow(); + AddPeerColorButton(_controls.buttonsLayout, show, _peer); +} + void Controller::fillSignaturesButton() { Expects(_controls.buttonsLayout != nullptr); @@ -1024,74 +1033,42 @@ void Controller::fillManageSection() { return; } - const auto canEditType = [&] { - return isChannel - ? channel->amCreator() - : chat->amCreator(); - }(); - const auto canEditSignatures = [&] { - return isChannel - ? (channel->canEditSignatures() && !channel->isMegagroup()) - : false; - }(); - const auto canEditPreHistoryHidden = [&] { - return isChannel - ? channel->canEditPreHistoryHidden() - : chat->canEditPreHistoryHidden(); - }(); + const auto canEditType = isChannel + ? channel->amCreator() + : chat->amCreator(); + const auto canEditSignatures = isChannel + && channel->canEditSignatures() + && !channel->isMegagroup(); + const auto canEditPreHistoryHidden = isChannel + ? channel->canEditPreHistoryHidden() + : chat->canEditPreHistoryHidden(); const auto canEditForum = isChannel ? (channel->isMegagroup() && channel->amCreator()) : chat->amCreator(); - - const auto canEditPermissions = [&] { - return isChannel - ? channel->canEditPermissions() - : chat->canEditPermissions(); - }(); - const auto canEditInviteLinks = [&] { - return isChannel - ? channel->canHaveInviteLink() - : chat->canHaveInviteLink(); - }(); - const auto canViewAdmins = [&] { - return isChannel - ? channel->canViewAdmins() - : chat->amIn(); - }(); - const auto canViewMembers = [&] { - return isChannel - ? channel->canViewMembers() - : chat->amIn(); - }(); - const auto canViewKicked = [&] { - return isChannel - ? (channel->isBroadcast() || channel->isGigagroup()) - : false; - }(); - const auto hasRecentActions = [&] { - return isChannel - ? (channel->hasAdminRights() || channel->amCreator()) - : false; - }(); - - const auto canEditStickers = [&] { - return isChannel - ? channel->canEditStickers() - : false; - }(); - const auto canDeleteChannel = [&] { - return isChannel - ? channel->canDelete() - : false; - }(); - - const auto canViewOrEditLinkedChat = [&] { - return !isChannel - ? false - : channel->linkedChat() - ? true - : (channel->isBroadcast() && channel->canEditInformation()); - }(); + const auto canEditPermissions = isChannel + ? channel->canEditPermissions() + : chat->canEditPermissions(); + const auto canEditInviteLinks = isChannel + ? channel->canHaveInviteLink() + : chat->canHaveInviteLink(); + const auto canViewAdmins = isChannel + ? channel->canViewAdmins() + : chat->amIn(); + const auto canViewMembers = isChannel + ? channel->canViewMembers() + : chat->amIn(); + const auto canViewKicked = isChannel + && (channel->isBroadcast() || channel->isGigagroup()); + const auto hasRecentActions = isChannel + && (channel->hasAdminRights() || channel->amCreator()); + const auto canEditStickers = isChannel && channel->canEditStickers(); + const auto canDeleteChannel = isChannel && channel->canDelete(); + const auto canEditColorIndex = isChannel + && !channel->isMegagroup() + && channel->canEditInformation(); + const auto canViewOrEditLinkedChat = isChannel + && (channel->linkedChat() + || (channel->isBroadcast() && channel->canEditInformation())); AddSkip(_controls.buttonsLayout, 0); @@ -1109,11 +1086,15 @@ void Controller::fillManageSection() { if (canEditForum) { fillForumButton(); } + if (canEditColorIndex) { + fillColorIndexButton(); + } if (canEditSignatures) { fillSignaturesButton(); } if (canEditPreHistoryHidden || canEditForum + || canEditColorIndex || canEditSignatures //|| canEditInviteLinks || canViewOrEditLinkedChat diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 3ea1b81dd1..368973e219 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -565,3 +565,12 @@ filterLinkSubsectionTitlePadding: margins(0px, 5px, 0px, -4px); filterLinkChatsList: PeerList(peerListBox) { padding: margins(0px, 0px, 0px, membersMarginBottom); } + +settingsColorSampleSize: 20px; +settingsColorSampleCenter: 8px; +settingsColorSampleCenterRadius: 2px; +settingsColorSamplePadding: margins(8px, 2px, 8px, 2px); +settingsColorSampleSkip: 6px; +settingsColorButton: SettingsButton(settingsButton) { + padding: margins(60px, 10px, 48px, 10px); +} \ No newline at end of file diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 01b6437f34..508627901a 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" #include "settings/settings_advanced.h" #include "settings/settings_experimental.h" +#include "boxes/peers/edit_peer_color_box.h" #include "boxes/connection_box.h" #include "boxes/auto_download_box.h" #include "boxes/reactions_settings_box.h" @@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/chat/attach/attach_extensions.h" +#include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/layers/generic_box.h" #include "ui/effects/radial_animation.h" @@ -55,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_message_reactions.h" #include "data/data_peer_values.h" +#include "data/data_user.h" #include "chat_helpers/emoji_sets_manager.h" #include "base/platform/base_platform_info.h" #include "platform/platform_specific.h" @@ -1561,51 +1564,44 @@ void SetupCloudThemes( wrap->setDuration(0)->toggleOn(list->empty() | rpl::map(!_1)); } -void SetupAutoNightMode( +void SetupThemeSettings( not_null controller, not_null container) { - if (!Core::App().settings().systemDarkMode().has_value()) { - return; - } - AddDivider(container); AddSkip(container, st::settingsPrivacySkip); - AddSubsectionTitle(container, tr::lng_settings_auto_night_mode()); + AddSubsectionTitle(container, tr::lng_settings_theme_settings()); - auto wrap = object_ptr(container); - const auto autoNight = wrap->add( - object_ptr( - wrap, - tr::lng_settings_auto_night_enabled(tr::now), - Core::App().settings().systemDarkModeEnabled(), - st::settingsCheckbox), - st::settingsCheckboxPadding); - - autoNight->checkedChanges( - ) | rpl::filter([=](bool checked) { - return (checked != Core::App().settings().systemDarkModeEnabled()); - }) | rpl::start_with_next([=](bool checked) { - if (checked && Window::Theme::Background()->editingTheme()) { - autoNight->setChecked(false); - controller->show(Ui::MakeInformBox( - tr::lng_theme_editor_cant_change_theme())); - } else { - Core::App().settings().setSystemDarkModeEnabled(checked); - Core::App().saveSettingsDelayed(); - } - }, autoNight->lifetime()); - - Core::App().settings().systemDarkModeEnabledChanges( - ) | rpl::filter([=](bool value) { - return (value != autoNight->checked()); - }) | rpl::start_with_next([=](bool value) { - autoNight->setChecked(value); - }, autoNight->lifetime()); - - container->add(object_ptr( + AddPeerColorButton( container, - std::move(wrap))); + controller->uiShow(), + controller->session().user()); + + const auto settings = &Core::App().settings(); + if (settings->systemDarkMode().has_value()) { + auto label = settings->systemDarkModeEnabledValue( + ) | rpl::map([=](bool enabled) { + return enabled + ? tr::lng_settings_auto_night_mode_on() + : tr::lng_settings_auto_night_mode_off(); + }) | rpl::flatten_latest(); + AddButtonWithLabel( + container, + tr::lng_settings_auto_night_mode(), + std::move(label), + st::settingsButton, + { &st::menuIconNightMode } + )->setClickedCallback([=] { + const auto now = !settings->systemDarkModeEnabled(); + if (now && Window::Theme::Background()->editingTheme()) { + controller->show(Ui::MakeInformBox( + tr::lng_theme_editor_cant_change_theme())); + } else { + settings->setSystemDarkModeEnabled(now); + Core::App().saveSettingsDelayed(); + } + }); + } AddSkip(container, st::settingsCheckboxesSkip); } @@ -1749,7 +1745,7 @@ void Chat::setupContent(not_null controller) { const auto content = Ui::CreateChild(this); SetupThemeOptions(controller, content); - SetupAutoNightMode(controller, content); + SetupThemeSettings(controller, content); SetupCloudThemes(controller, content); SetupChatBackground(controller, content); SetupStickersEmoji(controller, content); diff --git a/Telegram/SourceFiles/settings/settings_global_ttl.cpp b/Telegram/SourceFiles/settings/settings_global_ttl.cpp index cba6a2c00d..4c991fb222 100644 --- a/Telegram/SourceFiles/settings/settings_global_ttl.cpp +++ b/Telegram/SourceFiles/settings/settings_global_ttl.cpp @@ -377,7 +377,7 @@ void GlobalTTL::setupContent() { auto controller = std::make_unique(session); auto initBox = [=, controller = controller.get()]( not_null box) { - box->addButton(tr::lng_background_apply(), crl::guard(this, [=] { + box->addButton(tr::lng_settings_apply(), crl::guard(this, [=] { const auto &peers = box->collectSelectedRows(); if (peers.empty()) { return; From effc9873c9deb50c62f5d4db2a8d852076333b21 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 27 Oct 2023 17:49:31 +0400 Subject: [PATCH 70/99] Remove test data in giveaways. --- Telegram/SourceFiles/data/data_media_types.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 19bab78ae8..9cf49244a7 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -381,7 +381,6 @@ Giveaway ComputeGiveawayData( result.countries.push_back(qs(country)); } } - result.countries = { u"FR"_q, u"FI"_q, u"UA"_q, u"IT"_q }; return result; } From bcdb1bdfd27da80dd82438d36aa09836411a1b1d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 27 Oct 2023 17:49:37 +0400 Subject: [PATCH 71/99] Name color changing for me / channels. --- Telegram/Resources/langs/lang.strings | 2 + .../SourceFiles/boxes/gift_premium_box.cpp | 65 +- .../boxes/peers/edit_peer_color_box.cpp | 645 +++++++++++++++++- .../boxes/peers/edit_peer_color_box.h | 4 +- .../history/history_item_components.cpp | 2 + .../controls/history_view_draft_options.cpp | 37 +- .../info/boosts/info_boosts_inner_widget.cpp | 18 +- Telegram/SourceFiles/settings/settings.style | 7 +- .../SourceFiles/settings/settings_premium.cpp | 31 + .../SourceFiles/settings/settings_premium.h | 5 + Telegram/SourceFiles/ui/boxes/boost_box.cpp | 217 +++++- Telegram/SourceFiles/ui/boxes/boost_box.h | 25 +- .../SourceFiles/ui/chat/choose_send_as.cpp | 46 +- Telegram/SourceFiles/ui/effects/premium.style | 8 + .../window/themes/window_theme.cpp | 31 + .../SourceFiles/window/themes/window_theme.h | 4 + 16 files changed, 952 insertions(+), 195 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6f530972c1..7d8e4125c2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -794,6 +794,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them."; "lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them."; "lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name."; +"lng_settings_color_changed" = "Your name color has been updated!"; +"lng_settings_color_changed_channel" = "Your channel color has been updated!"; "lng_suggest_hide_new_title" = "Hide new chats?"; "lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?"; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 71f3c42c46..84c2db7e65 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -251,68 +251,24 @@ struct GiftCodeLink { }; } -[[nodiscard]] object_ptr MakeLinkLabel( - not_null parent, - rpl::producer text, - rpl::producer link, - std::shared_ptr show) { - auto result = object_ptr(parent); +[[nodiscard]] object_ptr MakeLinkCopyIcon( + not_null parent) { + auto result = object_ptr(parent); const auto raw = result.data(); - struct State { - State( - not_null parent, - rpl::producer value, - rpl::producer link) - : text(std::move(value)) - , link(std::move(link)) - , label(parent, text.value(), st::giveawayGiftCodeLink) - , bg(st::roundRadiusLarge, st::windowBgOver) { - } - - rpl::variable text; - rpl::variable link; - Ui::FlatLabel label; - Ui::RoundRect bg; - }; - - const auto state = raw->lifetime().make_state( - raw, - rpl::duplicate(text), - std::move(link)); - state->label.setSelectable(true); - - rpl::combine( - raw->widthValue(), - std::move(text) - ) | rpl::start_with_next([=](int outer, const auto&) { - const auto textWidth = state->label.textMaxWidth(); - const auto skipLeft = st::giveawayGiftCodeLink.margin.left(); - const auto skipRight = st::giveawayGiftCodeLinkCopyWidth; - const auto available = outer - skipRight - skipLeft; - const auto use = std::min(textWidth, available); - state->label.resizeToWidth(use); - state->label.move(outer - skipRight - use - skipLeft, 0); - }, raw->lifetime()); - raw->paintRequest() | rpl::start_with_next([=] { auto p = QPainter(raw); - state->bg.paint(p, raw->rect()); - const auto outer = raw->width(); - const auto width = st::giveawayGiftCodeLinkCopyWidth; const auto &icon = st::giveawayGiftCodeLinkCopy; - const auto left = outer - width + (width - icon.width()) / 2; + const auto left = (raw->width() - icon.width()) / 2; const auto top = (raw->height() - icon.height()) / 2; icon.paint(p, left, top, raw->width()); }, raw->lifetime()); - state->label.setAttribute(Qt::WA_TransparentForMouseEvents); + raw->resize( + st::giveawayGiftCodeLinkCopyWidth, + st::giveawayGiftCodeLinkHeight); - raw->resize(raw->width(), st::giveawayGiftCodeLinkHeight); - raw->setClickedCallback([=] { - QGuiApplication::clipboard()->setText(state->link.current()); - show->showToast(tr::lng_username_copied(tr::now)); - }); + raw->setAttribute(Qt::WA_TransparentForMouseEvents); return result; } @@ -502,11 +458,12 @@ void GiftCodeBox( const auto link = MakeGiftCodeLink(&controller->session(), slug); box->addRow( - MakeLinkLabel( + Ui::MakeLinkLabel( box, rpl::single(link.text), rpl::single(link.link), - box->uiShow()), + box->uiShow(), + MakeLinkCopyIcon(box)), st::giveawayGiftCodeLinkMargin); auto table = box->addRow( diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index f15ae47cb6..11c674565d 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -7,14 +7,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/edit_peer_color_box.h" +#include "apiwrap.h" +#include "base/unixtime.h" +#include "chat_helpers/compose/compose_show.h" #include "data/data_changes.h" +#include "data/data_channel.h" #include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_web_page.h" +#include "history/view/history_view_element.h" +#include "history/history.h" +#include "history/history_item.h" +#include "info/boosts/info_boosts_widget.h" +#include "info/info_memento.h" #include "lang/lang_keys.h" +#include "main/main_account.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "settings/settings_common.h" +#include "settings/settings_premium.h" +#include "ui/boxes/boost_box.h" #include "ui/chat/chat_style.h" +#include "ui/chat/chat_theme.h" +#include "ui/effects/path_shift_gradient.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/painter.h" +#include "window/themes/window_theme.h" +#include "window/section_widget.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "styles/style_widgets.h" @@ -23,32 +47,97 @@ namespace { using namespace Settings; -class ColorSample final : public Ui::RpWidget { +constexpr auto kFakeChannelId = ChannelId(0xFFFFFFF000ULL); +constexpr auto kFakeWebPageId = WebPageId(0xFFFFFFFF00000000ULL); +constexpr auto kSelectAnimationDuration = crl::time(150); + +class ColorSample final : public Ui::AbstractButton { public: ColorSample( not_null parent, - std::shared_ptr st, + std::shared_ptr style, rpl::producer colorIndex, const QString &name); + ColorSample( + not_null parent, + std::shared_ptr style, + uint8 colorIndex, + bool selected); + [[nodiscard]] uint8 index() const; int naturalWidth() const override; + void setSelected(bool selected); + private: void paintEvent(QPaintEvent *e) override; - std::shared_ptr _st; + std::shared_ptr _style; Ui::Text::String _name; uint8 _index = 0; + Ui::Animations::Simple _selectAnimation; + bool _selected = false; + bool _simple = false; + +}; + +class PreviewDelegate final : public HistoryView::DefaultElementDelegate { +public: + PreviewDelegate( + not_null parent, + not_null st, + Fn update); + + bool elementAnimationsPaused() override; + not_null elementPathShiftGradient() override; + HistoryView::Context elementContext() override; + +private: + const not_null _parent; + const std::unique_ptr _pathGradient; + +}; + +class PreviewWrap final : public Ui::RpWidget { +public: + PreviewWrap( + not_null box, + std::shared_ptr style, + std::shared_ptr theme, + not_null peer, + rpl::producer colorIndexValue); + ~PreviewWrap(); + +private: + using Element = HistoryView::Element; + + void paintEvent(QPaintEvent *e) override; + + void initElements(); + + const not_null _box; + const not_null _peer; + const not_null _fake; + const not_null _history; + const not_null _webpage; + const std::shared_ptr _theme; + const std::shared_ptr _style; + const std::unique_ptr _delegate; + const not_null _replyToItem; + const not_null _replyItem; + std::unique_ptr _element; + Ui::PeerUserpicView _userpic; + QPoint _position; }; ColorSample::ColorSample( not_null parent, - std::shared_ptr st, + std::shared_ptr style, rpl::producer colorIndex, const QString &name) -: RpWidget(parent) -, _st(st) +: AbstractButton(parent) +, _style(style) , _name(st::semiboldTextStyle, name) { std::move( colorIndex @@ -58,11 +147,35 @@ ColorSample::ColorSample( }, lifetime()); } +ColorSample::ColorSample( + not_null parent, + std::shared_ptr style, + uint8 colorIndex, + bool selected) +: AbstractButton(parent) +, _style(style) +, _index(colorIndex) +, _selected(selected) +, _simple(true) { +} + +void ColorSample::setSelected(bool selected) { + if (_selected == selected) { + return; + } + _selected = selected; + _selectAnimation.start( + [=] { update(); }, + _selected ? 0. : 1., + _selected ? 1. : 0., + kSelectAnimationDuration); +} + void ColorSample::paintEvent(QPaintEvent *e) { auto p = Painter(this); auto hq = PainterHighQualityEnabler(p); - const auto colors = _st->coloredValues(false, _index); - if (!colors.outlines[1].alpha()) { + const auto colors = _style->coloredValues(false, _index); + if (!_simple && !colors.outlines[1].alpha()) { const auto radius = height() / 2; p.setPen(Qt::NoPen); p.setBrush(colors.bg); @@ -81,32 +194,52 @@ void ColorSample::paintEvent(QPaintEvent *e) { 1, style::al_top); } else { - const auto size = width(); + const auto size = float64(width()); const auto half = size / 2.; + const auto full = QRectF(-half, -half, size, size); p.translate(size / 2., size / 2.); - p.rotate(-45.); p.setPen(Qt::NoPen); - p.setClipRect(-size, -size, 3 * size, size); + if (colors.outlines[1].alpha()) { + p.rotate(-45.); + p.setClipRect(-size, 0, 3 * size, size); + p.setBrush(colors.outlines[1]); + p.drawEllipse(full); + p.setClipRect(-size, -size, 3 * size, size); + } p.setBrush(colors.outlines[0]); - p.drawEllipse(-half, -half, size, size); - p.setClipRect(-size, 0, 3 * size, size); - p.setBrush(colors.outlines[1]); - p.drawEllipse(-half, -half, size, size); + p.drawEllipse(full); + p.setClipping(false); if (colors.outlines[2].alpha()) { - const auto center = st::settingsColorSampleCenter; - const auto radius = st::settingsColorSampleCenterRadius; - p.setClipping(false); + const auto multiplier = size / st::settingsColorSampleSize; + const auto center = st::settingsColorSampleCenter * multiplier; + const auto radius = st::settingsColorSampleCenterRadius + * multiplier; p.setBrush(colors.outlines[2]); p.drawRoundedRect( QRectF(-center / 2., -center / 2., center, center), radius, radius); } + const auto selected = _selectAnimation.value(_selected ? 1. : 0.); + if (selected > 0) { + const auto line = st::settingsColorRadioStroke * 1.; + const auto thickness = selected * line; + auto pen = st::boxBg->p; + pen.setWidthF(thickness); + p.setBrush(Qt::NoBrush); + p.setPen(pen); + const auto skip = 1.5 * line; + p.drawEllipse(full.marginsRemoved({ skip, skip, skip, skip })); + } } } +uint8 ColorSample::index() const { + return _index; +} + int ColorSample::naturalWidth() const { - if (_st->colorPatternIndex(_index)) { + if (_name.isEmpty() || _style->colorPatternIndex(_index)) { return st::settingsColorSampleSize; } const auto padding = st::settingsColorSamplePadding; @@ -115,14 +248,463 @@ int ColorSample::naturalWidth() const { padding.top() + st::semiboldFont->height + padding.bottom()); } +PreviewWrap::PreviewWrap( + not_null box, + std::shared_ptr style, + std::shared_ptr theme, + not_null peer, + rpl::producer colorIndexValue) +: RpWidget(box) +, _box(box) +, _peer(peer) +, _fake(_peer->owner().channel(kFakeChannelId)) +, _history(_fake->owner().history(_fake)) +, _webpage(_peer->owner().webpage( + kFakeWebPageId, + WebPageType::Article, + u"internal:peer-color-webpage-preview"_q, + u"internal:peer-color-webpage-preview"_q, + tr::lng_settings_color_link_name(tr::now), + tr::lng_settings_color_link_title(tr::now), + { tr::lng_settings_color_link_description(tr::now) }, + nullptr, // photo + nullptr, // document + WebPageCollage(), + 0, // duration + QString(), // author + false, // hasLargeMedia + 0)) // pendingTill +, _theme(theme) +, _style(style) +, _delegate(std::make_unique(box, _style.get(), [=] { + update(); +})) +, _replyToItem(_history->addNewLocalMessage( + _history->nextNonHistoryEntryId(), + (MessageFlag::FakeHistoryItem + | MessageFlag::HasFromId + | MessageFlag::Post), + UserId(), // via + FullReplyTo(), + base::unixtime::now(), // date + _fake->id, + QString(), // postAuthor + TextWithEntities{ _peer->isSelf() + ? tr::lng_settings_color_reply(tr::now) + : tr::lng_settings_color_reply_channel(tr::now), + }, + MTP_messageMediaEmpty(), + HistoryMessageMarkupData(), + uint64(0))) +, _replyItem(_history->addNewLocalMessage( + _history->nextNonHistoryEntryId(), + (MessageFlag::FakeHistoryItem + | MessageFlag::HasFromId + | MessageFlag::HasReplyInfo + | MessageFlag::Post), + UserId(), // via + FullReplyTo{ .messageId = _replyToItem->fullId() }, + base::unixtime::now(), // date + _fake->id, + QString(), // postAuthor + TextWithEntities{ _peer->isSelf() + ? tr::lng_settings_color_text(tr::now) + : tr::lng_settings_color_text_channel(tr::now), + }, + MTP_messageMediaWebPage( + MTP_flags(0), + MTP_webPagePending( + MTP_flags(0), + MTP_long(_webpage->id), + MTPstring(), + MTP_int(0))), + HistoryMessageMarkupData(), + uint64(0))) +, _element(_replyItem->createView(_delegate.get())) +, _position(0, st::msgMargin.bottom()) { + _style->apply(_theme.get()); + + _fake->setName(peer->name(), QString()); + std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) { + _fake->changeColorIndex(index); + update(); + }, lifetime()); + + const auto session = &_history->session(); + session->data().viewRepaintRequest( + ) | rpl::start_with_next([=](not_null view) { + if (view == _element.get()) { + update(); + } + }, lifetime()); + + initElements(); +} + +PreviewWrap::~PreviewWrap() { + _element = nullptr; + _replyItem->destroy(); + _replyToItem->destroy(); +} + +void PreviewWrap::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + const auto clip = e->rect(); + + p.setClipRect(clip); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(_box->width(), _box->window()->height()), + clip); + + auto context = _theme->preparePaintContext( + _style.get(), + rect(), + clip, + !window()->isActiveWindow()); + + p.translate(_position); + _element->draw(p, context); + + if (_element->displayFromPhoto()) { + auto userpicMinBottomSkip = st::historyPaddingBottom + + st::msgMargin.bottom(); + auto userpicBottom = height() + - _element->marginBottom() + - _element->marginTop(); + const auto item = _element->data(); + const auto userpicTop = userpicBottom - st::msgPhotoSize; + _peer->paintUserpicLeft( + p, + _userpic, + st::historyPhotoLeft, + userpicTop, + width(), + st::msgPhotoSize); + } +} + +void PreviewWrap::initElements() { + _element->initDimensions(); + + widthValue( + ) | rpl::filter([=](int width) { + return width > st::msgMinWidth; + }) | rpl::start_with_next([=](int width) { + const auto height = _position.y() + + _element->resizeGetHeight(width) + + st::msgMargin.top(); + resize(width, height); + }, lifetime()); +} + +PreviewDelegate::PreviewDelegate( + not_null parent, + not_null st, + Fn update) +: _parent(parent) +, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) { +} + +bool PreviewDelegate::elementAnimationsPaused() { + return _parent->window()->isActiveWindow(); +} + +auto PreviewDelegate::elementPathShiftGradient() +-> not_null { + return _pathGradient.get(); +} + +HistoryView::Context PreviewDelegate::elementContext() { + return HistoryView::Context::AdminLog; +} + +void Set( + std::shared_ptr show, + not_null peer, + uint8 colorIndex) { + const auto was = peer->colorIndex(); + peer->changeColorIndex(colorIndex); + peer->session().changes().peerUpdated( + peer, + Data::PeerUpdate::Flag::Color); + const auto done = [=] { + show->showToast(peer->isSelf() + ? tr::lng_settings_color_changed(tr::now) + : tr::lng_settings_color_changed_channel(tr::now)); + }; + const auto fail = [=](const MTP::Error &error) { + peer->changeColorIndex(was); + peer->session().changes().peerUpdated( + peer, + Data::PeerUpdate::Flag::Color); + show->showToast(error.type()); + }; + const auto send = [&](auto &&request) { + peer->session().api().request( + std::move(request) + ).done(done).fail(fail).send(); + }; + if (peer->isSelf()) { + send(MTPaccount_UpdateColor( + MTP_flags( + MTPaccount_UpdateColor::Flag::f_background_emoji_id), + MTP_int(colorIndex), + MTP_long(peer->backgroundEmojiId()))); + } else if (const auto channel = peer->asChannel()) { + send(MTPchannels_UpdateColor( + MTP_flags( + MTPchannels_UpdateColor::Flag::f_background_emoji_id), + channel->inputChannel, + MTP_int(colorIndex), + MTP_long(peer->backgroundEmojiId()))); + } else { + Unexpected("Invalid peer type in Set(colorIndex)."); + } +} + +void Apply( + std::shared_ptr show, + not_null peer, + uint8 colorIndex, + Fn close, + Fn cancel) { + const auto session = &peer->session(); + if (peer->colorIndex() == colorIndex) { + close(); + } else if (peer->isSelf() && !session->premium()) { + Settings::ShowPremiumPromoToast( + show, + tr::lng_settings_color_subscribe( + tr::now, + lt_link, + Ui::Text::Link( + Ui::Text::Bold( + tr::lng_send_as_premium_required_link(tr::now))), + Ui::Text::WithEntities), + u"name_color"_q); + cancel(); + } else if (peer->isSelf()) { + Set(show, peer, colorIndex); + close(); + } else { + session->api().request(MTPpremium_GetBoostsStatus( + peer->input + )).done([=](const MTPpremium_BoostsStatus &result) { + const auto &data = result.data(); + const auto required = session->account().appConfig().get( + "channel_color_level_min", + 5); + if (data.vlevel().v >= required) { + Set(show, peer, colorIndex); + close(); + return; + } + const auto next = data.vnext_level_boosts().value_or_empty(); + const auto openStatistics = [=] { + if (const auto controller = show->resolveWindow( + ChatHelpers::WindowUsage::PremiumPromo)) { + controller->showSection(Info::Boosts::Make(peer)); + } + }; + show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{ + .link = qs(data.vboost_url()), + .boost = { + .level = data.vlevel().v, + .boosts = data.vboosts().v, + .thisLevelBoosts = data.vcurrent_level_boosts().v, + .nextLevelBoosts = next, + }, + .requiredLevel = required, + }, openStatistics, nullptr)); + cancel(); + }).fail([=](const MTP::Error &error) { + show->showToast(error.type()); + cancel(); + }).send(); + } +} + +class ColorSelector final : public Ui::RpWidget { +public: + ColorSelector( + not_null box, + std::shared_ptr style, + rpl::producer> indices, + uint8 index, + Fn callback); + +private: + void fillFrom(std::vector indices); + + int resizeGetHeight(int newWidth) override; + + const std::shared_ptr _style; + std::vector> _samples; + const Fn _callback; + uint8 _index = 0; + +}; + +ColorSelector::ColorSelector( + not_null box, + std::shared_ptr style, + rpl::producer> indices, + uint8 index, + Fn callback) +: RpWidget(box) +, _style(style) +, _callback(std::move(callback)) +, _index(index) { + std::move( + indices + ) | rpl::start_with_next([=](std::vector indices) { + fillFrom(std::move(indices)); + }, lifetime()); +} + +void ColorSelector::fillFrom(std::vector indices) { + auto samples = std::vector>(); + const auto add = [&](uint8 index) { + auto i = ranges::find(_samples, index, &ColorSample::index); + if (i != end(_samples)) { + samples.push_back(std::move(*i)); + _samples.erase(i); + } else { + samples.push_back(std::make_unique( + this, + _style, + index, + index == _index)); + samples.back()->show(); + samples.back()->setClickedCallback([=] { + if (_index != index) { + _callback(index); + + ranges::find( + _samples, + _index, + &ColorSample::index + )->get()->setSelected(false); + _index = index; + ranges::find( + _samples, + _index, + &ColorSample::index + )->get()->setSelected(true); + } + }); + } + }; + for (const auto index : indices) { + add(index); + } + if (!ranges::contains(indices, _index)) { + add(_index); + } + _samples = std::move(samples); + if (width() > 0) { + resizeToWidth(width()); + } +} + +int ColorSelector::resizeGetHeight(int newWidth) { + if (newWidth <= 0) { + return 0; + } + const auto count = int(_samples.size()); + const auto columns = Ui::kSimpleColorIndexCount; + const auto rows = (count + columns - 1) / columns; + const auto skip = st::settingsColorRadioSkip; + const auto size = (newWidth - skip * (columns - 1)) / float64(columns); + const auto isize = int(base::SafeRound(size)); + auto top = 0; + auto left = 0.; + for (auto i = 0; i != count; ++i) { + const auto row = i / columns; + const auto column = i % columns; + _samples[i]->resize(isize, isize); + _samples[i]->move(int(base::SafeRound(left)), top); + left += size + skip; + if (!((i + 1) % columns)) { + top += isize + skip; + left = 0.; + } + } + return (top - skip) + ((count % columns) ? (isize + skip) : 0); +} + } // namespace void EditPeerColorBox( - not_null box, - std::shared_ptr show, - not_null peer, - std::shared_ptr st) { + not_null box, + std::shared_ptr show, + not_null peer, + std::shared_ptr style, + std::shared_ptr theme) { + box->setTitle(tr::lng_settings_color_title()); + box->setWidth(st::boxWideWidth); + struct State { + rpl::variable index; + bool changing = false; + bool applying = false; + }; + const auto state = box->lifetime().make_state(); + state->index = peer->colorIndex(); + + box->addRow(object_ptr( + box, + style, + theme, + peer, + state->index.value() + ), {}); + + const auto appConfig = &peer->session().account().appConfig(); + auto indices = rpl::single( + rpl::empty + ) | rpl::then( + appConfig->refreshed() + ) | rpl::map([=] { + const auto list = appConfig->get>( + "peer_colors_available", + { 0, 1, 2, 3, 4, 5, 6 }); + return list | ranges::views::transform([](int i) { + return uint8(i); + }) | ranges::to_vector; + }); + const auto margin = st::settingsColorRadioMargin; + const auto skip = st::settingsColorRadioSkip; + box->addRow( + object_ptr( + box, + style, + std::move(indices), + state->index.current(), + [=](uint8 index) { state->index = index; }), + { margin, skip, margin, skip }); + + const auto container = box->verticalLayout(); + AddDividerText(container, peer->isSelf() + ? tr::lng_settings_color_about() + : tr::lng_settings_color_about_channel()); + + box->addButton(tr::lng_settings_apply(), [=] { + if (state->applying) { + return; + } + state->applying = true; + Apply(show, peer, state->index.current(), crl::guard(box, [=] { + box->closeBox(); + }), crl::guard(box, [=] { + state->applying = false; + })); + }); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); } void AddPeerColorButton( @@ -144,11 +726,16 @@ void AddPeerColorButton( return peer->colorIndex(); }); const auto name = peer->shortName(); - const auto st = std::make_shared( + + const auto style = std::make_shared( peer->session().colorIndicesValue()); + const auto theme = std::shared_ptr( + Window::Theme::DefaultChatThemeOn(button->lifetime())); + style->apply(theme.get()); + const auto sample = Ui::CreateChild( button.get(), - st, + style, rpl::duplicate(colorIndexValue), name); sample->show(); @@ -167,7 +754,7 @@ void AddPeerColorButton( - (st::settingsColorButton.padding.right() - sampleSize) - st::settingsButton.style.font->width(button) - st::settingsButtonRightSkip; - if (st->colorPatternIndex(colorIndex)) { + if (style->colorPatternIndex(colorIndex)) { sample->resize(sampleSize, sampleSize); } else { const auto padding = st::settingsColorSamplePadding; @@ -188,7 +775,7 @@ void AddPeerColorButton( const auto right = st::settingsColorButton.padding.right() - st::settingsColorSampleSkip - st::settingsColorSampleSize - - (st->colorPatternIndex(colorIndex) + - (style->colorPatternIndex(colorIndex) ? 0 : st::settingsColorSamplePadding.right()); sample->move( @@ -197,4 +784,8 @@ void AddPeerColorButton( }, sample->lifetime()); sample->setAttribute(Qt::WA_TransparentForMouseEvents); + + button->setClickedCallback([=] { + show->show(Box(EditPeerColorBox, show, peer, style, theme)); + }); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h index b69e9e04fc..68aecaab25 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h @@ -14,6 +14,7 @@ class Show; namespace Ui { class GenericBox; class ChatStyle; +class ChatTheme; class VerticalLayout; } // namespace Ui @@ -21,7 +22,8 @@ void EditPeerColorBox( not_null box, std::shared_ptr show, not_null peer, - std::shared_ptr st = nullptr); + std::shared_ptr style = nullptr, + std::shared_ptr theme = nullptr); void AddPeerColorButton( not_null container, diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 5cf6ecb4c2..b75292aa09 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -838,6 +838,8 @@ void HistoryMessageReply::paint( ? resolvedMessage->displayFrom() : resolvedStory ? resolvedStory->peer().get() + : _externalSender + ? _externalSender : nullptr; const auto backgroundEmojiId = colorPeer ? colorPeer->backgroundEmojiId() 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 0b2fad3d74..49a5dabd43 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -70,39 +70,6 @@ private: }; -[[nodiscard]] std::unique_ptr DefaultThemeOn( - rpl::lifetime &lifetime) { - auto result = std::make_unique(); - - using namespace Window::Theme; - const auto push = [=, raw = result.get()] { - const auto background = Background(); - const auto &paper = background->paper(); - raw->setBackground({ - .prepared = background->prepared(), - .preparedForTiled = background->preparedForTiled(), - .gradientForFill = background->gradientForFill(), - .colorForFill = background->colorForFill(), - .colors = paper.backgroundColors(), - .patternOpacity = paper.patternOpacity(), - .gradientRotation = paper.gradientRotation(), - .isPattern = paper.isPattern(), - .tile = background->tile(), - }); - }; - - push(); - Background()->updates( - ) | rpl::start_with_next([=](const BackgroundUpdate &update) { - if (update.type == BackgroundUpdate::Type::New - || update.type == BackgroundUpdate::Type::Changed) { - push(); - } - }, lifetime); - - return result; -} - [[nodiscard]] TextWithEntities HighlightParsedLinks( TextWithEntities text, const std::vector &links) { @@ -124,7 +91,6 @@ private: return text; } - class PreviewWrap final : public Ui::RpWidget { public: PreviewWrap( @@ -195,7 +161,7 @@ PreviewWrap::PreviewWrap( : RpWidget(box) , _box(box) , _history(history) -, _theme(DefaultThemeOn(lifetime())) +, _theme(Window::Theme::DefaultChatThemeOn(lifetime())) , _style(std::make_unique( history->session().colorIndicesValue())) , _delegate(std::make_unique( @@ -376,7 +342,6 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { } auto p = Painter(this); - auto hq = PainterHighQualityEnabler(p); auto context = _theme->preparePaintContext( _style.get(), diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 26b12a6907..7528c093be 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -210,16 +210,14 @@ void InnerWidget::fill() { fakeShowed->events(), rpl::single(status.overview.isBoosted), dividerContent.data(), - Ui::BoostBoxData{ - .boost = Ui::BoostCounters{ - .level = status.overview.level, - .boosts = status.overview.boostCount, - .thisLevelBoosts - = status.overview.currentLevelBoostCount, - .nextLevelBoosts - = status.overview.nextLevelBoostCount, - .mine = status.overview.isBoosted, - } + Ui::BoostCounters{ + .level = status.overview.level, + .boosts = status.overview.boostCount, + .thisLevelBoosts + = status.overview.currentLevelBoostCount, + .nextLevelBoosts + = status.overview.nextLevelBoostCount, + .mine = status.overview.isBoosted, }, st::statisticsLimitsLinePadding); inner->add(object_ptr( diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 368973e219..7ce12a06b5 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -567,10 +567,13 @@ filterLinkChatsList: PeerList(peerListBox) { } settingsColorSampleSize: 20px; -settingsColorSampleCenter: 8px; +settingsColorSampleCenter: 6px; settingsColorSampleCenterRadius: 2px; settingsColorSamplePadding: margins(8px, 2px, 8px, 2px); settingsColorSampleSkip: 6px; settingsColorButton: SettingsButton(settingsButton) { padding: margins(60px, 10px, 48px, 10px); -} \ No newline at end of file +} +settingsColorRadioMargin: 17px; +settingsColorRadioSkip: 13px; +settingsColorRadioStroke: 2px; diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 3dd3120748..cb3eeccd89 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/widgets/checkbox.h" // Ui::RadiobuttonGroup. #include "ui/widgets/gradient_round_button.h" #include "ui/widgets/labels.h" @@ -1466,6 +1467,36 @@ QString LookupPremiumRef(PremiumPreview section) { return QString(); } +void ShowPremiumPromoToast( + std::shared_ptr show, + TextWithEntities textWithLink, + const QString &ref) { + using WeakToast = base::weak_ptr; + const auto toast = std::make_shared(); + (*toast) = show->showToast({ + .text = std::move(textWithLink), + .st = &st::defaultMultilineToast, + .duration = Ui::Toast::kDefaultDuration * 2, + .multiline = true, + .filter = crl::guard(&show->session(), [=]( + const ClickHandlerPtr &, + Qt::MouseButton button) { + if (button == Qt::LeftButton) { + if (const auto strong = toast->get()) { + strong->hideAnimated(); + (*toast) = nullptr; + if (const auto controller = show->resolveWindow( + ChatHelpers::WindowUsage::PremiumPromo)) { + Settings::ShowPremium(controller, ref); + } + return true; + } + } + return false; + }), + }); +} + not_null CreateSubscribeButton( SubscribeButtonArgs &&args) { Expects(args.show || args.controller); diff --git a/Telegram/SourceFiles/settings/settings_premium.h b/Telegram/SourceFiles/settings/settings_premium.h index 0ad7b036ca..6bc1775fdf 100644 --- a/Telegram/SourceFiles/settings/settings_premium.h +++ b/Telegram/SourceFiles/settings/settings_premium.h @@ -51,6 +51,11 @@ void StartPremiumPayment( [[nodiscard]] QString LookupPremiumRef(PremiumPreview section); +void ShowPremiumPromoToast( + std::shared_ptr show, + TextWithEntities textWithLink, + const QString &ref); + struct SubscribeButtonArgs final { Window::SessionController *controller = nullptr; not_null parent; diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index 20e2e71b68..66f504f1de 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_premium.h" +#include + namespace Ui { void StartFireworks(not_null parent) { @@ -57,7 +59,7 @@ void BoostBox( BoxShowFinishes(box), state->you.value(), box->verticalLayout(), - data, + data.boost, st::boxRowPadding); box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); @@ -170,31 +172,188 @@ void BoostBox( }, button->lifetime()); } +object_ptr MakeLinkLabel( + not_null parent, + rpl::producer text, + rpl::producer link, + std::shared_ptr show, + object_ptr right) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + const auto rawRight = right.release(); + if (rawRight) { + rawRight->setParent(raw); + rawRight->show(); + } + + struct State { + State( + not_null parent, + rpl::producer value, + rpl::producer link) + : text(std::move(value)) + , link(std::move(link)) + , label(parent, text.value(), st::giveawayGiftCodeLink) + , bg(st::roundRadiusLarge, st::windowBgOver) { + } + + rpl::variable text; + rpl::variable link; + Ui::FlatLabel label; + Ui::RoundRect bg; + }; + + const auto state = raw->lifetime().make_state( + raw, + rpl::duplicate(text), + std::move(link)); + state->label.setSelectable(true); + + rpl::combine( + raw->widthValue(), + std::move(text) + ) | rpl::start_with_next([=](int outer, const auto&) { + const auto textWidth = state->label.textMaxWidth(); + const auto skipLeft = st::giveawayGiftCodeLink.margin.left(); + const auto skipRight = rawRight + ? rawRight->width() + : st::giveawayGiftCodeLink.margin.right(); + const auto available = outer - skipRight - skipLeft; + const auto use = std::min(textWidth, available); + state->label.resizeToWidth(use); + const auto forCenter = (outer - use) / 2; + const auto x = (forCenter < skipLeft) + ? skipLeft + : (forCenter > outer - skipRight - use) + ? (outer - skipRight - use) + : forCenter; + state->label.moveToLeft(x, st::giveawayGiftCodeLink.margin.top()); + }, raw->lifetime()); + + raw->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(raw); + state->bg.paint(p, raw->rect()); + }, raw->lifetime()); + + state->label.setAttribute(Qt::WA_TransparentForMouseEvents); + + raw->resize(raw->width(), st::giveawayGiftCodeLinkHeight); + if (rawRight) { + raw->widthValue() | rpl::start_with_next([=](int width) { + rawRight->move(width - rawRight->width(), 0); + }, raw->lifetime()); + } + raw->setClickedCallback([=] { + QGuiApplication::clipboard()->setText(state->link.current()); + show->showToast(tr::lng_username_copied(tr::now)); + }); + + return result; +} + +void AskBoostBox( + not_null box, + AskBoostBoxData data, + Fn openStatistics, + Fn startGiveaway) { + box->setWidth(st::boxWideWidth); + box->setStyle(st::boostBox); + + const auto full = !data.boost.nextLevelBoosts; + + struct State { + rpl::variable you = false; + bool submitted = false; + }; + const auto state = box->lifetime().make_state(State{ + .you = data.boost.mine, + }); + + FillBoostLimit( + BoxShowFinishes(box), + state->you.value(), + box->verticalLayout(), + data.boost, + st::boxRowPadding); + + box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); + + auto title = tr::lng_boost_channel_title_color(); + auto text = rpl::combine( + tr::lng_boost_channel_needs_level_color( + lt_count, + rpl::single(float64(data.requiredLevel)), + Ui::Text::RichLangValue), + tr::lng_boost_channel_ask(Ui::Text::RichLangValue) + ) | rpl::map([](TextWithEntities &&text, TextWithEntities &&ask) { + return text.append(u"\n\n"_q).append(std::move(ask)); + }); + box->addRow( + object_ptr( + box, + std::move(title), + st::boostTitle), + st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0)); + box->addRow( + object_ptr( + box, + std::move(text), + st::boostText), + (st::boxRowPadding + + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + + auto stats = object_ptr(box, st::boostLinkStatsButton); + stats->setClickedCallback(openStatistics); + box->addRow(MakeLinkLabel( + box, + rpl::single(data.link), + rpl::single(data.link), + box->uiShow(), + std::move(stats))); + + auto submit = tr::lng_boost_channel_ask_button(); + const auto button = box->addButton(rpl::duplicate(submit), [=] { + QGuiApplication::clipboard()->setText(data.link); + box->uiShow()->showToast(tr::lng_username_copied(tr::now)); + }); + rpl::combine( + std::move(submit), + box->widthValue() + ) | rpl::start_with_next([=](const QString &, int width) { + const auto &padding = st::boostBox.buttonPadding; + button->resizeToWidth(width + - padding.left() + - padding.right()); + button->moveToLeft(padding.left(), button->y()); + }, button->lifetime()); +} + void FillBoostLimit( rpl::producer<> showFinished, rpl::producer you, not_null container, - BoostBoxData data, + BoostCounters data, style::margins limitLinePadding) { - const auto full = !data.boost.nextLevelBoosts; + const auto full = !data.nextLevelBoosts; - if (data.boost.mine && data.boost.boosts > 0) { - --data.boost.boosts; + if (data.mine && data.boosts > 0) { + --data.boosts; } if (full) { - data.boost.nextLevelBoosts = data.boost.boosts - + (data.boost.mine ? 1 : 0); - data.boost.thisLevelBoosts = 0; - if (data.boost.level > 0) { - --data.boost.level; + data.nextLevelBoosts = data.boosts + + (data.mine ? 1 : 0); + data.thisLevelBoosts = 0; + if (data.level > 0) { + --data.level; } - } else if (data.boost.mine - && data.boost.level > 0 - && data.boost.boosts < data.boost.thisLevelBoosts) { - --data.boost.level; - data.boost.nextLevelBoosts = data.boost.thisLevelBoosts; - data.boost.thisLevelBoosts = 0; + } else if (data.mine + && data.level > 0 + && data.boosts < data.thisLevelBoosts) { + --data.level; + data.nextLevelBoosts = data.thisLevelBoosts; + data.thisLevelBoosts = 0; } const auto addSkip = [&](int skip) { @@ -205,18 +364,18 @@ void FillBoostLimit( const auto levelWidth = [&](int add) { return st::normalFont->width( - tr::lng_boost_level(tr::now, lt_count, data.boost.level + add)); + tr::lng_boost_level(tr::now, lt_count, data.level + add)); }; const auto paddings = 2 * st::premiumLineTextSkip; const auto labelLeftWidth = paddings + levelWidth(0); const auto labelRightWidth = paddings + levelWidth(1); const auto ratio = [=](int boosts) { const auto min = std::min( - data.boost.boosts, - data.boost.thisLevelBoosts); + data.boosts, + data.thisLevelBoosts); const auto max = std::max({ - data.boost.boosts, - data.boost.nextLevelBoosts, + data.boosts, + data.nextLevelBoosts, 1, }); Assert(boosts >= min && boosts <= max); @@ -239,12 +398,12 @@ void FillBoostLimit( return (first + (index - 1) * other) / available; }; - const auto min = std::min(data.boost.boosts, data.boost.thisLevelBoosts); - const auto now = data.boost.boosts; - const auto max = (data.boost.nextLevelBoosts > min) - ? (data.boost.nextLevelBoosts) - : (data.boost.boosts > 0) - ? data.boost.boosts + const auto min = std::min(data.boosts, data.thisLevelBoosts); + const auto now = data.boosts; + const auto max = (data.nextLevelBoosts > min) + ? (data.nextLevelBoosts) + : (data.boosts > 0) + ? data.boosts : 1; auto bubbleRowState = ( std::move(you) @@ -280,8 +439,8 @@ void FillBoostLimit( container, st::boostLimits, Premium::LimitRowLabels{ - .leftLabel = level(data.boost.level), - .rightLabel = level(data.boost.level + 1), + .leftLabel = level(data.level), + .rightLabel = level(data.level + 1), .dynamic = true, }, std::move(ratioValue), diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index 2c688acda9..da8d57572e 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -7,10 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/object_ptr.h" + namespace Ui { void StartFireworks(not_null parent); +class Show; +class RpWidget; class GenericBox; class VerticalLayout; @@ -32,11 +36,30 @@ void BoostBox( BoostBoxData data, Fn)> boost); +struct AskBoostBoxData { + QString link; + BoostCounters boost; + int requiredLevel = 0; +}; + +void AskBoostBox( + not_null box, + AskBoostBoxData data, + Fn openStatistics, + Fn startGiveaway); + +[[nodiscard]] object_ptr MakeLinkLabel( + not_null parent, + rpl::producer text, + rpl::producer link, + std::shared_ptr show, + object_ptr right); + void FillBoostLimit( rpl::producer<> showFinished, rpl::producer you, not_null container, - BoostBoxData data, + BoostCounters data, style::margins limitLinePadding); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp index fc24f1aab8..0856b044a8 100644 --- a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp +++ b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp @@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "ui/controls/send_as_button.h" #include "ui/text/text_utilities.h" -#include "ui/toast/toast.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "main/main_session.h" @@ -168,39 +167,6 @@ rpl::producer> ListController::clicked() const { return _clicked.events(); } -void ShowPremiumPromoToast(not_null controller) { - using WeakToast = base::weak_ptr; - const auto toast = std::make_shared(); - - auto link = Ui::Text::Link( - tr::lng_send_as_premium_required_link(tr::now)); - link.entities.push_back( - EntityInText(EntityType::Semibold, 0, link.text.size())); - (*toast) = controller->showToast({ - .text = tr::lng_send_as_premium_required( - tr::now, - lt_link, - link, - Ui::Text::WithEntities), - .st = &st::defaultMultilineToast, - .duration = Ui::Toast::kDefaultDuration * 2, - .multiline = true, - .filter = crl::guard(&controller->session(), [=]( - const ClickHandlerPtr &, - Qt::MouseButton button) { - if (button == Qt::LeftButton) { - if (const auto strong = toast->get()) { - strong->hideAnimated(); - (*toast) = nullptr; - Settings::ShowPremium(controller, "send_as"); - return true; - } - } - return false; - }), - }); -} - } // namespace void ChooseSendAsBox( @@ -272,7 +238,17 @@ void SetupSendAsButton( if (i != end(list) && i->premiumRequired && !sendAs->session().premium()) { - ShowPremiumPromoToast(window); + Settings::ShowPremiumPromoToast( + window->uiShow(), + tr::lng_send_as_premium_required( + tr::now, + lt_link, + Ui::Text::Link( + Ui::Text::Bold( + tr::lng_send_as_premium_required_link( + tr::now))), + Ui::Text::WithEntities), + u"send_as"_q); return false; } session->sendAsPeers().saveChosen(peer, sendAs); diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index d7521700e7..1891e26e00 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -288,6 +288,14 @@ giveawayGiftCodeLinkHeight: 42px; giveawayGiftCodeLinkCopyWidth: 40px; giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px); +boostLinkStatsButton: IconButton(defaultIconButton) { + width: giveawayGiftCodeLinkCopyWidth; + height: giveawayGiftCodeLinkHeight; + icon: icon{{ "menu/stats", menuIconColor }}; + iconOver: icon{{ "menu/stats", menuIconColor }}; + ripple: emptyRippleAnimation; +} + giveawayGiftCodeTable: Table(defaultTable) { labelMinWidth: 91px; } diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index e8b06a5a6a..be3d87846b 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -1591,5 +1591,36 @@ SendMediaReady PrepareWallPaper(MTP::DcId dcId, const QImage &image) { QByteArray()); } +std::unique_ptr DefaultChatThemeOn(rpl::lifetime &lifetime) { + auto result = std::make_unique(); + + const auto push = [=, raw = result.get()] { + const auto background = Background(); + const auto &paper = background->paper(); + raw->setBackground({ + .prepared = background->prepared(), + .preparedForTiled = background->preparedForTiled(), + .gradientForFill = background->gradientForFill(), + .colorForFill = background->colorForFill(), + .colors = paper.backgroundColors(), + .patternOpacity = paper.patternOpacity(), + .gradientRotation = paper.gradientRotation(), + .isPattern = paper.isPattern(), + .tile = background->tile(), + }); + }; + + push(); + Background()->updates( + ) | rpl::start_with_next([=](const BackgroundUpdate &update) { + if (update.type == BackgroundUpdate::Type::New + || update.type == BackgroundUpdate::Type::Changed) { + push(); + } + }, lifetime); + + return result; +} + } // namespace Theme } // namespace Window diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index 32f43977c3..1425e4d07d 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -28,6 +28,7 @@ class Controller; namespace Ui { struct ChatThemeBackground; +class ChatTheme; } // namespace Ui namespace Webview { @@ -309,5 +310,8 @@ bool ReadPaletteValues( [[nodiscard]] Webview::ThemeParams WebViewParams(); +[[nodiscard]] std::unique_ptr DefaultChatThemeOn( + rpl::lifetime &lifetime); + } // namespace Theme } // namespace Window From cb6698cf4aa4a6f88786a6862a3c3d6a034c14ee Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 27 Oct 2023 23:40:39 +0400 Subject: [PATCH 72/99] Implement background emoji selector. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_peer_photo.cpp | 65 +++--- Telegram/SourceFiles/api/api_peer_photo.h | 14 +- .../boxes/peers/edit_peer_color_box.cpp | 189 ++++++++++++++++-- .../chat_helpers/chat_helpers.style | 3 + .../chat_helpers/emoji_list_widget.cpp | 36 +++- .../chat_helpers/emoji_list_widget.h | 3 + .../chat_helpers/stickers_list_footer.cpp | 23 ++- .../chat_helpers/stickers_list_footer.h | 4 + .../chat_helpers/tabbed_selector.cpp | 6 +- .../chat_helpers/tabbed_selector.h | 3 + .../data/stickers/data_stickers_set.cpp | 7 +- .../data/stickers/data_stickers_set.h | 2 + .../info_profile_emoji_status_panel.cpp | 179 +++++++++++------ .../profile/info_profile_emoji_status_panel.h | 20 +- .../ui/effects/reaction_fly_animation.cpp | 5 +- .../ui/effects/reaction_fly_animation.h | 3 + 17 files changed, 445 insertions(+), 118 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7d8e4125c2..846b14aef9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -790,6 +790,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_color_about" = "You can choose a color to tint your name, the links you send, and replies to your messages."; "lng_settings_color_about_channel" = "You can choose a color to tint your channel's name, the links it sends, and replies to its messages."; "lng_settings_color_emoji" = "Add icons to replies"; +"lng_settings_color_emoji_remove" = "Remove icon"; "lng_settings_color_emoji_off" = "Off"; "lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them."; "lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them."; diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 71512172b1..25a7065873 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -510,40 +510,57 @@ void PeerPhoto::requestUserPhotos( _userPhotosRequests.emplace(user, requestId); } +auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & { + switch (type) { + case EmojiListType::Profile: return _profileEmojiList; + case EmojiListType::Group: return _groupEmojiList; + case EmojiListType::Background: return _backgroundEmojiList; + } + Unexpected("Type in PeerPhoto::emojiList."); +} + +auto PeerPhoto::emojiList(EmojiListType type) const +-> const EmojiListData & { + return const_cast(this)->emojiList(type); +} + void PeerPhoto::requestEmojiList(EmojiListType type) { - if (_requestIdEmojiList) { + auto &list = emojiList(type); + if (list.requestId) { return; } - const auto isGroup = (type == EmojiListType::Group); - const auto d = [=](const MTPEmojiList &result) { - _requestIdEmojiList = 0; - result.match([](const MTPDemojiListNotModified &data) { - }, [&](const MTPDemojiList &data) { - auto &list = isGroup ? _profileEmojiList : _groupEmojiList; - list = ranges::views::all( - data.vdocument_id().v - ) | ranges::views::transform(&MTPlong::v) | ranges::to_vector; - }); + const auto send = [&](auto &&request) { + return _api.request( + std::move(request) + ).done([=](const MTPEmojiList &result) { + auto &list = emojiList(type); + list.requestId = 0; + result.match([](const MTPDemojiListNotModified &data) { + }, [&](const MTPDemojiList &data) { + list.list = ranges::views::all( + data.vdocument_id().v + ) | ranges::views::transform( + &MTPlong::v + ) | ranges::to_vector; + }); + }).fail([=] { + emojiList(type).requestId = 0; + }).send(); }; - const auto f = [=] { _requestIdEmojiList = 0; }; - _requestIdEmojiList = isGroup - ? _api.request( - MTPaccount_GetDefaultGroupPhotoEmojis() - ).done(d).fail(f).send() - : _api.request( - MTPaccount_GetDefaultProfilePhotoEmojis() - ).done(d).fail(f).send(); + list.requestId = (type == EmojiListType::Profile) + ? send(MTPaccount_GetDefaultProfilePhotoEmojis()) + : (type == EmojiListType::Group) + ? send(MTPaccount_GetDefaultGroupPhotoEmojis()) + : send(MTPaccount_GetDefaultBackgroundEmojis()); } rpl::producer PeerPhoto::emojiListValue( EmojiListType type) { - auto &list = (type == EmojiListType::Group) - ? _profileEmojiList - : _groupEmojiList; - if (list.current().empty() && !_requestIdEmojiList) { + auto &list = emojiList(type); + if (list.list.current().empty() && !list.requestId) { requestEmojiList(type); } - return list.value(); + return list.list.value(); } // Non-personal photo in case a personal photo is set. diff --git a/Telegram/SourceFiles/api/api_peer_photo.h b/Telegram/SourceFiles/api/api_peer_photo.h index 03920d3ef5..1d4bd1071e 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.h +++ b/Telegram/SourceFiles/api/api_peer_photo.h @@ -31,6 +31,7 @@ public: enum class EmojiListType { Profile, Group, + Background, }; struct UserPhoto { @@ -73,6 +74,10 @@ private: Suggestion, Fallback, }; + struct EmojiListData { + rpl::variable list; + mtpRequestId requestId = 0; + }; void ready( const FullMsgId &msgId, @@ -84,6 +89,9 @@ private: UploadType type, Fn done); + [[nodiscard]] EmojiListData &emojiList(EmojiListType type); + [[nodiscard]] const EmojiListData &emojiList(EmojiListType type) const; + const not_null _session; MTP::Sender _api; @@ -101,9 +109,9 @@ private: not_null, not_null> _nonPersonalPhotos; - mtpRequestId _requestIdEmojiList = 0; - rpl::variable _profileEmojiList; - rpl::variable _groupEmojiList; + EmojiListData _profileEmojiList; + EmojiListData _groupEmojiList; + EmojiListData _backgroundEmojiList; }; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 11c674565d..d5d0762214 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/compose/compose_show.h" #include "data/data_changes.h" #include "data/data_channel.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_web_page.h" @@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "info/boosts/info_boosts_widget.h" +#include "info/profile/info_profile_emoji_status_panel.h" #include "info/info_memento.h" #include "lang/lang_keys.h" #include "main/main_account.h" @@ -105,7 +107,8 @@ public: std::shared_ptr style, std::shared_ptr theme, not_null peer, - rpl::producer colorIndexValue); + rpl::producer colorIndexValue, + rpl::producer backgroundEmojiId); ~PreviewWrap(); private: @@ -253,7 +256,8 @@ PreviewWrap::PreviewWrap( std::shared_ptr style, std::shared_ptr theme, not_null peer, - rpl::producer colorIndexValue) + rpl::producer colorIndexValue, + rpl::producer backgroundEmojiId) : RpWidget(box) , _box(box) , _peer(peer) @@ -329,6 +333,10 @@ PreviewWrap::PreviewWrap( _fake->changeColorIndex(index); update(); }, lifetime()); + std::move(backgroundEmojiId) | rpl::start_with_next([=](DocumentId id) { + _fake->changeBackgroundEmojiId(id); + update(); + }, lifetime()); const auto session = &_history->session(); session->data().viewRepaintRequest( @@ -423,22 +431,28 @@ HistoryView::Context PreviewDelegate::elementContext() { void Set( std::shared_ptr show, not_null peer, - uint8 colorIndex) { - const auto was = peer->colorIndex(); - peer->changeColorIndex(colorIndex); - peer->session().changes().peerUpdated( - peer, - Data::PeerUpdate::Flag::Color); + uint8 colorIndex, + DocumentId backgroundEmojiId) { + const auto wasIndex = peer->colorIndex(); + const auto wasEmojiId = peer->backgroundEmojiId(); + + const auto setLocal = [=](uint8 index, DocumentId emojiId) { + using UpdateFlag = Data::PeerUpdate::Flag; + peer->changeColorIndex(index); + peer->changeBackgroundEmojiId(emojiId); + peer->session().changes().peerUpdated( + peer, + UpdateFlag::Color | UpdateFlag::BackgroundEmoji); + }; + setLocal(colorIndex, backgroundEmojiId); + const auto done = [=] { show->showToast(peer->isSelf() ? tr::lng_settings_color_changed(tr::now) : tr::lng_settings_color_changed_channel(tr::now)); }; const auto fail = [=](const MTP::Error &error) { - peer->changeColorIndex(was); - peer->session().changes().peerUpdated( - peer, - Data::PeerUpdate::Flag::Color); + setLocal(wasIndex, wasEmojiId); show->showToast(error.type()); }; const auto send = [&](auto &&request) { @@ -451,14 +465,14 @@ void Set( MTP_flags( MTPaccount_UpdateColor::Flag::f_background_emoji_id), MTP_int(colorIndex), - MTP_long(peer->backgroundEmojiId()))); + MTP_long(backgroundEmojiId))); } else if (const auto channel = peer->asChannel()) { send(MTPchannels_UpdateColor( MTP_flags( MTPchannels_UpdateColor::Flag::f_background_emoji_id), channel->inputChannel, MTP_int(colorIndex), - MTP_long(peer->backgroundEmojiId()))); + MTP_long(backgroundEmojiId))); } else { Unexpected("Invalid peer type in Set(colorIndex)."); } @@ -468,10 +482,12 @@ void Apply( std::shared_ptr show, not_null peer, uint8 colorIndex, + DocumentId backgroundEmojiId, Fn close, Fn cancel) { const auto session = &peer->session(); - if (peer->colorIndex() == colorIndex) { + if (peer->colorIndex() == colorIndex + && peer->backgroundEmojiId() == backgroundEmojiId) { close(); } else if (peer->isSelf() && !session->premium()) { Settings::ShowPremiumPromoToast( @@ -486,7 +502,7 @@ void Apply( u"name_color"_q); cancel(); } else if (peer->isSelf()) { - Set(show, peer, colorIndex); + Set(show, peer, colorIndex, backgroundEmojiId); close(); } else { session->api().request(MTPpremium_GetBoostsStatus( @@ -497,7 +513,7 @@ void Apply( "channel_color_level_min", 5); if (data.vlevel().v >= required) { - Set(show, peer, colorIndex); + Set(show, peer, colorIndex, backgroundEmojiId); close(); return; } @@ -635,6 +651,121 @@ int ColorSelector::resizeGetHeight(int newWidth) { return (top - skip) + ((count % columns) ? (isize + skip) : 0); } +[[nodiscard]] object_ptr CreateEmojiIconButton( + not_null parent, + std::shared_ptr show, + std::shared_ptr style, + rpl::producer colorIndexValue, + rpl::producer emojiIdValue, + Fn emojiIdChosen) { + const auto &basicSt = st::settingsButtonNoIcon; + const auto ratio = style::DevicePixelRatio(); + const auto added = st::normalFont->spacew; + const auto emojiSize = Data::FrameSizeFromTag({}) / ratio; + const auto noneWidth = added + + st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now)); + const auto emojiWidth = added + emojiSize; + const auto rightPadding = std::max(noneWidth, emojiWidth) + + basicSt.padding.right(); + const auto st = parent->lifetime().make_state( + basicSt); + st->padding.setRight(rightPadding); + auto result = CreateButton( + parent, + tr::lng_settings_color_emoji(), + *st, + {}); + const auto raw = result.data(); + + const auto right = Ui::CreateChild(raw); + right->show(); + + struct State { + Info::Profile::EmojiStatusPanel panel; + std::unique_ptr emoji; + DocumentId emojiId = 0; + uint8 index = 0; + }; + const auto state = right->lifetime().make_state(); + state->panel.backgroundEmojiChosen( + ) | rpl::start_with_next(emojiIdChosen, raw->lifetime()); + + std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) { + state->index = index; + if (state->emoji) { + right->update(); + } + }, right->lifetime()); + + const auto session = &show->session(); + std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) { + state->emojiId = emojiId; + state->emoji = emojiId + ? session->data().customEmojiManager().create( + emojiId, + [=] { right->update(); }) + : nullptr; + right->resize( + (emojiId ? emojiWidth : noneWidth) + added, + right->height()); + right->update(); + }, right->lifetime()); + + rpl::combine( + raw->sizeValue(), + right->widthValue() + ) | rpl::start_with_next([=](QSize outer, int width) { + right->resize(width, outer.height()); + const auto skip = st::settingsButton.padding.right(); + right->moveToRight(skip - added, 0, outer.width()); + }, right->lifetime()); + + right->paintRequest( + ) | rpl::start_with_next([=] { + if (state->panel.paintBadgeFrame(right)) { + return; + } + auto p = QPainter(right); + const auto height = right->height(); + if (state->emoji) { + const auto colors = style->coloredValues(false, state->index); + state->emoji->paint(p, { + .textColor = colors.name, + .position = QPoint(added, (height - emojiSize) / 2), + .internal = { + .forceFirstFrame = true, + }, + }); + } else { + const auto &font = st::normalFont; + p.setFont(font); + p.setPen(style->windowActiveTextFg()); + p.drawText( + QPoint(added, (height - font->height) / 2 + font->ascent), + tr::lng_settings_color_emoji_off(tr::now)); + } + }, right->lifetime()); + + raw->setClickedCallback([=] { + const auto customTextColor = [=] { + return style->coloredValues(false, state->index).name; + }; + const auto controller = show->resolveWindow( + ChatHelpers::WindowUsage::PremiumPromo); + if (controller) { + state->panel.show({ + .controller = controller, + .button = right, + .currentBackgroundEmojiId = state->emojiId, + .customTextColor = customTextColor, + .backgroundEmojiMode = true, + }); + } + }); + + return result; +} + } // namespace void EditPeerColorBox( @@ -648,18 +779,21 @@ void EditPeerColorBox( struct State { rpl::variable index; + rpl::variable emojiId; bool changing = false; bool applying = false; }; const auto state = box->lifetime().make_state(); state->index = peer->colorIndex(); + state->emojiId = peer->backgroundEmojiId(); box->addRow(object_ptr( box, style, theme, peer, - state->index.value() + state->index.value(), + state->emojiId.value() ), {}); const auto appConfig = &peer->session().account().appConfig(); @@ -691,12 +825,29 @@ void EditPeerColorBox( ? tr::lng_settings_color_about() : tr::lng_settings_color_about_channel()); + AddSkip(container, st::settingsColorSampleSkip); + + container->add(CreateEmojiIconButton( + container, + show, + style, + state->index.value(), + state->emojiId.value(), + [=](DocumentId id) { state->emojiId = id; })); + + AddSkip(container, st::settingsColorSampleSkip); + AddDividerText(container, peer->isSelf() + ? tr::lng_settings_color_emoji_about() + : tr::lng_settings_color_emoji_about_channel()); + box->addButton(tr::lng_settings_apply(), [=] { if (state->applying) { return; } state->applying = true; - Apply(show, peer, state->index.current(), crl::guard(box, [=] { + const auto index = state->index.current(); + const auto emojiId = state->emojiId.current(); + Apply(show, peer, index, emojiId, crl::guard(box, [=] { box->closeBox(); }), crl::guard(box, [=] { state->applying = false; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 81ff92cf01..d100850ee2 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -662,6 +662,9 @@ statusEmojiPan: EmojiPan(defaultEmojiPan) { fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }}; fadeRight: icon {{ "fade_horizontal", windowBg }}; } +backgroundEmojiPan: EmojiPan(defaultEmojiPan) { + padding: margins(7px, 7px, 4px, 0px); +} inlineBotsScroll: ScrollArea(defaultSolidScroll) { deltat: stickerPanPadding; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index cb3b034ce6..411e1638e8 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -467,6 +467,7 @@ EmojiListWidget::EmojiListWidget( , _localSetsManager( std::make_unique(&session())) , _customRecentFactory(std::move(descriptor.customRecentFactory)) +, _customTextColor(std::move(descriptor.customTextColor)) , _overBg(st::emojiPanRadius, st().overBg) , _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg) , _picker(this, st()) @@ -476,7 +477,7 @@ EmojiListWidget::EmojiListWidget( setAttribute(Qt::WA_OpaquePaintEvent); } - if (_mode != Mode::RecentReactions) { + if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) { setupSearch(); } @@ -791,10 +792,12 @@ object_ptr EmojiListWidget::createFooter() { }; auto result = object_ptr(FooterDescriptor{ .session = &session(), + .customTextColor = _customTextColor, .paused = footerPaused, .parent = this, .st = &st(), .features = { .stickersSettings = false }, + .forceFirstFrame = (_mode == Mode::BackgroundEmoji), }); _footer = result; @@ -1027,6 +1030,14 @@ void EmojiListWidget::fillRecentFrom(const std::vector &list) { if (!id && _mode == Mode::EmojiStatus) { const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f"); _recent.push_back({ .id = { Ui::Emoji::Find(star) } }); + } else if (!id && _mode == Mode::BackgroundEmoji) { + const auto fakeId = DocumentId(5246772116543512028ULL); + const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f"); + _recent.push_back({ + .custom = resolveCustomRecent(fakeId), + .id = { Ui::Emoji::Find(no) }, + }); + _recentCustomIds.emplace(fakeId); } else { _recent.push_back({ .custom = resolveCustomRecent(id), @@ -1188,7 +1199,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { void EmojiListWidget::validateEmojiPaintContext( const ExpandingContext &context) { auto value = Ui::Text::CustomEmojiPaintContext{ - .textColor = (_mode == Mode::EmojiStatus + .textColor = (_customTextColor + ? _customTextColor() + : (_mode == Mode::EmojiStatus) ? anim::color( st::stickerPanPremium1, st::stickerPanPremium2, @@ -1199,6 +1212,7 @@ void EmojiListWidget::validateEmojiPaintContext( .scale = context.progress, .paused = On(powerSavingFlag()) || paused(), .scaled = context.expanding, + .internal = { .forceFirstFrame = (_mode == Mode::BackgroundEmoji) }, }; if (!_emojiPaintContext) { _emojiPaintContext = std::make_unique< @@ -1384,7 +1398,17 @@ void EmojiListWidget::drawRecent( _emojiPaintContext->position = position + _innerPosition + _customPosition; + const auto isResetIcon = (_mode == Mode::BackgroundEmoji) + && v::is(recent.id.data); + if (isResetIcon) { + _emojiPaintContext->textColor = st::windowSubTextFg->c; + } custom->paint(p, *_emojiPaintContext); + if (isResetIcon) { + _emojiPaintContext->textColor = _customTextColor + ? _customTextColor() + : st().textFg->c; + } } else if (const auto emoji = std::get_if(&recent.id.data)) { if (_mode == Mode::EmojiStatus) { position += QPoint( @@ -1629,6 +1653,9 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { case Mode::TopicIcon: Settings::ShowPremium(resolved, u"forum_topic_icon"_q); break; + case Mode::BackgroundEmoji: + Settings::ShowPremium(resolved, u"name_color"_q); + break; } } } @@ -1995,7 +2022,10 @@ void EmojiListWidget::refreshCustom() { const auto &sets = owner->stickers().sets(); const auto push = [&](uint64 setId, bool installed) { auto it = sets.find(setId); - if (it == sets.cend() || it->second->stickers.isEmpty()) { + if (it == sets.cend() + || it->second->stickers.isEmpty() + || (_mode == Mode::BackgroundEmoji + && !it->second->textColor())) { return; } const auto canRemove = !!(it->second->flags diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 5cc197995e..5735d7a55d 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -74,11 +74,13 @@ enum class EmojiListMode { FullReactions, RecentReactions, UserpicBuilder, + BackgroundEmoji, }; struct EmojiListDescriptor { std::shared_ptr show; EmojiListMode mode = EmojiListMode::Full; + Fn customTextColor; Fn paused; std::vector customRecentList; Fn( @@ -386,6 +388,7 @@ private: base::flat_map< DocumentId, std::unique_ptr> _customRecent; + Fn _customTextColor; int _customSingleSize = 0; bool _allowWithoutPremium = false; Ui::RoundRect _overBg; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 0c09b6dd3c..66784f7748 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -291,12 +291,14 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor) descriptor.parent, descriptor.st ? *descriptor.st : st::defaultEmojiPan) , _session(descriptor.session) -, _paused(descriptor.paused) +, _customTextColor(std::move(descriptor.customTextColor)) +, _paused(std::move(descriptor.paused)) , _features(descriptor.features) , _iconState([=] { update(); }) , _subiconState([=] { update(); }) , _selectionBg(st::emojiPanRadius, st().categoriesBgOver) -, _subselectionBg(st().iconArea / 2, st().categoriesBgOver) { +, _subselectionBg(st().iconArea / 2, st().categoriesBgOver) +, _forceFirstFrame(descriptor.forceFirstFrame) { setMouseTracking(true); _iconsLeft = st().iconSkip @@ -1345,13 +1347,16 @@ void StickersListFooter::paintSetIconToCache( const auto y = (st().footer - icon.pixh) / 2; if (icon.custom) { icon.custom->paint(p, Ui::Text::CustomEmoji::Context{ - .textColor = st().textFg->c, + .textColor = (_customTextColor + ? _customTextColor() + : st().textFg->c), .size = QSize(icon.pixw, icon.pixh), .now = now, .scale = context.progress, .position = { x, y }, .paused = paused, .scaled = context.expanding, + .internal = { .forceFirstFrame = _forceFirstFrame }, }); } else if (icon.lottie && icon.lottie->ready()) { const auto frame = icon.lottie->frame(); @@ -1428,11 +1433,13 @@ void StickersListFooter::paintSetIconToCache( return icons[index]; }; const auto paintOne = [&](int left, const style::icon *icon) { - icon->paint( - p, - left + (_singleWidth - icon->width()) / 2, - (st().footer - icon->height()) / 2, - width()); + left += (_singleWidth - icon->width()) / 2; + const auto top = (st().footer - icon->height()) / 2; + if (_customTextColor) { + icon->paint(p, left, top, width(), _customTextColor()); + } else { + icon->paint(p, left, top, width()); + } }; if (_icons[info.index].setId == AllEmojiSectionSetId() && info.width > _singleWidth) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index f7a0afc1d4..5dbdd30f9d 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -115,10 +115,12 @@ class StickersListFooter final : public TabbedSelector::InnerFooter { public: struct Descriptor { not_null session; + Fn customTextColor; Fn paused; not_null parent; const style::EmojiPan *st = nullptr; ComposeFeatures features; + bool forceFirstFrame = false; }; explicit StickersListFooter(Descriptor &&descriptor); @@ -269,6 +271,7 @@ private: void clipCallback(Media::Clip::Notification notification, uint64 setId); const not_null _session; + const Fn _customTextColor; const Fn _paused; const ComposeFeatures _features; @@ -303,6 +306,7 @@ private: int _subiconsWidth = 0; bool _subiconsExpanded = false; bool _repaintScheduled = false; + bool _forceFirstFrame = false; rpl::event_stream<> _openSettingsRequests; rpl::event_stream _setChosen; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 43b08c4a42..4860481e70 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -331,7 +331,7 @@ TabbedSelector::TabbedSelector( Mode mode) : TabbedSelector(parent, { .show = std::move(show), - .st = (mode == Mode::EmojiStatus + .st = ((mode == Mode::EmojiStatus || mode == Mode::BackgroundEmoji) ? st::statusEmojiPan : st::defaultEmojiPan), .level = level, @@ -347,6 +347,7 @@ TabbedSelector::TabbedSelector( , _features(descriptor.features) , _show(std::move(descriptor.show)) , _level(descriptor.level) +, _customTextColor(std::move(descriptor.customTextColor)) , _mode(descriptor.mode) , _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg)) , _categoriesRounding( @@ -512,7 +513,10 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) { .show = _show, .mode = (_mode == Mode::EmojiStatus ? EmojiMode::EmojiStatus + : _mode == Mode::BackgroundEmoji + ? EmojiMode::BackgroundEmoji : EmojiMode::Full), + .customTextColor = _customTextColor, .paused = paused, .st = &_st, .features = _features, diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index d41aebb809..109ccead60 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -81,6 +81,7 @@ enum class TabbedSelectorMode { EmojiOnly, MediaEditor, EmojiStatus, + BackgroundEmoji, }; struct TabbedSelectorDescriptor { @@ -88,6 +89,7 @@ struct TabbedSelectorDescriptor { const style::EmojiPan &st; PauseReason level = {}; TabbedSelectorMode mode = TabbedSelectorMode::Full; + Fn customTextColor; ComposeFeatures features; }; @@ -272,6 +274,7 @@ private: const ComposeFeatures _features; const std::shared_ptr _show; const PauseReason _level = {}; + const Fn _customTextColor; Mode _mode = Mode::Full; int _roundRadius = 0; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp index f9db8da020..347a3f13ca 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp @@ -53,7 +53,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) { | (data.is_masks() ? Flag::Masks : Flag()) | (data.is_emojis() ? Flag::Emoji : Flag()) | (data.vinstalled_date() ? Flag::Installed : Flag()) - | (data.is_videos() ? Flag::Webm : Flag()); + | (data.is_videos() ? Flag::Webm : Flag()) + | (data.is_text_color() ? Flag::TextColor : Flag()); } StickersSet::StickersSet( @@ -108,6 +109,10 @@ StickersType StickersSet::type() const { : StickersType::Stickers; } +bool StickersSet::textColor() const { + return flags & StickersSetFlag::TextColor; +} + void StickersSet::setThumbnail(const ImageWithLocation &data) { Data::UpdateCloudFile( _thumbnail, diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.h b/Telegram/SourceFiles/data/stickers/data_stickers_set.h index 4fb30b3d10..bf9a6e4177 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.h @@ -57,6 +57,7 @@ enum class StickersSetFlag { Special = (1 << 7), Webm = (1 << 8), Emoji = (1 << 9), + TextColor = (1 << 10), }; inline constexpr bool is_flag_type(StickersSetFlag) { return true; }; using StickersSetFlags = base::flags; @@ -84,6 +85,7 @@ public: [[nodiscard]] MTPInputStickerSet mtpInput() const; [[nodiscard]] StickerSetIdentifier identifier() const; [[nodiscard]] StickersType type() const; + [[nodiscard]] bool textColor() const; void setThumbnail(const ImageWithLocation &data); diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp index 7a9e18b927..db8714c705 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_emoji_status_panel.h" +#include "api/api_peer_photo.h" +#include "apiwrap.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/data_document.h" @@ -66,26 +68,17 @@ void EmojiStatusPanel::show( not_null controller, not_null button, Data::CustomEmojiSizeTag animationSizeTag) { - const auto self = controller->session().user(); - const auto &statuses = controller->session().data().emojiStatuses(); - const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent); - const auto &other = statuses.list(Data::EmojiStatuses::Type::Default); - auto list = statuses.list(Data::EmojiStatuses::Type::Colored); - list.insert(begin(list), 0); - if (list.size() > kLimitFirstRow) { - list.erase(begin(list) + kLimitFirstRow, end(list)); - } - list.reserve(list.size() + recent.size() + other.size() + 1); - for (const auto &id : ranges::views::concat(recent, other)) { - if (!ranges::contains(list, id)) { - list.push_back(id); - } - } - if (!ranges::contains(list, self->emojiStatusId())) { - list.push_back(self->emojiStatusId()); - } + show({ + .controller = controller, + .button = button, + .animationSizeTag = animationSizeTag, + }); +} + +void EmojiStatusPanel::show(Descriptor &&descriptor) { + const auto controller = descriptor.controller; if (!_panel) { - create(controller); + create(descriptor); _panel->shownValue( ) | rpl::filter([=] { @@ -98,23 +91,67 @@ void EmojiStatusPanel::show( } }, _panel->lifetime()); } + const auto button = descriptor.button; if (const auto previous = _panelButton.data()) { if (previous != button) { previous->removeEventFilter(_panel.get()); } } _panelButton = button; - _animationSizeTag = animationSizeTag; - _panel->selector()->provideRecentEmoji(list); + _animationSizeTag = descriptor.animationSizeTag; + auto list = std::vector(); + if (descriptor.backgroundEmojiMode) { + controller->session().api().peerPhoto().emojiListValue( + Api::PeerPhoto::EmojiListType::Background + ) | rpl::start_with_next([=](std::vector &&list) { + list.insert(begin(list), 0); + if (const auto now = descriptor.currentBackgroundEmojiId) { + if (!ranges::contains(list, now)) { + list.push_back(now); + } + } + _panel->selector()->provideRecentEmoji(list); + }, _panel->lifetime()); + } else { + const auto self = controller->session().user(); + const auto &statuses = controller->session().data().emojiStatuses(); + const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent); + const auto &other = statuses.list(Data::EmojiStatuses::Type::Default); + auto list = statuses.list(Data::EmojiStatuses::Type::Colored); + list.insert(begin(list), 0); + if (list.size() > kLimitFirstRow) { + list.erase(begin(list) + kLimitFirstRow, end(list)); + } + list.reserve(list.size() + recent.size() + other.size() + 1); + for (const auto &id : ranges::views::concat(recent, other)) { + if (!ranges::contains(list, id)) { + list.push_back(id); + } + } + if (!ranges::contains(list, self->emojiStatusId())) { + list.push_back(self->emojiStatusId()); + } + _panel->selector()->provideRecentEmoji(list); + } const auto parent = _panel->parentWidget(); const auto global = button->mapToGlobal(QPoint()); const auto local = parent->mapFromGlobal(global); - _panel->moveTopRight( - local.y() + button->height() - (st::normalFont->height / 2), - local.x() + button->width() * 3); + if (descriptor.backgroundEmojiMode) { + _panel->moveBottomRight( + local.y() + (st::normalFont->height / 2), + local.x() + button->width() * 3); + } else { + _panel->moveTopRight( + local.y() + button->height() - (st::normalFont->height / 2), + local.x() + button->width() * 3); + } _panel->toggleAnimated(); } +void EmojiStatusPanel::repaint() { + _panel->selector()->update(); +} + bool EmojiStatusPanel::paintBadgeFrame(not_null widget) { if (!_animation) { return false; @@ -125,19 +162,31 @@ bool EmojiStatusPanel::paintBadgeFrame(not_null widget) { return false; } -void EmojiStatusPanel::create( - not_null controller) { +void EmojiStatusPanel::create(const Descriptor &descriptor) { using Selector = ChatHelpers::TabbedSelector; + using Descriptor = ChatHelpers::TabbedSelectorDescriptor; + using Mode = ChatHelpers::TabbedSelector::Mode; + const auto controller = descriptor.controller; const auto body = controller->window().widget()->bodyWidget(); _panel = base::make_unique_q( body, controller, object_ptr( nullptr, - controller->uiShow(), - Window::GifPauseReason::Layer, - ChatHelpers::TabbedSelector::Mode::EmojiStatus)); - _panel->setDropDown(true); + Descriptor{ + .show = controller->uiShow(), + .st = (descriptor.backgroundEmojiMode + ? st::backgroundEmojiPan + : st::statusEmojiPan), + .level = Window::GifPauseReason::Layer, + .mode = (descriptor.backgroundEmojiMode + ? Mode::BackgroundEmoji + : Mode::EmojiStatus), + .customTextColor = descriptor.customTextColor, + })); + _customTextColor = descriptor.customTextColor; + _backgroundEmojiMode = descriptor.backgroundEmojiMode; + _panel->setDropDown(!_backgroundEmojiMode); _panel->setDesiredHeightValues( 1., st::emojiPanMinHeight / 2, @@ -169,34 +218,46 @@ void EmojiStatusPanel::create( return Chosen{ .animation = data.messageSendingFrom }; }); - const auto weak = Ui::MakeWeak(_panel.get()); - const auto accept = [=](Chosen chosen) { - Expects(chosen.until != Selector::kPickCustomTimeId); - - // From PickUntilBox is called after EmojiStatusPanel is destroyed! - const auto owner = &controller->session().data(); - if (weak) { + if (descriptor.backgroundEmojiMode) { + rpl::merge( + std::move(statusChosen), + std::move(emojiChosen) + ) | rpl::start_with_next([=](const Chosen &chosen) { + const auto owner = &controller->session().data(); startAnimation(owner, body, chosen.id, chosen.animation); - } - owner->emojiStatuses().set(chosen.id, chosen.until); - }; + _backgroundEmojiChosen.fire_copy(chosen.id); + _panel->hideAnimated(); + }, _panel->lifetime()); + } else { + const auto weak = Ui::MakeWeak(_panel.get()); + const auto accept = [=](Chosen chosen) { + Expects(chosen.until != Selector::kPickCustomTimeId); - rpl::merge( - std::move(statusChosen), - std::move(emojiChosen) - ) | rpl::filter([=](const Chosen &chosen) { - return filter(controller, chosen.id); - }) | rpl::start_with_next([=](const Chosen &chosen) { - if (chosen.until == Selector::kPickCustomTimeId) { - _panel->hideAnimated(); - controller->show(Box(PickUntilBox, [=](TimeId seconds) { - accept({ chosen.id, base::unixtime::now() + seconds }); - })); - } else { - accept(chosen); - _panel->hideAnimated(); - } - }, _panel->lifetime()); + // PickUntilBox calls this after EmojiStatusPanel is destroyed! + const auto owner = &controller->session().data(); + if (weak) { + startAnimation(owner, body, chosen.id, chosen.animation); + } + owner->emojiStatuses().set(chosen.id, chosen.until); + }; + + rpl::merge( + std::move(statusChosen), + std::move(emojiChosen) + ) | rpl::filter([=](const Chosen &chosen) { + return filter(controller, chosen.id); + }) | rpl::start_with_next([=](const Chosen &chosen) { + if (chosen.until == Selector::kPickCustomTimeId) { + _panel->hideAnimated(); + controller->show(Box(PickUntilBox, [=](TimeId seconds) { + accept({ chosen.id, base::unixtime::now() + seconds }); + })); + } else { + accept(chosen); + _panel->hideAnimated(); + } + }, _panel->lifetime()); + } } bool EmojiStatusPanel::filter( @@ -223,13 +284,17 @@ void EmojiStatusPanel::startAnimation( .id = { { statusId } }, .flyIcon = from.frame, .flyFrom = body->mapFromGlobal(from.globalStartGeometry), + .forceFirstFrame = _backgroundEmojiMode, }; + const auto color = _customTextColor + ? _customTextColor + : [] { return st::profileVerifiedCheckBg->c; }; _animation = std::make_unique( body, &owner->reactions(), std::move(args), [=] { _animation->repaint(); }, - [] { return st::profileVerifiedCheckBg->c; }, + _customTextColor, _animationSizeTag); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h index 3d4373665f..246b171fe0 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h @@ -47,10 +47,25 @@ public: not_null button, Data::CustomEmojiSizeTag animationSizeTag = {}); + struct Descriptor { + not_null controller; + not_null button; + Data::CustomEmojiSizeTag animationSizeTag = {}; + DocumentId currentBackgroundEmojiId = 0; + Fn customTextColor; + bool backgroundEmojiMode = false; + }; + void show(Descriptor &&descriptor); + void repaint(); + + [[nodiscard]] rpl::producer backgroundEmojiChosen() const { + return _backgroundEmojiChosen.events(); + } + bool paintBadgeFrame(not_null widget); private: - void create(not_null controller); + void create(const Descriptor &descriptor); [[nodiscard]] bool filter( not_null controller, DocumentId chosenId) const; @@ -62,10 +77,13 @@ private: Ui::MessageSendingAnimationFrom from); base::unique_qptr _panel; + Fn _customTextColor; Fn _chooseFilter; QPointer _panelButton; std::unique_ptr _animation; + rpl::event_stream _backgroundEmojiChosen; Data::CustomEmojiSizeTag _animationSizeTag = {}; + bool _backgroundEmojiMode = false; }; diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp index 8a271233d8..395e38faeb 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp @@ -69,7 +69,8 @@ ReactionFlyAnimation::ReactionFlyAnimation( , _repaint(std::move(repaint)) , _flyFrom(args.flyFrom) , _scaleOutDuration(args.scaleOutDuration) -, _scaleOutTarget(args.scaleOutTarget) { +, _scaleOutTarget(args.scaleOutTarget) +, _forceFirstFrame(args.forceFirstFrame) { const auto &list = owner->list(::Data::Reactions::Type::All); auto centerIcon = (DocumentData*)nullptr; auto aroundAnimation = (DocumentData*)nullptr; @@ -251,6 +252,7 @@ void ReactionFlyAnimation::paintCenterFrame( target.x() + (target.width() - _customSize) / 2, target.y() + (target.height() - _customSize) / 2), .scaled = scaled, + .internal = { .forceFirstFrame = _forceFirstFrame }, }); } } @@ -278,6 +280,7 @@ void ReactionFlyAnimation::paintMiniCopies( .size = size, .now = now, .scaled = true, + .internal = { .forceFirstFrame = _forceFirstFrame }, }; for (const auto &mini : _miniCopies) { if (progress >= mini.duration) { diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h index 50aae6dc8d..0ea353f3bf 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h @@ -31,6 +31,7 @@ struct ReactionFlyAnimationArgs { float64 scaleOutTarget = 0.; float64 miniCopyMultiplier = 1.; bool effectOnly = false; + bool forceFirstFrame = false; [[nodiscard]] ReactionFlyAnimationArgs translated(QPoint point) const; }; @@ -42,6 +43,7 @@ struct ReactionFlyCenter { float64 centerSizeMultiplier = 0.; int customSize = 0; int size = 0; + bool forceFirstFrame = false; }; class ReactionFlyAnimation final { @@ -121,6 +123,7 @@ private: crl::time _scaleOutDuration = 0; float64 _scaleOutTarget = 0.; bool _noEffectScaleStarted = false; + bool _forceFirstFrame = false; bool _effectOnly = false; bool _valid = false; From c2a1817400ea285641367ee4c1a1b36f91dd7806 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 20:19:09 +0400 Subject: [PATCH 73/99] Always use first color as the main one. --- Telegram/SourceFiles/ui/chat/chat_style.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index b0b23e1d33..7055248db9 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -781,9 +781,7 @@ ColorIndexValues ChatStyle::computeColorIndexValues( }; result.bg = result.outlines[0]; result.bg.setAlpha(0.12 * 255); - result.name = (_dark && colorPatternIndex(colorIndex) == 1) - ? result.outlines[1] - : result.outlines[0]; + result.name = result.outlines[0]; return result; } From b40f30ca98f091f8228ef6ef20537dd82458043a Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 20:28:46 +0400 Subject: [PATCH 74/99] Update Implib.so. --- cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake b/cmake index fef4eba1b7..60474a5766 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit fef4eba1b747d3bb8d817386da72992167c277d2 +Subproject commit 60474a576690f63510d87d18ca348d9875c89f08 From b180070ba33fa8f3e205c71bc8dd0d409e9638cc Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 20:48:50 +0400 Subject: [PATCH 75/99] Improve very small quote areas with icons. --- Telegram/Resources/icons/chat/mini_quote.png | Bin 374 -> 1544 bytes .../Resources/icons/chat/mini_quote@2x.png | Bin 570 -> 1710 bytes .../Resources/icons/chat/mini_quote@3x.png | Bin 849 -> 1912 bytes Telegram/SourceFiles/ui/chat/chat.style | 2 +- Telegram/lib_ui | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/icons/chat/mini_quote.png b/Telegram/Resources/icons/chat/mini_quote.png index 08a844820e4623791cdd92909d45cdcaf4cd7efd..7c021f6ef96e5b0666950b1998eff6c7ca88bcbc 100644 GIT binary patch literal 1544 zcmbVMO=uid9G^zfHBG5dDqBD5IPs8TXXnjqW_M@Ew8q^{ccYV}>DH`)if`Y%-5s(s zZ!$BR-3_D^A!;uY9DhH| zFc01^b9q0nzoa5!MW8_nI8wJ9ie{Lhy>$no8SJwKTrzEqyZOaMjx`O9o04^)J4swN zGxIJ!Fh7<<^D{^>xV?MWp}I;5EbK$JZdGhgt!rG9SEcLVSm4+u#Glc);Xojp*C*H{ zaWShzL>>VkvT?c^j7bSa+09BoiV7emNHQ-06^N=Vv+a+g;#{MsPNqiM!st%p%D(TY zf>5i~BDH9QxFtbU6h#1%AW1w$@Lt3AVV$?V{tiP5d&o5%-y}90Fv0?<`Wi=7ZP{Qs zU0T~~`-yr?s6$5(BOtJ;2{cd_=TzNFQ@DWyT)`H$eUD^U%~0?>l$#4wAEjZi6(qXCX4 zD0%~w=8OS-cu%l_)FN>$NDVeESi*v1mpHcTMm0$)#HE6%cG0F5x~^tz&xba`*_6gn znE=UPAV|p}Ljdn#Gz_aaD#bldVph6U6x5l9$tk~e%$L4)|pha6NV@moT;cme^ zQuJ%k#lt1)t$U`EK$RDQI&9e?{L{qd-5oXCfzw71EW0g;Zn{kj+q84Jv_)MX=sQ8% z+gI7taBg7x`AfsS>F1w(_?O!wqp{AmHnuO2-3nf1hxW%I3%cVDg^+VrXQ;!5S`Jxj@5 z--P?Q%V+;uxHf(3!uZM`pFaeyM0ce3Gx=kShi4B>g??@9+_7{$^h_B2Ira4FR{Kcr T*6^1>ug<2&QfFRx?e)I_+BWx1 literal 374 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$e9(cMqhG>Xh z?f2($4Ujnc^^dQb(FP`FwiMo zvdl}2eX9(3EZ?c7I0SzH~UI!U-?S1rP?S+nvwXBVFZli&1;N11@wY{yqJMOM) zR~HQvkp&f$2yB6%Bx;-vRD!sSWpQEA#%MInG7>d7C4NK^jUmGTi|@L&VFo2~$-DR7 zz2E!&zyH_Y*ic`xWc3mRK}tMsXEU5pICO>i@LfiRSU43)?p6gsiXYHM9x`}Lhaii7 z<9#h^i+2mniV-8jiGE;=M>EEY4yOh!=-;-uYf#|a9jC<8P7CK)9E3px2&w|HWDYBZL{(Hm;=fSm zju!}kzV&)@GVZ8LB$5-MsIG2kM#dp`L@T}?3E<5@5u-8-T-{Jjy=IL>*U5lUMcF5c z;cTQDvnr#M#YmtP+j)T#W6Jv5E&wN^0tco=%|H?c(&Qs8G)d4F>m!7jCWv{cSLFCW z&q63=^T7a{Y&2zB0EIcjF)DLcFvrpXQI0UsU_QbG0WJwa49&Tbt`oze3j(Y&%+ckpvD*()CQrq?wU?GR36vraNZ95pC+cyZmZcW95w=NnQK9C zlWSr?fSpT*Eoy2<J36p!?-Tp>xrp@_Z?4+>%w(ro zH{3UgwwbRlH(y+||Fc(FMIR;pwjLz80#9 z{gZvRH~5M1O|Nx)W$j76zjWP^Pg@VW+V?W(L3yN?KK=<9{br)6@|71q){p!;`{bT4 zgmcL+uXN8$g{?_axAguA;hety_2%8vfin-+4{xh$9N6;1ja#McV4%Eo_oG!SZ}iq& zXgR#LFu$+!Y}@6Zt|fl&G_Ss_dtq}SbGB{ypHGb+N|v8rcc_T`AQC*&x~H*fcw7z6Q=2;a{syewNxA?4 delta 558 zcmZ3-yNhLlc)bD#69WUoABne%ffQqLkh>GZx^prwfgG3A2+uTMUj{88n}dO|J(Gb2 zqy&hifS7@Sc>yB>GmvHki7jA)%PK5jMzBE&*R&;l1}a

EamT!TWZ~?p22z1lqm| z2yjb(tal68D7Z%C&703$O>5UbV7|eXc{D^UOF*ZFxq7RoppJ^~ga@h#3au_4ETxGF zl4c)^W);uM`TD-@^Z9e<@`O5Dl;1eqX#D@$!EXNivuVpOpET%sSTMs!tx)%7PS~`k zhaYaZ|Ni*nLbb^UzifV1d2iDicBx(K)P*<;mIbYR{ITOfee;_mAs-U^Po^~eT~NJO z?uLrzr^|Kw|0`-9TcZ05%dIBa=yrH3m(~c%N zD!=}^Ymvqbz3JXw^@_Xd@9LdZ>QZ|Aro?Ir9~*PqA(J0`87mYxLVjl-G5L_R!aPEx z`{?h#e5SXy$#x$th_RZx?{1#M$*C943mg~t`;KL8n6^}JQ=*B~&UM{q&!$a&Y!NZj zZ~64ote4k+sj+Kkl0AO%X;Dvm4(F6T+k;jfxv+lO)~Mb;O+J@Z+&@ZdP3=-&mihei n&bxWPGj|A;DZDulU-p1=k)5CKrEg8Opt$pN^>bP0l+XkK^8@hs diff --git a/Telegram/Resources/icons/chat/mini_quote@3x.png b/Telegram/Resources/icons/chat/mini_quote@3x.png index 2399ff033c025b7aad46c14c65b065d5344b0446..2c572bd02c85bec4ee2d72543a704b64a225b33f 100644 GIT binary patch literal 1912 zcmbVNeNYr-9A41TJW((|LLge#G)(XI)QA{H{R2ldC2aa&)EK65u#B()zXRuT=-mTuw6UDrncaPN_j#Y+ z@A-Z0=G>fx6Etxe2!bYLXW8<=9Rug{Pep=j`U|=@z-^qEwOodv@srgv0@}4F7J^3i zagKZ?-@cfn1)q*$1ShKt_(Xt)AWK?6q-YPTz)rT1OErF33P8tmG@PPnx65}HCY%dQR15Z}O zttcXiqJF<$=hy25sSw4@W;2QrC_!ieLMxZ@3Kh`u^2`wi8!OWiCn}u4!zv@?6p9rq z0$d%^;1k2NygVEy5HU1Bi72kaR81iuLx*uyBYpAt!X{)TvjoT(4#*?XvZGXF(L7cbiY1!OECFuLRMm)NhQv~e zAUOoVJCdo~5tm`Ypu^y#WgO24etFIV8(15quvSFPnij{jxZZ&qNE{;##<^HBiD5%f zyTEX+(ubjh$pI3qH<9|3hoGQl7)qfY3ub82B}hIB2~(> zclwYQb~~BP%L>KQY_`pc0L?m%V@S$Oxb(QmsWs6+fQd~`(VE#5LTjXrm@%0p40;1T zvfn1q#cF0p_A~!;{}PD4ASqg{ZYx5o zX=NE?_%-*$5g4NNyIDZ`AL$-~$%0GqQxdzN5Jc;-<%9y~QMC_;bfEu~82a`=&yK*s zAW)a#5dLyYCYCI5W#CKkMaq5*MWXFLHZz^_th3<@V zM@v@>mAZG;p$mLd=lD(Ci|S7Oa4rPGEEv)J`u-mR z)pO&joLzYbZ4uqlu_f&%|D>PUd*Ek&?4es5>+*Vflus(QpWSam?j_}7v1`6>b=F&` z58E=w&rjI4J@V@9(t_xtmx3GV`dIDO&MT|huJ^Z>LAN*dmdw}gSv)&U!(g#)>zdTd zlWSU;uS8_Wz?~KS!Tal?Mqg9Nb%|MrF7)-krHO9pyuW^`=1j?nx{8c_G3#Gth}xRA zS+!#wnrRo^$qgM_ql~-1b^q3wn{+2&=`D#bgtnY@)>rm+DP)mv)bW)^ZzR57=V~k{ zN^I$>jxxqonF96sFyA2^+j_ggIb*}pgHtB*jRlpXs?O41;&w+BWup21*nchu~Q<&6cIwPVU>C6#<0aX$XtYb%dl${!F- zCleM<&w^u4F4=PToU37qg|9hPHR=9Gz>nsrF&iSH;x(}+*!CXBylI22*2}I2mYBY? v+H2fBZPUIzIiGduDhB51ou_|KYmfeb`r|;#_2ygZzcD*A$9CkU)$9KTX^XBd delta 839 zcmeytcad#^c)bA!69WUojfA`lK#H+A$lZxy-8q?;K#oglglC$sFM}44&B4Ifp2@%h zQUb(MK+M3vynvB`8AvmN#1=5YWepZEBiJB?x6N~S7#Ns#d%8G=L~yXa7__mUhZdEX|&K-Do| zY7;$V_}E$4f4+U|yE5d`gJPWtzU!|$zu6I>BgX00>U1&3tdM8Y>#x7+%^eIpR<8`< znt18Z=g-D|AAkMwN}ix9*8T87@ssv;_B}s3q&Qb!h<(%l_W#KMlt5^fB)KA%k6Pn9C%<3 zi`e3+9vMb6J6x5lf2FE723jb3s9bqj(!mhb8YkFuH*fkkAu$%UHy%J$7BbVkR9n)x zPd_!9etM(fq`1x&1v{b695ZP)K_1U1y>5#O-c9i0wwUql-?jRr?YG&R8Ebo3xnndf zCf>}++kQJKVfp3G=JV4Z9{`o~r@M?};EA}bQEyG0W$g)_y z=@O+20~8J%ObFnq>APE>v{9zty{5OnpWjfag7a;ewG0oxvWe5tMF;8*=~q`)D*iH` ze6l03{cvJN?j3G+rdd+k|9G~ABpi5P-DY@V$1eB9f_!bB%dWrvKZQ|{=eork{&?mz zg~o%S66_WdaXmY}8x#Z4E1&oXALcA`s5pCIg>3)v zf~ExrZzecY+046{Gwu9y{hPLLLU&FNZ$Q8m2kaN~#wy%3b>89ZJ6T-G@yGywpV CCvKSl diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index fd5affd421..e0fe81fdcb 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -57,7 +57,7 @@ messageTextStyle: TextStyle(defaultTextStyle) { blockquote: QuoteStyle(messageQuoteStyle) { padding: margins(10px, 2px, 20px, 2px); icon: icon{{ "chat/mini_quote", windowFg }}; - iconPosition: point(4px, 2px); + iconPosition: point(4px, 4px); } pre: QuoteStyle(messageQuoteStyle) { header: 20px; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 611224c52f..272a23e9bc 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 611224c52f3192f616018fc7a2c5930667531084 +Subproject commit 272a23e9bce7d03cc167d3dd4589ce674398c795 From 49bc8ccd6ae31966fcc7e4ff0d9a73728162d7d1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 20:49:01 +0400 Subject: [PATCH 76/99] Don't gray out background emoji reset icon. --- .../SourceFiles/chat_helpers/emoji_list_widget.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 411e1638e8..2a51048b56 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -1398,17 +1398,7 @@ void EmojiListWidget::drawRecent( _emojiPaintContext->position = position + _innerPosition + _customPosition; - const auto isResetIcon = (_mode == Mode::BackgroundEmoji) - && v::is(recent.id.data); - if (isResetIcon) { - _emojiPaintContext->textColor = st::windowSubTextFg->c; - } custom->paint(p, *_emojiPaintContext); - if (isResetIcon) { - _emojiPaintContext->textColor = _customTextColor - ? _customTextColor() - : st().textFg->c; - } } else if (const auto emoji = std::get_if(&recent.id.data)) { if (_mode == Mode::EmojiStatus) { position += QPoint( From c480ab1a3b1c050e103195b907a2125f2da87a38 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 21:13:52 +0400 Subject: [PATCH 77/99] Fix possible crash in non-cancelled request. --- Telegram/SourceFiles/data/data_group_call.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 613920d990..0567477c4d 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -123,6 +123,7 @@ void GroupCall::requestParticipants() { return; } } + api().request(base::take(_participantsRequestId)).cancel(); _participantsRequestId = api().request(MTPphone_GetGroupParticipants( input(), MTP_vector(), // ids @@ -132,8 +133,8 @@ void GroupCall::requestParticipants() { : _nextOffset), MTP_int(kRequestPerPage) )).done([=](const MTPphone_GroupParticipants &result) { + _participantsRequestId = 0; result.match([&](const MTPDphone_groupParticipants &data) { - _participantsRequestId = 0; const auto reloaded = processSavedFullCall(); _nextOffset = qs(data.vnext_offset()); _peer->owner().processUsers(data.vusers()); @@ -168,7 +169,7 @@ bool GroupCall::processSavedFullCall() { if (!_savedFull) { return false; } - _reloadRequestId = 0; + api().request(base::take(_reloadRequestId)).cancel(); _reloadLastFinished = crl::now(); processFullCallFields(*base::take(_savedFull)); return true; @@ -511,10 +512,8 @@ void GroupCall::reloadIfStale() { void GroupCall::reload() { if (_reloadRequestId || _applyingQueuedUpdates) { return; - } else if (_participantsRequestId) { - api().request(_participantsRequestId).cancel(); - _participantsRequestId = 0; } + api().request(base::take(_participantsRequestId)).cancel(); DEBUG_LOG(("Group Call Participants: " "Reloading with queued: %1" From fe0e526b79f05fc44d9a3cd306d789ba0df7e5ca Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 21:26:56 +0400 Subject: [PATCH 78/99] Support phrases for unclaimed giveaway prizes. --- Telegram/Resources/langs/lang.strings | 5 ++++ .../SourceFiles/boxes/gift_premium_box.cpp | 30 +++++++++++++------ Telegram/SourceFiles/data/data_media_types.h | 1 + .../export/data/export_data_types.cpp | 1 + .../export/data/export_data_types.h | 1 + .../export/output/export_output_html.cpp | 7 ++++- .../export/output/export_output_json.cpp | 1 + Telegram/SourceFiles/history/history_item.cpp | 5 +++- .../view/media/history_view_premium_gift.cpp | 10 +++++-- 9 files changed, 48 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 846b14aef9..89705be165 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2111,6 +2111,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prize_gift_about" = "You've received a gift from {channel}."; "lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription {duration}."; "lng_prize_open" = "Open Gift Link"; +"lng_prize_unclaimed_title" = "Unclaimed Prize"; +"lng_prize_unclaimed_about" = "You have an unclaimed prize from a giveaway by {channel}."; +"lng_prize_unclaimed_duration" = "This prize is a **Telegram Premium** subscription {duration}."; "lng_prizes_title#one" = "Giveaway Prize"; "lng_prizes_title#other" = "Giveaway Prizes"; @@ -2161,10 +2164,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription."; "lng_gift_link_label_from" = "From"; "lng_gift_link_label_to" = "To"; +"lng_gift_link_label_to_unclaimed" = "No recipient"; "lng_gift_link_label_gift" = "Gift"; "lng_gift_link_gift_premium" = "Telegram Premium {duration}"; "lng_gift_link_label_reason" = "Reason"; "lng_gift_link_reason_giveaway" = "Giveaway"; +"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway"; "lng_gift_link_reason_chosen" = "You were selected by the channel"; "lng_gift_link_label_date" = "Date"; "lng_gift_link_also_send" = "You can also {link} to a friend as a gift."; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 84c2db7e65..c7b59f44be 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -477,11 +477,18 @@ void GiftCodeBox( tr::lng_gift_link_label_from(), controller, current.from); - AddTableRow( - table, - tr::lng_gift_link_label_to(), - controller, - current.to); + if (current.to) { + AddTableRow( + table, + tr::lng_gift_link_label_to(), + controller, + current.to); + } else { + AddTableRow( + table, + tr::lng_gift_link_label_to(), + tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities)); + } AddTableRow( table, tr::lng_gift_link_label_gift(), @@ -493,11 +500,16 @@ void GiftCodeBox( table, tr::lng_gift_link_label_reason(), (current.giveawayId - ? (tr::lng_gift_link_reason_giveaway() | Ui::Text::ToLink()) + ? ((current.to + ? tr::lng_gift_link_reason_giveaway + : tr::lng_gift_link_reason_unclaimed)( + ) | Ui::Text::ToLink()) : current.giveaway - ? (tr::lng_gift_link_reason_giveaway( - Ui::Text::WithEntities - ) | rpl::type_erased()) + ? ((current.to + ? tr::lng_gift_link_reason_giveaway + : tr::lng_gift_link_reason_unclaimed)( + Ui::Text::WithEntities + ) | rpl::type_erased()) : tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities))); reason->setClickHandlerFilter([=](const auto &...) { controller->showPeerHistory( diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 02d321720d..9af199b4dd 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -104,6 +104,7 @@ struct GiftCode { ChannelData *channel = nullptr; int months = 0; bool viaGiveaway = false; + bool unclaimed = false; }; class Media { diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index bd7203cdb4..bbfcd37aad 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1330,6 +1330,7 @@ ServiceAction ParseServiceAction( ? peerFromMTP(*data.vboost_peer()) : PeerId(); content.viaGiveaway = data.is_via_giveaway(); + content.unclaimed = data.is_unclaimed(); content.months = data.vmonths().v; content.code = data.vslug().v; result.content = content; diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index e670c9a5c0..99848724c2 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -544,6 +544,7 @@ struct ActionGiftCode { PeerId boostPeerId = 0; int months = 0; bool viaGiveaway = false; + bool unclaimed = false; }; struct ActionRequestedPeer { diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 28018e8884..c2cb8d46e8 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1280,7 +1280,12 @@ auto HtmlWriter::Wrap::pushMessage( + wrapReplyToLink("the same background") + " for this chat"; }, [&](const ActionGiftCode &data) { - return data.viaGiveaway + return data.unclaimed + ? ("This is an unclaimed Telegram Premium for " + + NumberToString(data.months) + + (data.months > 1 ? " months" : "month") + + " prize in a giveaway organized by a channel.") + : data.viaGiveaway ? ("You won a Telegram Premium for " + NumberToString(data.months) + (data.months > 1 ? " months" : "month") diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 02545e1fdd..8cdbb5dbba 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -600,6 +600,7 @@ QByteArray SerializeMessage( push("boost_peer_id", data.boostPeerId); } push("months", data.months); + push("unclaimed", data.unclaimed); push("via_giveaway", data.viaGiveaway); }, [&](const ActionGiveawayLaunch &data) { pushAction("giveaway_launch"); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 6fafd5465a..bc2b4b5031 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4477,7 +4477,9 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { const auto months = action.vmonths().v; result.text = { - (action.is_via_giveaway() + (action.is_unclaimed() + ? tr::lng_prize_unclaimed_about + : action.is_via_giveaway() ? tr::lng_prize_about : tr::lng_prize_gift_about)( tr::now, @@ -4668,6 +4670,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { : nullptr), .months = data.vmonths().v, .viaGiveaway = data.is_via_giveaway(), + .unclaimed = data.is_unclaimed(), }); }, [](const auto &) { }); diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp index 1ec8cd507a..b7758eafa4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -45,6 +45,8 @@ QSize PremiumGift::size() { QString PremiumGift::title() { return _data.slug.isEmpty() ? tr::lng_premium_summary_title(tr::now) + : _data.unclaimed + ? tr::lng_prize_unclaimed_title(tr::now) : tr::lng_prize_title(tr::now); } @@ -53,7 +55,9 @@ TextWithEntities PremiumGift::subtitle() { return { GiftDuration(_data.months) }; } const auto name = _data.channel ? _data.channel->name() : "channel"; - auto result = (_data.viaGiveaway + auto result = (_data.unclaimed + ? tr::lng_prize_unclaimed_about + : _data.viaGiveaway ? tr::lng_prize_about : tr::lng_prize_gift_about)( tr::now, @@ -61,7 +65,9 @@ TextWithEntities PremiumGift::subtitle() { Ui::Text::Bold(name), Ui::Text::RichLangValue); result.append("\n\n"); - result.append((_data.viaGiveaway + result.append((_data.unclaimed + ? tr::lng_prize_unclaimed_duration + : _data.viaGiveaway ? tr::lng_prize_duration : tr::lng_prize_gift_duration)( tr::now, From 88e3c87cd96f5fc9b4d89e6710c63c05daf964fe Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 21:41:00 +0400 Subject: [PATCH 79/99] Crash on assertion instead of stack overflow. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 272a23e9bc..92c447aa87 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 272a23e9bce7d03cc167d3dd4589ce674398c795 +Subproject commit 92c447aa87754dc02774f4f2f12f93e705715cf1 From d47b99b0b5eb50266d5b40e4865c82836c355f58 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 21:51:37 +0400 Subject: [PATCH 80/99] Don't set box show crash annotation (GenericBox). --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 92c447aa87..952c89687c 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 92c447aa87754dc02774f4f2f12f93e705715cf1 +Subproject commit 952c89687c9c162b37318e6dbfa11211ed2299e9 From c8660b538583e175d4b4fb039adc95c0f14eece1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 22:27:30 +0400 Subject: [PATCH 81/99] Improve color/emoji admin log events. --- Telegram/Resources/langs/lang.strings | 6 ++- .../admin_log/history_admin_log_item.cpp | 40 ++++++++++++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 89705be165..401a153883 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3519,8 +3519,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}"; "lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam"; "lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam"; -"lng_admin_log_change_color" = "{from} changed color"; -"lng_admin_log_change_background_emoji" = "{from} changed background emoji"; +"lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}"; +"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}"; +"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}"; +"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}"; "lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}"; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index dbac38ff51..df8819d10e 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -1822,16 +1822,46 @@ void GenerateItems( tr::now, lt_from, fromLinkText, + lt_previous, + { '#' + QString::number(data.vprev_value().v + 1) }, + lt_color, + { '#' + QString::number(data.vnew_value().v + 1) }, Ui::Text::WithEntities); addSimpleServiceMessage(text); }; const auto createChangeBackgroundEmoji = [&](const LogChangeBackgroundEmoji &data) { - const auto text = tr::lng_admin_log_change_background_emoji( - tr::now, - lt_from, - fromLinkText, - Ui::Text::WithEntities); + const auto was = data.vprev_value().v; + const auto now = data.vnew_value().v; + const auto text = !was + ? tr::lng_admin_log_set_background_emoji( + tr::now, + lt_from, + fromLinkText, + lt_emoji, + Ui::Text::SingleCustomEmoji( + Data::SerializeCustomEmojiId(now)), + Ui::Text::WithEntities) + : !now + ? tr::lng_admin_log_removed_background_emoji( + tr::now, + lt_from, + fromLinkText, + lt_emoji, + Ui::Text::SingleCustomEmoji( + Data::SerializeCustomEmojiId(was)), + Ui::Text::WithEntities) + : tr::lng_admin_log_change_background_emoji( + tr::now, + lt_from, + fromLinkText, + lt_previous, + Ui::Text::SingleCustomEmoji( + Data::SerializeCustomEmojiId(was)), + lt_emoji, + Ui::Text::SingleCustomEmoji( + Data::SerializeCustomEmojiId(now)), + Ui::Text::WithEntities); addSimpleServiceMessage(text); }; From 29fb2634953e44730faa7a1a6d353fb7e1c1e033 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 22:28:44 +0400 Subject: [PATCH 82/99] Use lang-packed "copy" instead of "code". --- Telegram/Resources/langs/lang.strings | 4 +++- Telegram/SourceFiles/core/ui_integration.cpp | 4 ++++ Telegram/SourceFiles/core/ui_integration.h | 1 + Telegram/lib_ui | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 401a153883..38fa348559 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -129,6 +129,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reconnecting#other" = "Reconnect in {count} s..."; "lng_reconnecting_try_now" = "Try now"; +"lng_code_block_header_copy" = "copy"; + "lng_status_service_notifications" = "service notifications"; "lng_status_support" = "support"; "lng_status_bot" = "bot"; @@ -2853,7 +2855,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_formatting_link_create" = "Create"; "lng_text_copied" = "Text copied to clipboard."; -"lng_code_copied" = "Code copied to clipboard."; +"lng_code_copied" = "Block copied to clipboard."; "lng_spellchecker_submenu" = "Spelling"; "lng_spellchecker_add" = "Add to Dictionary"; diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 24a8bbee2c..ba2593e7b5 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -408,6 +408,10 @@ QString UiIntegration::phraseBotAllowWriteConfirm() { return tr::lng_bot_allow_write_confirm(tr::now); } +QString UiIntegration::phraseQuoteHeaderCopy() { + return tr::lng_code_block_header_copy(tr::now); +} + bool OpenGLLastCheckFailed() { return QFile::exists(OpenGLCheckFilePath()); } diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h index 4e23a63db4..36ee45777c 100644 --- a/Telegram/SourceFiles/core/ui_integration.h +++ b/Telegram/SourceFiles/core/ui_integration.h @@ -92,6 +92,7 @@ public: QString phraseBotAllowWrite() override; QString phraseBotAllowWriteTitle() override; QString phraseBotAllowWriteConfirm() override; + QString phraseQuoteHeaderCopy() override; }; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 952c89687c..287af101d0 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 952c89687c9c162b37318e6dbfa11211ed2299e9 +Subproject commit 287af101d02089cafeba588f75057414fd96f476 From 7a754f8b00064fc77476f692e41029b9182c5255 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 23:10:06 +0400 Subject: [PATCH 83/99] Update reply preview in scale / themes. --- .../settings/settings_scale_preview.cpp | 90 +++++++++++++------ Telegram/SourceFiles/ui/chat/chat_style.cpp | 24 ++--- Telegram/SourceFiles/ui/chat/chat_style.h | 6 ++ .../window/themes/window_theme_preview.cpp | 47 ++++++++-- 4 files changed, 119 insertions(+), 48 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_scale_preview.cpp b/Telegram/SourceFiles/settings/settings_scale_preview.cpp index 6da0a861e2..7e840e31cf 100644 --- a/Telegram/SourceFiles/settings/settings_scale_preview.cpp +++ b/Telegram/SourceFiles/settings/settings_scale_preview.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_values.h" #include "history/history_item_components.h" #include "main/main_session.h" +#include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/image/image_prepare.h" #include "ui/platform/ui_platform_utility.h" @@ -103,7 +104,7 @@ private: int _bubbleShadow = 0; int _localShiftLeft = 0; QImage _bubbleTail; - QRect _replyBar; + QRect _replyRect; QRect _name; QRect _reply; QRect _message; @@ -329,14 +330,7 @@ style::font Preview::scaled(const style::font &font, int size) const { style::QuoteStyle Preview::scaled(const style::QuoteStyle &value) const { return { - .padding = scaled(value.padding), - .verticalSkip = scaled(value.verticalSkip), - .header = scaled(value.header), - .headerPosition = scaled(value.headerPosition), .icon = value.icon, - .iconPosition = scaled(value.iconPosition), - .outline = scaled(value.outline), - .radius = scaled(value.radius), .scrollable = value.scrollable, }; } @@ -347,7 +341,6 @@ style::TextStyle Preview::scaled( return { .font = scaled(value.font, fontSize), .linkUnderline = value.linkUnderline, - .lineHeight = scaled(value.lineHeight), .blockquote = scaled(value.blockquote), .pre = scaled(value.pre), }; @@ -366,8 +359,22 @@ void Preview::updateToScale(int scale) { return; } _scale = scale; - _nameStyle = scaled(_nameStyle, 13); - _textStyle = scaled(_textStyle, 13); + _nameStyle = scaled(st::fwdTextStyle, 13); + _textStyle = scaled(st::messageTextStyle, 13); + _textStyle.blockquote.verticalSkip = scaled(4); + _textStyle.blockquote.outline = scaled(3); + _textStyle.blockquote.outlineShift = scaled(2); + _textStyle.blockquote.radius = scaled(5); + _textStyle.blockquote.padding = scaled(QMargins{ 10, 2, 20, 2 }); + _textStyle.blockquote.iconPosition = scaled(QPoint{ 4, 4 }); + _textStyle.pre.verticalSkip = scaled(4); + _textStyle.pre.outline = scaled(3); + _textStyle.pre.outlineShift = scaled(2); + _textStyle.pre.radius = scaled(5); + _textStyle.pre.header = scaled(20); + _textStyle.pre.headerPosition = scaled(QPoint{ 10, 2 }); + _textStyle.pre.padding = scaled(QMargins{ 10, 2, 4, 2 }); + _textStyle.pre.iconPosition = scaled(QPoint{ 4, 2 }); _nameText.setText( _nameStyle, u"Bob Harris"_q, @@ -381,21 +388,18 @@ void Preview::updateToScale(int scale) { u"Do you know what time it is?"_q, Ui::ItemTextDefaultOptions()); - _replyBar = QRect( - scaled(1), // st::msgReplyBarPos.x(), - scaled(6) + 0,// st::msgReplyPadding.top() + st::msgReplyBarPos.y(), - scaled(2), //st::msgReplyBarSize.width(), - scaled(36)); // st::msgReplyBarSize.height(), const auto namePosition = QPoint( - scaled(10), // st::msgReplyBarSkip - scaled(6)); // st::msgReplyPadding.top() + scaled(11), // st::historyReplyPadding.left() + scaled(2)); // st::historyReplyPadding.top() const auto replyPosition = QPoint( - scaled(10), // st::msgReplyBarSkip - scaled(6) + _nameStyle.font->height); // st::msgReplyPadding.top() + scaled(11), // st::historyReplyPadding.left() + (scaled(2) // st::historyReplyPadding.top() + + _nameStyle.font->height)); // + st::msgServiceNameFont->height + const auto paddingRight = scaled(6); // st::historyReplyPadding.right() const auto wantedWidth = std::max({ - namePosition.x() + _nameText.maxWidth(), - replyPosition.x() + _replyText.maxWidth(), + namePosition.x() + _nameText.maxWidth() + paddingRight, + replyPosition.x() + _replyText.maxWidth() + paddingRight, _messageText.maxWidth(), }); @@ -409,16 +413,25 @@ void Preview::updateToScale(int scale) { _messageText.countHeight(maxTextWidth), kMaxTextLines * _textStyle.font->height); + _replyRect = QRect( + 0, // st::msgReplyBarPos.x(), + scaled(2),// st::historyReplyTop + messageWidth, + (scaled(2) // st::historyReplyPadding.top() + + _nameStyle.font->height // + st::msgServiceNameFont->height + + _textStyle.font->height // + st::normalFont->height + + scaled(2))); // + st::historyReplyPadding.bottom() + _name = QRect( - namePosition, + _replyRect.topLeft() + namePosition, QSize(messageWidth - namePosition.x(), _nameStyle.font->height)); _reply = QRect( - replyPosition, + _replyRect.topLeft() + replyPosition, QSize(messageWidth - replyPosition.x(), _textStyle.font->height)); _message = QRect(0, 0, messageWidth, messageHeight); - // replyBar.bottom + st::msgReplyPadding.bottom(); - const auto replySkip = _replyBar.y() + _replyBar.height() + scaled(6); + // replyRect.bottom + st::historyReplyBottom; + const auto replySkip = _replyRect.y() + _replyRect.height() + scaled(2); _message.moveTop(replySkip); _content = QRect(0, 0, messageWidth, replySkip + messageHeight); @@ -676,9 +689,30 @@ void Preview::paintContent(Painter &p, QRect clip) { } void Preview::paintReply(Painter &p, QRect clip) { - p.setOpacity(HistoryMessageReply::kBarAlpha); - p.fillRect(_replyBar, st::msgInReplyBarColor); + { + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::msgInReplyBarColor); + + const auto outline = _textStyle.blockquote.outline; + const auto radius = _textStyle.blockquote.radius; + p.setOpacity(Ui::kDefaultOutline1Opacity); + p.setClipRect( + _replyRect.x(), + _replyRect.y(), + outline, + _replyRect.height()); + p.drawRoundedRect(_replyRect, radius, radius); + p.setOpacity(Ui::kDefaultBgOpacity); + p.setClipRect( + _replyRect.x() + outline, + _replyRect.y(), + _replyRect.width() - outline, + _replyRect.height()); + p.drawRoundedRect(_replyRect, radius, radius); + } p.setOpacity(1.); + p.setClipping(false); p.setPen(st::msgInServiceFg); _nameText.drawLeftElided( diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 7055248db9..7bdebaee17 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -54,15 +54,15 @@ void EnsurePreCache( const auto bg = bgOverride(); cache->bg = bg.value_or(color->c); if (!bg) { - cache->bg.setAlpha(0.12 * 255); + cache->bg.setAlpha(kDefaultBgOpacity * 255); } cache->outlines[0] = color->c; - cache->outlines[0].setAlpha(0.9 * 255); + cache->outlines[0].setAlpha(kDefaultOutline1Opacity * 255); cache->outlines[1] = cache->outlines[2] = QColor(0, 0, 0, 0); cache->header = color->c; - cache->header.setAlpha(0.25 * 255); + cache->header.setAlpha(kDefaultOutline2Opacity * 255); cache->icon = cache->outlines[0]; - cache->icon.setAlpha(0.6 * 255); + cache->icon.setAlpha(kDefaultOutline3Opacity * 255); } } // namespace @@ -110,21 +110,21 @@ int HistoryServiceMsgInvertedShrink() { ColorIndexValues SimpleColorIndexValues(QColor color, int patternIndex) { auto bg = color; - bg.setAlpha(0.12 * 255); + bg.setAlpha(kDefaultBgOpacity * 255); auto result = ColorIndexValues{ .name = color, .bg = bg, }; result.outlines[0] = color; - result.outlines[0].setAlpha(0.9 * 255); + result.outlines[0].setAlpha(kDefaultOutline1Opacity * 255); if (patternIndex > 1) { result.outlines[1] = result.outlines[0]; - result.outlines[1].setAlpha(0.3 * 255); + result.outlines[1].setAlpha(kDefaultOutline2Opacity * 255); result.outlines[2] = result.outlines[0]; - result.outlines[2].setAlpha(0.6 * 255); + result.outlines[2].setAlpha(kDefaultOutline3Opacity * 255); } else if (patternIndex > 0) { result.outlines[1] = result.outlines[0]; - result.outlines[1].setAlpha(0.5 * 255); + result.outlines[1].setAlpha(kDefaultOutlineOpacitySecond * 255); result.outlines[2] = QColor(0, 0, 0, 0); } else { result.outlines[1] = result.outlines[2] = QColor(0, 0, 0, 0); @@ -757,9 +757,9 @@ ColorIndexValues ChatStyle::computeColorIndexValues( .name = (*(selected ? listSelected : list)[paletteIndex])->c, }; result.bg = result.name; - result.bg.setAlphaF(0.12); + result.bg.setAlpha(kDefaultBgOpacity * 255); result.outlines[0] = result.name; - result.outlines[0].setAlphaF(0.9); + result.outlines[0].setAlpha(kDefaultOutline1Opacity * 255); result.outlines[1] = result.outlines[2] = QColor(0, 0, 0, 0); return result; } @@ -780,7 +780,7 @@ ColorIndexValues ChatStyle::computeColorIndexValues( .outlines = { color(0), color(1), color(2) } }; result.bg = result.outlines[0]; - result.bg.setAlpha(0.12 * 255); + result.bg.setAlpha(kDefaultBgOpacity * 255); result.name = result.outlines[0]; return result; } diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index c60993df6a..8ba6069416 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -35,6 +35,12 @@ inline constexpr auto kColorPatternsCount = Text::kMaxQuoteOutlines; inline constexpr auto kColorIndexCount = uint8(1 << 6); inline constexpr auto kSimpleColorIndexCount = uint8(7); +inline constexpr auto kDefaultBgOpacity = 0.12; +inline constexpr auto kDefaultOutline1Opacity = 0.9; +inline constexpr auto kDefaultOutline2Opacity = 0.3; +inline constexpr auto kDefaultOutline3Opacity = 0.6; +inline constexpr auto kDefaultOutlineOpacitySecond = 0.5; + struct MessageStyle { CornersPixmaps msgBgCornersSmall; CornersPixmaps msgBgCornersLarge; diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp index a3ea8a7086..a77de2d7fa 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp @@ -794,7 +794,12 @@ void Generator::paintRow(const Row &row) { void Generator::paintBubble(const Bubble &bubble) { auto height = bubble.height; if (!bubble.replyName.isEmpty()) { - height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + height += st::historyReplyTop + + st::historyReplyPadding.top() + + st::msgServiceNameFont->height + + st::normalFont->height + + st::historyReplyPadding.bottom() + + st::historyReplyBottom; } auto isPhoto = !bubble.photo.isNull(); @@ -854,19 +859,45 @@ void Generator::paintBubble(const Bubble &bubble) { trect = trect.marginsRemoved(st::msgPadding); } if (!bubble.replyName.isEmpty()) { - auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - + trect.setY(trect.y() + st::historyReplyTop); auto bar = (bubble.outbg ? st::msgOutReplyBarColor[_palette] : st::msgInReplyBarColor[_palette]); - auto rbar = style::rtlrect(trect.x() + st::msgReplyBarPos.x(), trect.y() + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), _rect.width()); - _p->fillRect(rbar, bar); + auto rbar = style::rtlrect( + trect.x(), + trect.y(), + trect.width(), + (st::historyReplyPadding.top() + + st::msgServiceNameFont->height + + st::normalFont->height + + st::historyReplyPadding.bottom()), + _rect.width()); + { + auto hq = PainterHighQualityEnabler(*_p); + _p->setPen(Qt::NoPen); + _p->setBrush(bar); + + const auto outline = st::messageTextStyle.blockquote.outline; + const auto radius = st::messageTextStyle.blockquote.radius; + _p->setOpacity(Ui::kDefaultOutline1Opacity); + _p->setClipRect(rbar.x(), rbar.y(), outline, rbar.height()); + _p->drawRoundedRect(rbar, radius, radius); + _p->setOpacity(Ui::kDefaultBgOpacity); + _p->setClipRect( + rbar.x() + outline, + rbar.y(), + rbar.width() - outline, + rbar.height()); + _p->drawRoundedRect(rbar, radius, radius); + } + _p->setOpacity(1.); + _p->setClipping(false); _p->setPen(bubble.outbg ? st::msgOutServiceFg[_palette] : st::msgInServiceFg[_palette]); - bubble.replyName.drawLeftElided(*_p, trect.x() + st::msgReplyBarSkip, trect.y() + st::msgReplyPadding.top(), bubble.width - st::msgReplyBarSkip, _rect.width()); + bubble.replyName.drawLeftElided(*_p, trect.x() + st::historyReplyPadding.left(), trect.y() + st::historyReplyPadding.top(), bubble.width - st::historyReplyPadding.left() - st::historyReplyPadding.right(), _rect.width()); _p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]); - bubble.replyText.drawLeftElided(*_p, trect.x() + st::msgReplyBarSkip, trect.y() + st::msgReplyPadding.top() + st::msgServiceNameFont->height, bubble.width - st::msgReplyBarSkip, _rect.width()); + bubble.replyText.drawLeftElided(*_p, trect.x() + st::historyReplyPadding.left(), trect.y() + st::historyReplyPadding.top() + st::msgServiceNameFont->height, bubble.width - st::historyReplyPadding.left() - st::historyReplyPadding.right(), _rect.width()); - trect.setY(trect.y() + h); + trect.setY(trect.y() + rbar.height() + st::historyReplyBottom); } if (!bubble.text.isEmpty()) { From c46b659aa53988016ecb4f0503d9e28a608296a4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Oct 2023 23:22:46 +0400 Subject: [PATCH 84/99] Version 4.11. - View full statistics in your channels and group chats. - Choose which link preview in added to the message. - Choose if link preview is above or below the text. - Choose if link preview has large or small image. - Quote parts of text in replies. - Add quote formatting. - Reply in another chat. - Add nice looking code blocks with syntax highlighting. - Copy full code block by click on its header. - Send a highlighted code block using ```language syntax. - Change your name color in Chat Settings. - Customize quotes, link previews and replies to your messages. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/SourceFiles/main/main_app_config.cpp | 4 ++-- Telegram/build/version | 12 ++++++------ changelog.txt | 15 +++++++++++++++ 7 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 71ceac802c..9829733415 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.11.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 5f159936d4..2c2060f4a4 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,10,5,0 - PRODUCTVERSION 4,10,5,0 + FILEVERSION 4,11,0,0 + PRODUCTVERSION 4,11,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "4.10.5.0" + VALUE "FileVersion", "4.11.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.10.5.0" + VALUE "ProductVersion", "4.11.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index fdb59b7198..d54e8a6d26 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,10,5,0 - PRODUCTVERSION 4,10,5,0 + FILEVERSION 4,11,0,0 + PRODUCTVERSION 4,11,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "4.10.5.0" + VALUE "FileVersion", "4.11.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.10.5.0" + VALUE "ProductVersion", "4.11.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index f63118ca65..8fb21e740e 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 4010005; -constexpr auto AppVersionStr = "4.10.5"; -constexpr auto AppBetaVersion = true; +constexpr auto AppVersion = 4011000; +constexpr auto AppVersionStr = "4.11"; +constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 3b4b788a11..fdc98ac39b 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -260,7 +260,7 @@ void AppConfig::parseColorIndices() { return colorIndex < Ui::kColorIndexCount; } }; - constexpr auto parseColors = [](const MTPJSONObjectValue &element) { + const auto parseColors = [&](const MTPJSONObjectValue &element) { const auto &data = element.data(); if (data.vvalue().type() != mtpc_jsonArray) { LOG(("API Error: Bad value for peer_colors element.")); @@ -286,7 +286,7 @@ void AppConfig::parseColorIndices() { } return result; }; - constexpr auto checkColorsObjectType = [](const MTPJSONValue &value) { + const auto checkColorsObjectType = [&](const MTPJSONValue &value) { if (value.type() != mtpc_jsonObject) { if (value.type() != mtpc_jsonArray || !value.c_jsonArray().vvalue().v.empty()) { diff --git a/Telegram/build/version b/Telegram/build/version index 5f32b2f0c1..0e22fe75e1 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4010005 -AppVersionStrMajor 4.10 -AppVersionStrSmall 4.10.5 -AppVersionStr 4.10.5 -BetaChannel 1 +AppVersion 4011000 +AppVersionStrMajor 4.11 +AppVersionStrSmall 4.11 +AppVersionStr 4.11.0 +BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.10.5.beta +AppVersionOriginal 4.11 diff --git a/changelog.txt b/changelog.txt index 6e5d10d740..dea2160da8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,18 @@ +4.11 (28.10.23) + +- View full statistics in your channels and group chats. +- Choose which link preview in added to the message. +- Choose if link preview is above or below the text. +- Choose if link preview has large or small image. +- Quote parts of text in replies. +- Add quote formatting. +- Reply in another chat. +- Add nice looking code blocks with syntax highlighting. +- Copy full code block by click on its header. +- Send a highlighted code block using ```language syntax. +- Change your name color in Chat Settings. +- Customize quotes, link previews and replies to your messages. + 4.10.5 beta (23.10.23) - Fix crash in replies to messages with spoilers. From 1a503e5f1d50887f7a8c235b047ca11be3fcfa4e Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 00:33:54 +0400 Subject: [PATCH 85/99] Version 4.11: Fix build with GCC. --- .../boxes/peers/edit_peer_color_box.cpp | 6 ----- Telegram/SourceFiles/history/history_item.cpp | 2 -- .../history_view_compose_controls.cpp | 27 ------------------- .../controls/history_view_draft_options.cpp | 2 -- .../view/history_view_context_menu.cpp | 1 - .../history/view/history_view_message.cpp | 3 --- .../view/media/history_view_giveaway.cpp | 7 ----- Telegram/SourceFiles/ui/boxes/boost_box.cpp | 2 -- .../window/window_session_controller.cpp | 1 - 9 files changed, 51 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index d5d0762214..1eede85e30 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -376,12 +376,9 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { _element->draw(p, context); if (_element->displayFromPhoto()) { - auto userpicMinBottomSkip = st::historyPaddingBottom - + st::msgMargin.bottom(); auto userpicBottom = height() - _element->marginBottom() - _element->marginTop(); - const auto item = _element->data(); const auto userpicTop = userpicBottom - st::msgPhotoSize; _peer->paintUserpicLeft( p, @@ -631,15 +628,12 @@ int ColorSelector::resizeGetHeight(int newWidth) { } const auto count = int(_samples.size()); const auto columns = Ui::kSimpleColorIndexCount; - const auto rows = (count + columns - 1) / columns; const auto skip = st::settingsColorRadioSkip; const auto size = (newWidth - skip * (columns - 1)) / float64(columns); const auto isize = int(base::SafeRound(size)); auto top = 0; auto left = 0.; for (auto i = 0; i != count; ++i) { - const auto row = i / columns; - const auto column = i % columns; _samples[i]->resize(isize, isize); _samples[i]->move(int(base::SafeRound(left)), top); left += size + skip; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index bc2b4b5031..6cf62353c7 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4474,8 +4474,6 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { auto prepareGiftCode = [&](const MTPDmessageActionGiftCode &action) { auto result = PreparedServiceText(); _history->session().giftBoxStickersPacks().load(); - const auto months = action.vmonths().v; - result.text = { (action.is_unclaimed() ? tr::lng_prize_unclaimed_about 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 a54b671921..09eb7e76c3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -103,22 +103,6 @@ using SetHistoryArgs = ComposeControls::SetHistoryArgs; using VoiceRecordBar = Controls::VoiceRecordBar; using ForwardPanel = Controls::ForwardPanel; -[[nodiscard]] auto ShowWebPagePreview(WebPageData *page) { - return page && !page->failed; -} - -WebPageText ProcessWebPageData(WebPageData *page) { - auto previewText = HistoryView::TitleAndDescriptionFromWebPage(page); - if (previewText.title.isEmpty()) { - if (page->document) { - previewText.title = tr::lng_attach_file(tr::now); - } else if (page->photo) { - previewText.title = tr::lng_attach_photo(tr::now); - } - } - return previewText; -} - } // namespace class FieldHeader final : public Ui::RpWidget { @@ -428,17 +412,6 @@ void FieldHeader::setShownMessage(HistoryItem *item) { _shownMessage = item; if (item) { updateShownMessageText(); - //if (item->fullId() == _editMsgId.current()) { - // _preview = {}; - // if (const auto media = item->media()) { - // if (const auto page = media->webpage()) { - // const auto preview = ProcessWebPageData(page); - // _title = preview.title; - // _description = preview.description; - // _preview.data = page; - // } - // } - //} } else { _shownMessageText.clear(); resolveMessageData(); 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 49a5dabd43..ac6dd6fc33 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -357,8 +357,6 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { _element->draw(p, context); if (_element->displayFromPhoto()) { - auto userpicMinBottomSkip = st::historyPaddingBottom - + st::msgMargin.bottom(); auto userpicBottom = height() - _element->marginBottom() - _element->marginTop(); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 8bad63c021..562069bc08 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -610,7 +610,6 @@ bool AddReplyToMessageAction( ? tr::lng_context_reply_msg(tr::now) : tr::lng_context_quote_and_reply(tr::now); text.replace('&', u"&&"_q); - const auto owner = &item->history()->owner(); const auto itemId = item->fullId(); menu->addAction(text, [=] { if (!item) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index f215e2bdd6..d8bf40a552 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1386,7 +1386,6 @@ void Message::paintFromName( const auto from = item->displayFrom(); const auto info = from ? nullptr : item->hiddenSenderInfo(); Assert(from || info); - const auto st = context.st; const auto nameFg = !context.outbg ? FromNameFg(context, colorIndex()) : stm->msgServiceFg->c; @@ -2683,7 +2682,6 @@ TextWithEntities Message::selectedQuote( return {}; } auto modified = selection; - const auto &modifications = text.modifications(); for (const auto &modification : text.modifications()) { if (modification.position >= selection.to) { break; @@ -2777,7 +2775,6 @@ TextSelection Message::selectionFromQuote( } offset = i + 1; } - const auto &modifications = text.modifications(); //for (const auto &modification : text.modifications()) { // if (modification.position >= selection.to) { // break; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 311e9bd882..dceb0cd1d5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -251,12 +251,8 @@ QSize Giveaway::countCurrentSize(int newWidth) { void Giveaway::draw(Painter &p, const PaintContext &context) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - const auto st = context.st; - const auto sti = context.imageStyle(); const auto stm = context.messageStyle(); - auto &semibold = stm->msgServiceFg; - auto padding = inBubblePadding(); const auto outer = width(); @@ -343,7 +339,6 @@ void Giveaway::paintChannels( } const auto size = _channels[0].geometry.height(); - const auto ratio = style::DevicePixelRatio(); const auto st = context.st; const auto stm = context.messageStyle(); const auto selected = context.selected(); @@ -491,8 +486,6 @@ void Giveaway::clickHandlerPressedChanged( } if (pressed) { if (!channel.ripple) { - const auto full = QRect(0, 0, width(), height()); - const auto outer = full.marginsRemoved(inBubblePadding()); const auto owner = &parent()->history()->owner(); channel.ripple = std::make_unique( st::defaultRippleAnimation, diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index 66f504f1de..408b54c4f9 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -260,8 +260,6 @@ void AskBoostBox( box->setWidth(st::boxWideWidth); box->setStyle(st::boostBox); - const auto full = !data.boost.nextLevelBoosts; - struct State { rpl::variable you = false; bool submitted = false; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 681604d939..71de8d7b1b 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -685,7 +685,6 @@ void SessionNavigation::applyBoost( done(false); return; } - auto already = false; auto different = PeerId(); auto earliest = TimeId(-1); const auto now = base::unixtime::now(); From af0e87c56986e3eb4d3159ae01a39dfc85f20dad Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 07:09:18 +0400 Subject: [PATCH 86/99] Fix a crash on languages box open. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 287af101d0..dc8313f6ae 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 287af101d02089cafeba588f75057414fd96f476 +Subproject commit dc8313f6ae6c87572512424818b9e24e7ef2383e From 04f040c9c55cd318b72deb0abac9cadae8e319ab Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 07:09:28 +0400 Subject: [PATCH 87/99] Fix a crash in emoji status set. --- Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp index 559e4ed356..ef86379741 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp @@ -65,7 +65,7 @@ EmojiFlyAnimation::EmojiFlyAnimation( QRect( rect.topLeft() + QPoint(skipx, skipy), QSize(_flySize, _flySize)), - _textColor(), + (_textColor ? _textColor() : st::infoPeerBadge.premiumFg->c), clip, crl::now()); if (_areaUpdated || _area.isEmpty()) { From 94ac9f93fad9c67a846a5ab298c6d3bd78352ff2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 07:54:39 +0400 Subject: [PATCH 88/99] Don't shrink non-photo media. --- Telegram/SourceFiles/data/data_web_page.cpp | 3 +++ .../SourceFiles/history/view/media/history_view_web_page.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index ec0f964545..932e9e2341 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -254,6 +254,9 @@ bool WebPageData::applyChanges( } return QString(); }(); + if (newDocument || !newCollage.items.empty() || !newPhoto) { + newHasLargeMedia = false; + } if (type == newType && url == resultUrl 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 f91e7ad553..1c2cd2c82a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -242,7 +242,7 @@ QSize WebPage::countOptimalSize() { using Flag = MediaWebPageFlag; if (_data->hasLargeMedia && (_flags & Flag::ForceLargeMedia)) { _asArticle = 0; - } else if (_data->photo && (_flags & Flag::ForceSmallMedia)) { + } else if (_data->hasLargeMedia && (_flags & Flag::ForceSmallMedia)) { _asArticle = 1; } else { _asArticle = _data->computeDefaultSmallMedia(); From eaf30d58be48fe7b7bd15a00faf2b9facc1969b9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 08:48:45 +0400 Subject: [PATCH 89/99] Open documents-in-web-previews in-app. --- .../history/view/media/history_view_web_page.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 1c2cd2c82a..38b6dc2582 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -801,7 +801,11 @@ ClickHandlerPtr WebPage::replaceAttachLink( if (!_attach->isReadyForOpen() || (_siteName.isEmpty() && _title.isEmpty() - && _description.isEmpty())) { + && _description.isEmpty()) + || (_data->document + && !_data->document->isWallPaper() + && !_data->document->isTheme()) + || !_data->collage.items.empty()) { return link; } return _openl; From e64a096dca74c0f3e55646f624bc083d2a83795c Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 08:51:42 +0400 Subject: [PATCH 90/99] Don't suggest shrinking media of direct photo links. --- Telegram/SourceFiles/data/data_web_page.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 932e9e2341..e8eef89784 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -254,7 +254,13 @@ bool WebPageData::applyChanges( } return QString(); }(); - if (newDocument || !newCollage.items.empty() || !newPhoto) { + const auto hasSiteName = !resultSiteName.isEmpty() ? 1 : 0; + const auto hasTitle = !resultTitle.isEmpty() ? 1 : 0; + const auto hasDescription = !newDescription.text.isEmpty() ? 1 : 0; + if (newDocument + || !newCollage.items.empty() + || !newPhoto + || (hasSiteName + hasTitle + hasDescription < 2)) { newHasLargeMedia = false; } From ec3fc8c749621cbbc626f34b84fded26dbb3c9d1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 08:55:30 +0400 Subject: [PATCH 91/99] Send entities with customized webpage preview. Fixes #26981. --- Telegram/SourceFiles/apiwrap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index b1fb115724..1efb5a275e 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3682,6 +3682,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { Api::ConvertOption::SkipLocal); if (!sentEntities.v.isEmpty()) { sendFlags |= MTPmessages_SendMessage::Flag::f_entities; + mediaFlags |= MTPmessages_SendMedia::Flag::f_entities; } const auto clearCloudDraft = action.clearDraft; const auto topicRootId = action.replyTo.topicRootId; From 597816db09dd4e40e904e3403d91ea61e4e9273d Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 09:52:42 +0400 Subject: [PATCH 92/99] Show Saved Messages in Reply in another chat. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/peer_list_box.cpp | 23 +++++++++++-------- Telegram/SourceFiles/boxes/peer_list_box.h | 10 ++++---- .../boxes/peer_list_controllers.cpp | 8 +++++-- .../SourceFiles/boxes/peer_list_controllers.h | 4 +--- .../boxes/peers/choose_peer_box.cpp | 4 ++-- .../controls/history_view_draft_options.cpp | 6 ++--- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 38fa348559..c4603bca88 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2361,6 +2361,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_saved_messages" = "Saved Messages"; "lng_saved_short" = "Save"; "lng_saved_forward_here" = "Forward messages here for quick access"; +"lng_saved_quote_here" = "Quote here to save"; "lng_scheduled_messages" = "Scheduled Messages"; "lng_scheduled_messages_empty" = "No scheduled messages here yet..."; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 28286f3771..2c1e361fb1 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -456,7 +456,7 @@ int PeerListController::descriptionTopSkipMin() const { void PeerListBox::addSelectItem( not_null peer, anim::type animated) { - const auto respect = _controller->respectSavedMessagesChat(); + const auto respect = !_controller->savedMessagesChatStatus().isEmpty(); const auto text = (respect && peer->isSelf()) ? tr::lng_saved_short(tr::now) : (respect && peer->isRepliesChat()) @@ -579,8 +579,8 @@ void PeerListRow::refreshStatus() { _statusType = StatusType::LastSeen; _statusValidTill = 0; if (auto user = peer()->asUser()) { - if (_isSavedMessagesChat) { - setStatusText(tr::lng_saved_forward_here(tr::now)); + if (!_savedMessagesStatus.isEmpty()) { + setStatusText(_savedMessagesStatus); } else { auto time = base::unixtime::now(); setStatusText(Data::OnlineText(user, time)); @@ -613,7 +613,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) { if (!_initialized) { return; } - const auto text = _isSavedMessagesChat + const auto text = !_savedMessagesStatus.isEmpty() ? tr::lng_saved_messages(tr::now) : _isRepliesMessagesChat ? tr::lng_replies_messages(tr::now) @@ -683,7 +683,7 @@ QString PeerListRow::generateName() { } QString PeerListRow::generateShortName() { - return _isSavedMessagesChat + return !_savedMessagesStatus.isEmpty() ? tr::lng_saved_short(tr::now) : _isRepliesMessagesChat ? tr::lng_replies_messages(tr::now) @@ -699,7 +699,7 @@ Ui::PeerUserpicView &PeerListRow::ensureUserpicView() { PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback( bool forceRound) { - const auto saved = _isSavedMessagesChat; + const auto saved = !_savedMessagesStatus.isEmpty(); const auto replies = _isRepliesMessagesChat; const auto peer = this->peer(); auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView(); @@ -745,7 +745,9 @@ int PeerListRow::paintNameIconGetWidth( int availableWidth, int outerWidth, bool selected) { - if (special() || _isSavedMessagesChat || _isRepliesMessagesChat) { + if (special() + || !_savedMessagesStatus.isEmpty() + || _isRepliesMessagesChat) { return 0; } return _bagde.drawGetWidth( @@ -855,7 +857,7 @@ void PeerListRow::paintDisabledCheckUserpic( auto iconBorderPen = st.checkbox.check.border->p; iconBorderPen.setWidth(st.checkbox.selectWidth); - if (_isSavedMessagesChat) { + if (!_savedMessagesStatus.isEmpty()) { Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2); } else if (_isRepliesMessagesChat) { Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2); @@ -1046,9 +1048,10 @@ void PeerListContent::setRowHidden(not_null row, bool hidden) { } void PeerListContent::addRowEntry(not_null row) { - if (_controller->respectSavedMessagesChat() && !row->special()) { + const auto savedMessagesStatus = _controller->savedMessagesChatStatus(); + if (!savedMessagesStatus.isEmpty() && !row->special()) { if (row->peer()->isSelf()) { - row->setIsSavedMessagesChat(true); + row->setSavedMessagesChatStatus(savedMessagesStatus); } else if (row->peer()->isRepliesChat()) { row->setIsRepliesMessagesChat(true); } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 837bb6e2c2..6dfce4630e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -185,8 +185,8 @@ public: void setIsSearchResult(bool isSearchResult) { _isSearchResult = isSearchResult; } - void setIsSavedMessagesChat(bool isSavedMessagesChat) { - _isSavedMessagesChat = isSavedMessagesChat; + void setSavedMessagesChatStatus(QString savedMessagesStatus) { + _savedMessagesStatus = savedMessagesStatus; } void setIsRepliesMessagesChat(bool isRepliesMessagesChat) { _isRepliesMessagesChat = isRepliesMessagesChat; @@ -278,12 +278,12 @@ private: StatusType _statusType = StatusType::Online; crl::time _statusValidTill = 0; base::flat_set _nameFirstLetters; + QString _savedMessagesStatus; int _absoluteIndex = -1; State _disabledState = State::Active; bool _hidden : 1 = false; bool _initialized : 1 = false; bool _isSearchResult : 1 = false; - bool _isSavedMessagesChat : 1 = false; bool _isRepliesMessagesChat : 1 = false; }; @@ -517,8 +517,8 @@ public: void peerListSearchAddRow(PeerListRowId id) override; void peerListSearchRefreshRows() override; - [[nodiscard]] virtual bool respectSavedMessagesChat() const { - return false; + [[nodiscard]] virtual QString savedMessagesChatStatus() const { + return QString(); } [[nodiscard]] virtual int customRowHeight() { Unexpected("PeerListController::customRowHeight."); diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 3e2c3abcdc..df1a24fba6 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -313,7 +313,7 @@ void ChatsListBoxController::rebuildRows() { return count; }; auto added = 0; - if (respectSavedMessagesChat()) { + if (!savedMessagesChatStatus().isEmpty()) { if (appendRow(session().data().history(session().user()))) { ++added; } @@ -330,7 +330,7 @@ void ChatsListBoxController::rebuildRows() { const auto history = static_cast(a).history(); return history->inChatList(); }); - if (respectSavedMessagesChat()) { + if (!savedMessagesChatStatus().isEmpty()) { delegate()->peerListPartitionRows([](const PeerListRow &a) { return a.peer()->isSelf(); }); @@ -696,6 +696,10 @@ void ChooseRecipientBoxController::rowClicked(not_null row) { } } +QString ChooseRecipientBoxController::savedMessagesChatStatus() const { + return tr::lng_saved_forward_here(tr::now); +} + auto ChooseRecipientBoxController::createRow( not_null history) -> std::unique_ptr { const auto peer = history->peer; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 35b4e0f15c..5d8ef334a2 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -218,9 +218,7 @@ public: Main::Session &session() const override; void rowClicked(not_null row) override; - bool respectSavedMessagesChat() const override { - return true; - } + QString savedMessagesChatStatus() const override; protected: void prepareViewHook() override; diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp index 60031e0cef..767b0c9151 100644 --- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp @@ -45,8 +45,8 @@ public: Main::Session &session() const override; void rowClicked(not_null row) override; - bool respectSavedMessagesChat() const override { - return true; + QString savedMessagesChatStatus() const override { + return tr::lng_saved_forward_here(tr::now); } private: 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 ac6dd6fc33..931aeef557 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -854,12 +854,12 @@ void ShowReplyToChatBox( ChooseRecipientBoxController::rowClicked(row); } - [[nodiscard]] rpl::producer singleChosen() const{ + [[nodiscard]] rpl::producer singleChosen() const { return _singleChosen.events(); } - bool respectSavedMessagesChat() const override { - return false; + QString savedMessagesChatStatus() const override { + return tr::lng_saved_quote_here(tr::now); } private: From 625ae87eea162e375303f319314fff65d65fb9f8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 10:12:48 +0400 Subject: [PATCH 93/99] Add "show-peer-id-below-about" option. --- .../SourceFiles/core/local_url_handlers.cpp | 15 +++++++++++++++ .../info/profile/info_profile_values.cpp | 19 +++++++++++++++++++ .../info/profile/info_profile_values.h | 2 ++ .../settings/settings_experimental.cpp | 2 ++ 4 files changed, 38 insertions(+) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index e8121204da..d029a9a780 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -644,6 +644,17 @@ bool OpenExternalLink( context); } +bool CopyPeerId( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + TextUtilities::SetClipboardText(TextForMimeData{ match->captured(1) }); + if (controller) { + controller->showToast(tr::lng_text_copied(tr::now)); + } + return true; +} + void ExportTestChatTheme( not_null controller, not_null theme) { @@ -985,6 +996,10 @@ const std::vector &InternalUrlHandlers() { u"^url:(.+)$"_q, OpenExternalLink }, + { + u"^copy:(.+)$"_q, + CopyPeerId + } }; return Result; } diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index 878366d155..198e739756 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -7,6 +7,7 @@ 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" @@ -37,6 +38,12 @@ 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.", +}); + auto PlainAboutValue(not_null peer) { return peer->session().changes().peerFlagsValue( peer, @@ -87,6 +94,8 @@ 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, @@ -209,6 +218,16 @@ 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 b3a110fe41..67c9238fb2 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -35,6 +35,8 @@ 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/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 97447adf13..6104cabb13 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -19,6 +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 "lang/lang_keys.h" #include "mainwindow.h" #include "media/player/media_player_instance.h" @@ -141,6 +142,7 @@ void SetupExperimental( addToggle(Dialogs::kOptionForumHideChatsList); addToggle(Core::kOptionFractionalScalingEnabled); addToggle(Window::kOptionViewProfileInChatsListContextMenu); + addToggle(Info::Profile::kOptionShowPeerIdBelowAbout); addToggle(Ui::GL::kOptionAllowLinuxNvidiaOpenGL); addToggle(Ui::kOptionUseSmallMsgBubbleRadius); addToggle(Media::Player::kOptionDisableAutoplayNext); From e2ea27cbef7026085495aa4cfe549f9eb4c8b43b Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 10:25:52 +0400 Subject: [PATCH 94/99] Fix select-by-words with link previews. --- .../history/view/history_view_message.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index d8bf40a552..a5eebddfdb 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2802,7 +2802,11 @@ TextSelection Message::adjustSelection( const auto textSelection = mediaBefore ? media->skipSelection(selection) : selection; - auto textAdjusted = hasVisibleText() + const auto useSelection = [](TextSelection selection, bool skipped) { + return !skipped || (selection != TextSelection(uint16(), uint16())); + }; + auto textAdjusted = (hasVisibleText() + && useSelection(textSelection, mediaBefore)) ? text().adjustSelection(textSelection, type) : textSelection; auto textResult = mediaBefore @@ -2813,7 +2817,9 @@ TextSelection Message::adjustSelection( ? selection : skipTextSelection(selection); if (mediaDisplayed) { - auto mediaAdjusted = media->adjustSelection(mediaSelection, type); + auto mediaAdjusted = useSelection(mediaSelection, !mediaBefore) + ? media->adjustSelection(mediaSelection, type) + : mediaSelection; mediaResult = mediaBefore ? mediaAdjusted : unskipTextSelection(mediaAdjusted); @@ -2825,7 +2831,9 @@ TextSelection Message::adjustSelection( : mediaBefore ? skipTextSelection(textSelection) : media->skipSelection(mediaSelection); - auto entryAdjusted = entry->adjustSelection(entrySelection, type); + auto entryAdjusted = useSelection(entrySelection, true) + ? entry->adjustSelection(entrySelection, type) + : entrySelection; entryResult = unskipTextSelection(entryAdjusted); if (mediaDisplayed) { entryResult = media->unskipSelection(entryResult); From 478c6c4d366afc52e58e28214fd4ee2e40b0027a Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 10:57:07 +0400 Subject: [PATCH 95/99] Fix pending link previews in preview options box. --- Telegram/Resources/langs/lang.strings | 1 + .../controls/history_view_draft_options.cpp | 57 +++++++++++++------ .../history_view_webpage_processor.cpp | 6 +- .../controls/history_view_webpage_processor.h | 2 +- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c4603bca88..4244b1adff 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1166,6 +1166,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mute_box_title" = "Mute notifications for..."; "lng_preview_loading" = "Getting Link Info..."; +"lng_preview_cant" = "Could not generate preview for this link."; "lng_profile_settings_section" = "Settings"; "lng_profile_bot_settings" = "Bot Settings"; 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 931aeef557..ea09d9b14f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/controls/history_view_draft_options.h" +#include "base/timer_rpl.h" #include "base/unixtime.h" #include "boxes/peer_list_box.h" #include "boxes/peer_list_controllers.h" @@ -589,6 +590,9 @@ void DraftOptionsBox( QString link; Ui::SettingsSlider *tabs = nullptr; PreviewWrap *wrap = nullptr; + + Fn performSwitch; + Fn requestAndSwitch; rpl::lifetime resolveLifetime; }; const auto state = box->lifetime().make_state(); @@ -740,35 +744,54 @@ void DraftOptionsBox( }; const auto &resolver = args.resolver; - const auto performSwitch = [=](const QString &link, WebPageData *page) { - if (page) { + state->performSwitch = [=](const QString &link, WebPageData *page) { + const auto now = base::unixtime::now(); + if (!page || page->pendingTill > 0 && page->pendingTill < now) { + show->showToast(tr::lng_preview_cant(tr::now)); + } else if (page->pendingTill > 0) { + const auto delay = std::max(page->pendingTill - now, TimeId()); + base::timer_once( + (delay + 1) * crl::time(1000) + ) | rpl::start_with_next([=] { + state->requestAndSwitch(link, true); + }, state->resolveLifetime); + + page->owner().webPageUpdates( + ) | rpl::start_with_next([=](not_null updated) { + if (updated == page && !updated->pendingTill) { + state->resolveLifetime.destroy(); + state->performSwitch(link, page); + } + }, state->resolveLifetime); + } else { state->preview = page; state->webpage.id = page->id; state->webpage.url = page->url; state->webpage.manual = true; state->link = link; state->shown.force_assign(Section::Link); - } else { - show->showToast(u"Could not generate preview for this link."_q); } }; + state->requestAndSwitch = [=](const QString &link, bool force) { + resolver->request(link, force); + + state->resolveLifetime = resolver->resolved( + ) | rpl::start_with_next([=](const QString &resolved) { + if (resolved == link) { + state->resolveLifetime.destroy(); + state->performSwitch( + link, + resolver->lookup(link).value_or(nullptr)); + } + }); + }; const auto switchTo = [=](const QString &link) { if (link == state->link) { return; - } - if (const auto value = resolver->lookup(link)) { - performSwitch(link, *value); + } else if (const auto value = resolver->lookup(link)) { + state->performSwitch(link, *value); } else { - resolver->request(link); - state->resolveLifetime = resolver->resolved( - ) | rpl::start_with_next([=](const QString &resolved) { - if (resolved == link) { - state->resolveLifetime.destroy(); - performSwitch( - link, - resolver->lookup(link).value_or(nullptr)); - } - }); + state->requestAndSwitch(link, false); } }; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp index 50abe57bd5..ca5fb31c09 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp @@ -134,8 +134,8 @@ QString WebpageResolver::find(not_null page) const { return QString(); } -void WebpageResolver::request(const QString &link) { - if (_requestLink == link) { +void WebpageResolver::request(const QString &link, bool force) { + if (_requestLink == link && !force) { return; } const auto done = [=](const MTPDmessageMediaWebPage &data) { @@ -191,7 +191,7 @@ WebpageProcessor::WebpageProcessor( if (!ShowWebPagePreview(_data) || _link.isEmpty()) { return; } - _resolver->request(_link); + _resolver->request(_link, true); }) { _history->session().downloaderTaskFinished( ) | rpl::filter([=] { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h index f27d1ad6cd..5aac1fd6e9 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h @@ -62,7 +62,7 @@ public: [[nodiscard]] QString find(not_null page) const; - void request(const QString &link); + void request(const QString &link, bool force = false); void cancel(const QString &link); private: From 3b7448ccabdacabd351de35d7d28f4a722061a64 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 11:36:06 +0400 Subject: [PATCH 96/99] Version 4.12. - Fix crash in emoji status select. - Fix crash in language change. - Suggest shrinking only photos in webpage previews. - Fix opening video files in webpage previews in-app. - Fix sending links and markup with customized webpage previews. - Show "Saved Messages" as a first row when replying in another chat. - Fix selecting words by double-click with webpage previews. - Fix delayed webpage preview generation in preview options. - Add "show-peer-id-below-about" experimental option. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 10 +++++----- changelog.txt | 12 ++++++++++++ 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 9829733415..ef4280feff 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.12.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 2c2060f4a4..69de28dc88 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,0,0 - PRODUCTVERSION 4,11,0,0 + FILEVERSION 4,12,0,0 + PRODUCTVERSION 4,12,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "4.11.0.0" + VALUE "FileVersion", "4.12.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.11.0.0" + VALUE "ProductVersion", "4.12.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index d54e8a6d26..2b17faabdf 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,0,0 - PRODUCTVERSION 4,11,0,0 + FILEVERSION 4,12,0,0 + PRODUCTVERSION 4,12,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "4.11.0.0" + VALUE "FileVersion", "4.12.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.11.0.0" + VALUE "ProductVersion", "4.12.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 8fb21e740e..f47adfecc6 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 4011000; -constexpr auto AppVersionStr = "4.11"; +constexpr auto AppVersion = 4012000; +constexpr auto AppVersionStr = "4.12"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 0e22fe75e1..9eccfd6324 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4011000 -AppVersionStrMajor 4.11 -AppVersionStrSmall 4.11 -AppVersionStr 4.11.0 +AppVersion 4012000 +AppVersionStrMajor 4.12 +AppVersionStrSmall 4.12 +AppVersionStr 4.12.0 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.11 +AppVersionOriginal 4.12 diff --git a/changelog.txt b/changelog.txt index dea2160da8..2dc035e39a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,15 @@ +4.12 (29.10.23) + +- Fix crash in emoji status select. +- Fix crash in language change. +- Suggest shrinking only photos in webpage previews. +- Fix opening video files in webpage previews in-app. +- Fix sending links and markup with customized webpage previews. +- Show "Saved Messages" as a first row when replying in another chat. +- Fix selecting words by double-click with webpage previews. +- Fix delayed webpage preview generation in preview options. +- Add "show-peer-id-below-about" experimental option. + 4.11 (28.10.23) - View full statistics in your channels and group chats. From 096ddcad6d993814a07a8c1b7c485ec118add317 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 12:15:20 +0400 Subject: [PATCH 97/99] Version 4.11.1. - Fix crash in emoji status select. - Fix crash in language change. - Suggest shrinking only photos in webpage previews. - Fix opening video files in webpage previews in-app. - Fix sending links and markup with customized webpage previews. - Show "Saved Messages" as a first row when replying in another chat. - Fix selecting words by double-click with webpage previews. - Fix delayed webpage preview generation in preview options. - Add "show-peer-id-below-about" experimental option. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- .../view/controls/history_view_draft_options.cpp | 2 +- Telegram/build/version | 10 +++++----- changelog.txt | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index ef4280feff..1422235cb3 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.11.1.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 69de28dc88..5b638b6027 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,12,0,0 - PRODUCTVERSION 4,12,0,0 + FILEVERSION 4,11,1,0 + PRODUCTVERSION 4,11,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "4.12.0.0" + VALUE "FileVersion", "4.11.1.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.12.0.0" + VALUE "ProductVersion", "4.11.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 2b17faabdf..5903ca8d05 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,12,0,0 - PRODUCTVERSION 4,12,0,0 + FILEVERSION 4,11,1,0 + PRODUCTVERSION 4,11,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "4.12.0.0" + VALUE "FileVersion", "4.11.1.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.12.0.0" + VALUE "ProductVersion", "4.11.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index f47adfecc6..8bdb91ffdd 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 4012000; -constexpr auto AppVersionStr = "4.12"; +constexpr auto AppVersion = 4011001; +constexpr auto AppVersionStr = "4.11.1"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; 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 ea09d9b14f..483a8d2800 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -746,7 +746,7 @@ void DraftOptionsBox( const auto &resolver = args.resolver; state->performSwitch = [=](const QString &link, WebPageData *page) { const auto now = base::unixtime::now(); - if (!page || page->pendingTill > 0 && page->pendingTill < now) { + if (!page || (page->pendingTill > 0 && page->pendingTill < now)) { show->showToast(tr::lng_preview_cant(tr::now)); } else if (page->pendingTill > 0) { const auto delay = std::max(page->pendingTill - now, TimeId()); diff --git a/Telegram/build/version b/Telegram/build/version index 9eccfd6324..de406d1d20 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4012000 -AppVersionStrMajor 4.12 -AppVersionStrSmall 4.12 -AppVersionStr 4.12.0 +AppVersion 4011001 +AppVersionStrMajor 4.11 +AppVersionStrSmall 4.11.1 +AppVersionStr 4.11.1 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.12 +AppVersionOriginal 4.11.1 diff --git a/changelog.txt b/changelog.txt index 2dc035e39a..c201151d7b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,4 @@ -4.12 (29.10.23) +4.11.1 (29.10.23) - Fix crash in emoji status select. - Fix crash in language change. From 5fee0a7a737610f28066893adab137c321d58cd2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 12:31:45 +0400 Subject: [PATCH 98/99] Don't allow replying to local messages. --- Telegram/SourceFiles/history/history_inner_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 2da2c97e41..c4284cd884 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2380,7 +2380,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }; const auto addReplyAction = [&](HistoryItem *item) { - if (!item) { + if (!item || !item->isRegular()) { return; } const auto canSendReply = [&] { From 745ad45d47c2fb5c602ebc98ac1afbe789d2e4d9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2023 12:31:10 +0400 Subject: [PATCH 99/99] Version 4.11.1: Update message text padding. --- Telegram/SourceFiles/ui/chat/chat.style | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index e0fe81fdcb..0f1e19cde5 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -22,7 +22,7 @@ msgDateFont: font(13px); msgMinWidth: 160px; msgPhotoSize: 33px; msgPhotoSkip: 40px; -msgPadding: margins(10px, 8px, 10px, 8px); +msgPadding: margins(11px, 8px, 11px, 8px); msgMargin: margins(16px, 6px, 56px, 2px); msgMarginTopAttached: 0px; msgShadow: 2px;