From e6e1b9446d692d1e8b8869edb2c9bc6427bb6773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Novomesk=C3=BD?= <dnovomesky@gmail.com> Date: Wed, 3 Jul 2024 16:38:23 +0200 Subject: [PATCH 001/163] Upgrade libjxl on Linux to 0.10.3 --- Telegram/build/docker/centos_env/Dockerfile | 2 +- snap/snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 2b0c82ed0..71c9533eb 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -250,7 +250,7 @@ COPY --link --from=lcms2 {{ LibrariesPath }}/lcms2-cache / COPY --link --from=brotli {{ LibrariesPath }}/brotli-cache / COPY --link --from=highway {{ LibrariesPath }}/highway-cache / -RUN git clone -b v0.10.2 --depth=1 {{ GIT }}/libjxl/libjxl.git \ +RUN git clone -b v0.10.3 --depth=1 {{ GIT }}/libjxl/libjxl.git \ && cd libjxl \ && git apply ../patches/libjxl.patch \ && git submodule update --init --recursive --depth=1 third_party/libjpeg-turbo \ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 454da33b1..033424e1d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -237,7 +237,7 @@ parts: libjxl: source: https://github.com/libjxl/libjxl.git source-depth: 1 - source-tag: v0.10.2 + source-tag: v0.10.3 plugin: cmake build-environment: - LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s From 9ca990473232ff0dced5fbdb9f848bae61a0b797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Novomesk=C3=BD?= <dnovomesky@gmail.com> Date: Thu, 4 Jul 2024 09:12:54 +0200 Subject: [PATCH 002/163] Upgrade libjxl to v0.10.3 --- Telegram/build/prepare/prepare.py | 8 +++----- cmake | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 60d60ea47..8b1f71c68 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -938,7 +938,7 @@ mac: """) stage('libjxl', """ - git clone -b v0.8.2 --recursive --shallow-submodules https://github.com/libjxl/libjxl.git + git clone -b v0.10.3 --recursive --shallow-submodules https://github.com/libjxl/libjxl.git cd libjxl """ + setVar("cmake_defines", """ -DBUILD_SHARED_LIBS=OFF @@ -954,12 +954,10 @@ stage('libjxl', """ -DJPEGXL_ENABLE_SJPEG=OFF -DJPEGXL_ENABLE_OPENEXR=OFF -DJPEGXL_ENABLE_SKCMS=ON - -DJPEGXL_BUNDLE_SKCMS=ON -DJPEGXL_ENABLE_VIEWERS=OFF -DJPEGXL_ENABLE_TCMALLOC=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_COVERAGE=OFF - -DJPEGXL_ENABLE_PROFILER=OFF -DJPEGXL_WARNINGS_AS_ERRORS=OFF """) + """ win: @@ -967,8 +965,8 @@ win: -A %WIN32X64% ^ -DCMAKE_INSTALL_PREFIX=%LIBS_DIR%/local ^ -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>" ^ - -DCMAKE_C_FLAGS="/DJXL_STATIC_DEFINE /DJXL_THREADS_STATIC_DEFINE" ^ - -DCMAKE_CXX_FLAGS="/DJXL_STATIC_DEFINE /DJXL_THREADS_STATIC_DEFINE" ^ + -DCMAKE_C_FLAGS="/DJXL_STATIC_DEFINE /DJXL_THREADS_STATIC_DEFINE /DJXL_CMS_STATIC_DEFINE" ^ + -DCMAKE_CXX_FLAGS="/DJXL_STATIC_DEFINE /DJXL_THREADS_STATIC_DEFINE /DJXL_CMS_STATIC_DEFINE" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_CXX_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ diff --git a/cmake b/cmake index 5742caae6..4a4bc4cd3 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 5742caae65e4163e7faec238eb4e3e5c219ad09c +Subproject commit 4a4bc4cd34b3ade038541a2b8b2c79f05393d67b From 8d0d9bb0bd2fbeb8ef826e582bef5b0620950620 Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Sat, 6 Jul 2024 00:43:18 +0400 Subject: [PATCH 003/163] Delay clearing transient parent until the pip window is really exposed --- Telegram/SourceFiles/media/view/media_view_pip.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index 0975b5c6f..da359769f 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -351,10 +351,14 @@ void PipPanel::init() { widget()->resize(0, 0); widget()->hide(); - rp()->shownValue( - ) | rpl::filter([=](bool shown) { - return shown; - }) | rpl::start_with_next([=] { + rpl::merge( + rp()->shownValue() | rpl::to_empty, + rp()->paintRequest() | rpl::to_empty + ) | rpl::map([=] { + return widget()->windowHandle() + && widget()->windowHandle()->isExposed(); + }) | rpl::distinct_until_changed( + ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] { // Workaround Qt's forced transient parent. Ui::Platform::ClearTransientParent(widget()); }, rp()->lifetime()); From aa140b2919c46c0e0f0117f59bbf946aa3124043 Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Wed, 3 Jul 2024 22:55:10 +0400 Subject: [PATCH 004/163] Fix warnings on Windows --- .../history/view/history_view_pinned_bar.cpp | 4 ++-- .../media/history_view_similar_channels.cpp | 4 ++-- .../SourceFiles/platform/win/specific_win.cpp | 7 +++---- Telegram/SourceFiles/platform/win/tray_win.h | 2 +- .../details/storage_settings_scheme.cpp | 2 +- Telegram/cmake/lib_tgvoip.cmake | 21 +++++++------------ 6 files changed, 16 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index d530e4779..eebbb1848 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -76,9 +76,9 @@ namespace { return rpl::single(ContentWithoutPreview(item, repaint)); } constexpr auto kFullLoaded = 2; - constexpr auto kSomeLoaded = 1; - constexpr auto kNotLoaded = 0; const auto loadedLevel = [=] { + constexpr auto kSomeLoaded = 1; + constexpr auto kNotLoaded = 0; const auto preview = media->replyPreview(); return media->replyPreviewLoaded() ? kFullLoaded diff --git a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp index 05a03df4d..aa14b12c1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp @@ -167,7 +167,7 @@ void SimilarChannels::draw(Painter &p, const PaintContext &context) const { _hasHeavyPart = 1; validateLastPremiumLock(); const auto drawOne = [&](const Channel &channel) { - const auto geometry = channel.geometry.translated(-_scrollLeft, 0); + const auto geometry = channel.geometry.translated(-int(_scrollLeft), 0); const auto right = geometry.x() + geometry.width(); if (right <= 0) { return; @@ -501,7 +501,7 @@ TextState SimilarChannels::textState( return result; } for (const auto &channel : _channels) { - if (channel.geometry.translated(-_scrollLeft, 0).contains(point)) { + if (channel.geometry.translated(-int(_scrollLeft), 0).contains(point)) { result.link = channel.link; _lastPoint = point + QPoint(_scrollLeft, 0) diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 7a3b133c4..a35cfd3c4 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -98,7 +98,6 @@ BOOL CALLBACK FindToActivate(HWND hwnd, LPARAM lParam) { return TRUE; } // Found a Top-Level window. - auto level = 0; if (WindowIdFromHWND(hwnd) == request->windowId) { request->result = hwnd; request->resultLevel = 3; @@ -310,8 +309,8 @@ void psDoFixPrevious() { if (oldKeyRes2 == ERROR_SUCCESS) RegCloseKey(oldKey2); if (existNew1 || existNew2) { - const auto deleteKeyRes1 = existOld1 ? RegDeleteKey(HKEY_LOCAL_MACHINE, oldKeyStr1.c_str()) : ERROR_SUCCESS; - const auto deleteKeyRes2 = existOld2 ? RegDeleteKey(HKEY_LOCAL_MACHINE, oldKeyStr2.c_str()) : ERROR_SUCCESS; + if (existOld1) RegDeleteKey(HKEY_LOCAL_MACHINE, oldKeyStr1.c_str()); + if (existOld2) RegDeleteKey(HKEY_LOCAL_MACHINE, oldKeyStr2.c_str()); } QString userDesktopLnk, commonDesktopLnk; @@ -326,7 +325,7 @@ void psDoFixPrevious() { } QFile userDesktopFile(userDesktopLnk), commonDesktopFile(commonDesktopLnk); if (QFile::exists(userDesktopLnk) && QFile::exists(commonDesktopLnk) && userDesktopLnk != commonDesktopLnk) { - bool removed = QFile::remove(commonDesktopLnk); + QFile::remove(commonDesktopLnk); } } catch (...) { } diff --git a/Telegram/SourceFiles/platform/win/tray_win.h b/Telegram/SourceFiles/platform/win/tray_win.h index cb79c3beb..aed4cc6e3 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.h +++ b/Telegram/SourceFiles/platform/win/tray_win.h @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unique_qptr.h" namespace Window { -class CounterLayerArgs; +struct CounterLayerArgs; } // namespace Window namespace Ui { diff --git a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp index 9baca321c..695f8f58a 100644 --- a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp +++ b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp @@ -553,7 +553,7 @@ bool ReadSetting( const auto proxy = readProxy(); if (proxy) { list.push_back(proxy); - } else if (index < -list.size()) { + } else if (index < -int64(list.size())) { ++index; } else if (index > list.size()) { --index; diff --git a/Telegram/cmake/lib_tgvoip.cmake b/Telegram/cmake/lib_tgvoip.cmake index 886cd9935..fbae70966 100644 --- a/Telegram/cmake/lib_tgvoip.cmake +++ b/Telegram/cmake/lib_tgvoip.cmake @@ -134,20 +134,13 @@ PRIVATE ) if (WIN32) - if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - target_compile_options(lib_tgvoip_bundled - PRIVATE - /wd4005 - /wd4244 # conversion from 'int' to 'float', possible loss of data (several in webrtc) - /wd5055 # operator '>' deprecated between enumerations and floating-point types - ) - else() - target_compile_definitions(lib_tgvoip_bundled - PUBLIC - # Doesn't build with mingw for now - TGVOIP_NO_DSP - ) - endif() + target_compile_options_if_exists(lib_tgvoip_bundled + PRIVATE + /wd4005 # 'identifier' : macro redefinition + /wd4068 # unknown pragma + /wd4996 # deprecated + /wd5055 # operator '>' deprecated between enumerations and floating-point types + ) elseif (APPLE) target_compile_definitions(lib_tgvoip_bundled PUBLIC From bf7042df4456302f2104fb41afcaa5c5dfaf3662 Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Wed, 3 Jul 2024 22:55:30 +0400 Subject: [PATCH 005/163] Enable warnings as errors on Windows --- .github/workflows/win.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index ca0c7bf09..6de53f12d 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -169,6 +169,8 @@ jobs: %TDESKTOP_BUILD_GENERATOR% ^ %TDESKTOP_BUILD_ARCH% ^ %TDESKTOP_BUILD_API% ^ + -D CMAKE_C_FLAGS="/WX" ^ + -D CMAKE_CXX_FLAGS="/WX" ^ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^ -D DESKTOP_APP_NO_PDB=ON ^ From 054a6db3aee58d2cd024e747bfba9c29f19b13b3 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 6 Jul 2024 11:07:07 +0400 Subject: [PATCH 006/163] Fix warnings on Windows in submodules. --- Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index f30400147..1a50fd230 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit f30400147d997fedc787e214467d305db6c159e7 +Subproject commit 1a50fd2300da3198e751a22bf728d33822180e15 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ebd8609ee..067733899 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ebd8609ee73d48186b905787dd5bb3bcbb4f9f3f +Subproject commit 067733899d62caa5411f54904a431d3484fd02e3 From 66d6b461f39013ba710e978aba2296f3d4a0e56c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 4 Jul 2024 09:34:27 +0300 Subject: [PATCH 007/163] Fixed support type of credits history entry for ads in earn section. --- Telegram/SourceFiles/ui/effects/credits.style | 2 ++ Telegram/SourceFiles/ui/effects/credits_graphics.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index e78642348..baab38fb7 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -47,3 +47,5 @@ creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { starIconSmall: icon{{ "payments/small_star", windowFg }}; starIconSmallPadding: margins(0px, -2px, 0px, 0px); + +creditsHistoryEntryTypeAds: icon {{ "folders/folders_channels", premiumButtonFg }}; diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 9f48191f0..3f4ce02f2 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -205,6 +205,8 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; case Data::CreditsHistoryEntry::PeerType::PremiumBot: return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::Ads: + return { st::historyPeer6UserpicBg, st::historyPeer6UserpicBg2 }; case Data::CreditsHistoryEntry::PeerType::Unsupported: return { st::historyPeerArchiveUserpicBg, @@ -227,6 +229,8 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( ? st::sessionIconAndroid : (entry.peerType == PeerType::Fragment) ? st::introFragmentIcon + : (entry.peerType == PeerType::Ads) + ? st::creditsHistoryEntryTypeAds : st::dialogsInaccessibleUserpic).paintInCenter(p, rect); }; } @@ -442,6 +446,8 @@ Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback( TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) ? tr::lng_bot_username_description1_link + : (entry.peerType == Data::CreditsHistoryEntry::PeerType::Ads) + ? tr::lng_credits_box_history_entry_ads : tr::lng_credits_summary_history_entry_inner_in)( tr::now, TextWithEntities::Simple); From b377c02ad38d5043cbfaab37b3db7a86cfe381c9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 4 Jul 2024 10:07:18 +0300 Subject: [PATCH 008/163] Added support of min boost level for channel ads to feature list. --- .../premium/features/feature_off_sponsored.png | Bin 0 -> 771 bytes .../features/feature_off_sponsored@2x.png | Bin 0 -> 1540 bytes .../features/feature_off_sponsored@3x.png | Bin 0 -> 2194 bytes .../boxes/peers/replace_boost_box.cpp | 1 + Telegram/SourceFiles/ui/boxes/boost_box.cpp | 11 +++++++++-- Telegram/SourceFiles/ui/boxes/boost_box.h | 1 + Telegram/SourceFiles/ui/effects/premium.style | 1 + 7 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Telegram/Resources/icons/settings/premium/features/feature_off_sponsored.png create mode 100644 Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@3x.png diff --git a/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored.png b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored.png new file mode 100644 index 0000000000000000000000000000000000000000..06a33dc3c089717f0c8ad67beca035d1daea3184 GIT binary patch literal 771 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfhpJ1#WBP} z@NS5$$Erk;bMvQMaB<}3bQMwMIJlc*nzQ`{F%6D~HJvgnf+n{_SR7?aiUf04EY#$2 z(a02<xF};+_l}T`XGvWF|L&_V4X^&a;^#f%6PC{_&wc!}_x_nP_1ZF*B)g9){W9lj zKYa7vy|C3+`T6+|Kb(;E>g%uR)5W`wIx)}BT^m+iUw=PudwhKS`Sa)Po0YpyKb<#k z-nS~-_QMAm`eSzQ-hDRh^P4w5I%4|z`io`$*fLA5bzK~Ipl-kX1{Wnm37-9SDq`IW z1*V^NRZ3j9e!cMIFg`v$wq{2gx#K!!=H}A9Y(dWR=FjhSdu%e>>xk-&fB)>V_*$K& zdZ|wI(9zRdBwmr0o^IF&v}pEhX{UdyHgulO+RDYkG~@X3<JQ*J($do5;o%t@F2A&~ zvs<KbC?!O#Q%p>3)zw!sN(u`bU8H)|=AYM|>eYYz^Or9!M}t?YcWT^w`}XavTT;KY zM7Xr3zIySZLn+{yieQ+wQ0KCTK`XcH+SN7jX_2MI<qPGz&Og@{>n_?kM~Sob(CO2s zw{JI}eb$Wk;Nd<0cJADnW7fTN(FA+>Cf}JpY9<v$MVH=xpX;Z7(sk|pANNJ?q$mii zxc<7jx_ZC+qpwwkUOSQ<9u`P213mv(;>S#Fsl%T?e_k8L{aSvux3@P(n1)Esx8Ejr z^UTkQHXomCH2bW@U&G#?<}-aJv1?8~If0|xVMW<)S>IG6NtwI+@;o{#Zogf(W{pCG z)>Ng5e#@0R1@>&++A0c+C}!2VG}WnIQ%^tjP-(i9wN+~V`Q;TAr!Baz`2EbBeEhMj toZPa@nL6BtCN`S_&s~pBO!@cv-2T3`qBhw)RyRQj(9_k=Wt~$(69A{aJm3HT literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..996851dbc9f57f50181b43546053c9f9a7389393 GIT binary patch literal 1540 zcmV+f2K)JmP)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ00001b5ch_0Itp) z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NHZb?KzR9Fe^SXn5hZ5U>3VeFD6 zOUM?b$Tl%UYRs4j2ew2)NLdQWgsD+z!~q8s2TGVDLu!yEJK2+xiG!K3jAm?OU;h94 z-FfDn?|r?#@B8QAuity{d7gW@?)!b7dwVG;JUkC4@NfeEvlCF{zpt8_+S%C|pDs5= zOG_&)EzQl%O<7sFySux!we{fOKw30kU*EmGy}7wLTU%RcbwuUD!ot3P|Na-}SzcbY zw6qje;xO~`^GWyHw{Pz5?i`NHOI20X>gsCd$@uvA%*+gJ@xqFV0+%CTLtkHiVPS#P ze*XOV{P}YMAt^{!R#uaflZ3OevGMHLGg5l}`ZW;)0|TXixe?LP(HR&RAhq-J^SHP; zX*F-<LPJA;{P;l#y}i9gMn<<B1zla;i;D}ea&mGQ@%{zk`u+R&gm`^@otKw)zs`+> zii%2QWhE(IU0oFw6+L?NNJ4jie;)!RB_$Gc+A0fDk!4X)QQO<w85tS4?DFz*Q&ZFI z>}&u*UQbUiC@4rnLxZoIl$7M<<puiI)>dq6Y=3`0U*=y?3JMD7b@AKQ)`mDGI1394 zBnx9nEZ~Q+vGHAo#ZO3Bi+j%T@$q!)unJ4s+S;Up@E;u=m2d^yw{PEa)%gbGap=vP zH{76m@!|zCj%?4*&zqQ-Ff+I~xQGYxB!Kv1T)~CR4%yh)(2*o3Co@D3509Ol9WuGT zzHV=C&selbPf!0N4oRi}&_hB(P~)-a>+54yr>3TSe0<;vD5x>GEKg5Q#{Gi}0f(%u ztziIZvbMIywY0jr3ZGD9K{PNhKs*Hp2Qz`e!9nCX+seHVa7bNU9dM{@d{B7#^yyPN zIYdu;dwXJHB8egC!o$N)PEPn*GEoE^64yUGJbd}`B|0|5aI*~!4TXh;q|(vRAwxsB z!;H)h0SEmgCMKq;s){g?=e4!9l<H$+W8mIlODeKF4gnYmg*?yA&BX#+q!?NaB|K52 zGA<+};1I<ox{{FmZ?t#s-l6rPMt~X-5z*7rgWma@gzOsuhcF2N2f0SkEsojT-2Cd* zE0j%$`T6+`4-cD}nTe~2a|Ik48XAI*l9CcGLR^`+I2epO^cv32&KSfT9UY}nlvW1e z37-%O$YE}=7S~68N1=ii9?Yyyo;<lrUc7Y(&yhgK+^DIk3DB=!zoLH~A0G=E`1I*h zSy>qk?HI(my1Hoa#mhuTM}PS6fv*LJ(EHFX?;45tD*sSiTwG>nXX%Z_e{nEW+@baO z@#BvlKa%|A<z-4r3Ljnkg!uBmC`dFEV(Rk<5d;VSSz1~;Jw5&W`LmOg6KO)Lsi}#* zLveBOFT|Ub{0<RNeSJO9kB*KoYfC_5i%vqeU0q!n7ei)6MFsPzuC5L#!npJiA=`a1 zWh^jw5Oiv4Dwj@cZ*Onn(&Wk&$jZvXJWgD6Jyt{zYSYuxtl@A~q+KsAE&>-Qd<A@u zl1UUHJ~uKl65}}ujEsz6$Rrtrnn)V*aU?03nVAI2y%!Qf>=Wy*wi*x+KtTgBOa$iU z=91`g?D1xZTp-re)JUj1I5?0)7;`09Y>U^}+}tFwFJHc(crup!i<XuaAnxw&vQQu_ zBs^Kd$Ds&1hRl_f6_Nyq;QflI3$XnC{RvJYjqU90h>jsiK#fB(G&G!;m>^-CH<~er zDc2JJNdX)Zr?j+`zK-$nMJETkH2=`Rkcm=GK4GRn{K@6WtrGeP3LwGZmwl{ZjbZ3k z>Yl$CG8-Ek2}Jys?*Sq<MhiM8<`x#6ot?6dL72W15)!!8l9?B7u8b|XKxW|^2p=9O q-xz?9%z|PM#D^1jID!A>3H%L+7`mZr(d-}q0000<MNUMnLSTY;iMhQ1 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0863b33e8df564738eaaae505f5f6d44a73ab374 GIT binary patch literal 2194 zcmYLLcOca7AHQ=l%9fdokQ_2Ak@>|TbjT(v>&wrcot+Wq98oSKdqi0$d)#rHLeeFq zIwPm^wb#k~efs_J`~LBM-p?Md=k<O)Pa50|#(G}xJO~70H8Rw*0KDNJW1<7nuV@Jl zz=1<7V7j3C4?<L+pyy$0<Y{UOf&zIa5IEHbME|D?V1j^wK#&qJ2m*NUpSlvd|F;^J zK>p4D>6oQxumJ)w9vkWD-1k1WmLE3E<G_L2+re@-sVi!m5Z2iUY!~(Kv%v8<`ZM^d zw!RK03(Lg|vW+}lJcIrH>;5qtsYjSwg(w6qwo8Ql#}BRR*bs_I5s-}f<*Q7{Z)^Wd z7=~msUSZc0esU+vUf3|tK9MUsCpY(g4ZKv=t~vVj_;&Vh{}3gw4~`|;;I;|))?}Rx zxy6Tj|9Eeiw!Jj6G?JGGwSt05<s4d%H|HUKQw`4OWN=^xT0b~=d-R2J+{vNGi;6Sn zo<~+~{@)L#+bxT3hZtal)x%{Y*1xT{B%m%zThQBGnPQ9ny<KUC^^|@Zaj@w+M*I5K zcQBpjE+^48$Dz%C>^VtSMB`D5-%Rr}8JlM7#J<>*N1dV4OsrOQ)-flCZyO<zz_3c= zXKt_ZhE>zeiZxGY8{{i&k^8GN0?M8((@SFov~L|DN?*%iXg$JkVLFeDX5^;!G`KtF zsMU2KMVEsK-X1tF4$JhGetoYie2s;oYVS=MEAYxVjMZBKR!tJe0jKK<r7jd?kI?IW z-dP^MamFoW*1i@cgVE{sox08!8~d>%cuDDw@vz`cKa7YlTZ>=cE6eJYwbUV5{li`A z<(jh+!nsto%faPaspGlQBw+rx&I#3~1r#*0?7D5E=zE`(?(xfNp>PSd68V9rm-(mR zetq>$UDqFUM-FF+DXF^l(|<}#fH0mO|F+C}$o!a*{kl_Ui;EEWW=L=J?}xv3sif~+ z5q%LGFI_AyVN`schAVG=C{kB6_y@o3Khr!Yx)bTx=%SOtBA~)}YkDfZLosL&K<bQ$ z7O|hOtTtPEmjk;eN_1#`m{t{(l-5+F7WRp|XFi@aXF#ul3#k=zSP|9}v}lmVC7Fby zTOFyZz7uo!IbfZy!qtaVwInB^;+<6*`o{ikhV1;i94V{X`-;QU&kNsdle{JhZ-=lf zV9+{(U$9>HM#e0wOcd$!WNb30oaFnvA~y0>eB83A#DziRg_GBI*j@OYz^oLiUX{*c zhLp&%{Xrz3zD;}pxi{*k`WeqaI<FkmI4AUywH^9)@K4pix%ez_ASF}i_NQ#s8?1MO zKjU;)aw9f=^kn94ko%-$$n8Pp8Qb<BinWT17(@*(9GsqXB`|MHj3|WXGik=1cH<au z`-@hN?SZ93Xn-<%E0YGXyQ?$G&uwy&84&_i5Dl5dQu^5d!V}KLArwia2SNMM<PK}~ zo1teG80m5yV8ACaMhZ}Dpf-oSF-I#bb^1u&LS8k5+pYbTNu=L^{EjFUxO=F1sd>3U zP_zGJ?IeJ@#}(Y#Y}?w5I~`5Ud0umv2P;bk-~pC!?v518#MLem(Uy^1ETBrp6Mj{p zZr2(^u^O`?H6VOA!)8#cM&VU1{S3N_ER{2{(^c-$=1I1?dd#yD$T-^+e9_#8f{p;P zW>eCyaRG%#yWaoX;yz{U66fkI?zXEImENSPvCgo6lGv+nE=d`K_7@$zbs1Nt8szb) zBdh-HM%VRk9sTd=t|<_s`Yv|#mX(bLgH%f@74&jR#B4~I?B^Qu@{CWcT`GwE)h5b` zx-iDI(Gly3ip;+dcb>)$Q)X;Ypa%j=_c#VE$|_@7m0_JcAk~y-`ps;qXw_tP=oP%x z&rWO;6fNL+buPS@UhzQ$-d!r95$iR+B5rH2hMOpd0R(N>nyj@dUwEMv7poI25HQ;+ z_ZEs*rQ;v+C=@%s6nFLK@QR4UtD^lTXG}fan3!C=M_<@K&nbWL8pOW#S#50K!QP7) zf@?_}`dUTf0kVDZ(Nx;hxLff8ehi$p7<-^K4QF|*EY}Q>mfRQT?8~iX^3sDa2@uh( z5$}3@u;t&H?17O)J6hi_P0)r79aSR(bN|Y~BIP>&o`kx3gOR*)WlBD`<Q)gHU>U6{ zbB<8E<_aeWib|?R|N7n=gV1=07ZOwU9OV$aPy&9_(7`K7#oRE~{LeBsD8(B2ONU4( z5Z8|q0|3Wf+S;5SK=mqrT^@a5Ng|b$;+flkQ2#sHMFAyAT_#a>{R_iar_EW)RTfJ1 z0A;;a%%q3|)R>Ckx2y^DpKTq}X4tP$=0ecSk@N9fPSQfZ#MDPwpViiVgmBco&yx>; ze|c?3!qM*!4?Z9uj?g2Oa5jDg(JBT&>0*x$noPhYAr62HVz?pV?NK{bk}r3N;$(4s z4ms&Eg80i$XfeaEEGWF!eQs-x!eOORv-c5@)YmZGRwCZ`{8em*t$ZZJ<!Gz)Jpkyr zaXuA5#Zg{(590+;odinBdK_hr61+j6xf3(P#IcK1DGTGQ4qojPmueQ_$9(#Y4ove! zocU?oQim3aJ-FU1DsN*b)q=P<%eTx=$mP(7A`Q|X7HgvoyuI(uGP}8{LX#h#n}e$b zC`wg5iT6x4tXw7_8fJnB)N$68$+~LTVrrV&(3a`OEaK7X5r%ero(JmK8ETZz{<$qm z@qm)-%k&$uLm8V%=Vi2i>FB_Hx}D{}2fRXiy4hpNN2TCba^$<iogH9dR-FLyyK7zc z^=5^Oy(^Oa1QBhG5pN^kooW4_2dAo?aI8*i0NG4%rp3pS?`g@zH}NR$`UgF=ru_}M z@rJpH&v^@+jme5#^yva$Y#u}p1<}@>zFReO!!Z9o#vD;|aZ3q(;DZ4jEgQL|i9i1V NBYiWydR>>q{{ktQFPs1X literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 70648d627..a3bc8b69b 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -459,6 +459,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null<ChannelData*> channel) { .customWallpaperLevel = group ? levelLimits.groupCustomWallpaperLevelMin() : levelLimits.channelCustomWallpaperLevelMin(), + .sponsoredLevel = levelLimits.channelRestrictSponsoredLevelMin(), }; } diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index 691d84f16..bb99e43a8 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -175,7 +175,7 @@ void AddFeaturesList( st::boostFeatureIconPosition); }; const auto proj = &Ui::Text::RichLangValue; - const auto max = std::max({ + const auto lowMax = std::max({ features.linkLogoLevel, features.transcribeLevel, features.emojiPackLevel, @@ -189,9 +189,13 @@ void AddFeaturesList( ? 0 : features.linkStylesByLevel.back().first), }); + const auto highMax = std::max(lowMax, features.sponsoredLevel); auto nameColors = 0; auto linkStyles = 0; - for (auto i = std::max(startFromLevel, 1); i <= max; ++i) { + for (auto i = std::max(startFromLevel, 1); i <= highMax; ++i) { + if ((i > lowMax) && (i < highMax)) { + continue; + } const auto unlocks = (i == startFromLevel); container->add( MakeFeaturesBadge( @@ -202,6 +206,9 @@ void AddFeaturesList( lt_count, rpl::single(float64(i)))), st::boostLevelBadgePadding); + if (i >= features.sponsoredLevel) { + add(tr::lng_channel_earn_off(proj), st::boostFeatureOffSponsored); + } if (i >= features.customWallpaperLevel) { add( (group diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index ecac121b9..6c129512d 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -40,6 +40,7 @@ struct BoostFeatures { int wallpaperLevel = 0; int wallpapersCount = 0; int customWallpaperLevel = 0; + int sponsoredLevel = 0; }; struct BoostBoxData { diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index afdac7146..33fae62eb 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -364,3 +364,4 @@ boostFeatureLink: icon{{ "settings/premium/features/feature_links", windowBgActi boostFeatureName: icon{{ "settings/premium/features/feature_color_names", windowBgActive }}; boostFeatureStories: icon{{ "settings/premium/features/feature_stories", windowBgActive }}; boostFeatureTranscribe: icon{{ "settings/premium/features/feature_voice", windowBgActive }}; +boostFeatureOffSponsored: icon{{ "settings/premium/features/feature_off_sponsored", windowBgActive }}; From b648548001866158420cda364f61c19dd86752b4 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 4 Jul 2024 08:57:41 +0400 Subject: [PATCH 009/163] Don't insert "data:image.." after image paste cancel. --- Telegram/SourceFiles/history/history_widget.cpp | 2 +- .../history/view/controls/history_view_compose_controls.cpp | 2 +- Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp | 4 ++++ Telegram/SourceFiles/ui/chat/attach/attach_prepare.h | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index fe0610daf..0a3c5a064 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5668,7 +5668,7 @@ bool HistoryWidget::confirmSendingFiles( cursor.setPosition(position, QTextCursor::KeepAnchor); } _field->setTextCursor(cursor); - if (!insertTextOnCancel.isEmpty()) { + if (Ui::InsertTextOnImageCancel(insertTextOnCancel)) { _field->textCursor().insertText(insertTextOnCancel); } })); 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 779f0386b..09aa2178e 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -3415,7 +3415,7 @@ Fn<void()> ComposeControls::restoreTextCallback( cursor.setPosition(position, QTextCursor::KeepAnchor); } _field->setTextCursor(cursor); - if (!insertTextOnCancel.isEmpty()) { + if (Ui::InsertTextOnImageCancel(insertTextOnCancel)) { _field->textCursor().insertText(insertTextOnCancel); } }); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index e3f90912f..505fa455b 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -82,6 +82,10 @@ bool CanBeInAlbumType(PreparedFile::Type type, AlbumType album) { Unexpected("AlbumType in CanBeInAlbumType."); } +bool InsertTextOnImageCancel(const QString &text) { + return !text.isEmpty() && !text.startsWith(u"data:image"_q); +} + PreparedList PreparedList::Reordered( PreparedList &&list, std::vector<int> order) { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index 49fd71fa1..c1fb3a379 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -88,6 +88,7 @@ struct PreparedFile { }; [[nodiscard]] bool CanBeInAlbumType(PreparedFile::Type type, AlbumType album); +[[nodiscard]] bool InsertTextOnImageCancel(const QString &text); struct PreparedList { enum class Error { From 8c97e915ec8580491485605ea19ab24ec7a7bcfa Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 4 Jul 2024 09:06:17 +0400 Subject: [PATCH 010/163] Create .mm source blanks for macOS modules. --- Telegram/create.bat | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Telegram/create.bat b/Telegram/create.bat index 85ec9439b..2307b0d2a 100644 --- a/Telegram/create.bat +++ b/Telegram/create.bat @@ -67,26 +67,30 @@ exit /b %errorlevel% set "CommandPath=%1" set "CommandPathUnix=!CommandPath:\=/!" set "CommandPathWin=!CommandPath:/=\!" - + if "!CommandPathUnix:~-4!" == "_mac" ( + set "CommandExt=mm" + ) else ( + set "CommandExt=cpp" + ) if "!CommandPathUnix!" == "" ( echo Provide source path. exit /b 1 - ) else if exist "SourceFiles\!CommandPathWin!.cpp" ( + ) else if exist "SourceFiles\!CommandPathWin!.!CommandExt!" ( echo This source already exists. exit /b 1 ) - echo Generating source !CommandPathUnix!.cpp.. - mkdir "SourceFiles\!CommandPathWin!.cpp" - rmdir "SourceFiles\!CommandPathWin!.cpp" + echo Generating source !CommandPathUnix!.!CommandExt!.. + mkdir "SourceFiles\!CommandPathWin!.!CommandExt!" + rmdir "SourceFiles\!CommandPathWin!.!CommandExt!" - call :write_comment !CommandPathWin!.cpp + call :write_comment !CommandPathWin!.!CommandExt! set "quote=""" set "quote=!quote:~0,1!" set "source1=#include !quote!!CommandPathUnix!.h!quote!" ( echo !source1! echo. - )>> "SourceFiles\!CommandPathWin!.cpp" + )>> "SourceFiles\!CommandPathWin!.!CommandExt!" exit /b ) From 219671a3bcd73cd5859fbc174eaa09abe082a3fd Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 4 Jul 2024 12:01:59 +0400 Subject: [PATCH 011/163] Fix archive in Main Menu context menu. --- Telegram/SourceFiles/window/window_peer_menu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index bce7a9ab1..f8259eec7 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1472,7 +1472,8 @@ void Filler::fillArchiveActions() { const auto controller = _controller; const auto hidden = controller->session().settings().archiveCollapsed(); - { + const auto inmenu = controller->session().settings().archiveInMainMenu(); + if (!inmenu) { const auto text = hidden ? tr::lng_context_archive_expand(tr::now) : tr::lng_context_archive_collapse(tr::now); @@ -1481,7 +1482,6 @@ void Filler::fillArchiveActions() { controller->session().saveSettingsDelayed(); }, hidden ? &st::menuIconExpand : &st::menuIconCollapse); } - const auto inmenu = controller->session().settings().archiveInMainMenu(); { const auto text = inmenu ? tr::lng_context_archive_to_list(tr::now) From 1028219276fc1d87fde9e9fd8dad9cb2af2180ed Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 6 Jul 2024 10:52:44 +0400 Subject: [PATCH 012/163] Allow chats list preview for narrow photos. --- Telegram/SourceFiles/data/data_media_types.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 15dfedecd..a7b2690ea 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -121,8 +121,8 @@ struct AlbumCounts { ImageRoundRadius radius, bool spoiler) { const auto original = image->original(); - if (original.width() * 10 < original.height() - || original.height() * 10 < original.width()) { + if (original.width() * 20 < original.height() + || original.height() * 20 < original.width()) { return QImage(); } const auto factor = style::DevicePixelRatio(); From b6664625ea6b92fc12180225799ebe6fefc478bc Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 6 Jul 2024 13:25:39 +0400 Subject: [PATCH 013/163] Fix assigning text after formatted text. Fixes #28115. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 067733899..366fc9843 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 067733899d62caa5411f54904a431d3484fd02e3 +Subproject commit 366fc9843562da1628385e796a4d6f114f8057a7 From f20475f07edaf2394a3cba5ec7397284be897acf Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 6 Jul 2024 13:30:39 +0400 Subject: [PATCH 014/163] Show forbidden icon on disabled webview button. --- Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 5e9dde92e..569c61bb9 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -178,6 +178,8 @@ void Panel::Button::updateFg(QColor fg) { void Panel::Button::updateArgs(MainButtonArgs &&args) { _textFull = std::move(args.text); setDisabled(!args.isActive); + setPointerCursor(false); + setCursor(args.isActive ? style::cur_pointer : Qt::ForbiddenCursor); setVisible(args.isVisible); toggleProgress(args.isProgressVisible); update(); From df277b366b13d41e180168529a5385dfde1a02a4 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 6 Jul 2024 13:30:58 +0400 Subject: [PATCH 015/163] Fix build on Windows. --- Telegram/SourceFiles/_other/updater_win.cpp | 4 ++-- Telegram/ThirdParty/libtgvoip | 2 +- Telegram/lib_webrtc | 2 +- Telegram/lib_webview | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/_other/updater_win.cpp b/Telegram/SourceFiles/_other/updater_win.cpp index 0b9b24785..e779c8f20 100644 --- a/Telegram/SourceFiles/_other/updater_win.cpp +++ b/Telegram/SourceFiles/_other/updater_win.cpp @@ -571,8 +571,8 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) { } if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) { WCHAR wstrPath[maxFileLen]; - DWORD wstrPathLen; - if (wstrPathLen = GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { + DWORD wstrPathLen = GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen); + if (wstrPathLen) { wsprintf(wstrPath + wstrPathLen, L"\\%s\\", _programName); hDumpFile = _generateDumpFileAtPath(wstrPath); } diff --git a/Telegram/ThirdParty/libtgvoip b/Telegram/ThirdParty/libtgvoip index 25facad34..2d2592860 160000 --- a/Telegram/ThirdParty/libtgvoip +++ b/Telegram/ThirdParty/libtgvoip @@ -1 +1 @@ -Subproject commit 25facad342c3280315f9ef553906f46c3eeba1e4 +Subproject commit 2d2592860478e60d972b96e67ee034b8a71bb57a diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index f701713cd..eb9496540 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit f701713cd798bd7d5f69d318fdefb125d101aa76 +Subproject commit eb9496540356945e2c9fb700bcfa51444fd36f41 diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 659b91812..363db4e49 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 659b9181240aae16c05ef8ab7e6c4dd527afcf8a +Subproject commit 363db4e49a0b78e5dd08bd922e09cf8810318c09 From 149c69c9f5deb5577fb44394796ed15774024b73 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 6 Jul 2024 22:13:13 +0400 Subject: [PATCH 016/163] Use a separate string for Your Stars in Settings. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/settings/settings_main.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index db9afbe0e..ea754fcf9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1115,6 +1115,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_faq" = "Telegram FAQ"; "lng_settings_faq_link" = "https://telegram.org/faq#general-questions"; "lng_settings_features" = "Telegram Features"; +"lng_settings_credits" = "Your Stars"; "lng_settings_logout" = "Log Out"; "lng_sure_logout" = "Are you sure you want to log out?"; diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index d08ce9a11..363ad342f 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -503,7 +503,7 @@ void SetupPremium( AddPremiumStar( AddButtonWithLabel( wrap->entity(), - tr::lng_credits_summary_title(), + tr::lng_settings_credits(), controller->session().creditsValue( ) | rpl::map([=](uint64 c) { return c ? Lang::FormatCountToShort(c).string : QString{}; From a01d48f0631a6cce547ce3137a15102edd8ddafb Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 6 Jul 2024 22:21:04 +0400 Subject: [PATCH 017/163] Update submodules. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 366fc9843..96be2a6b7 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 366fc9843562da1628385e796a4d6f114f8057a7 +Subproject commit 96be2a6b72f0405a9c01407148303fba2dab101c From 78093173a92e9b0b60086ed5d91bdc051be7fb67 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 6 Jul 2024 22:25:44 +0400 Subject: [PATCH 018/163] Version 5.2.3. - Fix crash in bot star stats page. - Bug fixes and other minor improvements. --- 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 ++++---- Telegram/lib_storage | 2 +- changelog.txt | 5 +++++ 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 53203464b..46918013d 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ <Identity Name="TelegramMessengerLLP.TelegramDesktop" ProcessorArchitecture="ARCHITECTURE" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" - Version="5.2.2.0" /> + Version="5.2.3.0" /> <Properties> <DisplayName>Telegram Desktop</DisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 2d9446598..c47677eb2 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,2,0 - PRODUCTVERSION 5,2,2,0 + FILEVERSION 5,2,3,0 + PRODUCTVERSION 5,2,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.2.2.0" + VALUE "FileVersion", "5.2.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.2.0" + VALUE "ProductVersion", "5.2.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index abd5f4d0e..5594959fe 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,2,0 - PRODUCTVERSION 5,2,2,0 + FILEVERSION 5,2,3,0 + PRODUCTVERSION 5,2,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.2.2.0" + VALUE "FileVersion", "5.2.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.2.0" + VALUE "ProductVersion", "5.2.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 57252c888..e1ac92bf4 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 = 5002002; -constexpr auto AppVersionStr = "5.2.2"; +constexpr auto AppVersion = 5002003; +constexpr auto AppVersionStr = "5.2.3"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 553191613..4d2b86059 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5002002 +AppVersion 5002003 AppVersionStrMajor 5.2 -AppVersionStrSmall 5.2.2 -AppVersionStr 5.2.2 +AppVersionStrSmall 5.2.3 +AppVersionStr 5.2.3 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.2.2 +AppVersionOriginal 5.2.3 diff --git a/Telegram/lib_storage b/Telegram/lib_storage index 0971b69ca..ccdc72548 160000 --- a/Telegram/lib_storage +++ b/Telegram/lib_storage @@ -1 +1 @@ -Subproject commit 0971b69ca90f1697ef81276d9820dcd6d26de4ac +Subproject commit ccdc72548a5065b5991b4e06e610d76bc4f6023e diff --git a/changelog.txt b/changelog.txt index 6e398bcc6..c0298f855 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +5.2.3 (07.07.24) + +- Fix crash in bot star stats page. +- Bug fixes and other minor improvements. + 5.2.2 (02.07.24) - Fix topics search in topic groups. From 6effac7915e271e7762bd7997a1c858e67bb3e9e Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 9 Jul 2024 15:47:14 +0200 Subject: [PATCH 019/163] Migrate games to AttachWebView. --- Telegram/SourceFiles/api/api_bot.cpp | 6 +- Telegram/SourceFiles/boxes/share_box.cpp | 200 ++---------------- Telegram/SourceFiles/boxes/share_box.h | 12 +- .../SourceFiles/core/click_handler_types.cpp | 37 +++- .../SourceFiles/core/local_url_handlers.cpp | 19 -- .../view/history_view_context_menu.cpp | 11 +- .../history/view/history_view_context_menu.h | 4 + .../inline_bots/bot_attach_web_view.cpp | 81 +++++++ .../inline_bots/bot_attach_web_view.h | 15 +- .../ui/chat/attach/attach_bot_webview.cpp | 51 ++++- .../ui/chat/attach/attach_bot_webview.h | 10 + 11 files changed, 223 insertions(+), 223 deletions(-) diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index cf4611563..929051261 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -127,11 +127,7 @@ void SendBotCallbackData( UrlClickHandler::Open(link); return; } - const auto scoreLink = AppendShareGameScoreUrl( - session, - link, - item->fullId()); - BotGameUrlClickHandler(bot, scoreLink).onClick({ + BotGameUrlClickHandler(bot, link).onClick({ Qt::LeftButton, QVariant::fromValue(ClickHandlerContext{ .itemId = item->fullId(), diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 043a8ad18..e7e3054aa 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -1409,55 +1409,6 @@ std::vector<not_null<Data::Thread*>> ShareBox::Inner::selected() const { return result; } -QString AppendShareGameScoreUrl( - not_null<Main::Session*> session, - const QString &url, - const FullMsgId &fullId) { - auto shareHashData = QByteArray(0x20, Qt::Uninitialized); - auto shareHashDataInts = reinterpret_cast<uint64*>(shareHashData.data()); - const auto peer = fullId.peer - ? session->data().peerLoaded(fullId.peer) - : static_cast<PeerData*>(nullptr); - const auto channelAccessHash = uint64((peer && peer->isChannel()) - ? peer->asChannel()->access - : 0); - shareHashDataInts[0] = session->userId().bare; - shareHashDataInts[1] = fullId.peer.value; - shareHashDataInts[2] = uint64(fullId.msg.bare); - shareHashDataInts[3] = channelAccessHash; - - // Count SHA1() of data. - auto key128Size = 0x10; - auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized); - hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data()); - - //// Mix in channel access hash to the first 64 bits of SHA1 of data. - //*reinterpret_cast<uint64*>(shareHashEncrypted.data()) ^= channelAccessHash; - - // Encrypt data. - if (!session->local().encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) { - return url; - } - - auto shareHash = shareHashEncrypted.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - auto shareUrl = u"tg://share_game_score?hash="_q + QString::fromLatin1(shareHash); - - auto shareComponent = u"tgShareScoreUrl="_q + qthelp::url_encode(shareUrl); - - auto hashPosition = url.indexOf('#'); - if (hashPosition < 0) { - return url + '#' + shareComponent; - } - auto hash = url.mid(hashPosition + 1); - if (hash.indexOf('=') >= 0 || hash.indexOf('?') >= 0) { - return url + '&' + shareComponent; - } - if (!hash.isEmpty()) { - return url + '?' + shareComponent; - } - return url + shareComponent; -} - ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs( const std::vector<not_null<Data::Thread*>> &result, const MessageIdsList &msgIds) { @@ -1612,9 +1563,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( } void FastShareMessage( - not_null<Window::SessionController*> controller, + std::shared_ptr<Main::SessionShow> show, not_null<HistoryItem*> item) { - const auto show = controller->uiShow(); const auto history = item->history(); const auto owner = &history->owner(); const auto session = &history->session(); @@ -1643,7 +1593,7 @@ void FastShareMessage( } if (item->hasDirectLink()) { using namespace HistoryView; - CopyPostLink(controller, item->fullId(), Context::History); + CopyPostLink(show, item->fullId(), Context::History); } else if (const auto bot = item->getMessageBot()) { if (const auto media = item->media()) { if (const auto game = media->game()) { @@ -1675,23 +1625,27 @@ void FastShareMessage( auto copyLinkCallback = canCopyLink ? Fn<void()>(std::move(copyCallback)) : Fn<void()>(); - controller->show( - Box<ShareBox>(ShareBox::Descriptor{ - .session = session, - .copyCallback = std::move(copyLinkCallback), - .submitCallback = ShareBox::DefaultForwardCallback( - show, - history, - msgIds), - .filterCallback = std::move(filterCallback), - .forwardOptions = { - .sendersCount = ItemsForwardSendersCount(items), - .captionsCount = ItemsForwardCaptionsCount(items), - .show = !hasOnlyForcedForwardedInfo, - }, - .premiumRequiredError = SharePremiumRequiredError(), - }), - Ui::LayerOption::CloseOther); + show->show(Box<ShareBox>(ShareBox::Descriptor{ + .session = session, + .copyCallback = std::move(copyLinkCallback), + .submitCallback = ShareBox::DefaultForwardCallback( + show, + history, + msgIds), + .filterCallback = std::move(filterCallback), + .forwardOptions = { + .sendersCount = ItemsForwardSendersCount(items), + .captionsCount = ItemsForwardCaptionsCount(items), + .show = !hasOnlyForcedForwardedInfo, + }, + .premiumRequiredError = SharePremiumRequiredError(), + }), Ui::LayerOption::CloseOther); +} + +void FastShareMessage( + not_null<Window::SessionController*> controller, + not_null<HistoryItem*> item) { + FastShareMessage(controller->uiShow(), item); } void FastShareLink( @@ -1793,111 +1747,3 @@ auto SharePremiumRequiredError() -> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> { return WritePremiumRequiredError; } - -void ShareGameScoreByHash( - not_null<Window::SessionController*> controller, - const QString &hash) { - auto &session = controller->session(); - auto key128Size = 0x10; - - auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) { - controller->show( - Ui::MakeInformBox(tr::lng_confirm_phone_link_invalid()), - Ui::LayerOption::CloseOther); - return; - } - - // Decrypt data. - auto hashData = QByteArray(hashEncrypted.size() - key128Size, Qt::Uninitialized); - if (!session.local().decrypt(hashEncrypted.constData() + key128Size, hashData.data(), hashEncrypted.size() - key128Size, hashEncrypted.constData())) { - return; - } - - // Count SHA1() of data. - char dataSha1[20] = { 0 }; - hashSha1(hashData.constData(), hashData.size(), dataSha1); - - //// Mix out channel access hash from the first 64 bits of SHA1 of data. - //auto channelAccessHash = *reinterpret_cast<uint64*>(hashEncrypted.data()) ^ *reinterpret_cast<uint64*>(dataSha1); - - //// Check next 64 bits of SHA1() of data. - //auto skipSha1Part = sizeof(channelAccessHash); - //if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) { - // Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now))); - // return; - //} - - // Check 128 bits of SHA1() of data. - if (memcmp(dataSha1, hashEncrypted.constData(), key128Size) != 0) { - controller->show( - Ui::MakeInformBox(tr::lng_share_wrong_user()), - Ui::LayerOption::CloseOther); - return; - } - - auto hashDataInts = reinterpret_cast<uint64*>(hashData.data()); - if (hashDataInts[0] != session.userId().bare) { - controller->show( - Ui::MakeInformBox(tr::lng_share_wrong_user()), - Ui::LayerOption::CloseOther); - return; - } - - const auto peerId = PeerId(hashDataInts[1]); - const auto channelAccessHash = hashDataInts[3]; - if (!peerIsChannel(peerId) && channelAccessHash) { - // If there is no channel id, there should be no channel access_hash. - controller->show( - Ui::MakeInformBox(tr::lng_share_wrong_user()), - Ui::LayerOption::CloseOther); - return; - } - - const auto msgId = MsgId(int64(hashDataInts[2])); - if (const auto item = session.data().message(peerId, msgId)) { - FastShareMessage(controller, item); - } else { - const auto weak = base::make_weak(controller); - const auto resolveMessageAndShareScore = crl::guard(weak, [=]( - PeerData *peer) { - auto done = crl::guard(weak, [=] { - const auto item = weak->session().data().message( - peerId, - msgId); - if (item) { - FastShareMessage(weak.get(), item); - } else { - weak->show( - Ui::MakeInformBox(tr::lng_edit_deleted()), - Ui::LayerOption::CloseOther); - } - }); - auto &api = weak->session().api(); - api.requestMessageData(peer, msgId, std::move(done)); - }); - - const auto peer = peerIsChannel(peerId) - ? controller->session().data().peerLoaded(peerId) - : nullptr; - if (peer || !peerIsChannel(peerId)) { - resolveMessageAndShareScore(peer); - } else { - const auto owner = &controller->session().data(); - controller->session().api().request(MTPchannels_GetChannels( - MTP_vector<MTPInputChannel>( - 1, - MTP_inputChannel( - MTP_long(peerToChannel(peerId).bare), - MTP_long(channelAccessHash))) - )).done([=](const MTPmessages_Chats &result) { - result.match([&](const auto &data) { - owner->processChats(data.vchats()); - }); - if (const auto peer = owner->peerLoaded(peerId)) { - resolveMessageAndShareScore(peer); - } - }).send(); - } - } -} diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index 32e824b15..d0bf28ce9 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -59,13 +59,11 @@ class SlideWrap; class PopupMenu; } // namespace Ui -QString AppendShareGameScoreUrl( - not_null<Main::Session*> session, - const QString &url, - const FullMsgId &fullId); -void ShareGameScoreByHash( - not_null<Window::SessionController*> controller, - const QString &hash); +class ShareBox; + +void FastShareMessage( + std::shared_ptr<Main::SessionShow> show, + not_null<HistoryItem*> item); void FastShareMessage( not_null<Window::SessionController*> controller, not_null<HistoryItem*> item); diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 9b03e0b74..eadb16181 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/view/history_view_element.h" #include "history/history_item.h" +#include "inline_bots/bot_attach_web_view.h" +#include "data/data_game.h" #include "data/data_user.h" #include "data/data_session.h" #include "window/window_controller.h" @@ -171,23 +173,40 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const { if (Core::InternalPassportLink(url)) { return; } - - const auto open = [=] { + const auto openLink = [=] { UrlClickHandler::Open(url, context.other); }; - if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive)) { - open(); - } else if (!_bot - || _bot->isVerified() + const auto my = context.other.value<ClickHandlerContext>(); + const auto weakController = my.sessionWindow; + const auto controller = weakController.get(); + const auto item = controller + ? controller->session().data().message(my.itemId) + : nullptr; + const auto media = item ? item->media() : nullptr; + const auto game = media ? media->game() : nullptr; + if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) { + openLink(); + } + const auto bot = _bot; + const auto title = game->title; + const auto itemId = my.itemId; + const auto openGame = [=] { + bot->session().attachWebView().showGame({ + .bot = bot, + .context = itemId, + .url = url, + .title = title, + }); + }; + if (_bot->isVerified() || _bot->session().local().isBotTrustedOpenGame(_bot->id)) { - open(); + openGame(); } else { - const auto my = context.other.value<ClickHandlerContext>(); if (const auto controller = my.sessionWindow.get()) { const auto callback = [=, bot = _bot](Fn<void()> close) { close(); bot->session().local().markBotTrustedOpenGame(bot->id); - open(); + openGame(); }; controller->show(Ui::MakeConfirmBox({ .text = tr::lng_allow_bot_pass( diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 03c3254ab..9f4860da3 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -327,21 +327,6 @@ bool ConfirmPhone( return true; } -bool ShareGameScore( - Window::SessionController *controller, - const Match &match, - const QVariant &context) { - if (!controller) { - return false; - } - const auto params = url_parse_params( - match->captured(1), - qthelp::UrlParamNameTransform::ToLower); - ShareGameScoreByHash(controller, params.value(u"hash"_q)); - controller->window().activate(); - return true; -} - bool ApplySocksProxy( Window::SessionController *controller, const Match &match, @@ -1230,10 +1215,6 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() { u"^confirmphone/?\\?(.+)(#|$)"_q, ConfirmPhone }, - { - u"^share_game_score/?\\?(.+)(#|$)"_q, - ShareGameScore - }, { u"^socks/?\\?(.+)(#|$)"_q, ApplySocksProxy diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index a53ab2028..49013f1c8 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1284,7 +1284,14 @@ void CopyPostLink( not_null<Window::SessionController*> controller, FullMsgId itemId, Context context) { - const auto item = controller->session().data().message(itemId); + CopyPostLink(controller->uiShow(), itemId, context); +} + +void CopyPostLink( + std::shared_ptr<Main::SessionShow> show, + FullMsgId itemId, + Context context) { + const auto item = show->session().data().message(itemId); if (!item || !item->hasDirectLink()) { return; } @@ -1311,7 +1318,7 @@ void CopyPostLink( return channel->hasUsername(); }(); - controller->showToast(isPublicLink + show->showToast(isPublicLink ? tr::lng_channel_public_link_copied(tr::now) : tr::lng_context_about_private_link(tr::now)); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index 8f00f4da8..efb2a5fdd 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -61,6 +61,10 @@ void CopyPostLink( not_null<Window::SessionController*> controller, FullMsgId itemId, Context context); +void CopyPostLink( + std::shared_ptr<Main::SessionShow> show, + FullMsgId itemId, + Context context); void CopyStoryLink( std::shared_ptr<Main::SessionShow> show, FullStoryId storyId); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index eb938ed26..ff2637011 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_blocked_peers.h" #include "api/api_common.h" #include "base/qthelp_url.h" +#include "boxes/share_box.h" #include "core/click_handler_types.h" #include "data/data_bot_app.h" #include "data/data_changes.h" @@ -802,6 +803,16 @@ void AttachWebView::botInvokeCustomMethod( }).send(); } +void AttachWebView::botShareGameScore() { + if (!_panel || !_gameContext) { + return; + } else if (const auto item = _session->data().message(_gameContext)) { + FastShareMessage(uiShow(), item); + } else { + _panel->showToast({ tr::lng_message_not_found(tr::now) }); + } +} + void AttachWebView::botClose() { crl::on_main(this, [=] { cancel(); }); } @@ -1547,6 +1558,7 @@ void AttachWebView::show( _lastShownUrl = url; _lastShownQueryId = queryId; _lastShownButtonText = buttonText; + _gameContext = {}; base::take(_panel); _catchingCancelInShowCall = true; _panel = Ui::BotWebView::Show({ @@ -1562,6 +1574,24 @@ void AttachWebView::show( started(queryId); } +void AttachWebView::showGame(ShowGameParams &¶ms) { + ActiveWebViews().emplace(this); + + base::take(_panel); + _gameContext = params.context; + + _catchingCancelInShowCall = true; + _panel = Ui::BotWebView::Show({ + .url = params.url, + .storageId = _session->local().resolveStorageIdBots(), + .title = rpl::single(params.title), + .bottom = rpl::single('@' + params.bot->username()), + .delegate = static_cast<Ui::BotWebView::Delegate*>(this), + .menuButtons = Ui::BotWebView::MenuButton::ShareGame, + }); + _catchingCancelInShowCall = false; +} + void AttachWebView::started(uint64 queryId) { Expects(_bot != nullptr); Expects(_context != nullptr); @@ -1601,6 +1631,57 @@ void AttachWebView::started(uint64 queryId) { }, _panel->lifetime()); } +std::shared_ptr<Main::SessionShow> AttachWebView::uiShow() { + class Show final : public Main::SessionShow { + public: + explicit Show(not_null<AttachWebView*> that) : _that(that) { + } + + void showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr<Ui::BoxContent>, + std::unique_ptr<Ui::LayerWidget>> &&layer, + Ui::LayerOptions options, + anim::type animated) const override { + using UniqueLayer = std::unique_ptr<Ui::LayerWidget>; + using ObjectBox = object_ptr<Ui::BoxContent>; + const auto panel = _that ? _that->_panel.get() : nullptr; + if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) { + Unexpected("Layers in AttachWebView are not implemented."); + } else if (auto box = std::get_if<ObjectBox>(&layer)) { + if (panel) { + panel->showBox(std::move(*box), options, animated); + } + } else if (panel) { + panel->hideLayer(animated); + } + } + [[nodiscard]] not_null<QWidget*> toastParent() const override { + const auto panel = _that ? _that->_panel.get() : nullptr; + + Ensures(panel != nullptr); + return panel->toastParent(); + } + [[nodiscard]] bool valid() const override { + return _that && (_that->_panel != nullptr); + } + operator bool() const override { + return valid(); + } + + [[nodiscard]] Main::Session &session() const override { + Expects(_that.get() != nullptr); + return *_that->_session; + } + + private: + const base::weak_ptr<AttachWebView> _that; + + }; + return std::make_shared<Show>(this); +} + void AttachWebView::showToast( const QString &text, Window::SessionController *controller) { diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 0f8cb6e13..87648b07e 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -29,6 +29,7 @@ class Panel; namespace Main { class Session; +class SessionShow; } // namespace Main namespace Window { @@ -155,12 +156,21 @@ public: [[nodiscard]] std::optional<Api::SendAction> lookupLastAction( const QString &url) const; + struct ShowGameParams { + not_null<UserData*> bot; + FullMsgId context; + QString url; + QString title; + }; + void showGame(ShowGameParams &¶ms); + + [[nodiscard]] std::shared_ptr<Main::SessionShow> uiShow(); + static void ClearAll(); private: struct Context; - Webview::ThemeParams botThemeParams() override; bool botHandleLocalUri(QString uri, bool keepOpen) override; void botHandleInvoice(QString slug) override; @@ -176,6 +186,7 @@ private: void botSharePhone(Fn<void(bool shared)> callback) override; void botInvokeCustomMethod( Ui::BotWebView::CustomMethodRequest request) override; + void botShareGameScore() override; void botClose() override; [[nodiscard]] static Context LookupContext( @@ -271,6 +282,8 @@ private: rpl::event_stream<> _attachBotsUpdates; base::flat_set<not_null<UserData*>> _disclaimerAccepted; + FullMsgId _gameContext; + std::unique_ptr<Ui::BotWebView::Panel> _panel; bool _catchingCancelInShowCall = false; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 569c61bb9..d367e3382 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "webview/webview_interface.h" #include "base/debug_log.h" #include "base/invoke_queued.h" +#include "base/qt_signal_producer.h" #include "styles/style_payments.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" @@ -34,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <QtCore/QJsonArray> #include <QtGui/QGuiApplication> #include <QtGui/QClipboard> +#include <QtGui/QWindow> namespace Ui::BotWebView { namespace { @@ -373,6 +375,13 @@ Panel::~Panel() { void Panel::requestActivate() { _widget->showAndActivate(); + if (const auto widget = _webview ? _webview->window.widget() : nullptr) { + InvokeQueued(widget, [=] { + if (widget->isVisible()) { + _webview->window.focus(); + } + }); + } } void Panel::toggleProgress(bool shown) { @@ -527,9 +536,15 @@ bool Panel::showWebview( _webview->window.navigate(url); } }, &st::menuIconRestore); - callback(tr::lng_bot_terms(tr::now), [=] { - File::OpenUrl(tr::lng_mini_apps_tos_url(tr::now)); - }, &st::menuIconGroupLog); + if (_menuButtons & MenuButton::ShareGame) { + callback(tr::lng_iv_share(tr::now), [=] { + _delegate->botShareGameScore(); + }, &st::menuIconShare); + } else { + callback(tr::lng_bot_terms(tr::now), [=] { + File::OpenUrl(tr::lng_mini_apps_tos_url(tr::now)); + }, &st::menuIconGroupLog); + } const auto main = (_menuButtons & MenuButton::RemoveFromMainMenu); if (main || (_menuButtons & MenuButton::RemoveFromMenu)) { const auto handler = [=] { @@ -691,6 +706,8 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { requestClipboardText(arguments); } else if (command == "web_app_set_header_color") { processHeaderColor(arguments); + } else if (command == "share_score") { + _delegate->botShareGameScore(); } }); @@ -722,6 +739,17 @@ postEvent: function(eventType, eventData) { setupProgressGeometry(); + base::qt_signal_producer( + _widget->window()->windowHandle(), + &QWindow::activeChanged + ) | rpl::filter([=] { + return _webview && _widget->window()->windowHandle()->isActive(); + }) | rpl::start_with_next([=] { + if (_webview && !_webview->window.widget()->isHidden()) { + _webview->window.focus(); + } + }, _webview->lifetime); + return true; } @@ -1207,6 +1235,13 @@ void Panel::updateFooterHeight() { } void Panel::showBox(object_ptr<BoxContent> box) { + showBox(std::move(box), LayerOption::KeepOther, anim::type::normal); +} + +void Panel::showBox( + object_ptr<BoxContent> box, + LayerOptions options, + anim::type animated) { if (const auto widget = _webview ? _webview->window.widget() : nullptr) { const auto hideNow = !widget->isHidden(); if (hideNow || _webview->lastHidingBox) { @@ -1220,10 +1255,12 @@ void Panel::showBox(object_ptr<BoxContent> box) { && widget->isHidden() && _webview->lastHidingBox == raw) { widget->show(); + _webviewBottom->show(); } }, _webview->lifetime); if (hideNow) { widget->hide(); + _webviewBottom->hide(); } } } @@ -1237,6 +1274,14 @@ void Panel::showToast(TextWithEntities &&text) { _widget->showToast(std::move(text)); } +not_null<QWidget*> Panel::toastParent() const { + return _widget->uiShow()->toastParent(); +} + +void Panel::hideLayer(anim::type animated) { + _widget->hideLayer(animated); +} + void Panel::showCriticalError(const TextWithEntities &text) { _progress = nullptr; _webviewProgress = false; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 91e423279..d9071c846 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -20,6 +20,8 @@ namespace Ui { class BoxContent; class RpWidget; class SeparatePanel; +enum class LayerOption; +using LayerOptions = base::flags<LayerOption>; } // namespace Ui namespace Webview { @@ -40,6 +42,7 @@ enum class MenuButton { OpenBot = 0x01, RemoveFromMenu = 0x02, RemoveFromMainMenu = 0x04, + ShareGame = 0x08, }; inline constexpr bool is_flag_type(MenuButton) { return true; } using MenuButtons = base::flags<MenuButton>; @@ -67,6 +70,7 @@ public: virtual void botAllowWriteAccess(Fn<void(bool allowed)> callback) = 0; virtual void botSharePhone(Fn<void(bool shared)> callback) = 0; virtual void botInvokeCustomMethod(CustomMethodRequest request) = 0; + virtual void botShareGameScore() = 0; virtual void botClose() = 0; }; @@ -89,7 +93,13 @@ public: rpl::producer<QString> bottomText); void showBox(object_ptr<BoxContent> box); + void showBox( + object_ptr<BoxContent> box, + LayerOptions options, + anim::type animated); + void hideLayer(anim::type animated); void showToast(TextWithEntities &&text); + not_null<QWidget*> toastParent() const; void showCriticalError(const TextWithEntities &text); void showWebviewError( const QString &text, From 94ad8f9bc38d2225601c9eb9529c75f789acc7e9 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 9 Jul 2024 16:59:56 +0200 Subject: [PATCH 020/163] Don't register collapsed row click as userpic. Fixes #28149. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index cb33e8a9a..6e60f072d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -3814,7 +3814,9 @@ ChosenRow InnerWidget::computeChosenRow() const { bool InnerWidget::isUserpicPress() const { return (_lastRowLocalMouseX >= 0) - && (_lastRowLocalMouseX < _st->nameLeft); + && (_lastRowLocalMouseX < _st->nameLeft) + && (_collapsedSelected < 0 + || _collapsedSelected >= _collapsedRows.size()); } bool InnerWidget::isUserpicPressOnWide() const { From ebba58217c08decc97b6bc85cc7847bb16b122d6 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 9 Jul 2024 17:00:24 +0200 Subject: [PATCH 021/163] Don't set focus to shown third section. I hope it fixes #28142. --- Telegram/SourceFiles/window/section_widget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index ae24ad4d3..4ec94b0ab 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_widget.h" #include "mainwidget.h" +#include "mainwindow.h" #include "ui/ui_utility.h" #include "ui/chat/chat_theme.h" #include "ui/painter.h" @@ -456,7 +457,7 @@ void SectionWidget::showFinished() { showChildren(); showFinishedHook(); - setInnerFocus(); + controller()->widget()->setInnerFocus(); } rpl::producer<int> SectionWidget::desiredHeight() const { From 46b69a938b6d0ed3ef2660d632c8c4c6a9934271 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 10 Jul 2024 11:41:18 +0200 Subject: [PATCH 022/163] Revert "Register tg:// scheme on initial launch." This reverts commit 8cbeadc68a88acb207305ad02e6c01df3a80b690. --- Telegram/SourceFiles/core/application.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index e3e0de6c6..79dfd0977 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -270,14 +270,9 @@ void Application::run() { refreshGlobalProxy(); // Depends on app settings being read. - if (const auto old = Local::oldSettingsVersion()) { - if (old < AppVersion) { - autoRegisterUrlScheme(); - Platform::NewVersionLaunched(old); - } - } else { - // Initial launch. + if (const auto old = Local::oldSettingsVersion(); old < AppVersion) { autoRegisterUrlScheme(); + Platform::NewVersionLaunched(old); } if (cAutoStart() && !Platform::AutostartSupported()) { From f7d698b9ff05aaa829d119e4c38d485e01a53259 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 11 Jul 2024 14:23:34 +0200 Subject: [PATCH 023/163] Fix last seen within week/month. --- Telegram/SourceFiles/data/data_peer_values.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 60a1c5f43..e4694f88d 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -65,12 +65,14 @@ std::optional<QString> OnlineTextCommon(LastseenStatus status, TimeId now) { return tr::lng_status_online(tr::now); } else if (status.isLongAgo()) { return tr::lng_status_offline(tr::now); - } else if (status.isRecently() || status.isHidden()) { + } else if (status.isRecently()) { return tr::lng_status_recently(tr::now); } else if (status.isWithinWeek()) { return tr::lng_status_last_week(tr::now); } else if (status.isWithinMonth()) { return tr::lng_status_last_month(tr::now); + } else if (status.isHidden()) { + return tr::lng_status_recently(tr::now); } return std::nullopt; } From 03d4dd00d44d51440466c3b554e28f6018aaa903 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 12 Jul 2024 10:30:42 +0200 Subject: [PATCH 024/163] Delete selection on Ctrl+Backspace. Fixes #28143. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 96be2a6b7..9b69f3855 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 96be2a6b72f0405a9c01407148303fba2dab101c +Subproject commit 9b69f3855ac9feb760aef92ce98e874129303d4d From 5b9278ecedbe990ba9e5e00dd8baf3248885991f Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Wed, 10 Jul 2024 17:51:45 +0400 Subject: [PATCH 025/163] Switch Docker to distro-provided cmake --- Telegram/build/docker/centos_env/Dockerfile | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 71c9533eb..e4f7d1fb1 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -2,8 +2,6 @@ {%- set GIT_FREEDESKTOP = GIT ~ "/gitlab-freedesktop-mirrors" -%} {%- set QT = "6.7.2" -%} {%- set QT_TAG = "v" ~ QT -%} -{%- set CMAKE_VER = "3.27.6" -%} -{%- set CMAKE_FILE = "cmake-" ~ CMAKE_VER ~ "-Linux-x86_64.sh" -%} {%- set CFLAGS_DEBUG = "-g -pipe -fPIC -fstack-protector-all -fstack-clash-protection -fcf-protection -D_GLIBCXX_ASSERTIONS" -%} {%- set CFLAGS_LTO = "-flto=auto -ffat-lto-objects" -%} {%- set LibrariesPath = "/usr/src/Libraries" -%} @@ -18,7 +16,7 @@ ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig:/usr/loc RUN dnf -y install epel-release \ && dnf config-manager --set-enabled powertools \ - && dnf -y install autoconf automake libtool pkgconfig make patch git \ + && dnf -y install cmake autoconf automake libtool pkgconfig make patch git \ python3.11-pip python3.11-devel gperf flex bison clang lld nasm yasm \ file which perl-open perl-XML-Parser perl-IPC-Cmd xorg-x11-util-macros \ gcc-toolset-12-gcc gcc-toolset-12-gcc-c++ gcc-toolset-12-binutils \ @@ -34,12 +32,6 @@ WORKDIR {{ LibrariesPath }} RUN python3 -m pip install meson ninja -RUN mkdir /opt/cmake \ - && curl -sSLo {{ CMAKE_FILE }} {{ GIT }}/Kitware/CMake/releases/download/v{{ CMAKE_VER }}/{{ CMAKE_FILE }} \ - && sh {{ CMAKE_FILE }} --prefix=/opt/cmake --skip-license \ - && ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake \ - && rm {{ CMAKE_FILE }} - FROM builder-base AS builder ENV AR gcc-ar ENV RANLIB gcc-ranlib From c6e1cf639e16249ab811b153b8e956740403851a Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 12 Jul 2024 10:32:23 +0200 Subject: [PATCH 026/163] Update API scheme to layer 184. --- Telegram/Resources/langs/lang.strings | 1 + .../export/data/export_data_types.cpp | 7 ++++ .../export/data/export_data_types.h | 10 ++++- .../export/output/export_output_html.cpp | 6 +++ .../export/output/export_output_json.cpp | 7 ++++ Telegram/SourceFiles/history/history_item.cpp | 37 +++++++++++++++++++ .../history/history_item_components.h | 9 +++++ .../view/history_view_service_message.cpp | 2 + Telegram/SourceFiles/mtproto/scheme/api.tl | 5 ++- .../settings/settings_credits_graphics.cpp | 28 ++++++++++++++ .../settings/settings_credits_graphics.h | 3 ++ 11 files changed, 112 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ea754fcf9..0c60906ec 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1889,6 +1889,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_boost_apply#one" = "{from} boosted the group"; "lng_action_boost_apply#other" = "{from} boosted the group {count} times"; "lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?"; +"lng_action_payment_refunded" = "{peer} refunded back {amount}"; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 16dbfb994..9e65eac39 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1515,6 +1515,13 @@ ServiceAction ParseServiceAction( result.content = content; }, [&](const MTPDmessageActionRequestedPeerSentMe &data) { // Should not be in user inbox. + }, [&](const MTPDmessageActionPaymentRefunded &data) { + auto content = ActionPaymentRefunded(); + content.currency = ParseString(data.vcurrency()); + content.amount = data.vtotal_amount().v; + content.peerId = ParsePeerId(data.vpeer()); + content.transactionId = data.vcharge().data().vid().v; + 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 cc16c47ba..72824a2ee 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -576,6 +576,13 @@ struct ActionBoostApply { int boosts = 0; }; +struct ActionPaymentRefunded { + PeerId peerId = 0; + Utf8String currency; + uint64 amount = 0; + Utf8String transactionId; +}; + struct ServiceAction { std::variant< v::null_t, @@ -617,7 +624,8 @@ struct ServiceAction { ActionGiftCode, ActionGiveawayLaunch, ActionGiveawayResults, - ActionBoostApply> content; + ActionBoostApply, + ActionPaymentRefunded> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index f49776ede..d988fc00c 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1315,6 +1315,12 @@ auto HtmlWriter::Wrap::pushMessage( + " boosted the group " + QByteArray::number(data.boosts) + (data.boosts > 1 ? " times" : " time"); + }, [&](const ActionPaymentRefunded &data) { + const auto amount = FormatMoneyAmount(data.amount, data.currency); + auto result = peers.wrapPeerName(data.peerId) + + " refunded back " + + amount; + return result; }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index adbad2b7f..5d4c7e42d 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -625,6 +625,13 @@ QByteArray SerializeMessage( pushActor(); pushAction("boost_apply"); push("boosts", data.boosts); + }, [&](const ActionPaymentRefunded &data) { + pushAction("refunded_payment"); + push("amount", data.amount); + push("currency", data.currency); + pushBare("peer_name", wrapPeerName(data.peerId)); + push("peer_id", data.peerId); + push("charge_id", data.transactionId); }, [](v::null_t) {}); if (v::is_null(message.action.content)) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index d9c306013..d2386e7e6 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/text/text_isolated_emoji.h" #include "ui/text/text_utilities.h" +#include "settings/settings_credits_graphics.h" // ShowRefundInfoBox. #include "storage/file_upload.h" #include "storage/storage_shared_media.h" #include "main/main_account.h" @@ -4028,6 +4029,22 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { } } else if (type == mtpc_messageActionGiveawayResults) { UpdateComponents(HistoryServiceGiveawayResults::Bit()); + } else if (type == mtpc_messageActionPaymentRefunded) { + const auto &data = action.c_messageActionPaymentRefunded(); + UpdateComponents(HistoryServicePaymentRefund::Bit()); + const auto refund = Get<HistoryServicePaymentRefund>(); + refund->peer = _history->owner().peer(peerFromMTP(data.vpeer())); + refund->amount = data.vtotal_amount().v; + refund->currency = qs(data.vcurrency()); + refund->transactionId = qs(data.vcharge().data().vid()); + const auto id = fullId(); + refund->link = std::make_shared<LambdaClickHandler>([=]( + ClickContext context) { + const auto my = context.other.value<ClickHandlerContext>(); + if (const auto window = my.sessionWindow.get()) { + Settings::ShowRefundInfoBox(window, id); + } + }); } if (const auto replyTo = message.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { @@ -4941,6 +4958,25 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto preparePaymentRefunded = [&](const MTPDmessageActionPaymentRefunded &action) { + auto result = PreparedServiceText(); + const auto refund = Get<HistoryServicePaymentRefund>(); + Assert(refund != nullptr); + Assert(refund->peer != nullptr); + + const auto amount = refund->amount; + const auto currency = refund->currency; + result.links.push_back(refund->peer->createOpenLink()); + result.text = tr::lng_action_payment_refunded( + tr::now, + lt_peer, + Ui::Text::Link(refund->peer->name(), 1), // Link 1. + lt_amount, + { Ui::FillAmountAndCurrency(amount, currency) }, + Ui::Text::WithEntities); + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -4983,6 +5019,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareGiveawayLaunch, prepareGiveawayResults, prepareBoostApply, + preparePaymentRefunded, PrepareEmptyText<MTPDmessageActionRequestedPeerSentMe>, PrepareErrorText<MTPDmessageActionEmpty>)); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 8d262a52e..7e9fb6396 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -671,6 +671,15 @@ struct HistoryServiceCustomLink ClickHandlerPtr link; }; +struct HistoryServicePaymentRefund +: public RuntimeComponent<HistoryServicePaymentRefund, HistoryItem> { + ClickHandlerPtr link; + PeerData *peer = nullptr; + QString transactionId; + QString currency; + uint64 amount = 0; +}; + enum class HistorySelfDestructType { Photo, Video, diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 107bb9416..09fa2b43f 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -679,6 +679,8 @@ TextState Service::textState(QPoint point, StateRequest request) const { result.link = results->lnk; } else if (const auto custom = item->Get<HistoryServiceCustomLink>()) { result.link = custom->link; + } else if (const auto payment = item->Get<HistoryServicePaymentRefund>()) { + result.link = payment->link; } else if (media && data()->showSimilarChannels()) { result = media->textState(mediaPoint, request); } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 7b8700811..394a50efa 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -102,7 +102,7 @@ channel#aadfc8f flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5 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#2633421b 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<BotInfo> 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<long> available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; -channelFull#bbab348d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; +channelFull#bbab348d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -179,6 +179,7 @@ messageActionGiveawayLaunch#332ba9ed = MessageAction; messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = MessageAction; messageActionBoostApply#cc02aa6d boosts:int = MessageAction; messageActionRequestedPeerSentMe#93b31848 button_id:int peers:Vector<RequestedPeer> = MessageAction; +messageActionPaymentRefunded#41b3e202 flags:# peer:Peer currency:string total_amount:long payload:flags.0?bytes charge:PaymentCharge = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -2493,4 +2494,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 183 +// LAYER 184 diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 648a64900..3b88e8d48 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_components.h" // HistoryServicePaymentRefund. #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. #include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" @@ -633,6 +634,33 @@ void ReceiptCreditsBox( }, button->lifetime()); } +void ShowRefundInfoBox( + not_null<Window::SessionController*> controller, + FullMsgId refundItemId) { + const auto owner = &controller->session().data(); + const auto item = owner->message(refundItemId); + const auto refund = item + ? item->Get<HistoryServicePaymentRefund>() + : nullptr; + if (!refund) { + return; + } + Assert(refund->peer != nullptr); + auto info = Data::CreditsHistoryEntry(); + info.id = refund->transactionId; + info.date = base::unixtime::parse(item->date()); + info.credits = refund->amount; + info.barePeerId = refund->peer->id.value; + info.peerType = Data::CreditsHistoryEntry::PeerType::Peer; + info.refunded = true; + info.in = true; + controller->show(Box( + ::Settings::ReceiptCreditsBox, + controller, + nullptr, // premiumBot + info)); +} + object_ptr<Ui::RpWidget> GenericEntryPhoto( not_null<Ui::RpWidget*> parent, Fn<Fn<void(Painter &, int, int, int, int)>(Fn<void()>)> callback, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index efea501b9..406bfabca 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -54,6 +54,9 @@ void ReceiptCreditsBox( not_null<Window::SessionController*> controller, PeerData *premiumBot, const Data::CreditsHistoryEntry &e); +void ShowRefundInfoBox( + not_null<Window::SessionController*> controller, + FullMsgId refundItemId); [[nodiscard]] object_ptr<Ui::RpWidget> GenericEntryPhoto( not_null<Ui::RpWidget*> parent, From 8ac1ad348462e7ff2475200cc9d7800312c9fcae Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 8 Jul 2024 09:20:12 +0300 Subject: [PATCH 027/163] Updated Qt to 6.2.9 on macOS. --- Telegram/build/prepare/prepare.py | 2 +- Telegram/build/qt_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 8b1f71c68..6704bdbd2 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -445,7 +445,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 20a7c5ffd8 + git checkout e0cdca2e79 """) stage('msys64', """ diff --git a/Telegram/build/qt_version.py b/Telegram/build/qt_version.py index 1703e15d0..2bc778741 100644 --- a/Telegram/build/qt_version.py +++ b/Telegram/build/qt_version.py @@ -2,7 +2,7 @@ import sys, os def resolve(arch): if sys.platform == 'darwin': - os.environ['QT'] = '6.2.8' + os.environ['QT'] = '6.2.9' elif sys.platform == 'win32': if arch == 'arm' or 'qt6' in sys.argv: print('Choosing Qt 6.') From 3f0b962ae52b993e4c133937a552b7ad9b403dc1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 10 Jul 2024 09:19:33 +0300 Subject: [PATCH 028/163] Fixed color reset for chat filters on saving. --- .../SourceFiles/boxes/choose_filter_box.cpp | 2 ++ .../boxes/filters/edit_filter_box.cpp | 4 +++ .../SourceFiles/data/data_chat_filters.cpp | 25 +++++++++++++++---- Telegram/SourceFiles/data/data_chat_filters.h | 4 +++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 5af8577bf..4e99a01ba 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -39,6 +39,7 @@ Data::ChatFilter ChangedFilter( filter.id(), filter.title(), filter.iconEmoji(), + filter.colorIndex(), filter.flags(), std::move(always), filter.pinned(), @@ -58,6 +59,7 @@ Data::ChatFilter ChangedFilter( filter.id(), filter.title(), filter.iconEmoji(), + filter.colorIndex(), filter.flags(), std::move(always), filter.pinned(), diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index ce23d235f..df85e6254 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -83,6 +83,7 @@ not_null<FilterChatsPreview*> SetupChatsPreview( rules.id(), rules.title(), rules.iconEmoji(), + rules.colorIndex(), (rules.flags() & ~flag), rules.always(), rules.pinned(), @@ -104,6 +105,7 @@ not_null<FilterChatsPreview*> SetupChatsPreview( rules.id(), rules.title(), rules.iconEmoji(), + rules.colorIndex(), rules.flags(), std::move(always), std::move(pinned), @@ -170,6 +172,7 @@ void EditExceptions( rules.id(), rules.title(), rules.iconEmoji(), + rules.colorIndex(), ((rules.flags() & ~options) | rawController->chosenOptions()), include ? std::move(changed) : std::move(removeFrom), @@ -240,6 +243,7 @@ void CreateIconSelector( rules.id(), rules.title(), Ui::LookupFilterIcon(icon).emoji, + rules.colorIndex(), rules.flags(), rules.always(), rules.pinned(), diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index efac543af..7a6f63a74 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -43,6 +43,7 @@ ChatFilter::ChatFilter( FilterId id, const QString &title, const QString &iconEmoji, + std::optional<uint8> colorIndex, Flags flags, base::flat_set<not_null<History*>> always, std::vector<not_null<History*>> pinned, @@ -50,6 +51,7 @@ ChatFilter::ChatFilter( : _id(id) , _title(title) , _iconEmoji(iconEmoji) +, _colorIndex(colorIndex) , _always(std::move(always)) , _pinned(std::move(pinned)) , _never(std::move(never)) @@ -95,6 +97,9 @@ ChatFilter ChatFilter::FromTL( data.vid().v, qs(data.vtitle()), qs(data.vemoticon().value_or_empty()), + data.vcolor() + ? std::make_optional(data.vcolor()->v) + : std::nullopt, flags, std::move(list), std::move(pinned), @@ -140,6 +145,9 @@ ChatFilter ChatFilter::FromTL( data.vid().v, qs(data.vtitle()), qs(data.vemoticon().value_or_empty()), + data.vcolor() + ? std::make_optional(data.vcolor()->v) + : std::nullopt, (Flag::Chatlist | (data.is_has_my_invites() ? Flag::HasMyLinks : Flag())), std::move(list), @@ -189,18 +197,20 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { } if (_flags & Flag::Chatlist) { using TLFlag = MTPDdialogFilterChatlist::Flag; - const auto flags = TLFlag::f_emoticon; + const auto flags = TLFlag::f_emoticon + | (_colorIndex ? TLFlag::f_color : TLFlag(0)); return MTP_dialogFilterChatlist( MTP_flags(flags), MTP_int(replaceId ? replaceId : _id), MTP_string(_title), MTP_string(_iconEmoji), - MTPint(), // color + MTP_int(_colorIndex.value_or(0)), MTP_vector<MTPInputPeer>(pinned), MTP_vector<MTPInputPeer>(include)); } using TLFlag = MTPDdialogFilter::Flag; const auto flags = TLFlag::f_emoticon + | (_colorIndex ? TLFlag::f_color : TLFlag(0)) | ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0)) | ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0)) | ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0)) @@ -221,7 +231,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { MTP_int(replaceId ? replaceId : _id), MTP_string(_title), MTP_string(_iconEmoji), - MTPint(), // color + MTP_int(_colorIndex.value_or(0)), MTP_vector<MTPInputPeer>(pinned), MTP_vector<MTPInputPeer>(include), MTP_vector<MTPInputPeer>(never)); @@ -239,6 +249,10 @@ QString ChatFilter::iconEmoji() const { return _iconEmoji; } +std::optional<uint8> ChatFilter::colorIndex() const { + return _colorIndex; +} + ChatFilter::Flags ChatFilter::flags() const { return _flags; } @@ -555,7 +569,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) { _list.insert( begin(_list) + position, - ChatFilter(filter.id(), {}, {}, {}, {}, {}, {})); + ChatFilter(filter.id(), {}, {}, {}, {}, {}, {}, {})); applyChange(*(begin(_list) + position), std::move(filter)); } @@ -582,7 +596,7 @@ void ChatFilters::applyRemove(int position) { Expects(position >= 0 && position < _list.size()); const auto i = begin(_list) + position; - applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {})); + applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {}, {})); _list.erase(i); } @@ -711,6 +725,7 @@ const ChatFilter &ChatFilters::applyUpdatedPinned( id, i->title(), i->iconEmoji(), + i->colorIndex(), i->flags(), std::move(always), std::move(pinned), diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 7b5a96476..e123ab2c1 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -52,6 +52,7 @@ public: FilterId id, const QString &title, const QString &iconEmoji, + std::optional<uint8> colorIndex, Flags flags, base::flat_set<not_null<History*>> always, std::vector<not_null<History*>> pinned, @@ -71,6 +72,7 @@ public: [[nodiscard]] FilterId id() const; [[nodiscard]] QString title() const; [[nodiscard]] QString iconEmoji() const; + [[nodiscard]] std::optional<uint8> colorIndex() const; [[nodiscard]] Flags flags() const; [[nodiscard]] bool chatlist() const; [[nodiscard]] bool hasMyLinks() const; @@ -84,6 +86,7 @@ private: FilterId _id = 0; QString _title; QString _iconEmoji; + std::optional<uint8> _colorIndex; base::flat_set<not_null<History*>> _always; std::vector<not_null<History*>> _pinned; base::flat_set<not_null<History*>> _never; @@ -94,6 +97,7 @@ private: inline bool operator==(const ChatFilter &a, const ChatFilter &b) { return (a.title() == b.title()) && (a.iconEmoji() == b.iconEmoji()) + && (a.colorIndex() == b.colorIndex()) && (a.flags() == b.flags()) && (a.always() == b.always()) && (a.never() == b.never()); From 6818b8d8dc6d8f21754bc823b620d5be9575b355 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 14 Jul 2024 22:22:05 +0300 Subject: [PATCH 029/163] Fixed row name in table for credits history entries in earn box. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/gift_premium_box.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0c60906ec..e7b50f139 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2364,6 +2364,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance."; "lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance."; "lng_credits_box_history_entry_peer" = "Recipient"; +"lng_credits_box_history_entry_peer_in" = "From"; "lng_credits_box_history_entry_via" = "Via"; "lng_credits_box_history_entry_play_market" = "Play Market"; "lng_credits_box_history_entry_app_store" = "App Store"; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 972a7a5f4..58dc2941f 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1648,7 +1648,9 @@ void AddCreditsHistoryEntryTable( st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(entry.barePeerId); if (peerId) { - auto text = tr::lng_credits_box_history_entry_peer(); + auto text = entry.in + ? tr::lng_credits_box_history_entry_peer_in() + : tr::lng_credits_box_history_entry_peer(); AddTableRow(table, std::move(text), controller, peerId); } if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) { From 1d5e4040f408d1d6541d10c0a4f57491a4b551d4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 14 Jul 2024 22:59:27 +0300 Subject: [PATCH 030/163] Added api support of flag to view credits stats from full channel. --- Telegram/SourceFiles/data/data_channel.cpp | 8 ++++++-- Telegram/SourceFiles/data/data_channel.h | 1 + .../channel_statistics/earn/info_channel_earn_list.cpp | 9 ++++++++- Telegram/SourceFiles/window/window_peer_menu.cpp | 4 +++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 2f7694395..6162024fd 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1089,7 +1089,8 @@ void ApplyChannelUpdate( | Flag::CanGetStatistics | Flag::ViewAsMessages | Flag::CanViewRevenue - | Flag::PaidMediaAllowed; + | Flag::PaidMediaAllowed + | Flag::CanViewCreditsRevenue; channel->setFlags((channel->flags() & ~mask) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (update.is_can_view_participants() @@ -1107,7 +1108,10 @@ void ApplyChannelUpdate( ? Flag::ViewAsMessages : Flag()) | (update.is_paid_media_allowed() ? Flag::PaidMediaAllowed : Flag()) - | (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag())); + | (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag()) + | (update.is_can_view_stars_revenue() + ? Flag::CanViewCreditsRevenue + : Flag())); channel->setUserpicPhoto(update.vchat_photo()); if (const auto migratedFrom = update.vmigrated_from_chat_id()) { channel->addFlags(Flag::Megagroup); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index d6f880bda..81ad7778d 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -67,6 +67,7 @@ enum class ChannelDataFlag : uint64 { SimilarExpanded = (1ULL << 31), CanViewRevenue = (1ULL << 32), PaidMediaAllowed = (1ULL << 33), + CanViewCreditsRevenue = (1ULL << 34), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags<ChannelDataFlag>; diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index 84ad81390..c16acc204 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -281,6 +281,9 @@ void InnerWidget::load() { rpl::lifetime apiPremiumBotLifetime; }; const auto state = lifetime().make_state<State>(_peer); + using ChannelFlag = ChannelDataFlag; + const auto canViewCredits = !_peer->isChannel() + || (_peer->asChannel()->flags() & ChannelFlag::CanViewCreditsRevenue); Info::Statistics::FillLoading( this, @@ -363,7 +366,11 @@ void InnerWidget::load() { _state.premiumBotId = bot->id; state->apiCredits.request( ) | rpl::start_with_error_done([=](const QString &error) { - fail(error); + if (canViewCredits) { + fail(error); + } else { + _state.creditsEarn = {}; + } finish(); }, [=] { _state.creditsEarn = state->apiCredits.data(); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index f8259eec7..74f6f7ed8 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1090,6 +1090,8 @@ void Filler::addViewStatistics() { using Flag = ChannelDataFlag; const auto canGetStats = (channel->flags() & Flag::CanGetStatistics); const auto canViewEarn = (channel->flags() & Flag::CanViewRevenue); + const auto canViewCreditsEarn + = (channel->flags() & Flag::CanViewCreditsRevenue); if (canGetStats) { _addAction(tr::lng_stats_title(tr::now), [=] { if (const auto strong = weak.get()) { @@ -1107,7 +1109,7 @@ void Filler::addViewStatistics() { } }, &st::menuIconBoosts); } - if (canViewEarn) { + if (canViewEarn || canViewCreditsEarn) { _addAction(tr::lng_channel_earn_title(tr::now), [=] { if (const auto strong = weak.get()) { controller->showSection(Info::ChannelEarn::Make(peer)); From 4b9eb37bd53503c856a8e4d3014636f15b84b9ef Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 15 Jul 2024 00:05:19 +0300 Subject: [PATCH 031/163] Added phrases for single winner of giveaway in channels. --- Telegram/Resources/langs/lang.strings | 3 +++ Telegram/SourceFiles/data/data_media_types.cpp | 6 +++++- .../history/view/media/history_view_giveaway.cpp | 12 +++++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e7b50f139..451f34da5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2813,12 +2813,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_badge" = "x{amount}"; "lng_prizes_results_title" = "Winners Selected!"; +"lng_prizes_results_title_one" = "Winner Selected!"; "lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram."; "lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram."; "lng_prizes_results_link" = "Giveaway"; +"lng_prizes_results_winner" = "Winner"; "lng_prizes_results_winners" = "Winners"; "lng_prizes_results_more#one" = "and {count} more!"; "lng_prizes_results_more#other" = "and {count} more!"; +"lng_prizes_results_one" = "The winner received their gift link in a private message."; "lng_prizes_results_all" = "All winners received gift links in private messages."; "lng_prizes_results_some" = "Some winners couldn't be selected."; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index a7b2690ea..da16410b0 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2631,7 +2631,11 @@ const GiveawayResults *MediaGiveawayResults::giveawayResults() const { } TextWithEntities MediaGiveawayResults::notificationText() const { - return Ui::Text::Colorized({ tr::lng_prizes_results_title(tr::now) }); + return Ui::Text::Colorized({ + ((_data.winnersCount == 1) + ? tr::lng_prizes_results_title_one + : tr::lng_prizes_results_title)(tr::now) + }); } QString MediaGiveawayResults::pinnedTextSubstring() const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 310af8fa6..f2f98d05e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -200,9 +200,11 @@ auto GenerateGiveawayResults( margins, links)); }; + const auto isSingleWinner = (data->winnersCount == 1); pushText( - Ui::Text::Bold( - tr::lng_prizes_results_title(tr::now)), + (isSingleWinner + ? tr::lng_prizes_results_title_one + : tr::lng_prizes_results_title)(tr::now, Ui::Text::Bold), st::chatGiveawayPrizesTitleMargin); const auto showGiveawayHandler = JumpToMessageClickHandler( data->channel, @@ -219,7 +221,9 @@ auto GenerateGiveawayResults( st::chatGiveawayPrizesMargin, { { 1, showGiveawayHandler } }); pushText( - Ui::Text::Bold(tr::lng_prizes_results_winners(tr::now)), + (isSingleWinner + ? tr::lng_prizes_results_winner + : tr::lng_prizes_results_winners)(tr::now, Ui::Text::Bold), st::chatGiveawayPrizesTitleMargin); push(std::make_unique<PeerBubbleListPart>( @@ -235,6 +239,8 @@ auto GenerateGiveawayResults( } pushText({ data->unclaimedCount ? tr::lng_prizes_results_some(tr::now) + : isSingleWinner + ? tr::lng_prizes_results_one(tr::now) : tr::lng_prizes_results_all(tr::now) }, st::chatGiveawayEndDateMargin); }; From 4f7a124f3e85f3f61d862b94fb5a45236976f38f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 15 Jul 2024 00:37:33 +0300 Subject: [PATCH 032/163] Added phrases to credits history entries when peer type is premium bot. --- Telegram/Resources/langs/lang.strings | 2 ++ .../SourceFiles/boxes/gift_premium_box.cpp | 6 ++++ .../info_statistics_list_controllers.cpp | 2 -- .../settings/settings_credits_graphics.cpp | 29 ++++++++++++++++++- .../ui/effects/credits_graphics.cpp | 21 +++++++++++--- 5 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 451f34da5..5c461c1ef 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2370,6 +2370,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_app_store" = "App Store"; "lng_credits_box_history_entry_fragment" = "Fragment"; "lng_credits_box_history_entry_ads" = "Ads Platform"; +"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up"; +"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot"; "lng_credits_box_history_entry_id" = "Transaction ID"; "lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; "lng_credits_box_history_entry_success_date" = "Transaction date"; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 58dc2941f..fdf1eda03 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1702,6 +1702,12 @@ void AddCreditsHistoryEntryTable( table, tr::lng_credits_box_history_entry_via(), tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue)); + } else if (entry.peerType == Type::PremiumBot) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_via(), + tr::lng_credits_box_history_entry_via_premium_bot( + Ui::Text::RichLangValue)); } if (!entry.id.isEmpty()) { constexpr auto kOneLineCount = 18; diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 956c7d0c3..a176be565 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -954,8 +954,6 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { if (const auto peerId = PeerId(item.barePeerId)) { const auto peer = session().data().peer(peerId); return std::make_unique<CreditsRow>(peer, descriptor); - } else if (item.peerType == Type::PremiumBot) { - return std::make_unique<CreditsRow>(_premiumBot, descriptor); } else { return std::make_unique<CreditsRow>(descriptor); } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 3b88e8d48..282945d9e 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -448,7 +448,7 @@ void ReceiptCreditsBox( const auto &stUser = st::boostReplaceUserpic; const auto session = &controller->session(); const auto peer = (e.peerType == Type::PremiumBot) - ? premiumBot + ? nullptr : e.barePeerId ? session->data().peer(PeerId(e.barePeerId)).get() : nullptr; @@ -622,6 +622,33 @@ void ReceiptCreditsBox( Ui::AddSkip(content); + if (e.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot) { + const auto widget = Ui::CreateChild<Ui::RpWidget>(content); + using ColoredMiniStars = Ui::Premium::ColoredMiniStars; + const auto stars = widget->lifetime().make_state<ColoredMiniStars>( + widget, + false, + Ui::Premium::MiniStars::Type::BiStars); + stars->setColorOverride(Ui::Premium::CreditsIconGradientStops()); + widget->resize( + st::boxWidth - stUser.photoSize, + stUser.photoSize * 2); + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + widget->moveToLeft(stUser.photoSize / 2, 0); + const auto starsRect = Rect(widget->size()); + stars->setPosition(starsRect.topLeft()); + stars->setSize(starsRect.size()); + widget->lower(); + }, widget->lifetime()); + widget->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + auto p = QPainter(widget); + p.fillRect(r, Qt::transparent); + stars->paint(p); + }, widget->lifetime()); + } + const auto button = box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 3f4ce02f2..e99a4d4eb 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -193,6 +193,21 @@ not_null<MaskedInputField*> AddInputFieldForCredits( PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry) { + using PeerType = Data::CreditsHistoryEntry::PeerType; + if (entry.peerType == PeerType::PremiumBot) { + const auto svg = std::make_shared<QSvgRenderer>(Ui::Premium::Svg()); + return [=](Painter &p, int x, int y, int, int size) mutable { + const auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + { + auto gradient = QLinearGradient(x + size, y + size, x, y); + gradient.setStops(Ui::Premium::ButtonGradientStops()); + p.setBrush(gradient); + } + p.drawEllipse(x, y, size, size); + svg->render(&p, QRectF(x, y, size, size) - Margins(size / 5.)); + }; + } const auto bg = [&]() -> EmptyUserpic::BgColors { switch (entry.peerType) { case Data::CreditsHistoryEntry::PeerType::Peer: @@ -218,10 +233,6 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const auto userpic = std::make_shared<EmptyUserpic>(bg, QString()); return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { userpic->paintCircle(p, x, y, outerWidth, size); - using PeerType = Data::CreditsHistoryEntry::PeerType; - if (entry.peerType == PeerType::PremiumBot) { - return; - } const auto rect = QRect(x, y, size, size); ((entry.peerType == PeerType::AppStore) ? st::sessionIconiPhone @@ -446,6 +457,8 @@ Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback( TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) ? tr::lng_bot_username_description1_link + : (entry.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot) + ? tr::lng_credits_box_history_entry_premium_bot : (entry.peerType == Data::CreditsHistoryEntry::PeerType::Ads) ? tr::lng_credits_box_history_entry_ads : tr::lng_credits_summary_history_entry_inner_in)( From a52d4eb4e8c4a0e3ca71ea359d2c5abb733343a4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 18:09:25 +0300 Subject: [PATCH 033/163] Fixed peer in list of credits history entries. --- .../info/bot/earn/info_bot_earn_list.cpp | 16 ++++++++-------- .../earn/info_channel_earn_list.cpp | 2 +- .../info_statistics_list_controllers.cpp | 8 +++----- .../info_statistics_list_controllers.h | 2 +- .../SourceFiles/settings/settings_credits.cpp | 8 ++++---- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp b/Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp index 169fe8883..cbe32faf5 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp @@ -261,7 +261,7 @@ void InnerWidget::fillHistory() { const auto sectionIndex = history->lifetime().make_state<int>(0); - const auto fill = [=]( + const auto fill = [=, peer = _peer]( not_null<PeerData*> premiumBot, const Data::CreditsStatusSlice &fullSlice, const Data::CreditsStatusSlice &inSlice, @@ -368,7 +368,7 @@ void InnerWidget::fillHistory() { fullSlice, fullWrap->entity(), entryClicked, - premiumBot, + peer, star, true, true); @@ -377,7 +377,7 @@ void InnerWidget::fillHistory() { inSlice, inWrap->entity(), entryClicked, - premiumBot, + peer, star, true, false); @@ -386,7 +386,7 @@ void InnerWidget::fillHistory() { outSlice, outWrap->entity(), std::move(entryClicked), - premiumBot, + peer, star, false, true); @@ -398,11 +398,11 @@ void InnerWidget::fillHistory() { const auto apiLifetime = history->lifetime().make_state<rpl::lifetime>(); rpl::single(rpl::empty) | rpl::then( _stateUpdated.events() - ) | rpl::start_with_next([=] { + ) | rpl::start_with_next([=, peer = _peer] { using Api = Api::CreditsHistory; - const auto apiFull = apiLifetime->make_state<Api>(_peer, true, true); - const auto apiIn = apiLifetime->make_state<Api>(_peer, true, false); - const auto apiOut = apiLifetime->make_state<Api>(_peer, false, true); + const auto apiFull = apiLifetime->make_state<Api>(peer, true, true); + const auto apiIn = apiLifetime->make_state<Api>(peer, true, false); + const auto apiOut = apiLifetime->make_state<Api>(peer, false, true); apiFull->request({}, [=](Data::CreditsStatusSlice fullSlice) { apiIn->request({}, [=](Data::CreditsStatusSlice inSlice) { apiOut->request({}, [=](Data::CreditsStatusSlice outSlice) { diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index c16acc204..c96dac4ea 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -1419,7 +1419,7 @@ void InnerWidget::fill() { data.creditsStatusSlice, tabCreditsList->entity(), entryClicked, - premiumBot, + _peer, star, true, true); diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index a176be565..a9649fb4d 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -131,7 +131,7 @@ struct BoostsDescriptor final { struct CreditsDescriptor final { Data::CreditsStatusSlice firstSlice; Fn<void(const Data::CreditsHistoryEntry &)> entryClickedCallback; - not_null<PeerData*> premiumBot; + not_null<PeerData*> peer; not_null<QImage*> creditIcon; bool in = false; bool out = false; @@ -889,7 +889,6 @@ private: void applySlice(const Data::CreditsStatusSlice &slice); const not_null<Main::Session*> _session; - const not_null<PeerData*> _premiumBot; Fn<void(const Data::CreditsHistoryEntry &)> _entryClickedCallback; not_null<QImage*> const _creditIcon; @@ -903,11 +902,10 @@ private: }; CreditsController::CreditsController(CreditsDescriptor d) -: _session(&d.premiumBot->session()) -, _premiumBot(d.premiumBot) +: _session(&d.peer->session()) , _entryClickedCallback(std::move(d.entryClickedCallback)) , _creditIcon(d.creditIcon) -, _api(d.premiumBot->session().user(), d.in, d.out) +, _api(d.peer, d.in, d.out) , _firstSlice(std::move(d.firstSlice)) { PeerListController::setStyleOverrides(&st::boostsListBox); } diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h index 2d381cb7c..8423de55c 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h @@ -55,7 +55,7 @@ void AddCreditsHistoryList( const Data::CreditsStatusSlice &firstSlice, not_null<Ui::VerticalLayout*> container, Fn<void(const Data::CreditsHistoryEntry &)> entryClickedCallback, - not_null<PeerData*> premiumBot, + not_null<PeerData*> peer, not_null<QImage*> creditIcon, bool in, bool out); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 0890f2703..5088ce62a 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -128,6 +128,7 @@ void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) { container, object_ptr<Ui::VerticalLayout>(container))); const auto content = history->entity(); + const auto self = _controller->session().user(); Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); @@ -231,7 +232,7 @@ void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) { fullSlice, fullWrap->entity(), entryClicked, - premiumBot, + self, &_star, true, true); @@ -240,7 +241,7 @@ void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) { inSlice, inWrap->entity(), entryClicked, - premiumBot, + self, &_star, true, false); @@ -249,7 +250,7 @@ void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) { outSlice, outWrap->entity(), std::move(entryClicked), - premiumBot, + self, &_star, false, true); @@ -263,7 +264,6 @@ void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) { const auto apiLifetime = content->lifetime().make_state<rpl::lifetime>(); { using Api = Api::CreditsHistory; - const auto self = _controller->session().user(); const auto apiFull = apiLifetime->make_state<Api>(self, true, true); const auto apiIn = apiLifetime->make_state<Api>(self, true, false); const auto apiOut = apiLifetime->make_state<Api>(self, false, true); From 2a63496054324e4a797d26cc029b701225fd5cee Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 18 Jul 2024 01:54:05 +0300 Subject: [PATCH 034/163] Extended conditions to ability to view channel message statistics. --- Telegram/SourceFiles/history/history_inner_widget.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 1a302bd10..7838dda79 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2227,8 +2227,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (!item->isService() && peerIsChannel(itemId.peer) && !_peer->isMegagroup()) { + constexpr auto kMinViewsCount = 10; if (const auto channel = _peer->asChannel()) { - if (channel->flags() & ChannelDataFlag::CanGetStatistics) { + if ((channel->flags() & ChannelDataFlag::CanGetStatistics) + || (channel->canPostMessages() + && item->viewsCount() >= kMinViewsCount)) { auto callback = crl::guard(controller, [=] { controller->showSection( Info::Statistics::Make(channel, itemId, {})); From 2a5071b66c648f05674b595bfab9659cb4e39dcb Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 4 Jul 2024 12:18:30 +0400 Subject: [PATCH 035/163] Initial location sending on Windows. --- Telegram/CMakeLists.txt | 1 + Telegram/Resources/langs/lang.strings | 1 + Telegram/Resources/picker_html/picker.css | 60 +++ Telegram/Resources/picker_html/picker.js | 80 ++++ Telegram/Resources/qrc/telegram/picker.qrc | 6 + Telegram/SourceFiles/api/api_sending.cpp | 106 ++++++ Telegram/SourceFiles/api/api_sending.h | 19 +- .../SourceFiles/core/current_geo_location.cpp | 50 +++ .../SourceFiles/core/current_geo_location.h | 41 ++ Telegram/SourceFiles/data/data_location.h | 10 + .../data/raw/raw_countries_bounds.cpp | 193 ++++++++++ .../data/raw/raw_countries_bounds.h | 23 ++ .../inline_bots/bot_attach_web_view.cpp | 30 ++ .../linux/current_geo_location_linux.cpp | 18 + .../linux/current_geo_location_linux.h | 10 + .../platform/mac/current_geo_location_mac.h | 10 + .../platform/mac/current_geo_location_mac.mm | 18 + .../platform/platform_current_geo_location.h | 18 + .../platform/win/current_geo_location_win.cpp | 59 +++ .../platform/win/current_geo_location_win.h | 10 + .../ui/controls/location_picker.cpp | 350 ++++++++++++++++++ .../SourceFiles/ui/controls/location_picker.h | 67 ++++ Telegram/cmake/td_ui.cmake | 19 + 23 files changed, 1195 insertions(+), 4 deletions(-) create mode 100644 Telegram/Resources/picker_html/picker.css create mode 100644 Telegram/Resources/picker_html/picker.js create mode 100644 Telegram/Resources/qrc/telegram/picker.qrc create mode 100644 Telegram/SourceFiles/core/current_geo_location.cpp create mode 100644 Telegram/SourceFiles/core/current_geo_location.h create mode 100644 Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp create mode 100644 Telegram/SourceFiles/data/raw/raw_countries_bounds.h create mode 100644 Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp create mode 100644 Telegram/SourceFiles/platform/linux/current_geo_location_linux.h create mode 100644 Telegram/SourceFiles/platform/mac/current_geo_location_mac.h create mode 100644 Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm create mode 100644 Telegram/SourceFiles/platform/platform_current_geo_location.h create mode 100644 Telegram/SourceFiles/platform/win/current_geo_location_win.cpp create mode 100644 Telegram/SourceFiles/platform/win/current_geo_location_win.h create mode 100644 Telegram/SourceFiles/ui/controls/location_picker.cpp create mode 100644 Telegram/SourceFiles/ui/controls/location_picker.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6528a858e..6f3bc7a00 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1614,6 +1614,7 @@ PRIVATE qrc/telegram/animations.qrc qrc/telegram/export.qrc qrc/telegram/iv.qrc + qrc/telegram/picker.qrc qrc/telegram/telegram.qrc qrc/telegram/sounds.qrc winrc/Telegram.rc diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5c461c1ef..122260d22 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3194,6 +3194,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_unread_bar_some" = "Unread messages"; "lng_maps_point" = "Location"; +"lng_maps_point_send" = "Send This Location"; "lng_live_location" = "Live Location"; "lng_live_location_now" = "updated just now"; "lng_live_location_minutes#one" = "updated {count} minute ago"; diff --git a/Telegram/Resources/picker_html/picker.css b/Telegram/Resources/picker_html/picker.css new file mode 100644 index 000000000..0c791008d --- /dev/null +++ b/Telegram/Resources/picker_html/picker.css @@ -0,0 +1,60 @@ +:root { + --font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif; + --font-serif: Iowan Old Style, Apple Garamond, Baskerville, Georgia, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; + --font-mono: Menlo, Cascadia Code, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; +} + +html { + width: 100%; + height: 100%; + padding: 0; + margin: 0; +} + +body { + font-family: var(--font-sans); + font-size: 17px; + line-height: 25px; + width: 100%; + height: 100%; + padding: 0; + margin: 0; + background-color: var(--td-window-bg); + color: var(--td-window-fg); +} + +html.custom_scroll ::-webkit-scrollbar { + border-radius: 5px !important; + border: 3px solid transparent !important; + background-color: var(--td-scroll-bg) !important; + background-clip: content-box !important; + width: 10px !important; +} +html.custom_scroll ::-webkit-scrollbar:hover { + background-color: var(--td-scroll-bg-over) !important; +} +html.custom_scroll ::-webkit-scrollbar-thumb { + border-radius: 5px !important; + border: 3px solid transparent !important; + background-color: var(--td-scroll-bar-bg) !important; + background-clip: content-box !important; +} +html.custom_scroll ::-webkit-scrollbar-thumb:hover { + background-color: var(--td-scroll-bar-bg-over) !important; +} + +#map { + position: relative; + width: 100%; + height: 100%; +} +#marker { + pointer-events: none; + display: flex; + z-index: 2; + position: absolute; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +} diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js new file mode 100644 index 000000000..6a35060f0 --- /dev/null +++ b/Telegram/Resources/picker_html/picker.js @@ -0,0 +1,80 @@ +var LocationPicker = { + startZoom: 14, + flySpeed: 2.4, + notify: function(message) { + if (window.external && window.external.invoke) { + window.external.invoke(JSON.stringify(message)); + } + }, + frameKeyDown: function (e) { + const keyW = (e.key === 'w') + || (e.code === 'KeyW') + || (e.keyCode === 87); + const keyQ = (e.key === 'q') + || (e.code === 'KeyQ') + || (e.keyCode === 81); + const keyM = (e.key === 'm') + || (e.code === 'KeyM') + || (e.keyCode === 77); + if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) { + e.preventDefault(); + LocationPicker.notify({ + event: 'keydown', + modifier: e.ctrlKey ? 'ctrl' : 'cmd', + key: keyW ? 'w' : keyQ ? 'q' : 'm', + }); + } else if (e.key === 'Escape' || e.keyCode === 27) { + e.preventDefault(); + LocationPicker.notify({ + event: 'keydown', + key: 'escape', + }); + } + }, + updateStyles: function (styles) { + if (LocationPicker.styles !== styles) { + LocationPicker.styles = styles; + document.getElementsByTagName('html')[0].style = styles; + } + }, + init: function (token, center, bounds) { + mapboxgl.accessToken = token; + + var options = { container: 'map' }; + if (center) { + center = [center[1], center[0]]; + options.center = center; + options.zoom = LocationPicker.startZoom; + } else if (bounds) { + options.bounds = bounds; + center = new mapboxgl.LngLatBounds(bounds).getCenter(); + } else { + center = [0, 0]; + } + LocationPicker.map = new mapboxgl.Map(options); + + const marker = new mapboxgl.Marker() + .setLngLat(center) + .addTo(LocationPicker.map); + const drop = document.getElementById('marker_drop'); + const element = marker.getElement(); + drop.innerHTML = element.innerHTML; + const offset = marker.getOffset(); + drop.style.transform = 'translate(' + offset.x + 'px, ' + offset.y + 'px)'; + marker.remove(); + }, + narrowTo: function (point) { + LocationPicker.map.flyTo({ + center: [point[1], point[0]], + zoom: LocationPicker.startZoom, + speed: LocationPicker.flySpeed, + }); + }, + send: function () { + LocationPicker.notify({ + event: 'send', + latitude: LocationPicker.map.getCenter().lat, + longitude: LocationPicker.map.getCenter().lng + }); + } +}; diff --git a/Telegram/Resources/qrc/telegram/picker.qrc b/Telegram/Resources/qrc/telegram/picker.qrc new file mode 100644 index 000000000..10a810aa9 --- /dev/null +++ b/Telegram/Resources/qrc/telegram/picker.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/picker"> + <file alias="picker.css">../../picker_html/picker.css</file> + <file alias="picker.js">../../picker_html/picker.js</file> + </qresource> +</RCC> diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index ade419248..bf4d472fe 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -62,6 +62,85 @@ void InnerFillMessagePostFlags( } } +void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { + const auto history = action.history; + const auto peer = history->peer; + const auto session = &history->session(); + const auto api = &session->api(); + + action.clearDraft = false; + action.generateLocal = false; + api->sendAction(action); + + const auto randomId = base::RandomValue<uint64>(); + + auto flags = NewMessageFlags(peer); + auto sendFlags = MTPmessages_SendMedia::Flags(0); + if (action.replyTo) { + flags |= MessageFlag::HasReplyInfo; + sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; + } + const auto anonymousPost = peer->amAnonymous(); + const auto silentPost = ShouldSendSilent(peer, action.options); + InnerFillMessagePostFlags(action.options, peer, flags); + if (silentPost) { + sendFlags |= MTPmessages_SendMedia::Flag::f_silent; + } + const auto sendAs = action.options.sendAs; + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost + ? 0 + : session->userPeerId(); + if (sendAs) { + sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; + } + const auto messagePostAuthor = peer->isBroadcast() + ? session->user()->name() + : QString(); + + if (action.options.scheduled) { + flags |= MessageFlag::IsOrWasScheduled; + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + } + if (action.options.shortcutId) { + flags |= MessageFlag::ShortcutMessage; + sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; + } + if (action.options.effectId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_effect; + } + if (action.options.invertCaption) { + flags |= MessageFlag::InvertMedia; + sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; + } + + auto &histories = history->owner().histories(); + histories.sendPreparedMessage( + history, + action.replyTo, + randomId, + Data::Histories::PrepareMessage<MTPmessages_SendMedia>( + MTP_flags(sendFlags), + peer->input, + Data::Histories::ReplyToPlaceholder(), + std::move(inputMedia), + MTPstring(), + MTP_long(randomId), + MTPReplyMarkup(), + MTPvector<MTPMessageEntity>(), + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()), + Data::ShortcutIdToMTP(session, action.options.shortcutId), + MTP_long(action.options.effectId) + ), [=](const MTPUpdates &result, const MTP::Response &response) { + }, [=](const MTP::Error &error, const MTP::Response &response) { + api->sendMessageFail(error, peer, randomId); + }); + + api->finishForwarding(action); +} + template <typename MediaData> void SendExistingMedia( MessageToSend &&message, @@ -362,6 +441,33 @@ bool SendDice(MessageToSend &message) { return true; } +void SendLocation(SendAction action, float64 lat, float64 lon) { + SendSimpleMedia( + action, + MTP_inputMediaGeoPoint( + MTP_inputGeoPoint( + MTP_flags(0), + MTP_double(lat), + MTP_double(lon), + MTPint()))); // accuracy_radius +} + +void SendVenue(SendAction action, Data::InputVenue venue) { + SendSimpleMedia( + action, + MTP_inputMediaVenue( + MTP_inputGeoPoint( + MTP_flags(0), + MTP_double(venue.lat), + MTP_double(venue.lon), + MTPint()), // accuracy_radius + MTP_string(venue.title), + MTP_string(venue.address), + MTP_string(venue.provider), + MTP_string(venue.id), + MTP_string(venue.venueType))); +} + void FillMessagePostFlags( const SendAction &action, not_null<PeerData*> peer, diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h index 2fdbad843..c4bafc537 100644 --- a/Telegram/SourceFiles/api/api_sending.h +++ b/Telegram/SourceFiles/api/api_sending.h @@ -7,15 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -namespace Main { -class Session; -} // namespace Main - class History; class PhotoData; class DocumentData; struct FilePrepareResult; +namespace Data { +struct InputVenue; +} // namespace Data + +namespace Main { +class Session; +} // namespace Main + namespace Api { struct MessageToSend; @@ -33,6 +37,13 @@ void SendExistingPhoto( bool SendDice(MessageToSend &message); +// We can't create Data::LocationPoint() and use it +// for a local sending message, because we can't request +// map thumbnail in messages history without access hash. +void SendLocation(SendAction action, float64 lat, float64 lon); + +void SendVenue(SendAction action, Data::InputVenue venue); + void FillMessagePostFlags( const SendAction &action, not_null<PeerData*> peer, diff --git a/Telegram/SourceFiles/core/current_geo_location.cpp b/Telegram/SourceFiles/core/current_geo_location.cpp new file mode 100644 index 000000000..295bde824 --- /dev/null +++ b/Telegram/SourceFiles/core/current_geo_location.cpp @@ -0,0 +1,50 @@ +/* +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 "core/current_geo_location.h" + +#include "base/platform/base_platform_info.h" +#include "data/raw/raw_countries_bounds.h" +#include "platform/platform_current_geo_location.h" + +namespace Core { + +GeoLocation ResolveCurrentCountryLocation() { + const auto iso2 = Platform::SystemCountry().toUpper(); + const auto &bounds = Raw::CountryBounds(); + const auto i = bounds.find(iso2); + if (i == end(bounds)) { + return { + .accuracy = GeoLocationAccuracy::Failed, + }; + } + return { + .point = { + (i->second.minLat + i->second.maxLat) / 2., + (i->second.minLon + i->second.maxLon) / 2., + }, + .bounds = { + i->second.minLat, + i->second.minLon, + i->second.maxLat - i->second.minLat, + i->second.maxLon - i->second.minLon, + }, + .accuracy = GeoLocationAccuracy::Country, + }; +} + +void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback) { + using namespace Platform; + return ResolveCurrentExactLocation([done = std::move(callback)]( + GeoLocation result) { + done(result.accuracy != GeoLocationAccuracy::Failed + ? result + : ResolveCurrentCountryLocation()); + }); +} + +} // namespace Core diff --git a/Telegram/SourceFiles/core/current_geo_location.h b/Telegram/SourceFiles/core/current_geo_location.h new file mode 100644 index 000000000..2715699ee --- /dev/null +++ b/Telegram/SourceFiles/core/current_geo_location.h @@ -0,0 +1,41 @@ +/* +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 Core { + +enum class GeoLocationAccuracy : uchar { + Exact, + Country, + Failed, +}; + +struct GeoLocation { + QPointF point; + QRectF bounds; + GeoLocationAccuracy accuracy = GeoLocationAccuracy::Failed; + + [[nodiscard]] bool exact() const { + return accuracy == GeoLocationAccuracy::Exact; + } + [[nodiscard]] bool country() const { + return accuracy == GeoLocationAccuracy::Country; + } + [[nodiscard]] bool failed() const { + return accuracy == GeoLocationAccuracy::Failed; + } + + explicit operator bool() const { + return !failed(); + } +}; + +[[nodiscard]] GeoLocation ResolveCurrentCountryLocation(); +void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback); + +} // namespace Core diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index a5e0090db..6fb00d550 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -45,6 +45,16 @@ private: }; +struct InputVenue { + float64 lat = 0.; + float64 lon = 0.; + QString title; + QString address; + QString provider; + QString id; + QString venueType; +}; + [[nodiscard]] GeoPointLocation ComputeLocation(const LocationPoint &point); } // namespace Data diff --git a/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp b/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp new file mode 100644 index 000000000..d4e383121 --- /dev/null +++ b/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp @@ -0,0 +1,193 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/raw/raw_countries_bounds.h" + +// Source: https://github.com/sandstrom/country-bounding-boxes + +namespace Raw { + +const base::flat_map<QString, GeoBounds> &CountryBounds() { + static const auto result = base::flat_map<QString, GeoBounds>{ + { u"AF"_q, GeoBounds{ 60.53, 29.32, 75.16, 38.49 } }, + { u"AO"_q, GeoBounds{ 11.64, -17.93, 24.08, -4.44 } }, + { u"AL"_q, GeoBounds{ 19.3, 39.62, 21.02, 42.69 } }, + { u"AE"_q, GeoBounds{ 51.58, 22.5, 56.4, 26.06 } }, + { u"AR"_q, GeoBounds{ -73.42, -55.25, -53.63, -21.83 } }, + { u"AM"_q, GeoBounds{ 43.58, 38.74, 46.51, 41.25 } }, + { u"AQ"_q, GeoBounds{ -180.0, -90.0, 180.0, -63.27 } }, + { u"TF"_q, GeoBounds{ 68.72, -49.78, 70.56, -48.63 } }, + { u"AU"_q, GeoBounds{ 113.34, -43.63, 153.57, -10.67 } }, + { u"AT"_q, GeoBounds{ 9.48, 46.43, 16.98, 49.04 } }, + { u"AZ"_q, GeoBounds{ 44.79, 38.27, 50.39, 41.86 } }, + { u"BI"_q, GeoBounds{ 29.02, -4.5, 30.75, -2.35 } }, + { u"BE"_q, GeoBounds{ 2.51, 49.53, 6.16, 51.48 } }, + { u"BJ"_q, GeoBounds{ 0.77, 6.14, 3.8, 12.24 } }, + { u"BF"_q, GeoBounds{ -5.47, 9.61, 2.18, 15.12 } }, + { u"BD"_q, GeoBounds{ 88.08, 20.67, 92.67, 26.45 } }, + { u"BG"_q, GeoBounds{ 22.38, 41.23, 28.56, 44.23 } }, + { u"BS"_q, GeoBounds{ -78.98, 23.71, -77.0, 27.04 } }, + { u"BA"_q, GeoBounds{ 15.75, 42.65, 19.6, 45.23 } }, + { u"BY"_q, GeoBounds{ 23.2, 51.32, 32.69, 56.17 } }, + { u"BZ"_q, GeoBounds{ -89.23, 15.89, -88.11, 18.5 } }, + { u"BO"_q, GeoBounds{ -69.59, -22.87, -57.5, -9.76 } }, + { u"BR"_q, GeoBounds{ -73.99, -33.77, -34.73, 5.24 } }, + { u"BN"_q, GeoBounds{ 114.2, 4.01, 115.45, 5.45 } }, + { u"BT"_q, GeoBounds{ 88.81, 26.72, 92.1, 28.3 } }, + { u"BW"_q, GeoBounds{ 19.9, -26.83, 29.43, -17.66 } }, + { u"CF"_q, GeoBounds{ 14.46, 2.27, 27.37, 11.14 } }, + { u"CA"_q, GeoBounds{ -141.0, 41.68, -52.65, 73.23 } }, + { u"CH"_q, GeoBounds{ 6.02, 45.78, 10.44, 47.83 } }, + { u"CL"_q, GeoBounds{ -75.64, -55.61, -66.96, -17.58 } }, + { u"CN"_q, GeoBounds{ 73.68, 18.2, 135.03, 53.46 } }, + { u"CI"_q, GeoBounds{ -8.6, 4.34, -2.56, 10.52 } }, + { u"CM"_q, GeoBounds{ 8.49, 1.73, 16.01, 12.86 } }, + { u"CD"_q, GeoBounds{ 12.18, -13.26, 31.17, 5.26 } }, + { u"CG"_q, GeoBounds{ 11.09, -5.04, 18.45, 3.73 } }, + { u"CO"_q, GeoBounds{ -78.99, -4.3, -66.88, 12.44 } }, + { u"CR"_q, GeoBounds{ -85.94, 8.23, -82.55, 11.22 } }, + { u"CU"_q, GeoBounds{ -84.97, 19.86, -74.18, 23.19 } }, + { u"CY"_q, GeoBounds{ 32.26, 34.57, 34.0, 35.17 } }, + { u"CZ"_q, GeoBounds{ 12.24, 48.56, 18.85, 51.12 } }, + { u"DE"_q, GeoBounds{ 5.99, 47.3, 15.02, 54.98 } }, + { u"DJ"_q, GeoBounds{ 41.66, 10.93, 43.32, 12.7 } }, + { u"DK"_q, GeoBounds{ 8.09, 54.8, 12.69, 57.73 } }, + { u"DO"_q, GeoBounds{ -71.95, 17.6, -68.32, 19.88 } }, + { u"DZ"_q, GeoBounds{ -8.68, 19.06, 12.0, 37.12 } }, + { u"EC"_q, GeoBounds{ -80.97, -4.96, -75.23, 1.38 } }, + { u"EG"_q, GeoBounds{ 24.7, 22.0, 36.87, 31.59 } }, + { u"ER"_q, GeoBounds{ 36.32, 12.46, 43.08, 18.0 } }, + { u"ES"_q, GeoBounds{ -9.39, 35.95, 3.04, 43.75 } }, + { u"EE"_q, GeoBounds{ 23.34, 57.47, 28.13, 59.61 } }, + { u"ET"_q, GeoBounds{ 32.95, 3.42, 47.79, 14.96 } }, + { u"FI"_q, GeoBounds{ 20.65, 59.85, 31.52, 70.16 } }, + { u"FJ"_q, GeoBounds{ -180.0, -18.29, 180.0, -16.02 } }, + { u"FK"_q, GeoBounds{ -61.2, -52.3, -57.75, -51.1 } }, + { u"FR"_q, GeoBounds{ -5.0, 42.5, 9.56, 51.15 } }, + { u"GA"_q, GeoBounds{ 8.8, -3.98, 14.43, 2.33 } }, + { u"GB"_q, GeoBounds{ -7.57, 49.96, 1.68, 58.64 } }, + { u"GE"_q, GeoBounds{ 39.96, 41.06, 46.64, 43.55 } }, + { u"GH"_q, GeoBounds{ -3.24, 4.71, 1.06, 11.1 } }, + { u"GN"_q, GeoBounds{ -15.13, 7.31, -7.83, 12.59 } }, + { u"GM"_q, GeoBounds{ -16.84, 13.13, -13.84, 13.88 } }, + { u"GW"_q, GeoBounds{ -16.68, 11.04, -13.7, 12.63 } }, + { u"GQ"_q, GeoBounds{ 9.31, 1.01, 11.29, 2.28 } }, + { u"GR"_q, GeoBounds{ 20.15, 34.92, 26.6, 41.83 } }, + { u"GL"_q, GeoBounds{ -73.3, 60.04, -12.21, 83.65 } }, + { u"GT"_q, GeoBounds{ -92.23, 13.74, -88.23, 17.82 } }, + { u"GY"_q, GeoBounds{ -61.41, 1.27, -56.54, 8.37 } }, + { u"HN"_q, GeoBounds{ -89.35, 12.98, -83.15, 16.01 } }, + { u"HR"_q, GeoBounds{ 13.66, 42.48, 19.39, 46.5 } }, + { u"HT"_q, GeoBounds{ -74.46, 18.03, -71.62, 19.92 } }, + { u"HU"_q, GeoBounds{ 16.2, 45.76, 22.71, 48.62 } }, + { u"ID"_q, GeoBounds{ 95.29, -10.36, 141.03, 5.48 } }, + { u"IN"_q, GeoBounds{ 68.18, 7.97, 97.4, 35.49 } }, + { u"IE"_q, GeoBounds{ -9.98, 51.67, -6.03, 55.13 } }, + { u"IR"_q, GeoBounds{ 44.11, 25.08, 63.32, 39.71 } }, + { u"IQ"_q, GeoBounds{ 38.79, 29.1, 48.57, 37.39 } }, + { u"IS"_q, GeoBounds{ -24.33, 63.5, -13.61, 66.53 } }, + { u"IL"_q, GeoBounds{ 34.27, 29.5, 35.84, 33.28 } }, + { u"IT"_q, GeoBounds{ 6.75, 36.62, 18.48, 47.12 } }, + { u"JM"_q, GeoBounds{ -78.34, 17.7, -76.2, 18.52 } }, + { u"JO"_q, GeoBounds{ 34.92, 29.2, 39.2, 33.38 } }, + { u"JP"_q, GeoBounds{ 129.41, 31.03, 145.54, 45.55 } }, + { u"KZ"_q, GeoBounds{ 46.47, 40.66, 87.36, 55.39 } }, + { u"KE"_q, GeoBounds{ 33.89, -4.68, 41.86, 5.51 } }, + { u"KG"_q, GeoBounds{ 69.46, 39.28, 80.26, 43.3 } }, + { u"KH"_q, GeoBounds{ 102.35, 10.49, 107.61, 14.57 } }, + { u"KR"_q, GeoBounds{ 126.12, 34.39, 129.47, 38.61 } }, + { u"KW"_q, GeoBounds{ 46.57, 28.53, 48.42, 30.06 } }, + { u"LA"_q, GeoBounds{ 100.12, 13.88, 107.56, 22.46 } }, + { u"LB"_q, GeoBounds{ 35.13, 33.09, 36.61, 34.64 } }, + { u"LR"_q, GeoBounds{ -11.44, 4.36, -7.54, 8.54 } }, + { u"LY"_q, GeoBounds{ 9.32, 19.58, 25.16, 33.14 } }, + { u"LK"_q, GeoBounds{ 79.7, 5.97, 81.79, 9.82 } }, + { u"LS"_q, GeoBounds{ 27.0, -30.65, 29.33, -28.65 } }, + { u"LT"_q, GeoBounds{ 21.06, 53.91, 26.59, 56.37 } }, + { u"LU"_q, GeoBounds{ 5.67, 49.44, 6.24, 50.13 } }, + { u"LV"_q, GeoBounds{ 21.06, 55.62, 28.18, 57.97 } }, + { u"MA"_q, GeoBounds{ -17.02, 21.42, -1.12, 35.76 } }, + { u"MD"_q, GeoBounds{ 26.62, 45.49, 30.02, 48.47 } }, + { u"MG"_q, GeoBounds{ 43.25, -25.6, 50.48, -12.04 } }, + { u"MX"_q, GeoBounds{ -117.13, 14.54, -86.81, 32.72 } }, + { u"MK"_q, GeoBounds{ 20.46, 40.84, 22.95, 42.32 } }, + { u"ML"_q, GeoBounds{ -12.17, 10.1, 4.27, 24.97 } }, + { u"MM"_q, GeoBounds{ 92.3, 9.93, 101.18, 28.34 } }, + { u"ME"_q, GeoBounds{ 18.45, 41.88, 20.34, 43.52 } }, + { u"MN"_q, GeoBounds{ 87.75, 41.6, 119.77, 52.05 } }, + { u"MZ"_q, GeoBounds{ 30.18, -26.74, 40.78, -10.32 } }, + { u"MR"_q, GeoBounds{ -17.06, 14.62, -4.92, 27.4 } }, + { u"MW"_q, GeoBounds{ 32.69, -16.8, 35.77, -9.23 } }, + { u"MY"_q, GeoBounds{ 100.09, 0.77, 119.18, 6.93 } }, + { u"NA"_q, GeoBounds{ 11.73, -29.05, 25.08, -16.94 } }, + { u"NC"_q, GeoBounds{ 164.03, -22.4, 167.12, -20.11 } }, + { u"NE"_q, GeoBounds{ 0.3, 11.66, 15.9, 23.47 } }, + { u"NG"_q, GeoBounds{ 2.69, 4.24, 14.58, 13.87 } }, + { u"NI"_q, GeoBounds{ -87.67, 10.73, -83.15, 15.02 } }, + { u"NL"_q, GeoBounds{ 3.31, 50.8, 7.09, 53.51 } }, + { u"NO"_q, GeoBounds{ 4.99, 58.08, 31.29, 70.92 } }, + { u"NP"_q, GeoBounds{ 80.09, 26.4, 88.17, 30.42 } }, + { u"NZ"_q, GeoBounds{ 166.51, -46.64, 178.52, -34.45 } }, + { u"OM"_q, GeoBounds{ 52.0, 16.65, 59.81, 26.4 } }, + { u"PK"_q, GeoBounds{ 60.87, 23.69, 77.84, 37.13 } }, + { u"PA"_q, GeoBounds{ -82.97, 7.22, -77.24, 9.61 } }, + { u"PE"_q, GeoBounds{ -81.41, -18.35, -68.67, -0.06 } }, + { u"PH"_q, GeoBounds{ 117.17, 5.58, 126.54, 18.51 } }, + { u"PG"_q, GeoBounds{ 141.0, -10.65, 156.02, -2.5 } }, + { u"PL"_q, GeoBounds{ 14.07, 49.03, 24.03, 54.85 } }, + { u"PR"_q, GeoBounds{ -67.24, 17.95, -65.59, 18.52 } }, + { u"KP"_q, GeoBounds{ 124.27, 37.67, 130.78, 42.99 } }, + { u"PT"_q, GeoBounds{ -9.53, 36.84, -6.39, 42.28 } }, + { u"PY"_q, GeoBounds{ -62.69, -27.55, -54.29, -19.34 } }, + { u"QA"_q, GeoBounds{ 50.74, 24.56, 51.61, 26.11 } }, + { u"RO"_q, GeoBounds{ 20.22, 43.69, 29.63, 48.22 } }, + { u"RU"_q, GeoBounds{ -180.0, 41.15, 180.0, 81.25 } }, + { u"RW"_q, GeoBounds{ 29.02, -2.92, 30.82, -1.13 } }, + { u"SA"_q, GeoBounds{ 34.63, 16.35, 55.67, 32.16 } }, + { u"SD"_q, GeoBounds{ 21.94, 8.62, 38.41, 22.0 } }, + { u"SS"_q, GeoBounds{ 23.89, 3.51, 35.3, 12.25 } }, + { u"SN"_q, GeoBounds{ -17.63, 12.33, -11.47, 16.6 } }, + { u"SB"_q, GeoBounds{ 156.49, -10.83, 162.4, -6.6 } }, + { u"SL"_q, GeoBounds{ -13.25, 6.79, -10.23, 10.05 } }, + { u"SV"_q, GeoBounds{ -90.1, 13.15, -87.72, 14.42 } }, + { u"SO"_q, GeoBounds{ 40.98, -1.68, 51.13, 12.02 } }, + { u"RS"_q, GeoBounds{ 18.83, 42.25, 22.99, 46.17 } }, + { u"SR"_q, GeoBounds{ -58.04, 1.82, -53.96, 6.03 } }, + { u"SK"_q, GeoBounds{ 16.88, 47.76, 22.56, 49.57 } }, + { u"SI"_q, GeoBounds{ 13.7, 45.45, 16.56, 46.85 } }, + { u"SE"_q, GeoBounds{ 11.03, 55.36, 23.9, 69.11 } }, + { u"SZ"_q, GeoBounds{ 30.68, -27.29, 32.07, -25.66 } }, + { u"SY"_q, GeoBounds{ 35.7, 32.31, 42.35, 37.23 } }, + { u"TD"_q, GeoBounds{ 13.54, 7.42, 23.89, 23.41 } }, + { u"TG"_q, GeoBounds{ -0.05, 5.93, 1.87, 11.02 } }, + { u"TH"_q, GeoBounds{ 97.38, 5.69, 105.59, 20.42 } }, + { u"TJ"_q, GeoBounds{ 67.44, 36.74, 74.98, 40.96 } }, + { u"TM"_q, GeoBounds{ 52.5, 35.27, 66.55, 42.75 } }, + { u"TL"_q, GeoBounds{ 124.97, -9.39, 127.34, -8.27 } }, + { u"TT"_q, GeoBounds{ -61.95, 10.0, -60.9, 10.89 } }, + { u"TN"_q, GeoBounds{ 7.52, 30.31, 11.49, 37.35 } }, + { u"TR"_q, GeoBounds{ 26.04, 35.82, 44.79, 42.14 } }, + { u"TW"_q, GeoBounds{ 120.11, 21.97, 121.95, 25.3 } }, + { u"TZ"_q, GeoBounds{ 29.34, -11.72, 40.32, -0.95 } }, + { u"UG"_q, GeoBounds{ 29.58, -1.44, 35.04, 4.25 } }, + { u"UA"_q, GeoBounds{ 22.09, 44.36, 40.08, 52.34 } }, + { u"UY"_q, GeoBounds{ -58.43, -34.95, -53.21, -30.11 } }, + { u"US"_q, GeoBounds{ -125.0, 25.0, -66.96, 49.5 } }, + { u"UZ"_q, GeoBounds{ 55.93, 37.14, 73.06, 45.59 } }, + { u"VE"_q, GeoBounds{ -73.3, 0.72, -59.76, 12.16 } }, + { u"VN"_q, GeoBounds{ 102.17, 8.6, 109.34, 23.35 } }, + { u"VU"_q, GeoBounds{ 166.63, -16.6, 167.84, -14.63 } }, + { u"PS"_q, GeoBounds{ 34.93, 31.35, 35.55, 32.53 } }, + { u"YE"_q, GeoBounds{ 42.6, 12.59, 53.11, 19.0 } }, + { u"ZA"_q, GeoBounds{ 16.34, -34.82, 32.83, -22.09 } }, + { u"ZM"_q, GeoBounds{ 21.89, -17.96, 33.49, -8.24 } }, + { u"ZW"_q, GeoBounds{ 25.26, -22.27, 32.85, -15.51 } } + }; + return result; +} + +} // namespace Raw diff --git a/Telegram/SourceFiles/data/raw/raw_countries_bounds.h b/Telegram/SourceFiles/data/raw/raw_countries_bounds.h new file mode 100644 index 000000000..4f0944c81 --- /dev/null +++ b/Telegram/SourceFiles/data/raw/raw_countries_bounds.h @@ -0,0 +1,23 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include <QtCore/QString> + +namespace Raw { + +struct GeoBounds { + double minLat = 0.; + double minLon = 0.; + double maxLat = 0.; + double maxLon = 0.; +}; + +[[nodiscard]] const base::flat_map<QString, GeoBounds> &CountryBounds(); + +} // namespace Raw diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index ff2637011..c6b013956 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -9,9 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_blocked_peers.h" #include "api/api_common.h" +#include "api/api_sending.h" #include "base/qthelp_url.h" #include "boxes/share_box.h" #include "core/click_handler_types.h" +#include "core/shortcuts.h" #include "data/data_bot_app.h" #include "data/data_changes.h" #include "data/data_user.h" @@ -28,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "iv/iv_instance.h" #include "ui/boxes/confirm_box.h" #include "ui/chat/attach/attach_bot_webview.h" +#include "ui/controls/location_picker.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/dropdown_menu.h" #include "ui/widgets/popup_menu.h" @@ -155,6 +158,10 @@ constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); return result; } +[[nodiscard]] QString ResolveMapsToken(not_null<Main::Session*> session) { + return u""_q; +} + void ShowChooseBox( not_null<Window::SessionController*> controller, PeerTypes types, @@ -1793,6 +1800,21 @@ void AttachWebView::toggleInMenu( }).send(); } +void ChooseAndSendLocation( + not_null<Window::SessionController*> controller, + Api::SendAction action) { + const auto callback = [=](Ui::LocationInfo info) { + Api::SendLocation(action, info.lat, info.lon); + }; + Ui::LocationPicker::Show({ + .parent = controller->widget(), + .callback = crl::guard(controller, callback), + .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); }, + .storageId = controller->session().local().resolveStorageIdBots(), + .closeRequests = controller->content()->death(), + }); +} + std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu( not_null<QWidget*> parent, not_null<Window::SessionController*> controller, @@ -1847,6 +1869,14 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu( { sendMenuType }); }, &st::menuIconCreatePoll); } + const auto session = &controller->session(); + const auto locationType = ChatRestriction::SendOther; + if (Data::CanSendAnyOf(peer, locationType) + && Ui::LocationPicker::Available(ResolveMapsToken(session))) { + raw->addAction(tr::lng_maps_point(tr::now), [=] { + ChooseAndSendLocation(controller, actionFactory()); + }, &st::menuIconAddress); + } for (const auto &bot : bots->attachBots()) { if (!bot.inAttachMenu || !PeerMatchesTypes(peer, bot.user, bot.types)) { diff --git a/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp new file mode 100644 index 000000000..5ac0c49a7 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp @@ -0,0 +1,18 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/linux/current_geo_location_linux.h" + +#include "core/current_geo_location.h" + +namespace Platform { + +void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { + callback({}); +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/current_geo_location_linux.h b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.h new file mode 100644 index 000000000..6bb819543 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.h @@ -0,0 +1,10 @@ +/* +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 "platform/platform_current_geo_location.h" diff --git a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.h b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.h new file mode 100644 index 000000000..6bb819543 --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.h @@ -0,0 +1,10 @@ +/* +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 "platform/platform_current_geo_location.h" diff --git a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm new file mode 100644 index 000000000..03bf727ed --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm @@ -0,0 +1,18 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/mac/current_geo_location_mac.h" + +#include "core/current_geo_location.h" + +namespace Platform { + +void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { + callback({}); +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/platform_current_geo_location.h b/Telegram/SourceFiles/platform/platform_current_geo_location.h new file mode 100644 index 000000000..245342fc3 --- /dev/null +++ b/Telegram/SourceFiles/platform/platform_current_geo_location.h @@ -0,0 +1,18 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Core { +struct GeoLocation; +} // namespace Core + +namespace Platform { + +void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback); + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp b/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp new file mode 100644 index 000000000..37682c108 --- /dev/null +++ b/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp @@ -0,0 +1,59 @@ +/* +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 "platform/win/current_geo_location_win.h" + +#include "base/platform/win/base_windows_winrt.h" +#include "core/current_geo_location.h" + +#include <winrt/Windows.Devices.Geolocation.h> +#include <winrt/Windows.Foundation.h> + +namespace Platform { + +void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Devices::Geolocation; + + const auto success = base::WinRT::Try([&] { + Geolocator geolocator; + geolocator.DesiredAccuracy(PositionAccuracy::High); + if (geolocator.LocationStatus() == PositionStatus::NotAvailable) { + callback({}); + return; + } + geolocator.GetGeopositionAsync().Completed([=]( + IAsyncOperation<Geoposition> that, + AsyncStatus status) { + if (status != AsyncStatus::Completed) { + crl::on_main([=] { + callback({}); + }); + return; + } + const auto point = base::WinRT::Try([&] { + const auto coordinate = that.GetResults().Coordinate(); + return coordinate.Point().Position(); + }); + crl::on_main([=] { + if (!point) { + callback({}); + } else { + callback({ + .point = { point->Latitude, point->Longitude }, + .accuracy = Core::GeoLocationAccuracy::Exact, + }); + } + }); + }); + }); + if (!success) { + callback({}); + } +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/current_geo_location_win.h b/Telegram/SourceFiles/platform/win/current_geo_location_win.h new file mode 100644 index 000000000..6bb819543 --- /dev/null +++ b/Telegram/SourceFiles/platform/win/current_geo_location_win.h @@ -0,0 +1,10 @@ +/* +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 "platform/platform_current_geo_location.h" diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp new file mode 100644 index 000000000..21c5b9861 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -0,0 +1,350 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/controls/location_picker.h" + +#include "base/platform/base_platform_info.h" +#include "core/current_geo_location.h" +#include "lang/lang_keys.h" +#include "ui/widgets/rp_window.h" +#include "ui/widgets/buttons.h" +#include "webview/webview_data_stream_memory.h" +#include "webview/webview_embed.h" +#include "webview/webview_interface.h" +#include "styles/style_dialogs.h" +#include "styles/style_window.h" + +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonValue> +#include <QtGui/QGuiApplication> +#include <QtGui/QScreen> + +namespace Ui { +namespace { + +Core::GeoLocation LastExactLocation; +QString MapsProviderToken; + +[[nodiscard]] QByteArray DefaultCenter() { + if (!LastExactLocation) { + return "null"; + } + return "["_q + + QByteArray::number(LastExactLocation.point.x() - 1) + + ","_q + + QByteArray::number(LastExactLocation.point.y() - 1) + + "]"_q; +} + +[[nodiscard]] QByteArray DefaultBounds() { + const auto country = Core::ResolveCurrentCountryLocation(); + if (!country) { + return "null"; + } + return "[["_q + + QByteArray::number(country.bounds.x()) + + ","_q + + QByteArray::number(country.bounds.y()) + + "],["_q + + QByteArray::number(country.bounds.x() + country.bounds.width()) + + ","_q + + QByteArray::number(country.bounds.y() + country.bounds.height()) + + "]]"_q; +} + +[[nodiscard]] QByteArray ComputeStyles() { + return ""; +} + +[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value) { + return value + .replace('&', "&") + .replace('"', """) + .replace('\'', "'") + .replace('<', "<") + .replace('>', ">"); +} + +[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value) { + return value + .replace('\\', "\\\\") + .replace('"', "\\\"") + .replace('\'', "\\\'"); +} + +[[nodiscard]] QByteArray ReadResource(const QString &name) { + auto file = QFile(u":/picker/"_q + name); + return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray(); +} + +[[nodiscard]] QByteArray PickerContent() { + return R"(<!DOCTYPE html> +<html style=")" ++ EscapeForAttribute(ComputeStyles()) ++ R"("> + <head> + <meta charset="utf-8"> + <meta name="robots" content="noindex, nofollow"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <script src="/location/picker.js"></script> + <link rel="stylesheet" href="/location/picker.css" /> + <script src='https://api.mapbox.com/mapbox-gl-js/v3.4.0/mapbox-gl.js'></script> + <link href='https://api.mapbox.com/mapbox-gl-js/v3.4.0/mapbox-gl.css' rel='stylesheet' /> + </head> + <body> + <div id="marker"><div id="marker_drop"></div></div> + <div id="map"></div> + <script>LocationPicker.notify({ event: 'ready' });</script> + </body> +</html> +)"_q; +} + +} // namespace + +LocationPicker::LocationPicker(Descriptor &&descriptor) +: _callback(std::move(descriptor.callback)) +, _quit(std::move(descriptor.quit)) +, _window(std::make_unique<RpWindow>()) +, _updateStyles([=] { + const auto str = EscapeForScriptString(ComputeStyles()); + if (_webview) { + _webview->eval("IV.updateStyles('" + str + "');"); + } +}) { + std::move( + descriptor.closeRequests + ) | rpl::start_with_next([=] { + _window = nullptr; + delete this; + }, _lifetime); + + setup(descriptor); +} + +bool LocationPicker::Available(const QString &token) { + static const auto Supported = Webview::NavigateToDataSupported(); + MapsProviderToken = token; + return Supported && !MapsProviderToken.isEmpty(); +} + +void LocationPicker::setup(const Descriptor &descriptor) { + setupWindow(descriptor); + setupWebview(descriptor); +} + +void LocationPicker::setupWindow(const Descriptor &descriptor) { + const auto window = _window.get(); + + const auto parent = descriptor.parent + ? descriptor.parent->window()->geometry() + : QGuiApplication::primaryScreen()->availableGeometry(); + window->setGeometry(QRect( + parent.x() + (parent.width() - st::windowMinHeight) / 2, + parent.y() + (parent.height() - st::windowMinWidth) / 2, + st::windowMinHeight, + st::windowMinWidth)); + window->setMinimumSize({ st::windowMinHeight, st::windowMinWidth }); + + _container = Ui::CreateChild<Ui::RpWidget>(window->body().get()); + const auto button = Ui::CreateChild<FlatButton>( + window->body(), + tr::lng_maps_point_send(tr::now), + st::dialogsUpdateButton); + button->show(); + button->setClickedCallback([=] { + _webview->eval("LocationPicker.send();"); + }); + window->body()->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + _container->setGeometry(QRect(QPoint(), size).marginsRemoved( + { 0, 0, 0, button->height() })); + button->resizeToWidth(size.width()); + button->setGeometry( + 0, + size.height() - button->height(), + button->width(), + button->height()); + }, _container->lifetime()); + + _container->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(_container).fillRect(clip, st::windowBg); + }, _container->lifetime()); + + _container->show(); + window->show(); +} + +void LocationPicker::setupWebview(const Descriptor &descriptor) { + Expects(!_webview); + + const auto window = _window.get(); + _webview = std::make_unique<Webview::Window>( + _container, + Webview::WindowConfig{ + .opaqueBg = st::windowBg->c, + .storageId = descriptor.storageId, + }); + const auto raw = _webview.get(); + + window->lifetime().add([=] { + _webview = nullptr; + }); + + window->events( + ) | rpl::start_with_next([=](not_null<QEvent*> e) { + if (e->type() == QEvent::Close) { + close(); + } else if (e->type() == QEvent::KeyPress) { + const auto event = static_cast<QKeyEvent*>(e.get()); + if (event->key() == Qt::Key_Escape) { + close(); + } + } + }, window->lifetime()); + raw->widget()->show(); + + _container->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + raw->widget()->setGeometry(QRect(QPoint(), size)); + }, _container->lifetime()); + + raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) { + return true; + }); + raw->setNavigationDoneHandler([=](bool success) { + }); + raw->setMessageHandler([=](const QJsonDocument &message) { + crl::on_main(_window.get(), [=] { + const auto object = message.object(); + const auto event = object.value("event").toString(); + if (event == u"ready"_q) { + initMap(); + resolveCurrentLocation(); + } else if (event == u"keydown"_q) { + const auto key = object.value("key").toString(); + const auto modifier = object.value("modifier").toString(); + processKey(key, modifier); + } else if (event == u"send"_q) { + const auto lat = object.value("latitude").toDouble(); + const auto lon = object.value("longitude").toDouble(); + _callback({ lat, lon }); + close(); + } + }); + }); + raw->setDataRequestHandler([=](Webview::DataRequest request) { + const auto pos = request.id.find('#'); + if (pos != request.id.npos) { + request.id = request.id.substr(0, pos); + } + if (!request.id.starts_with("location/")) { + return Webview::DataResult::Failed; + } + const auto finishWith = [&](QByteArray data, std::string mime) { + request.done({ + .stream = std::make_unique<Webview::DataStreamFromMemory>( + std::move(data), + std::move(mime)), + }); + return Webview::DataResult::Done; + }; + if (!_subscribedToColors) { + _subscribedToColors = true; + + rpl::merge( + Lang::Updated(), + style::PaletteChanged() + ) | rpl::start_with_next([=] { + _updateStyles.call(); + }, _webview->lifetime()); + } + const auto id = std::string_view(request.id).substr(9); + if (id == "picker.html") { + return finishWith(PickerContent(), "text/html; charset=utf-8"); + } + const auto css = id.ends_with(".css"); + const auto js = !css && id.ends_with(".js"); + if (!css && !js) { + return Webview::DataResult::Failed; + } + const auto qstring = QString::fromUtf8(id.data(), id.size()); + const auto pattern = u"^[a-zA-Z\\.\\-_0-9]+$"_q; + if (QRegularExpression(pattern).match(qstring).hasMatch()) { + const auto bytes = ReadResource(qstring); + if (!bytes.isEmpty()) { + const auto mime = css ? "text/css" : "text/javascript"; + return finishWith(bytes, mime); + } + } + return Webview::DataResult::Failed; + }); + + raw->init(R"()"); + raw->navigateToData("location/picker.html"); +} + +void LocationPicker::initMap() { + const auto token = MapsProviderToken.toUtf8(); + const auto center = DefaultCenter(); + const auto bounds = DefaultBounds(); + const auto arguments = "'" + token + "', " + center + ", " + bounds; + _webview->eval("LocationPicker.init(" + arguments + ");"); +} + +void LocationPicker::resolveCurrentLocation() { + using namespace Core; + const auto window = _window.get(); + ResolveCurrentGeoLocation(crl::guard(window, [=](GeoLocation location) { + if (location) { + LastExactLocation = location; + } + if (_webview && location.accuracy == GeoLocationAccuracy::Exact) { + const auto point = QByteArray::number(location.point.x()) + + ","_q + + QByteArray::number(location.point.y()); + _webview->eval("LocationPicker.narrowTo([" + point + "]);"); + } + })); +} + +void LocationPicker::processKey( + const QString &key, + const QString &modifier) { + const auto ctrl = ::Platform::IsMac() ? u"cmd"_q : u"ctrl"_q; + if (key == u"escape"_q || (key == u"w"_q && modifier == ctrl)) { + close(); + } else if (key == u"m"_q && modifier == ctrl) { + minimize(); + } else if (key == u"q"_q && modifier == ctrl) { + quit(); + } +} + +void LocationPicker::close() { + _window->close(); +} + +void LocationPicker::minimize() { + if (_window) { + _window->setWindowState(_window->windowState() + | Qt::WindowMinimized); + } +} + +void LocationPicker::quit() { + if (const auto onstack = _quit) { + onstack(); + } +} + +not_null<LocationPicker*> LocationPicker::Show(Descriptor &&descriptor) { + return new LocationPicker(std::move(descriptor)); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h new file mode 100644 index 000000000..220c0c426 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -0,0 +1,67 @@ +/* +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/invoke_queued.h" +#include "base/weak_ptr.h" +#include "webview/webview_common.h" + +namespace Webview { +class Window; +} // namespace Webview + +namespace Ui { + +class RpWindow; +class RpWidget; + +struct LocationInfo { + float64 lat = 0.; + float64 lon = 0.; +}; + +class LocationPicker final : public base::has_weak_ptr { +public: + struct Descriptor { + RpWidget *parent = nullptr; + Fn<void(LocationInfo)> callback; + Fn<void()> quit; + Webview::StorageId storageId; + rpl::producer<> closeRequests; + }; + + [[nodiscard]] static bool Available(const QString &token); + static not_null<LocationPicker*> Show(Descriptor &&descriptor); + + void close(); + void minimize(); + void quit(); + +private: + explicit LocationPicker(Descriptor &&descriptor); + + void setup(const Descriptor &descriptor); + void setupWindow(const Descriptor &descriptor); + void setupWebview(const Descriptor &descriptor); + void processKey(const QString &key, const QString &modifier); + void resolveCurrentLocation(); + void initMap(); + + rpl::lifetime _lifetime; + + Fn<void(LocationInfo)> _callback; + Fn<void()> _quit; + std::unique_ptr<RpWindow> _window; + RpWidget *_container = nullptr; + std::unique_ptr<Webview::Window> _webview; + SingleQueuedInvokation _updateStyles; + bool _subscribedToColors = false; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 3555c9218..b11016acb 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -70,6 +70,8 @@ PRIVATE chat_helpers/stickers_emoji_image_loader.cpp chat_helpers/stickers_emoji_image_loader.h + core/current_geo_location.cpp + core/current_geo_location.h core/file_location.cpp core/file_location.h core/mime_type.cpp @@ -78,6 +80,8 @@ PRIVATE countries/countries_instance.cpp countries/countries_instance.h + data/raw/raw_countries_bounds.cpp + data/raw/raw_countries_bounds.h data/data_birthday.cpp data/data_birthday.h data/data_channel_earn.h @@ -194,9 +198,16 @@ PRIVATE payments/ui/payments_panel_data.h payments/ui/payments_panel_delegate.h + platform/linux/current_geo_location_linux.cpp + platform/linux/current_geo_location_linux.h platform/mac/file_bookmark_mac.h platform/mac/file_bookmark_mac.mm + platform/mac/current_geo_location_mac.h + platform/mac/current_geo_location_mac.mm + platform/win/current_geo_location_win.cpp + platform/win/current_geo_location_win.h platform/platform_file_bookmark.h + platform/platform_current_geo_location.h settings/settings_common.cpp settings/settings_common.h @@ -344,6 +355,8 @@ PRIVATE ui/controls/invite_link_buttons.h ui/controls/invite_link_label.cpp ui/controls/invite_link_label.h + ui/controls/location_picker.cpp + ui/controls/location_picker.h ui/controls/peer_list_dummy.cpp ui/controls/peer_list_dummy.h ui/controls/send_as_button.cpp @@ -439,6 +452,12 @@ PRIVATE ui/ui_pch.h ) +nice_target_sources(td_ui ${res_loc} +PRIVATE + picker_html/picker.css + picker_html/picker.js +) + if (DESKTOP_APP_SPECIAL_TARGET) remove_target_sources(td_ui ${src_loc} ui/controls/window_outdated_bar_dummy.cpp From 025ab40687f9a0f40f6058358afd318f9522ce7c Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 5 Jul 2024 21:25:25 +0400 Subject: [PATCH 036/163] Implement location sending on macOS. --- Telegram/Resources/picker_html/picker.js | 14 ++- .../SourceFiles/core/current_geo_location.cpp | 2 +- .../platform/mac/current_geo_location_mac.mm | 103 +++++++++++++++++- .../ui/controls/location_picker.cpp | 34 ++++-- Telegram/Telegram.plist | 14 ++- Telegram/Telegram/Telegram Lite.entitlements | 2 + Telegram/Telegram/Telegram.entitlements | 2 + cmake | 2 +- 8 files changed, 155 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js index 6a35060f0..54eb60aba 100644 --- a/Telegram/Resources/picker_html/picker.js +++ b/Telegram/Resources/picker_html/picker.js @@ -37,17 +37,21 @@ var LocationPicker = { document.getElementsByTagName('html')[0].style = styles; } }, - init: function (token, center, bounds) { - mapboxgl.accessToken = token; + init: function (params) { + mapboxgl.accessToken = params.token; + if (params.protocol) { + mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com'; + } var options = { container: 'map' }; + var center = params.center; if (center) { center = [center[1], center[0]]; options.center = center; options.zoom = LocationPicker.startZoom; - } else if (bounds) { - options.bounds = bounds; - center = new mapboxgl.LngLatBounds(bounds).getCenter(); + } else if (params.bounds) { + options.bounds = params.bounds; + center = new mapboxgl.LngLatBounds(params.bounds).getCenter(); } else { center = [0, 0]; } diff --git a/Telegram/SourceFiles/core/current_geo_location.cpp b/Telegram/SourceFiles/core/current_geo_location.cpp index 295bde824..eb2125bf5 100644 --- a/Telegram/SourceFiles/core/current_geo_location.cpp +++ b/Telegram/SourceFiles/core/current_geo_location.cpp @@ -40,7 +40,7 @@ GeoLocation ResolveCurrentCountryLocation() { void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback) { using namespace Platform; return ResolveCurrentExactLocation([done = std::move(callback)]( - GeoLocation result) { + GeoLocation result) { done(result.accuracy != GeoLocationAccuracy::Failed ? result : ResolveCurrentCountryLocation()); diff --git a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm index 03bf727ed..2812a0c33 100644 --- a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm +++ b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm @@ -9,10 +9,111 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/current_geo_location.h" +#include <CoreLocation/CoreLocation.h> + +@interface LocationDelegate : NSObject<CLLocationManagerDelegate> + +- (id) initWithCallback:(Fn<void(Core::GeoLocation)>)callback; +- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations; +- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error; +- (void) locationManager:(CLLocationManager *) manager didChangeAuthorizationStatus:(CLAuthorizationStatus) status; +- (void) dealloc; + +@end + +@implementation LocationDelegate { +CLLocationManager *_manager; +Fn<void(Core::GeoLocation)> _callback; +} + +- (void) fail { + [_manager stopUpdatingLocation]; + + const auto onstack = _callback; + [self release]; + + onstack({}); +} + +- (void) processWithStatus:(CLAuthorizationStatus)status { + switch (status) { + case kCLAuthorizationStatusNotDetermined: + if (@available(macOS 10.15, *)) { + [_manager requestWhenInUseAuthorization]; + } else { + [_manager startUpdatingLocation]; + } + break; + case kCLAuthorizationStatusAuthorizedAlways: + [_manager startUpdatingLocation]; + return; + case kCLAuthorizationStatusRestricted: + case kCLAuthorizationStatusDenied: + default: + [self fail]; + return; + } +} + +- (id) initWithCallback:(Fn<void(Core::GeoLocation)>)callback { + if (self = [super init]) { + _callback = std::move(callback); + _manager = [[CLLocationManager alloc] init]; + _manager.desiredAccuracy = kCLLocationAccuracyThreeKilometers; + _manager.delegate = self; + if ([CLLocationManager locationServicesEnabled]) { + if (@available(macOS 11, *)) { + [self processWithStatus:[_manager authorizationStatus]]; + } else { + [self processWithStatus:[CLLocationManager authorizationStatus]]; + } + } else { + [self fail]; + } + } + return self; +} + +- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation*>*)locations { + [_manager stopUpdatingLocation]; + + auto result = Core::GeoLocation(); + if ([locations count] > 0) { + const auto coordinate = [locations lastObject].coordinate; + result.accuracy = Core::GeoLocationAccuracy::Exact; + result.point = QPointF(coordinate.latitude, coordinate.longitude); + } + + const auto onstack = _callback; + [self release]; + + onstack(result); +} + +- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + if (error.code != kCLErrorLocationUnknown) { + [self fail]; + } +} + +- (void) locationManager:(CLLocationManager *) manager didChangeAuthorizationStatus:(CLAuthorizationStatus) status { + [self processWithStatus:status]; +} + +- (void) dealloc { + if (_manager) { + _manager.delegate = nil; + [_manager release]; + } + [super dealloc]; +} + +@end + namespace Platform { void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { - callback({}); + [[LocationDelegate alloc] initWithCallback:std::move(callback)]; } } // namespace Platform diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 21c5b9861..804593b07 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_dialogs.h" #include "styles/style_window.h" +#include <QtCore/QFile> #include <QtCore/QJsonDocument> #include <QtCore/QJsonObject> #include <QtCore/QJsonValue> @@ -27,6 +28,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace { +#ifdef Q_OS_MAC +const auto kProtocolOverride = "mapboxapihelper"; +#else // Q_OS_MAC +const auto kProtocolOverride = ""; +#endif // Q_OS_MAC + Core::GeoLocation LastExactLocation; QString MapsProviderToken; @@ -35,9 +42,9 @@ QString MapsProviderToken; return "null"; } return "["_q - + QByteArray::number(LastExactLocation.point.x() - 1) + + QByteArray::number(LastExactLocation.point.x()) + ","_q - + QByteArray::number(LastExactLocation.point.y() - 1) + + QByteArray::number(LastExactLocation.point.y()) + "]"_q; } @@ -189,6 +196,7 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { Webview::WindowConfig{ .opaqueBg = st::windowBg->c, .storageId = descriptor.storageId, + .dataProtocolOverride = kProtocolOverride, }); const auto raw = _webview.get(); @@ -293,18 +301,25 @@ void LocationPicker::initMap() { const auto token = MapsProviderToken.toUtf8(); const auto center = DefaultCenter(); const auto bounds = DefaultBounds(); - const auto arguments = "'" + token + "', " + center + ", " + bounds; - _webview->eval("LocationPicker.init(" + arguments + ");"); + const auto protocol = *kProtocolOverride + ? "'"_q + kProtocolOverride + "'" + : "null"; + const auto params = "token: '" + token + "'" + + ", center: " + center + + ", bounds: " + bounds + + ", protocol: " + protocol; + _webview->eval("LocationPicker.init({ " + params + " });"); } void LocationPicker::resolveCurrentLocation() { using namespace Core; const auto window = _window.get(); ResolveCurrentGeoLocation(crl::guard(window, [=](GeoLocation location) { - if (location) { - LastExactLocation = location; + if (location.accuracy != GeoLocationAccuracy::Exact) { + return; } - if (_webview && location.accuracy == GeoLocationAccuracy::Exact) { + LastExactLocation = location; + if (_webview) { const auto point = QByteArray::number(location.point.x()) + ","_q + QByteArray::number(location.point.y()); @@ -327,7 +342,10 @@ void LocationPicker::processKey( } void LocationPicker::close() { - _window->close(); + crl::on_main(this, [=] { + _window = nullptr; + delete this; + }); } void LocationPicker::minimize() { diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist index 53b8daf8b..b68e2109c 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -12,12 +12,20 @@ <string>Icon.icns</string> <key>CFBundleIdentifier</key> <string>@bundle_identifier_plist@</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>@output_name@</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>@desktop_app_version_string@</string> <key>CFBundleSignature</key> <string>????</string> + <key>CFBundleSupportedPlatforms</key> + <array> + <string>MacOSX</string> + </array> <key>CFBundleURLTypes</key> <array> <dict> @@ -39,16 +47,18 @@ <false/> <key>LSApplicationCategoryType</key> <string>public.app-category.social-networking</string> - <key>LSMinimumSystemVersion</key> - <string>@CMAKE_OSX_DEPLOYMENT_TARGET@</string> <key>LSFileQuarantineEnabled</key> <true/> + <key>LSMinimumSystemVersion</key> + <string>@CMAKE_OSX_DEPLOYMENT_TARGET@</string> <key>NOTE</key> <string></string> <key>NSMicrophoneUsageDescription</key> <string>We need access to your microphone so that you can record voice messages and make calls.</string> <key>NSCameraUsageDescription</key> <string>We need access to your camera so that you can record video messages and make video calls.</string> + <key>NSLocationUsageDescription</key> + <string>We need access to your location so that you can send your current locations.</string> <key>NSPrincipalClass</key> <string>NSApplication</string> <key>NSSupportsAutomaticGraphicsSwitching</key> diff --git a/Telegram/Telegram/Telegram Lite.entitlements b/Telegram/Telegram/Telegram Lite.entitlements index 46355b637..050eea08f 100644 --- a/Telegram/Telegram/Telegram Lite.entitlements +++ b/Telegram/Telegram/Telegram Lite.entitlements @@ -18,5 +18,7 @@ <true/> <key>com.apple.security.device.camera</key> <true/> + <key>com.apple.security.personal-information.location</key> + <true/> </dict> </plist> diff --git a/Telegram/Telegram/Telegram.entitlements b/Telegram/Telegram/Telegram.entitlements index 97c1f6d58..af2883220 100644 --- a/Telegram/Telegram/Telegram.entitlements +++ b/Telegram/Telegram/Telegram.entitlements @@ -6,5 +6,7 @@ <true/> <key>com.apple.security.device.camera</key> <true/> + <key>com.apple.security.personal-information.location</key> + <true/> </dict> </plist> diff --git a/cmake b/cmake index 4a4bc4cd3..78b441c9c 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 4a4bc4cd34b3ade038541a2b8b2c79f05393d67b +Subproject commit 78b441c9c6ad8a14a8f97a28825babcadc6bf781 From 8e6d7bb1903a84c12610e348b27663dc58bb3f7e Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sun, 7 Jul 2024 15:10:35 +0400 Subject: [PATCH 037/163] Improve location picker design. --- .../chat_helpers/chat_helpers.style | 4 ++ .../ui/controls/location_picker.cpp | 70 +++++++++++++------ .../SourceFiles/ui/controls/location_picker.h | 5 +- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 0b1290dc8..8c7bcbb93 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1423,3 +1423,7 @@ paidTagLabel: FlatLabel(defaultFlatLabel) { style: semiboldTextStyle; } paidTagPadding: margins(16px, 6px, 16px, 6px); + +pickLocationWindow: size(364px, 680px); +pickLocationMapHeight: 220px; +pickLocationCollapsedHeight: 108px; diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 804593b07..5b77f017a 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -10,11 +10,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "core/current_geo_location.h" #include "lang/lang_keys.h" -#include "ui/widgets/rp_window.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/separate_panel.h" #include "ui/widgets/buttons.h" +#include "ui/wrap/vertical_layout.h" #include "webview/webview_data_stream_memory.h" #include "webview/webview_embed.h" #include "webview/webview_interface.h" +#include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" @@ -117,7 +120,10 @@ QString MapsProviderToken; LocationPicker::LocationPicker(Descriptor &&descriptor) : _callback(std::move(descriptor.callback)) , _quit(std::move(descriptor.quit)) -, _window(std::make_unique<RpWindow>()) +, _window(std::make_unique<SeparatePanel>()) +, _body((_window->setInnerSize(st::pickLocationWindow) + , _window->showInner(base::make_unique_q<RpWidget>(_window.get())) + , _window->inner())) , _updateStyles([=] { const auto str = EscapeForScriptString(ComputeStyles()); if (_webview) { @@ -148,35 +154,50 @@ void LocationPicker::setup(const Descriptor &descriptor) { void LocationPicker::setupWindow(const Descriptor &descriptor) { const auto window = _window.get(); + window->setWindowFlag(Qt::WindowStaysOnTopHint, false); + window->closeRequests() | rpl::start_with_next([=] { + close(); + }, _lifetime); + const auto parent = descriptor.parent ? descriptor.parent->window()->geometry() : QGuiApplication::primaryScreen()->availableGeometry(); - window->setGeometry(QRect( - parent.x() + (parent.width() - st::windowMinHeight) / 2, - parent.y() + (parent.height() - st::windowMinWidth) / 2, - st::windowMinHeight, - st::windowMinWidth)); - window->setMinimumSize({ st::windowMinHeight, st::windowMinWidth }); + window->setTitle(tr::lng_maps_point()); + window->move( + parent.x() + (parent.width() - window->width()) / 2, + parent.y() + (parent.height() - window->height()) / 2); - _container = Ui::CreateChild<Ui::RpWidget>(window->body().get()); - const auto button = Ui::CreateChild<FlatButton>( - window->body(), + _container = CreateChild<RpWidget>(_body.get()); + const auto scroll = CreateChild<ScrollArea>(_body.get()); + const auto controls = scroll->setOwnedWidget( + object_ptr<VerticalLayout>(scroll)); + const auto toppad = controls->add(object_ptr<RpWidget>(controls)); + + const auto button = controls->add(object_ptr<FlatButton>( + controls, tr::lng_maps_point_send(tr::now), - st::dialogsUpdateButton); - button->show(); + st::dialogsUpdateButton)); button->setClickedCallback([=] { _webview->eval("LocationPicker.send();"); }); - window->body()->sizeValue( - ) | rpl::start_with_next([=](QSize size) { - _container->setGeometry(QRect(QPoint(), size).marginsRemoved( - { 0, 0, 0, button->height() })); - button->resizeToWidth(size.width()); - button->setGeometry( - 0, - size.height() - button->height(), - button->width(), - button->height()); + controls->add(object_ptr<RpWidget>(controls))->resize( + st::pickLocationWindow); + + rpl::combine( + _body->sizeValue(), + scroll->scrollTopValue() + ) | rpl::start_with_next([=](QSize size, int scrollTop) { + const auto width = size.width(); + const auto height = size.height(); + const auto sub = std::min( + (st::pickLocationMapHeight - st::pickLocationCollapsedHeight), + scrollTop); + const auto mapHeight = st::pickLocationMapHeight - sub; + const auto scrollHeight = height - mapHeight; + button->resizeToWidth(width); + _container->setGeometry(0, 0, width, mapHeight); + scroll->setGeometry(0, mapHeight, width, scrollHeight); + toppad->resize(width, sub); }, _container->lifetime()); _container->paintRequest() | rpl::start_with_next([=](QRect clip) { @@ -184,6 +205,9 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { }, _container->lifetime()); _container->show(); + scroll->show(); + controls->show(); + button->show(); window->show(); } diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index 220c0c426..f18957b9d 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -17,7 +17,7 @@ class Window; namespace Ui { -class RpWindow; +class SeparatePanel; class RpWidget; struct LocationInfo { @@ -56,7 +56,8 @@ private: Fn<void(LocationInfo)> _callback; Fn<void()> _quit; - std::unique_ptr<RpWindow> _window; + std::unique_ptr<SeparatePanel> _window; + not_null<RpWidget*> _body; RpWidget *_container = nullptr; std::unique_ptr<Webview::Window> _webview; SingleQueuedInvokation _updateStyles; From 310837c9e18261ceca238c2fbd682fa15f8e8e9e Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sun, 7 Jul 2024 21:01:26 +0400 Subject: [PATCH 038/163] Add venues list and chosen place name. --- Telegram/CMakeLists.txt | 10 +- .../Resources/icons/chat/filled_location.png | Bin 0 -> 536 bytes .../icons/chat/filled_location@2x.png | Bin 0 -> 987 bytes .../icons/chat/filled_location@3x.png | Bin 0 -> 1525 bytes Telegram/Resources/langs/lang.strings | 3 + Telegram/Resources/picker_html/picker.js | 16 +- .../chat_helpers/chat_helpers.style | 51 +- .../chat_helpers/gifs_list_widget.cpp | 8 +- .../SourceFiles/core/current_geo_location.cpp | 126 ++++ .../SourceFiles/core/current_geo_location.h | 20 + Telegram/SourceFiles/data/data_location.h | 4 + Telegram/SourceFiles/data/data_session.cpp | 16 + Telegram/SourceFiles/data/data_session.h | 3 + .../inline_bots/bot_attach_web_view.cpp | 9 +- Telegram/SourceFiles/iv/iv_controller.cpp | 73 +-- Telegram/SourceFiles/main/main_session.cpp | 8 +- .../SourceFiles/mtproto/mtproto_config.cpp | 46 +- Telegram/SourceFiles/mtproto/mtproto_config.h | 11 +- .../linux/current_geo_location_linux.cpp | 5 + .../platform/mac/current_geo_location_mac.mm | 6 + .../platform/platform_current_geo_location.h | 4 + .../platform/win/current_geo_location_win.cpp | 9 + .../ui/controls/location_picker.cpp | 578 ++++++++++++++++-- .../SourceFiles/ui/controls/location_picker.h | 79 ++- Telegram/SourceFiles/ui/webview_helpers.cpp | 82 +++ Telegram/SourceFiles/ui/webview_helpers.h | 27 + Telegram/cmake/td_ui.cmake | 13 +- 27 files changed, 1066 insertions(+), 141 deletions(-) create mode 100644 Telegram/Resources/icons/chat/filled_location.png create mode 100644 Telegram/Resources/icons/chat/filled_location@2x.png create mode 100644 Telegram/Resources/icons/chat/filled_location@3x.png create mode 100644 Telegram/SourceFiles/ui/webview_helpers.cpp create mode 100644 Telegram/SourceFiles/ui/webview_helpers.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6f3bc7a00..592f1329f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1472,6 +1472,8 @@ PRIVATE ui/chat/choose_send_as.h ui/chat/choose_theme_controller.cpp ui/chat/choose_theme_controller.h + ui/controls/location_picker.cpp + ui/controls/location_picker.h ui/controls/silent_toggle.cpp ui/controls/silent_toggle.h ui/controls/userpic_button.cpp @@ -1493,6 +1495,10 @@ PRIVATE ui/image/image_location.h ui/image/image_location_factory.cpp ui/image/image_location_factory.h + ui/text/format_song_document_name.cpp + ui/text/format_song_document_name.h + ui/widgets/label_with_custom_emoji.cpp + ui/widgets/label_with_custom_emoji.h ui/countryinput.cpp ui/countryinput.h ui/dynamic_thumbnails.cpp @@ -1506,10 +1512,6 @@ PRIVATE ui/resize_area.h ui/search_field_controller.cpp ui/search_field_controller.h - ui/text/format_song_document_name.cpp - ui/text/format_song_document_name.h - ui/widgets/label_with_custom_emoji.cpp - ui/widgets/label_with_custom_emoji.h ui/unread_badge.cpp ui/unread_badge.h window/main_window.cpp diff --git a/Telegram/Resources/icons/chat/filled_location.png b/Telegram/Resources/icons/chat/filled_location.png new file mode 100644 index 0000000000000000000000000000000000000000..12cd2dcc81112e4b61bf1abab39b18c1a0eff905 GIT binary patch literal 536 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgftjW{GF~maf zYRK8HrT~$18|7OMDHMkPW(jA_OBeqll2^@D%O-v)_{FZNZZ5e!Vyd=o2B)XY>~FCO z|9tB}&a-vrzP~o*H<kMTOrq`Ku6I9c{_T?IX;7&D$TZh4eP>Ks<hNKW1;v8_@z?XV zUw>V?vQcr7M$_75r$5`Mg)k;O7Ja#E-m*;BX$upZXO`6lBt%Zr=$gXW^x$)yL4cgu z49&x5jz6AgSHAfsN7Dpr1BM{cG^w;a6RF-6&wkrYvTSCYu<G!`2~Nva+{_8`y==ml zvo$It_1WJ#fBpKsamODQzB|C+5jrDt#!jYd*G<m0uhH<5IllTT*O?r%$*DgT6D{WY zi5PvZv0Hxr$$10gM6M5+degb>=da#iYp0Q0ajyI5qq#~q=FOZwS;h0m(jT#&lMZ|{ zIyYhR$tPvILrWcJ`7QTpV9vR{POC_!U0BEK#u)>i)3HC8HFa_gBahw9E8l+mqFzqI z`s>=(uV>cn?{7V`o3EWYp?29RLkXU3w{_PoTa`8SdH+xOR~|x^&yH||Vwb_w)z4*} HQ$iB}F6`5b literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/filled_location@2x.png b/Telegram/Resources/icons/chat/filled_location@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cdef3f274a76935568c1501b895922906429f340 GIT binary patch literal 987 zcmV<110?*3P)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ00001b5ch_0Itp) z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFMM*?KR9Fe^SUpH`Q5e1jhBygD z`4bH-Leykawg}%4)D+<yVS_=F5R_tb;N&Dufrg7?;!q&%qF4|_qu3w)3|2~(-}mqZ zVtL>9oO`sbJDvMJfA2lsdp+lB+VAl@1HH|_fSTNFHhaC^rKP2rnHjs?ZnN3ob8~ZZ zeSLj)c6NApn9t`MjfR@JG1d6^csw3|d3pKE9w2}QvN3l<Y|KhD8m-ssY$snpkRgH4 zP>>XPVq&6DD11qiK7kN&sTfI;+wH!)yOVA}hX^@LB;y1$r_))jR&_!QmS6%aL6{J> zSS-b2(O@)5hZPKkp&Vg%cbBwR0$|AH@z~hdSglr5$|XfGge{*-qOn+vH0}X_Eg7k4 z<ePeMaPao_c4TCP5d(U7c*tZjc(5^^ot<4-Ss5N4<|X&{_n4$stHsm4qx19g!db{> zv!8z&Z81(!6bfP9mdFnF_V#$+)6>(Tp`mU&6an#Cn0E^Wm0T{zo6gP6Q5JxBEzBt` zSud4JjOp|9Gj1|U3<v~94s%jV0a#Q}5b75f7tCD=2n0sXo9heT-rkr?e0q8s7$Bce zKp-%3nCqu%8aeHckB^iFb*t4nJv~hk0D(ZEsPpFf!g9IHTq2;8lM~%ZLJ<(Jg}Htz zQ%$GSyeZH`BJu6d5~nB%g)nEL^`Sr@AoK;{^73+fd;8D-0|T7Os9>(2%2cPOrev(u zI520TN$C9iTrH1R!Iq3<o-%0m_xBm4GNf+na5&!I-}%cmioy`K$`_+0lS!kIJU^96 zsdodV%jNp`_~0|-QCPvSr-mTLHOWWv%!7l2Ub}&Qa&q$Z^_5SOL9vU4RUaCH#m>%- zOeD_<g+l%51}-L(smFH`<hsjC^%gxIkMb)3LX}0du-R}p%>O798i_=N<|+g%OplI^ zs8p(ce0<C-c2)Z5)QpafUR_;L+1+(~P*dNhb_O$Jd{vR$Ut^t*ts`UAH)vsDq1kM9 z&qrqkH}c}*V&5W#Mr&(po$=k{_4Re3NuLS9V6eN7&&t-;R-eNp?KU<xum$UE3#Xf# zo09S$p{=g2wwD7K`2GGLi7{llyu4hgRPYJW=kpm7{*sku;2!|1B}CFd<az)A002ov JPDHLkV1jj1xJdv2 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/filled_location@3x.png b/Telegram/Resources/icons/chat/filled_location@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..11caf17cade61efbac78edf521e8528e02b041d8 GIT binary patch literal 1525 zcmV<R1q%9!P)<h;3K|Lk000e1NJLTq002k;002k`0ssI2+K(g<00001b5ch_0Itp) z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS=Ur9tkRA>e5TgxkUT@-iib-&B~ zQX&&2QZA(=W(+YUrKFI7F=b%lKOo77Tv8;G7@*v9A5cW@ce#c4`@Ns9=RNPa?6dZM z&VIjV@EB;HwSMcjers>XInUbradC0}IA_2)1I`(6&cN>(aQ!{BbmZgXQ&?D7P*4yP z6B88`6&V?c-^g%!dU|$thTkVAC)3l@Q&UsV&(D@-mO~aA8d_UhJ2p1<^78VN@B<jY z2tlA6MRv0W1O&9Sv^+dK5X#YkAQ0gov0I3B1m51>&CSi%*Vj6FIZYq|6$q_UC(4wV zn7FdC!l~Z`4nok1DzYLD{~oTcu1sVKxj_qZD>U+RG&VMVe0&JWCIx{UDfo5S7g1SR zNm6Gw2#M@d$`hTRpa1suX18V%0TLk{k2+CoQc{wn`^u<Lq(f3sy|g%YclY)6bt47x z>`02#G>t;2s;Vk^>e<XlEhL!`gy-h*@i9BSJTFpnJ-FQn75xWkXlTG2Egf$y5~=I! z>#gPd#pdVdcXxLuZ$T)`5C9p(+B}H3;^N}Kz(68c1v@%AnwXe4JUoOwAt3=&@$vC0 z4CN965IZ<HNC8tZ-QC@kNmXI?_V!9jO0+p(gi8gcTtbGHP2A$<=5~2`NlC24jE;`_ z`}-SZg$n>Bo-&3Eh%w^iw&&&LQL-vAi;Ih1US8}>005?pqKqMiP5bR7RbNz8MB%bw zzP`Tjj`#ZdYP0AL0Kn4;U|p|~ktl{#SZZpjQ5s$Q$jHd%=BBPoW!%`<09a*YUlPTT z3QJB-W+zj5hlYk!7RKf9@G!%xbrHpo3PX=cn}A{2+1X)uRj%#rZIy+6NfbjWjQ&{s z)`ZRsTb$C%1x6rI45=_*UtgsZ({V&Zgek@b-~uBG9ZOp*V?ZkG{{Egpp><_vXKRf{ z7A`PIcvsjc?=L%U1IiP|hFM)*WzqfV1K1Ww84&MD(u$p*pZ_Vw<SIQqy`rMR1oW>P zFn}pY8-`bmEl_9B+uKW-OC{#^_BJIYMTbja0st^&6lDxC3J${|X~pK}=M4!A96>=r zv$L}~IXMP?7q|cb%m`p)7Z77a<&u_`Mwv@3=JWHjv$HcZGgC>8OBmtO;wcu0QL-70 zN!v)SuC8bmg@l9{v6$Ksj!$KP;geWcSePl^2yk<AgK*Su1mL#g#fLJTvN81cFVa1R zY3S?g<DLu&0oKbvhsY6n9`r^Pb;YptX#H$)E-3^+24@ffj(^ayZy`2+Zf-6?rVs?5 z3)#8ld68O3GA*dAtW2JCHZxMwGzy{cK*MK#wgMSmB(<_RrCa;|vS{Vy<+@_5G-Bqq zwzgy}ixDT%Nnft-8A2b|NQ4Y~X=$mIE5qhOA4*0IT2A^q6k9OW8yg$@`1q*RVXfuq z=_xKQj;hHj9L_3OTZWdcsj0~-wH!Ry6y|MLXr(hOYinyB9@Jef4&4+e+7;-(GsFqI za0((LBZHzr76vEsgw-<w>+0%cDW>!G_xCd-3b+Oa2I$!(B5}xYe}7+~0Ns#-g9CvM zkF4_0pRwc+();^+R#uiQm14Zr)z!Kyz-6qdsS(RAH>$O@m8%RDxUH>Cu0DI%ySlnm za*3BcJw5j1l^%;n>iGCLu^1a{a&pqs(^I-QI}>1IZh3jxCfam>Hw?!*yv?W|dwIm2 zr`_G%=;&w$mBYEh!^2JAB+;MY-OhA~I)j6Q7Zw(b{uGy%mK^un5vKEpb44>VGuqnb z=H`TtoS7#5O91SJ<LH~RHhfoyxby#)Bnqeb@#9B(dpo{_ba8Rf(a}NgzYAw{20CZJ bac1BjE>b#z=)o^$00000NkvXXu0mjfNxH1Z literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 122260d22..b15436b2b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3195,6 +3195,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_maps_point" = "Location"; "lng_maps_point_send" = "Send This Location"; +"lng_maps_or_choose" = "Or choose a venue"; +"lng_maps_places_in_area" = "Places in this area"; +"lng_maps_no_places" = "No places found"; "lng_live_location" = "Live Location"; "lng_live_location_now" = "updated just now"; "lng_live_location_minutes#one" = "updated {count} minute ago"; diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js index 54eb60aba..cb936cf0b 100644 --- a/Telegram/Resources/picker_html/picker.js +++ b/Telegram/Resources/picker_html/picker.js @@ -31,10 +31,22 @@ var LocationPicker = { }); } }, + isNight: function() { + var html = document.getElementsByTagName('html')[0]; + return html.style.getPropertyValue('--td-night') == '1'; + }, + lightPreset: function() { + return LocationPicker.isNight() ? 'night' : 'day'; + }, updateStyles: function (styles) { if (LocationPicker.styles !== styles) { LocationPicker.styles = styles; document.getElementsByTagName('html')[0].style = styles; + + LocationPicker.map.setConfigProperty( + 'basemap', + 'lightPreset', + LocationPicker.lightPreset()); } }, init: function (params) { @@ -43,7 +55,9 @@ var LocationPicker = { mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com'; } - var options = { container: 'map' }; + var options = { container: 'map', config: { + basemap: { lightPreset: LocationPicker.lightPreset() } + } }; var center = params.center; if (center) { center = [center[1], center[0]]; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 8c7bcbb93..452a9f89b 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1426,4 +1426,53 @@ paidTagPadding: margins(16px, 6px, 16px, 6px); pickLocationWindow: size(364px, 680px); pickLocationMapHeight: 220px; -pickLocationCollapsedHeight: 108px; +pickLocationCollapsedHeight: 92px; +pickLocationRowHeight: 52px; +pickLocationVenue: PeerListItem(defaultPeerListItem) { + height: pickLocationRowHeight; + photoSize: 42px; + photoPosition: point(18px, 5px); + namePosition: point(70px, 9px); + statusPosition: point(70px, 29px); + button: OutlineButton(defaultPeerListButton) { + textBg: contactsBg; + textBgOver: contactsBgOver; + ripple: defaultRippleAnimation; + } + statusFg: contactsStatusFg; + statusFgOver: contactsStatusFgOver; + statusFgActive: contactsStatusFgOnline; +} +pickLocationButton: FlatButton { + height: pickLocationRowHeight; + bgColor: contactsBg; + overBgColor: contactsBgOver; + ripple: defaultRippleAnimation; +} +pickLocationButtonText: FlatLabel(defaultFlatLabel) { + minWidth: 128px; + style: semiboldTextStyle; + textFg: windowBoldFg; +} +pickLocationButtonStatus: FlatLabel(defaultFlatLabel) { + minWidth: 128px; + textFg: windowSubTextFg; +} +pickLocationButtonSkip: 6px; +pickLocationSendIcon: icon{{ "chat/filled_location", windowFgActive }}; +pickLocationVenueItem: PeerListItem(defaultPeerListItem) { + button: OutlineButton(defaultPeerListButton) { + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); + } + height: 52px; + photoPosition: point(18px, 5px); + namePosition: point(70px, 7px); + statusPosition: point(70px, 27px); + photoSize: 42px; +} +pickLocationVenueList: PeerList(defaultPeerList) { + item: pickLocationVenueItem; + padding: margins(0px, 0px, 0px, 0px); +} +pickLocationIconSkip: 6px; diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 2021e2943..f2ad99f46 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_media.h" #include "data/stickers/data_stickers.h" #include "menu/menu_send.h" // SendMenu::FillSendMenu +#include "mtproto/mtproto_config.h" #include "core/click_handler_types.h" #include "ui/controls/tabbed_search.h" #include "ui/widgets/buttons.h" @@ -48,7 +49,6 @@ namespace ChatHelpers { namespace { constexpr auto kSearchRequestDelay = 400; -constexpr auto kSearchBotUsername = "gif"_cs; constexpr auto kMinRepaintDelay = crl::time(33); constexpr auto kMinAfterScrollDelay = crl::time(33); @@ -864,13 +864,11 @@ void GifsListWidget::searchForGifs(const QString &query) { } if (!_searchBot && !_searchBotRequestId) { - auto username = kSearchBotUsername.utf16(); + const auto username = session().serverConfig().gifSearchUsername; _searchBotRequestId = _api.request(MTPcontacts_ResolveUsername( MTP_string(username) )).done([=](const MTPcontacts_ResolvedPeer &result) { - Expects(result.type() == mtpc_contacts_resolvedPeer); - - auto &data = result.c_contacts_resolvedPeer(); + auto &data = result.data(); session().data().processUsers(data.vusers()); session().data().processChats(data.vchats()); const auto peer = session().data().peerLoaded( diff --git a/Telegram/SourceFiles/core/current_geo_location.cpp b/Telegram/SourceFiles/core/current_geo_location.cpp index eb2125bf5..b649ea2ea 100644 --- a/Telegram/SourceFiles/core/current_geo_location.cpp +++ b/Telegram/SourceFiles/core/current_geo_location.cpp @@ -8,10 +8,122 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/current_geo_location.h" #include "base/platform/base_platform_info.h" +#include "base/invoke_queued.h" +#include "base/timer.h" #include "data/raw/raw_countries_bounds.h" #include "platform/platform_current_geo_location.h" +#include "ui/ui_utility.h" + +#include <QtNetwork/QNetworkAccessManager> +#include <QtNetwork/QNetworkReply> +#include <QtCore/QCoreApplication> +#include <QtCore/QPointer> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> namespace Core { +namespace { + +constexpr auto kDestroyManagerTimeout = 20 * crl::time(1000); + +void ResolveLocationAddressGeneric( + const GeoLocation &location, + const QString &token, + Fn<void(GeoAddress)> callback) { + const auto partialUrl = u"https://api.mapbox.com/search/geocode/v6" + "/reverse?longitude=%1&latitude=%2&access_token=%3"_q + .arg(location.point.y()) + .arg(location.point.x()); + static auto Cache = base::flat_map<QString, GeoAddress>(); + const auto i = Cache.find(partialUrl); + if (i != end(Cache)) { + callback(i->second); + return; + } + const auto finishWith = [=](GeoAddress result) { + Cache[partialUrl] = result; + callback(result); + }; + + struct State final : QObject { + explicit State(QObject *parent) + : QObject(parent) + , manager(this) + , destroyer([=] { if (sent.empty()) delete this; }) { + } + + QNetworkAccessManager manager; + std::vector<QPointer<QNetworkReply>> sent; + base::Timer destroyer; + }; + + static auto state = QPointer<State>(); + if (!state) { + state = Ui::CreateChild<State>(qApp); + } + const auto destroyReplyDelayed = [](QNetworkReply *reply) { + InvokeQueued(reply, [=] { + for (auto i = begin(state->sent); i != end(state->sent);) { + if (!*i || *i == reply) { + i = state->sent.erase(i); + } else { + ++i; + } + } + delete reply; + if (state->sent.empty()) { + state->destroyer.callOnce(kDestroyManagerTimeout); + } + }); + }; + + auto request = QNetworkRequest(partialUrl.arg(token)); + request.setRawHeader("Referer", "http://desktop-app-resource/"); + + const auto reply = state->manager.get(request); + QObject::connect(reply, &QNetworkReply::finished, [=] { + destroyReplyDelayed(reply); + + const auto json = QJsonDocument::fromJson(reply->readAll()); + if (!json.isObject()) { + finishWith({}); + return; + } + const auto features = json["features"].toArray(); + if (features.isEmpty()) { + finishWith({}); + return; + } + const auto feature = features.at(0).toObject(); + const auto properties = feature["properties"].toObject(); + const auto context = properties["context"].toObject(); + auto names = QStringList(); + auto add = [&](std::vector<QString> keys) { + for (const auto &key : keys) { + const auto value = context[key]; + if (value.isObject()) { + const auto name = value.toObject()["name"].toString(); + if (!name.isEmpty()) { + names.push_back(name); + break; + } + } + } + }; + add({ u"address"_q, u"street"_q, u"neighborhood"_q }); + add({ u"place"_q, u"region"_q }); + add({ u"country"_q }); + finishWith({ .name = names.join(", ") }); + }); + QObject::connect(reply, &QNetworkReply::errorOccurred, [=] { + destroyReplyDelayed(reply); + + finishWith({}); + }); +} + +} // namespace GeoLocation ResolveCurrentCountryLocation() { const auto iso2 = Platform::SystemCountry().toUpper(); @@ -47,4 +159,18 @@ void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback) { }); } +void ResolveLocationAddress( + const GeoLocation &location, + const QString &token, + Fn<void(GeoAddress)> callback) { + auto done = [=, done = std::move(callback)](GeoAddress result) mutable { + if (!result && !token.isEmpty()) { + ResolveLocationAddressGeneric(location, token, std::move(done)); + } else { + done(result); + } + }; + Platform::ResolveLocationAddress(location, std::move(done)); +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/current_geo_location.h b/Telegram/SourceFiles/core/current_geo_location.h index 2715699ee..e3b92222e 100644 --- a/Telegram/SourceFiles/core/current_geo_location.h +++ b/Telegram/SourceFiles/core/current_geo_location.h @@ -33,9 +33,29 @@ struct GeoLocation { explicit operator bool() const { return !failed(); } + + friend inline bool operator==( + const GeoLocation&, + const GeoLocation&) = default; +}; + +struct GeoAddress { + QString name; + + [[nodiscard]] bool empty() const { + return name.isEmpty(); + } + explicit operator bool() const { + return !empty(); + } }; [[nodiscard]] GeoLocation ResolveCurrentCountryLocation(); void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback); +void ResolveLocationAddress( + const GeoLocation &location, + const QString &token, + Fn<void(GeoAddress)> callback); + } // namespace Core diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index 6fb00d550..10f05adfb 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -53,6 +53,10 @@ struct InputVenue { QString provider; QString id; QString venueType; + + friend inline bool operator==( + const InputVenue &, + const InputVenue &) = default; }; [[nodiscard]] GeoPointLocation ComputeLocation(const LocationPoint &point); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 869476243..e43e6423a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3328,6 +3328,22 @@ void Session::documentApplyFields( } } +not_null<DocumentData*> Session::venueIconDocument(const QString &icon) { + const auto i = _venueIcons.find(icon); + if (i != end(_venueIcons)) { + return i->second; + } + const auto result = documentFromWeb(MTP_webDocumentNoProxy( + MTP_string(u"https://ss3.4sqi.net/img/categories_v2/"_q + + icon + + u"_64.png"_q), + MTP_int(0), + MTP_string("image/png"), + MTP_vector<MTPDocumentAttribute>()), {}, {}); + _venueIcons.emplace(icon, result); + return result; +} + not_null<WebPageData*> Session::webpage(WebPageId id) { auto i = _webpages.find(id); if (i == _webpages.cend()) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 12c4cba3a..06b4b267d 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -559,6 +559,8 @@ public: const MTPWebDocument &data, const ImageLocation &thumbnailLocation, const ImageLocation &videoThumbnailLocation); + [[nodiscard]] not_null<DocumentData*> venueIconDocument( + const QString &icon); [[nodiscard]] not_null<WebPageData*> webpage(WebPageId id); not_null<WebPageData*> processWebpage(const MTPWebPage &data); @@ -1002,6 +1004,7 @@ private: FullStoryId, base::flat_set<not_null<HistoryItem*>>> _storyItems; base::flat_map<uint64, not_null<HistoryItem*>> _highlightings; + base::flat_map<QString, not_null<DocumentData*>> _venueIcons; base::flat_set<not_null<WebPageData*>> _webpagesUpdated; base::flat_set<not_null<GameData*>> _gamesUpdated; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index c6b013956..b9233313c 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -162,6 +162,10 @@ constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); return u""_q; } +[[nodiscard]] QString ResolveGeocodingToken(not_null<Main::Session*> session) { + return u""_q; +} + void ShowChooseBox( not_null<Window::SessionController*> controller, PeerTypes types, @@ -1808,6 +1812,7 @@ void ChooseAndSendLocation( }; Ui::LocationPicker::Show({ .parent = controller->widget(), + .session = &controller->session(), .callback = crl::guard(controller, callback), .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); }, .storageId = controller->session().local().resolveStorageIdBots(), @@ -1872,7 +1877,9 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu( const auto session = &controller->session(); const auto locationType = ChatRestriction::SendOther; if (Data::CanSendAnyOf(peer, locationType) - && Ui::LocationPicker::Available(ResolveMapsToken(session))) { + && Ui::LocationPicker::Available( + ResolveMapsToken(session), + ResolveGeocodingToken(session))) { raw->addAction(tr::lng_maps_point(tr::now), [=] { ChooseAndSendLocation(controller, actionFactory()); }, &st::menuIconAddress); diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 3b30dc6db..4161fe876 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "ui/basic_click_handlers.h" #include "ui/painter.h" +#include "ui/webview_helpers.h" #include "webview/webview_data_stream_memory.h" #include "webview/webview_embed.h" #include "webview/webview_interface.h" @@ -39,8 +40,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <QtGui/QWindow> #include <charconv> -#include "base/call_delayed.h" - namespace Iv { namespace { @@ -79,67 +78,7 @@ namespace { static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{ { "iv-join-channel", tr::lng_iv_join_channel }, }; - static const auto serialize = [](const style::color *color) { - const auto qt = (*color)->c; - if (qt.alpha() == 255) { - return '#' - + QByteArray::number(qt.red(), 16).right(2) - + QByteArray::number(qt.green(), 16).right(2) - + QByteArray::number(qt.blue(), 16).right(2); - } - return "rgba(" - + QByteArray::number(qt.red()) + "," - + QByteArray::number(qt.green()) + "," - + QByteArray::number(qt.blue()) + "," - + QByteArray::number(qt.alpha() / 255.) + ")"; - }; - static const auto escape = [](tr::phrase<> phrase) { - const auto text = phrase(tr::now); - - auto result = QByteArray(); - for (auto i = 0; i != text.size(); ++i) { - uint ucs4 = text[i].unicode(); - if (QChar::isHighSurrogate(ucs4) && i + 1 != text.size()) { - ushort low = text[i + 1].unicode(); - if (QChar::isLowSurrogate(low)) { - ucs4 = QChar::surrogateToUcs4(ucs4, low); - ++i; - } - } - if (ucs4 == '\'' || ucs4 == '\"' || ucs4 == '\\') { - result.append('\\').append(char(ucs4)); - } else if (ucs4 < 32 || ucs4 > 127) { - result.append('\\' + QByteArray::number(ucs4, 16) + ' '); - } else { - result.append(char(ucs4)); - } - } - return result; - }; - auto result = QByteArray(); - for (const auto &[name, phrase] : phrases) { - result += "--td-lng-" + name + ":'" + escape(phrase) + "'; "; - } - for (const auto &[name, color] : map) { - result += "--td-" + name + ':' + serialize(color) + ';'; - } - return result; -} - -[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value) { - return value - .replace('&', "&") - .replace('"', """) - .replace('\'', "'") - .replace('<', "<") - .replace('>', ">"); -} - -[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value) { - return value - .replace('\\', "\\\\") - .replace('"', "\\\"") - .replace('\'', "\\\'"); + return Ui::ComputeStyles(map, phrases); } [[nodiscard]] QByteArray WrapPage(const Prepared &page) { @@ -159,7 +98,7 @@ namespace { <html)"_q + classAttribute + R"( style=")" - + EscapeForAttribute(ComputeStyles()) + + Ui::EscapeForAttribute(ComputeStyles()) + R"("> <head> <meta charset="utf-8"> @@ -194,7 +133,7 @@ Controller::Controller( Fn<ShareBoxResult(ShareBoxDescriptor)> showShareBox) : _delegate(delegate) , _updateStyles([=] { - const auto str = EscapeForScriptString(ComputeStyles()); + const auto str = Ui::EscapeForScriptString(ComputeStyles()); if (_webview) { _webview->eval("IV.updateStyles('" + str + "');"); } @@ -612,7 +551,7 @@ QByteArray Controller::navigateScript(int index, const QString &hash) { return "IV.navigateTo(" + QByteArray::number(index) + ", '" - + EscapeForScriptString(qthelp::url_decode(hash).toUtf8()) + + Ui::EscapeForScriptString(qthelp::url_decode(hash).toUtf8()) + "');"; } @@ -679,7 +618,7 @@ bool Controller::active() const { void Controller::showJoinedTooltip() { if (_webview && _ready) { _webview->eval("IV.showTooltip('" - + EscapeForScriptString( + + Ui::EscapeForScriptString( tr::lng_action_you_joined(tr::now).toUtf8()) + "');"); } diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index ea6111574..c14d49051 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -73,10 +73,14 @@ constexpr auto kTmpPasswordReserveTime = TimeId(10); if (domain.startsWith(prefix, Qt::CaseInsensitive)) { return domain.endsWith('/') ? domain - : MTP::ConfigFields().internalLinksDomain; + : MTP::ConfigFields( + session->mtp().environment() + ).internalLinksDomain; } } - return MTP::ConfigFields().internalLinksDomain; + return MTP::ConfigFields( + session->mtp().environment() + ).internalLinksDomain; } } // namespace diff --git a/Telegram/SourceFiles/mtproto/mtproto_config.cpp b/Telegram/SourceFiles/mtproto/mtproto_config.cpp index 454339f49..1da5325ef 100644 --- a/Telegram/SourceFiles/mtproto/mtproto_config.cpp +++ b/Telegram/SourceFiles/mtproto/mtproto_config.cpp @@ -16,18 +16,30 @@ namespace { constexpr auto kVersion = 1; -} // namespace - -QString ConfigDefaultReactionEmoji() { +[[nodiscard]] QString ConfigDefaultReactionEmoji() { static const auto result = QString::fromUtf8("\xf0\x9f\x91\x8d"); return result; } -Config::Config(Environment environment) : _dcOptions(environment) { - _fields.webFileDcId = _dcOptions.isTestMode() ? 2 : 4; - _fields.txtDomainString = _dcOptions.isTestMode() - ? u"tapv3.stel.com"_q - : u"apv3.stel.com"_q; +} // namespace + +ConfigFields::ConfigFields(Environment environment) +: webFileDcId(environment == Environment::Test ? 2 : 4) +, txtDomainString(environment == Environment::Test + ? u"tapv3.stel.com"_q + : u"apv3.stel.com"_q) +, reactionDefaultEmoji(ConfigDefaultReactionEmoji()) +, gifSearchUsername(environment == Environment::Test + ? u"izgifbot"_q + : u"gif"_q) +, venueSearchUsername(environment == Environment::Test + ? u"foursquarebot"_q + : u"foursquare"_q) { +} + +Config::Config(Environment environment) +: _dcOptions(environment) +, _fields(environment) { } Config::Config(const Config &other) @@ -46,7 +58,9 @@ QByteArray Config::serialize() const { + 3 * sizeof(qint32) + Serialize::stringSize(_fields.reactionDefaultEmoji) + sizeof(quint64) - + sizeof(qint32); + + sizeof(qint32) + + Serialize::stringSize(_fields.gifSearchUsername) + + Serialize::stringSize(_fields.venueSearchUsername); auto result = QByteArray(); result.reserve(size); @@ -91,7 +105,9 @@ QByteArray Config::serialize() const { << qint32(_fields.captionLengthMax) << _fields.reactionDefaultEmoji << quint64(_fields.reactionDefaultCustom) - << qint32(_fields.ratingDecay); + << qint32(_fields.ratingDecay) + << _fields.gifSearchUsername + << _fields.venueSearchUsername; } return result; } @@ -190,6 +206,10 @@ std::unique_ptr<Config> Config::FromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { read(raw->_fields.ratingDecay); } + if (!stream.atEnd()) { + read(raw->_fields.gifSearchUsername); + read(raw->_fields.venueSearchUsername); + } if (stream.status() != QDataStream::Ok || !raw->_dcOptions.constructFromSerialized(dcOptionsSerialized)) { @@ -256,8 +276,12 @@ void Config::apply(const MTPDconfig &data) { _fields.autologinToken = qs(data.vautologin_token().value_or_empty()); _fields.ratingDecay = data.vrating_e_decay().v; if (_fields.ratingDecay <= 0) { - _fields.ratingDecay = ConfigFields().ratingDecay; + _fields.ratingDecay = ConfigFields( + _dcOptions.environment() + ).ratingDecay; } + _fields.gifSearchUsername = qs(data.vgif_search_username().value_or_empty()); + _fields.venueSearchUsername = qs(data.vvenue_search_username().value_or_empty()); if (data.vdc_options().v.empty()) { LOG(("MTP Error: config with empty dc_options received!")); diff --git a/Telegram/SourceFiles/mtproto/mtproto_config.h b/Telegram/SourceFiles/mtproto/mtproto_config.h index 8a4db80df..289230b64 100644 --- a/Telegram/SourceFiles/mtproto/mtproto_config.h +++ b/Telegram/SourceFiles/mtproto/mtproto_config.h @@ -11,9 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace MTP { -[[nodiscard]] QString ConfigDefaultReactionEmoji(); - struct ConfigFields { + explicit ConfigFields(Environment environment); + int chatSizeMax = 200; int megagroupSizeMax = 10000; int forwardedCountMax = 100; @@ -40,9 +40,12 @@ struct ConfigFields { bool blockedMode = false; int captionLengthMax = 1024; int ratingDecay = 2419200; - QString reactionDefaultEmoji = ConfigDefaultReactionEmoji(); - uint64 reactionDefaultCustom; + QString reactionDefaultEmoji; + uint64 reactionDefaultCustom = 0; QString autologinToken; + + QString gifSearchUsername; + QString venueSearchUsername; }; class Config final { diff --git a/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp index 5ac0c49a7..6f42c0afd 100644 --- a/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp @@ -14,5 +14,10 @@ namespace Platform { void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { callback({}); } +void ResolveLocationAddress( + const Core::GeoLocation &location, + Fn<void(Core::GeoAddress)> callback) { + callback({}); +} } // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm index 2812a0c33..452bc6e3a 100644 --- a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm +++ b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm @@ -116,4 +116,10 @@ void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { [[LocationDelegate alloc] initWithCallback:std::move(callback)]; } +void ResolveLocationAddress( + const Core::GeoLocation &location, + Fn<void(Core::GeoAddress)> callback) { + callback({}); +} + } // namespace Platform diff --git a/Telegram/SourceFiles/platform/platform_current_geo_location.h b/Telegram/SourceFiles/platform/platform_current_geo_location.h index 245342fc3..9feb4b376 100644 --- a/Telegram/SourceFiles/platform/platform_current_geo_location.h +++ b/Telegram/SourceFiles/platform/platform_current_geo_location.h @@ -9,10 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Core { struct GeoLocation; +struct GeoAddress; } // namespace Core namespace Platform { void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback); +void ResolveLocationAddress( + const Core::GeoLocation &location, + Fn<void(Core::GeoAddress)> callback); } // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp b/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp index 37682c108..a83dedb88 100644 --- a/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp +++ b/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp @@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <winrt/Windows.Devices.Geolocation.h> #include <winrt/Windows.Foundation.h> +#include <winrt/Windows.Services.Maps.h> +#include <winrt/Windows.Foundation.Collections.h> + namespace Platform { void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { @@ -56,4 +59,10 @@ void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { } } +void ResolveLocationAddress( + const Core::GeoLocation &location, + Fn<void(Core::GeoAddress)> callback) { + callback({}); +} + } // namespace Platform diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 5b77f017a..a4658e7be 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -7,16 +7,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/controls/location_picker.h" +#include "apiwrap.h" #include "base/platform/base_platform_info.h" +#include "boxes/peer_list_box.h" #include "core/current_geo_location.h" +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_file_origin.h" +#include "data/data_location.h" +#include "data/data_session.h" +#include "data/data_user.h" #include "lang/lang_keys.h" +#include "main/session/session_show.h" +#include "main/main_session.h" +#include "mtproto/mtproto_config.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/separate_panel.h" #include "ui/widgets/buttons.h" #include "ui/wrap/vertical_layout.h" +#include "ui/painter.h" +#include "ui/vertical_list.h" +#include "ui/webview_helpers.h" #include "webview/webview_data_stream_memory.h" #include "webview/webview_embed.h" #include "webview/webview_interface.h" +#include "window/themes/window_theme.h" #include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" @@ -39,6 +54,246 @@ const auto kProtocolOverride = ""; Core::GeoLocation LastExactLocation; QString MapsProviderToken; +QString GeocodingProviderToken; + +using VenueData = Data::InputVenue; + +class VenueRowDelegate { +public: + virtual void rowPaintIcon( + QPainter &p, + int x, + int y, + int size, + const QString &type) = 0; +}; + +class VenueRow final : public PeerListRow { +public: + VenueRow(not_null<VenueRowDelegate*> delegate, const VenueData &data); + + void update(const VenueData &data); + + [[nodiscard]] VenueData data() const; + + QString generateName() override; + QString generateShortName() override; + PaintRoundImageCallback generatePaintUserpicCallback( + bool forceRound) override; + +private: + const not_null<VenueRowDelegate*> _delegate; + VenueData _data; + +}; + +VenueRow::VenueRow( + not_null<VenueRowDelegate*> delegate, + const VenueData &data) +: PeerListRow(UniqueRowIdFromString(data.id)) +, _delegate(delegate) +, _data(data) { + setCustomStatus(data.address); +} + +void VenueRow::update(const VenueData &data) { + _data = data; + setCustomStatus(data.address); +} + +VenueData VenueRow::data() const { + return _data; +} + +QString VenueRow::generateName() { + return _data.title; +} + +QString VenueRow::generateShortName() { + return generateName(); +} + +PaintRoundImageCallback VenueRow::generatePaintUserpicCallback( + bool forceRound) { + return [=]( + QPainter &p, + int x, + int y, + int outerWidth, + int size) { + _delegate->rowPaintIcon(p, x, y, size, _data.venueType); + }; +} + +class LinksController final + : public PeerListController + , public VenueRowDelegate + , public base::has_weak_ptr { +public: + LinksController( + not_null<Main::Session*> session, + rpl::producer<std::vector<VenueData>> content); + + void prepare() override; + void rowClicked(not_null<PeerListRow*> row) override; + void rowRightActionClicked(not_null<PeerListRow*> row) override; + Main::Session &session() const override; + + void rowPaintIcon( + QPainter &p, + int x, + int y, + int size, + const QString &type) override; + +private: + struct VenueIcon { + not_null<DocumentData*> document; + std::shared_ptr<Data::DocumentMedia> media; + uint32 paletteVersion : 31 = 0; + uint32 iconLoaded : 1 = 0; + QImage image; + QImage icon; + }; + + void appendRow(const VenueData &data); + + void rebuild(const std::vector<VenueData> &rows); + + const not_null<Main::Session*> _session; + rpl::variable<std::vector<VenueData>> _rows; + + base::flat_map<QString, VenueIcon> _icons; + + rpl::lifetime _lifetime; + +}; + +[[nodiscard]] QString NormalizeVenuesQuery(QString query) { + return query.trimmed().toLower(); +} + +LinksController::LinksController( + not_null<Main::Session*> session, + rpl::producer<std::vector<VenueData>> content) +: _session(session) +, _rows(std::move(content)) { +} + +void LinksController::prepare() { + _rows.value( + ) | rpl::start_with_next([=](const std::vector<VenueData> &rows) { + rebuild(rows); + }, _lifetime); +} + +void LinksController::rebuild(const std::vector<VenueData> &rows) { + auto i = 0; + auto count = delegate()->peerListFullRowsCount(); + while (i < rows.size()) { + if (i < count) { + const auto row = delegate()->peerListRowAt(i); + static_cast<VenueRow*>(row.get())->update(rows[i]); + } else { + appendRow(rows[i]); + } + ++i; + } + while (i < count) { + delegate()->peerListRemoveRow(delegate()->peerListRowAt(i)); + --count; + } + delegate()->peerListRefreshRows(); +} + +void LinksController::rowClicked(not_null<PeerListRow*> row) { + const auto venue = static_cast<VenueRow*>(row.get())->data(); + venue; +} + +void LinksController::rowRightActionClicked(not_null<PeerListRow*> row) { + delegate()->peerListShowRowMenu(row, true); +} + +Main::Session &LinksController::session() const { + return *_session; +} + +void LinksController::appendRow(const VenueData &data) { + delegate()->peerListAppendRow(std::make_unique<VenueRow>(this, data)); +} + +void LinksController::rowPaintIcon( + QPainter &p, + int x, + int y, + int size, + const QString &icon) { + auto i = _icons.find(icon); + if (i == end(_icons)) { + i = _icons.emplace(icon, VenueIcon{ + .document = _session->data().venueIconDocument(icon), + }).first; + i->second.media = i->second.document->createMediaView(); + i->second.document->forceToCache(true); + i->second.document->save({}, QString(), LoadFromCloudOrLocal, true); + } + auto &data = i->second; + const auto version = uint32(style::PaletteVersion()); + const auto loaded = (!data.media || data.media->loaded()) ? 1 : 0; + const auto prepare = data.image.isNull() + || (data.iconLoaded != loaded) + || (data.paletteVersion != version); + if (prepare) { + const auto skip = st::pickLocationIconSkip; + const auto inner = size - skip * 2; + const auto ratio = style::DevicePixelRatio(); + + if (loaded && data.media) { + const auto bytes = base::take(data.media)->bytes(); + data.icon = Images::Read({ .content = bytes }).image; + if (!data.icon.isNull()) { + data.icon = data.icon.scaled( + QSize(inner, inner) * ratio, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } + } + + const auto full = QSize(size, size) * ratio; + auto image = (data.image.size() == full) + ? base::take(data.image) + : QImage(full, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + image.setDevicePixelRatio(ratio); + + const auto bg = EmptyUserpic::UserpicColor( + EmptyUserpic::ColorIndex(UniqueRowIdFromString(icon))); + auto p = QPainter(&image); + auto hq = PainterHighQualityEnabler(p); + { + auto gradient = QLinearGradient(0, 0, 0, size); + gradient.setStops({ + { 0., bg.color1->c }, + { 1., bg.color2->c } + }); + p.setBrush(gradient); + } + p.setPen(Qt::NoPen); + p.drawEllipse(QRect(0, 0, size, size)); + if (!data.icon.isNull()) { + p.drawImage( + QRect(skip, skip, inner, inner), + style::colorizeImage(data.icon, st::historyPeerUserpicFg)); + } + p.end(); + + data.paletteVersion = version; + data.iconLoaded = loaded; + data.image = std::move(image); + } + p.drawImage(x, y, data.image); +} [[nodiscard]] QByteArray DefaultCenter() { if (!LastExactLocation) { @@ -68,23 +323,16 @@ QString MapsProviderToken; } [[nodiscard]] QByteArray ComputeStyles() { - return ""; -} - -[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value) { - return value - .replace('&', "&") - .replace('"', """) - .replace('\'', "'") - .replace('<', "<") - .replace('>', ">"); -} - -[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value) { - return value - .replace('\\', "\\\\") - .replace('"', "\\\"") - .replace('\'', "\\\'"); + static const auto map = base::flat_map<QByteArray, const style::color*>{ + { "window-bg", &st::windowBg }, + { "window-bg-over", &st::windowBgOver }, + { "window-bg-ripple", &st::windowBgRipple }, + { "window-active-text-fg", &st::windowActiveTextFg }, + }; + static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{ + { "maps-places-in-area", tr::lng_maps_places_in_area }, + }; + return Ui::ComputeStyles(map, phrases, Window::Theme::IsNightMode()); } [[nodiscard]] QByteArray ReadResource(const QString &name) { @@ -115,6 +363,129 @@ QString MapsProviderToken; )"_q; } +[[nodiscard]] object_ptr<AbstractButton> MakeSendLocationButton( + QWidget *parent, + rpl::producer<QString> address) { + auto result = object_ptr<FlatButton>( + parent, + QString(), + st::pickLocationButton); + const auto raw = result.data(); + + const auto st = &st::pickLocationVenue; + const auto icon = CreateChild<RpWidget>(raw); + icon->setGeometry( + st->photoPosition.x(), + st->photoPosition.y(), + st->photoSize, + st->photoSize); + icon->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(icon); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgActive); + p.drawEllipse(icon->rect()); + st::pickLocationSendIcon.paintInCenter(p, icon->rect()); + }, icon->lifetime()); + icon->show(); + + const auto hadAddress = std::make_shared<bool>(false); + auto statusText = std::move( + address + ) | rpl::map([=](const QString &text) { + if (!text.isEmpty()) { + *hadAddress = true; + return text; + } + return *hadAddress ? tr::lng_contacts_loading(tr::now) : QString(); + }); + const auto name = CreateChild<FlatLabel>( + raw, + tr::lng_maps_point_send(tr::now), + st::pickLocationButtonText); + name->show(); + const auto status = CreateChild<FlatLabel>( + raw, + rpl::duplicate(statusText), + st::pickLocationButtonStatus); + status->showOn(std::move( + statusText + ) | rpl::map([](const QString &text) { + return !text.isEmpty(); + }) | rpl::distinct_until_changed()); + rpl::combine( + result->widthValue(), + status->shownValue() + ) | rpl::start_with_next([=](int width, bool statusShown) { + const auto available = width + - st->namePosition.x() + - st->button.padding.right(); + const auto namePosition = st->namePosition; + const auto statusPosition = st->statusPosition; + name->resizeToWidth(available); + const auto nameTop = statusShown + ? namePosition.y() + : (st->height - name->height()) / 2; + name->moveToLeft(namePosition.x(), nameTop, width); + status->resizeToWidth(available); + status->moveToLeft(statusPosition.x(), statusPosition.y(), width); + }, name->lifetime()); + + return result; +} + +void SetupVenues( + not_null<VerticalLayout*> container, + std::shared_ptr<Main::SessionShow> show, + rpl::producer<std::vector<VenueData>> value) { + auto &lifetime = container->lifetime(); + const auto delegate = lifetime.make_state<PeerListContentDelegateShow>( + show); + const auto controller = lifetime.make_state<LinksController>( + &show->session(), + std::move(value)); + controller->setStyleOverrides(&st::pickLocationVenueList); + const auto content = container->add(object_ptr<PeerListContent>( + container, + controller)); + delegate->setContent(content); + controller->setDelegate(delegate); + + show->session().downloaderTaskFinished() | rpl::start_with_next([=] { + content->update(); + }, content->lifetime()); +} + +[[nodiscard]] PickerVenueList ParseVenues( + not_null<Main::Session*> session, + const MTPmessages_BotResults &venues) { + const auto &data = venues.data(); + session->data().processUsers(data.vusers()); + + auto &list = data.vresults().v; + auto result = PickerVenueList(); + result.list.reserve(list.size()); + for (const auto &found : list) { + found.match([&](const auto &data) { + data.vsend_message().match([&]( + const MTPDbotInlineMessageMediaVenue &data) { + data.vgeo().match([&](const MTPDgeoPoint &geo) { + result.list.push_back({ + .lat = geo.vlat().v, + .lon = geo.vlong().v, + .title = qs(data.vtitle()), + .address = qs(data.vaddress()), + .provider = qs(data.vprovider()), + .id = qs(data.vvenue_id()), + .venueType = qs(data.vvenue_type()), + }); + }, [](const auto &) {}); + }, [](const auto &) {}); + }); + } + return result; +} + } // namespace LocationPicker::LocationPicker(Descriptor &&descriptor) @@ -127,9 +498,12 @@ LocationPicker::LocationPicker(Descriptor &&descriptor) , _updateStyles([=] { const auto str = EscapeForScriptString(ComputeStyles()); if (_webview) { - _webview->eval("IV.updateStyles('" + str + "');"); + _webview->eval("LocationPicker.updateStyles('" + str + "');"); } -}) { +}) +, _venueState(PickerVenueLoading()) +, _session(descriptor.session) +, _api(&_session->mtp()) { std::move( descriptor.closeRequests ) | rpl::start_with_next([=] { @@ -140,15 +514,26 @@ LocationPicker::LocationPicker(Descriptor &&descriptor) setup(descriptor); } -bool LocationPicker::Available(const QString &token) { +std::shared_ptr<Main::SessionShow> LocationPicker::uiShow() { + return Main::MakeSessionShow(nullptr, _session); +} + +bool LocationPicker::Available( + const QString &mapsToken, + const QString &geocodingToken) { static const auto Supported = Webview::NavigateToDataSupported(); - MapsProviderToken = token; + MapsProviderToken = mapsToken; + GeocodingProviderToken = geocodingToken; return Supported && !MapsProviderToken.isEmpty(); } void LocationPicker::setup(const Descriptor &descriptor) { setupWindow(descriptor); setupWebview(descriptor); + if (LastExactLocation) { + venuesRequest(LastExactLocation); + resolveAddress(LastExactLocation); + } } void LocationPicker::setupWindow(const Descriptor &descriptor) { @@ -168,24 +553,32 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { parent.y() + (parent.height() - window->height()) / 2); _container = CreateChild<RpWidget>(_body.get()); - const auto scroll = CreateChild<ScrollArea>(_body.get()); - const auto controls = scroll->setOwnedWidget( - object_ptr<VerticalLayout>(scroll)); + _scroll = CreateChild<ScrollArea>(_body.get()); + const auto controls = _scroll->setOwnedWidget( + object_ptr<VerticalLayout>(_scroll)); const auto toppad = controls->add(object_ptr<RpWidget>(controls)); - const auto button = controls->add(object_ptr<FlatButton>( - controls, - tr::lng_maps_point_send(tr::now), - st::dialogsUpdateButton)); + const auto button = controls->add( + MakeSendLocationButton(controls, _geocoderAddress.value()), + { 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip }); button->setClickedCallback([=] { _webview->eval("LocationPicker.send();"); }); - controls->add(object_ptr<RpWidget>(controls))->resize( - st::pickLocationWindow); + + AddDivider(controls); + AddSkip(controls); + AddSubsectionTitle(controls, tr::lng_maps_or_choose()); + + SetupVenues(controls, uiShow(), _venueState.value( + ) | rpl::filter([=](const PickerVenueState &state) { + return v::is<PickerVenueList>(state); + }) | rpl::map([=](PickerVenueState &&state) { + return std::move(v::get<PickerVenueList>(state).list); + })); rpl::combine( _body->sizeValue(), - scroll->scrollTopValue() + _scroll->scrollTopValue() ) | rpl::start_with_next([=](QSize size, int scrollTop) { const auto width = size.width(); const auto height = size.height(); @@ -194,9 +587,9 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { scrollTop); const auto mapHeight = st::pickLocationMapHeight - sub; const auto scrollHeight = height - mapHeight; - button->resizeToWidth(width); _container->setGeometry(0, 0, width, mapHeight); - scroll->setGeometry(0, mapHeight, width, scrollHeight); + _scroll->setGeometry(0, mapHeight, width, scrollHeight); + controls->resizeToWidth(width); toppad->resize(width, sub); }, _container->lifetime()); @@ -205,7 +598,7 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { }, _container->lifetime()); _container->show(); - scroll->show(); + _scroll->hide(); controls->show(); button->show(); window->show(); @@ -256,7 +649,7 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { const auto object = message.object(); const auto event = object.value("event").toString(); if (event == u"ready"_q) { - initMap(); + mapReady(); resolveCurrentLocation(); } else if (event == u"keydown"_q) { const auto key = object.value("key").toString(); @@ -321,7 +714,31 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { raw->navigateToData("location/picker.html"); } -void LocationPicker::initMap() { +void LocationPicker::resolveAddress(Core::GeoLocation location) { + if (_geocoderResolvingFor == location) { + return; + } + _geocoderResolvingFor = location; + const auto done = [=](Core::GeoAddress address) { + if (_geocoderResolvingFor != location) { + return; + } else if (address) { + _geocoderAddress = address.name; + } else { + _geocoderAddress = u"(%1, %2)"_q + .arg(location.point.x(), 0, 'f') + .arg(location.point.y(), 0, 'f'); + } + }; + Core::ResolveLocationAddress( + location, + GeocodingProviderToken, + crl::guard(this, done)); +} + +void LocationPicker::mapReady() { + Expects(_scroll != nullptr); + const auto token = MapsProviderToken.toUtf8(); const auto center = DefaultCenter(); const auto bounds = DefaultBounds(); @@ -333,16 +750,103 @@ void LocationPicker::initMap() { + ", bounds: " + bounds + ", protocol: " + protocol; _webview->eval("LocationPicker.init({ " + params + " });"); + + _scroll->show(); +} + +void LocationPicker::venuesRequest( + Core::GeoLocation location, + QString query) { + query = NormalizeVenuesQuery(query); + auto &cache = _venuesCache[query]; + const auto i = ranges::find( + cache, + location, + &VenuesCacheEntry::location); + if (i != end(cache)) { + _venueState = i->result; + return; + } else if (_venuesRequestLocation == location + && _venuesRequestQuery == query) { + return; + } else if (const auto oldRequestId = base::take(_venuesRequestId)) { + _api.request(oldRequestId).cancel(); + } + _venueState = PickerVenueLoading(); + _venuesRequestLocation = location; + _venuesRequestQuery = query; + if (_venuesBot) { + venuesSendRequest(); + } else if (_venuesBotRequestId) { + return; + } + const auto username = _session->serverConfig().venueSearchUsername; + _venuesBotRequestId = _api.request(MTPcontacts_ResolveUsername( + MTP_string(username) + )).done([=](const MTPcontacts_ResolvedPeer &result) { + auto &data = result.data(); + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + const auto peer = _session->data().peerLoaded( + peerFromMTP(data.vpeer())); + const auto user = peer ? peer->asUser() : nullptr; + if (user && user->isBotInlineGeo()) { + _venuesBot = user; + venuesSendRequest(); + } else { + LOG(("API Error: Bad peer returned by: %1").arg(username)); + } + }).fail([=] { + LOG(("API Error: Error returned on lookup: %1").arg(username)); + }).send(); +} + +void LocationPicker::venuesSendRequest() { + Expects(_venuesBot != nullptr); + + if (_venuesRequestId || !_venuesRequestLocation) { + return; + } + _venuesRequestId = _api.request(MTPmessages_GetInlineBotResults( + MTP_flags(MTPmessages_GetInlineBotResults::Flag::f_geo_point), + _venuesBot->inputUser, + MTP_inputPeerEmpty(), + MTP_inputGeoPoint( + MTP_flags(0), + MTP_double(_venuesRequestLocation.point.x()), + MTP_double(_venuesRequestLocation.point.y()), + MTP_int(0)), // accuracy_radius + MTP_string(_venuesRequestQuery), + MTP_string() // offset + )).done([=](const MTPmessages_BotResults &result) { + auto parsed = ParseVenues(_session, result); + _venuesCache[_venuesRequestQuery].push_back({ + .location = _venuesRequestLocation, + .result = parsed, + }); + if (parsed.list.empty()) { + _venueState = PickerVenueNothingFound{ _venuesRequestQuery }; + } else { + _venueState = std::move(parsed); + } + }).fail([=] { + _venueState = PickerVenueNothingFound{ _venuesRequestQuery }; + }).send(); } void LocationPicker::resolveCurrentLocation() { using namespace Core; const auto window = _window.get(); ResolveCurrentGeoLocation(crl::guard(window, [=](GeoLocation location) { - if (location.accuracy != GeoLocationAccuracy::Exact) { + const auto changed = (LastExactLocation != location); + if (location.accuracy != GeoLocationAccuracy::Exact || !changed) { return; } LastExactLocation = location; + if (location) { + venuesRequest(location); + resolveAddress(location); + } if (_webview) { const auto point = QByteArray::number(location.point.x()) + ","_q diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index f18957b9d..9a4ca56d5 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -9,8 +9,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/invoke_queued.h" #include "base/weak_ptr.h" +#include "core/current_geo_location.h" +#include "mtproto/sender.h" #include "webview/webview_common.h" +namespace Data { +struct InputVenue; +} // namespace Data + +namespace Main { +class Session; +class SessionShow; +} // namespace Main + namespace Webview { class Window; } // namespace Webview @@ -19,23 +30,61 @@ namespace Ui { class SeparatePanel; class RpWidget; +class ScrollArea; struct LocationInfo { float64 lat = 0.; float64 lon = 0.; }; +struct PickerVenueLoading { + friend inline bool operator==( + PickerVenueLoading, + PickerVenueLoading) = default; +}; + +struct PickerVenueNothingFound { + QString query; + + friend inline bool operator==( + const PickerVenueNothingFound&, + const PickerVenueNothingFound&) = default; +}; + +struct PickerVenueWaitingForLocation { + friend inline bool operator==( + PickerVenueWaitingForLocation, + PickerVenueWaitingForLocation) = default; +}; + +struct PickerVenueList { + std::vector<Data::InputVenue> list; + + friend inline bool operator==( + const PickerVenueList&, + const PickerVenueList&) = default; +}; + +using PickerVenueState = std::variant< + PickerVenueLoading, + PickerVenueNothingFound, + PickerVenueWaitingForLocation, + PickerVenueList>; + class LocationPicker final : public base::has_weak_ptr { public: struct Descriptor { RpWidget *parent = nullptr; + not_null<Main::Session*> session; Fn<void(LocationInfo)> callback; Fn<void()> quit; Webview::StorageId storageId; rpl::producer<> closeRequests; }; - [[nodiscard]] static bool Available(const QString &token); + [[nodiscard]] static bool Available( + const QString &mapsToken, + const QString &geocodingToken); static not_null<LocationPicker*> Show(Descriptor &&descriptor); void close(); @@ -43,14 +92,25 @@ public: void quit(); private: + struct VenuesCacheEntry { + Core::GeoLocation location; + PickerVenueList result; + }; + explicit LocationPicker(Descriptor &&descriptor); + [[nodiscard]] std::shared_ptr<Main::SessionShow> uiShow(); + void setup(const Descriptor &descriptor); void setupWindow(const Descriptor &descriptor); void setupWebview(const Descriptor &descriptor); void processKey(const QString &key, const QString &modifier); void resolveCurrentLocation(); - void initMap(); + void resolveAddress(Core::GeoLocation location); + void mapReady(); + + void venuesRequest(Core::GeoLocation location, QString query = {}); + void venuesSendRequest(); rpl::lifetime _lifetime; @@ -59,10 +119,25 @@ private: std::unique_ptr<SeparatePanel> _window; not_null<RpWidget*> _body; RpWidget *_container = nullptr; + ScrollArea *_scroll = nullptr; std::unique_ptr<Webview::Window> _webview; SingleQueuedInvokation _updateStyles; bool _subscribedToColors = false; + Core::GeoLocation _geocoderResolvingFor; + rpl::variable<QString> _geocoderAddress; + + rpl::variable<PickerVenueState> _venueState; + + const not_null<Main::Session*> _session; + MTP::Sender _api; + UserData *_venuesBot = nullptr; + mtpRequestId _venuesBotRequestId = 0; + mtpRequestId _venuesRequestId = 0; + Core::GeoLocation _venuesRequestLocation; + QString _venuesRequestQuery; + base::flat_map<QString, std::vector<VenuesCacheEntry>> _venuesCache; + }; } // namespace Ui diff --git a/Telegram/SourceFiles/ui/webview_helpers.cpp b/Telegram/SourceFiles/ui/webview_helpers.cpp new file mode 100644 index 000000000..24b7155ce --- /dev/null +++ b/Telegram/SourceFiles/ui/webview_helpers.cpp @@ -0,0 +1,82 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/webview_helpers.h" + +#include "lang/lang_keys.h" + +namespace Ui { + +[[nodiscard]] QByteArray ComputeStyles( + const base::flat_map<QByteArray, const style::color*> &colors, + const base::flat_map<QByteArray, tr::phrase<>> &phrases, + bool nightTheme) { + static const auto serialize = [](const style::color *color) { + const auto qt = (*color)->c; + if (qt.alpha() == 255) { + return '#' + + QByteArray::number(qt.red(), 16).right(2) + + QByteArray::number(qt.green(), 16).right(2) + + QByteArray::number(qt.blue(), 16).right(2); + } + return "rgba(" + + QByteArray::number(qt.red()) + "," + + QByteArray::number(qt.green()) + "," + + QByteArray::number(qt.blue()) + "," + + QByteArray::number(qt.alpha() / 255.) + ")"; + }; + static const auto escape = [](tr::phrase<> phrase) { + const auto text = phrase(tr::now); + + auto result = QByteArray(); + for (auto i = 0; i != text.size(); ++i) { + uint ucs4 = text[i].unicode(); + if (QChar::isHighSurrogate(ucs4) && i + 1 != text.size()) { + ushort low = text[i + 1].unicode(); + if (QChar::isLowSurrogate(low)) { + ucs4 = QChar::surrogateToUcs4(ucs4, low); + ++i; + } + } + if (ucs4 == '\'' || ucs4 == '\"' || ucs4 == '\\') { + result.append('\\').append(char(ucs4)); + } else if (ucs4 < 32 || ucs4 > 127) { + result.append('\\' + QByteArray::number(ucs4, 16) + ' '); + } else { + result.append(char(ucs4)); + } + } + return result; + }; + auto result = QByteArray(); + for (const auto &[name, phrase] : phrases) { + result += "--td-lng-"_q + name + ":'"_q + escape(phrase) + "'; "_q; + } + for (const auto &[name, color] : colors) { + result += "--td-"_q + name + ':' + serialize(color) + ';'; + } + result += "--td-night:"_q + (nightTheme ? "1" : "0") + ';'; + return result; +} + +QByteArray EscapeForAttribute(QByteArray value) { + return value + .replace('&', "&") + .replace('"', """) + .replace('\'', "'") + .replace('<', "<") + .replace('>', ">"); +} + +QByteArray EscapeForScriptString(QByteArray value) { + return value + .replace('\\', "\\\\") + .replace('"', "\\\"") + .replace('\'', "\\\'"); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/webview_helpers.h b/Telegram/SourceFiles/ui/webview_helpers.h new file mode 100644 index 000000000..3d8a51066 --- /dev/null +++ b/Telegram/SourceFiles/ui/webview_helpers.h @@ -0,0 +1,27 @@ +/* +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/flat_map.h" + +namespace tr { +template <typename ...Tags> +struct phrase; +} // namespace tr + +namespace Ui { + +[[nodiscard]] QByteArray ComputeStyles( + const base::flat_map<QByteArray, const style::color*> &colors, + const base::flat_map<QByteArray, tr::phrase<>> &phrases, + bool nightTheme = false); + +[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value); +[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value); + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index b11016acb..9bc3f6c76 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -355,8 +355,6 @@ PRIVATE ui/controls/invite_link_buttons.h ui/controls/invite_link_label.cpp ui/controls/invite_link_label.h - ui/controls/location_picker.cpp - ui/controls/location_picker.h ui/controls/peer_list_dummy.cpp ui/controls/peer_list_dummy.h ui/controls/send_as_button.cpp @@ -403,6 +401,11 @@ PRIVATE ui/text/text_options.cpp ui/text/text_options.h + ui/widgets/fields/special_fields.cpp + ui/widgets/fields/special_fields.h + ui/widgets/fields/time_part_input_with_placeholder.cpp + ui/widgets/fields/time_part_input_with_placeholder.h + ui/widgets/color_editor.cpp ui/widgets/color_editor.h ui/widgets/continuous_sliders.cpp @@ -441,10 +444,8 @@ PRIVATE ui/unread_badge_paint.h ui/userpic_view.cpp ui/userpic_view.h - ui/widgets/fields/special_fields.cpp - ui/widgets/fields/special_fields.h - ui/widgets/fields/time_part_input_with_placeholder.cpp - ui/widgets/fields/time_part_input_with_placeholder.h + ui/webview_helpers.cpp + ui/webview_helpers.h window/window_slide_animation.cpp window/window_slide_animation.h From de52ac6b28b8aaed265b0dbdf406d338e8f7e1c2 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 11 Jul 2024 16:31:33 +0200 Subject: [PATCH 039/163] Resolve different addresses. --- Telegram/Resources/picker_html/picker.css | 12 +++- Telegram/Resources/picker_html/picker.js | 45 ++++++++++--- .../SourceFiles/core/current_geo_location.cpp | 50 ++++++++++++-- .../SourceFiles/core/current_geo_location.h | 1 + .../linux/current_geo_location_linux.cpp | 1 + .../platform/mac/current_geo_location_mac.mm | 1 + .../platform/platform_current_geo_location.h | 1 + .../platform/win/current_geo_location_win.cpp | 1 + .../ui/controls/location_picker.cpp | 66 ++++++++++++++++--- .../SourceFiles/ui/controls/location_picker.h | 4 ++ 10 files changed, 159 insertions(+), 23 deletions(-) diff --git a/Telegram/Resources/picker_html/picker.css b/Telegram/Resources/picker_html/picker.css index 0c791008d..4d8f378ae 100644 --- a/Telegram/Resources/picker_html/picker.css +++ b/Telegram/Resources/picker_html/picker.css @@ -50,7 +50,7 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover { } #marker { pointer-events: none; - display: flex; + display: none; z-index: 2; position: absolute; width: 100%; @@ -58,3 +58,13 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover { justify-content: center; align-items: center; } +#marker_drop { + margin-bottom: 0px; + transition: margin 160ms ease-in-out; +} +#marker_drop.moving { + margin-bottom: 24px; +} +#marker_shadow { + position: absolute; +} diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js index cb936cf0b..27ffd3b6c 100644 --- a/Telegram/Resources/picker_html/picker.js +++ b/Telegram/Resources/picker_html/picker.js @@ -70,16 +70,41 @@ var LocationPicker = { center = [0, 0]; } LocationPicker.map = new mapboxgl.Map(options); - - const marker = new mapboxgl.Marker() - .setLngLat(center) - .addTo(LocationPicker.map); - const drop = document.getElementById('marker_drop'); - const element = marker.getElement(); - drop.innerHTML = element.innerHTML; - const offset = marker.getOffset(); - drop.style.transform = 'translate(' + offset.x + 'px, ' + offset.y + 'px)'; - marker.remove(); + LocationPicker.createMarker(center); + LocationPicker.trackMovement(); + }, + marker: function() { + return document.getElementById('marker_drop'); + }, + createMarker: function(center) { + document.getElementById('marker').style.display = 'flex'; + }, + clearMovingTimer: function() { + if (LocationPicker.clearMovingTimeoutId) { + clearTimeout(LocationPicker.clearMovingTimeoutId); + LocationPicker.clearMovingTimeoutId = 0; + } + }, + startMovingTimer: function(done) { + LocationPicker.clearMovingTimer(); + LocationPicker.clearMovingTimeoutId = setTimeout(done, 500); + }, + trackMovement: function() { + LocationPicker.map.on('movestart', function() { + LocationPicker.marker().classList.add('moving'); + LocationPicker.clearMovingTimer(); + LocationPicker.notify({ event: 'movestart' }); + }); + LocationPicker.map.on('moveend', function() { + LocationPicker.startMovingTimer(function() { + LocationPicker.marker().classList.remove('moving'); + LocationPicker.notify({ + event: 'moveend', + latitude: LocationPicker.map.getCenter().lat, + longitude: LocationPicker.map.getCenter().lng + }); + }); + }); }, narrowTo: function (point) { LocationPicker.map.flyTo({ diff --git a/Telegram/SourceFiles/core/current_geo_location.cpp b/Telegram/SourceFiles/core/current_geo_location.cpp index b649ea2ea..818ea361a 100644 --- a/Telegram/SourceFiles/core/current_geo_location.cpp +++ b/Telegram/SourceFiles/core/current_geo_location.cpp @@ -27,14 +27,51 @@ namespace { constexpr auto kDestroyManagerTimeout = 20 * crl::time(1000); +[[nodiscard]] QString ChooseLanguage(const QString &language) { + // https://docs.mapbox.com/api/search/geocoding#language-coverage + auto result = language.toLower().replace('-', '_'); + static const auto kGood = std::array{ + // Global coverage. + u"de"_q, u"en"_q, u"es"_q, u"fr"_q, u"it"_q, u"nl"_q, u"pl"_q, + + // Local coverage. + u"az"_q, u"bn"_q, u"ca"_q, u"cs"_q, u"da"_q, u"el"_q, u"fa"_q, + u"fi"_q, u"ga"_q, u"hu"_q, u"id"_q, u"is"_q, u"ja"_q, u"ka"_q, + u"km"_q, u"ko"_q, u"lt"_q, u"lv"_q, u"mn"_q, u"pt"_q, u"ro"_q, + u"sk"_q, u"sq"_q, u"sv"_q, u"th"_q, u"tl"_q, u"uk"_q, u"vi"_q, + u"zh"_q, u"zh_Hans"_q, u"zh_TW"_q, + + // Limited coverage. + u"ar"_q, u"bs"_q, u"gu"_q, u"he"_q, u"hi"_q, u"kk"_q, u"lo"_q, + u"my"_q, u"nb"_q, u"ru"_q, u"sr"_q, u"te"_q, u"tk"_q, u"tr"_q, + u"zh_Hant"_q, + }; + for (const auto &known : kGood) { + if (known.toLower() == result) { + return known; + } + } + if (const auto delimeter = result.indexOf('_'); delimeter > 0) { + result = result.mid(0, delimeter); + for (const auto &known : kGood) { + if (known == result) { + return known; + } + } + } + return u"en"_q; +} + void ResolveLocationAddressGeneric( const GeoLocation &location, + const QString &language, const QString &token, Fn<void(GeoAddress)> callback) { const auto partialUrl = u"https://api.mapbox.com/search/geocode/v6" - "/reverse?longitude=%1&latitude=%2&access_token=%3"_q + "/reverse?longitude=%1&latitude=%2&language=%3&access_token=%4"_q .arg(location.point.y()) - .arg(location.point.x()); + .arg(location.point.x()) + .arg(ChooseLanguage(language)); static auto Cache = base::flat_map<QString, GeoAddress>(); const auto i = Cache.find(partialUrl); if (i != end(Cache)) { @@ -161,16 +198,21 @@ void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback) { void ResolveLocationAddress( const GeoLocation &location, + const QString &language, const QString &token, Fn<void(GeoAddress)> callback) { auto done = [=, done = std::move(callback)](GeoAddress result) mutable { if (!result && !token.isEmpty()) { - ResolveLocationAddressGeneric(location, token, std::move(done)); + ResolveLocationAddressGeneric( + location, + language, + token, + std::move(done)); } else { done(result); } }; - Platform::ResolveLocationAddress(location, std::move(done)); + Platform::ResolveLocationAddress(location, language, std::move(done)); } } // namespace Core diff --git a/Telegram/SourceFiles/core/current_geo_location.h b/Telegram/SourceFiles/core/current_geo_location.h index e3b92222e..dab4ffd00 100644 --- a/Telegram/SourceFiles/core/current_geo_location.h +++ b/Telegram/SourceFiles/core/current_geo_location.h @@ -55,6 +55,7 @@ void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback); void ResolveLocationAddress( const GeoLocation &location, + const QString &language, const QString &token, Fn<void(GeoAddress)> callback); diff --git a/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp index 6f42c0afd..f87c343d3 100644 --- a/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp @@ -16,6 +16,7 @@ void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { } void ResolveLocationAddress( const Core::GeoLocation &location, + const QString &language, Fn<void(Core::GeoAddress)> callback) { callback({}); } diff --git a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm index 452bc6e3a..1a5382f19 100644 --- a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm +++ b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm @@ -118,6 +118,7 @@ void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { void ResolveLocationAddress( const Core::GeoLocation &location, + const QString &language, Fn<void(Core::GeoAddress)> callback) { callback({}); } diff --git a/Telegram/SourceFiles/platform/platform_current_geo_location.h b/Telegram/SourceFiles/platform/platform_current_geo_location.h index 9feb4b376..269ee81f3 100644 --- a/Telegram/SourceFiles/platform/platform_current_geo_location.h +++ b/Telegram/SourceFiles/platform/platform_current_geo_location.h @@ -17,6 +17,7 @@ namespace Platform { void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback); void ResolveLocationAddress( const Core::GeoLocation &location, + const QString &language, Fn<void(Core::GeoAddress)> callback); } // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp b/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp index a83dedb88..9c7967588 100644 --- a/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp +++ b/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp @@ -61,6 +61,7 @@ void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) { void ResolveLocationAddress( const Core::GeoLocation &location, + const QString &language, Fn<void(Core::GeoAddress)> callback) { callback({}); } diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index a4658e7be..2eef9d698 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_location.h" #include "data/data_session.h" #include "data/data_user.h" +#include "lang/lang_instance.h" #include "lang/lang_keys.h" #include "main/session/session_show.h" #include "main/main_session.h" @@ -46,6 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace { +constexpr auto kResolveAddressDelay = 3 * crl::time(1000); + #ifdef Q_OS_MAC const auto kProtocolOverride = "mapboxapihelper"; #else // Q_OS_MAC @@ -355,7 +358,30 @@ void LinksController::rowPaintIcon( <link href='https://api.mapbox.com/mapbox-gl-js/v3.4.0/mapbox-gl.css' rel='stylesheet' /> </head> <body> - <div id="marker"><div id="marker_drop"></div></div> + <div id="marker"> + <div id="marker_shadow" style="transform: translate(0px, -14px);"> +<svg display="block" height="41px" width="27px" viewBox="0 0 27 41"> + <defs> + <radialGradient id="shadowGradient"> + <stop offset="10%" stop-opacity="0.4"></stop> + <stop offset="100%" stop-opacity="0.05"></stop> + </radialGradient> + </defs> + <ellipse + cx="13.5" + cy="34.8" + rx="10.5" + ry="5.25" + fill=")" + "url(#shadowGradient)" + R"("></ellipse> +</svg> + </div> + <div id="marker_drop" style="transform: translate(0px, -14px);"> +<svg display="block" height="41px" width="27px" viewBox="0 0 27 41"> + <path fill="#3FB1CE" d="M27,13.5C27,19.07 20.25,27 14.75,34.5C14.02,35.5 12.98,35.5 12.25,34.5C6.75,27 0,19.22 0,13.5C0,6.04 6.04,0 13.5,0C20.96,0 27,6.04 27,13.5Z"></path><path opacity="0.25" d="M13.5,0C6.04,0 0,6.04 0,13.5C0,19.22 6.75,27 12.25,34.5C13,35.52 14.02,35.5 14.75,34.5C20.25,27 27,19.07 27,13.5C27,6.04 20.96,0 13.5,0ZM13.5,1C20.42,1 26,6.58 26,13.5C26,15.9 24.5,19.18 22.22,22.74C19.95,26.3 16.71,30.14 13.94,33.91C13.74,34.18 13.61,34.32 13.5,34.44C13.39,34.32 13.26,34.18 13.06,33.91C10.28,30.13 7.41,26.31 5.02,22.77C2.62,19.23 1,15.95 1,13.5C1,6.58 6.58,1 13.5,1Z"></path> + <circle fill="white" cx="13.5" cy="13.5" r="5.5"></circle> +</svg> + </div> + </div> <div id="map"></div> <script>LocationPicker.notify({ event: 'ready' });</script> </body> @@ -408,26 +434,26 @@ void LinksController::rowPaintIcon( raw, rpl::duplicate(statusText), st::pickLocationButtonStatus); - status->showOn(std::move( + status->showOn(rpl::duplicate( statusText ) | rpl::map([](const QString &text) { return !text.isEmpty(); }) | rpl::distinct_until_changed()); rpl::combine( result->widthValue(), - status->shownValue() - ) | rpl::start_with_next([=](int width, bool statusShown) { + std::move(statusText) + ) | rpl::start_with_next([=](int width, const QString &statusText) { const auto available = width - st->namePosition.x() - st->button.padding.right(); const auto namePosition = st->namePosition; const auto statusPosition = st->statusPosition; name->resizeToWidth(available); - const auto nameTop = statusShown - ? namePosition.y() - : (st->height - name->height()) / 2; + const auto nameTop = statusText.isEmpty() + ? ((st->height - name->height()) / 2) + : namePosition.y(); name->moveToLeft(namePosition.x(), nameTop, width); - status->resizeToWidth(available); + status->resizeToNaturalWidth(available); status->moveToLeft(statusPosition.x(), statusPosition.y(), width); }, name->lifetime()); @@ -501,6 +527,7 @@ LocationPicker::LocationPicker(Descriptor &&descriptor) _webview->eval("LocationPicker.updateStyles('" + str + "');"); } }) +, _geocoderResolveTimer([=] { resolveAddressByTimer(); }) , _venueState(PickerVenueLoading()) , _session(descriptor.session) , _api(&_session->mtp()) { @@ -660,6 +687,17 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { const auto lon = object.value("longitude").toDouble(); _callback({ lat, lon }); close(); + } else if (event == u"movestart"_q) { + _geocoderAddress = QString(); + _geocoderResolveTimer.cancel(); + } else if (event == u"moveend"_q) { + const auto lat = object.value("latitude").toDouble(); + const auto lon = object.value("longitude").toDouble(); + _geocoderResolvePostponed = Core::GeoLocation{ + .point = { lat, lon }, + .accuracy = Core::GeoLocationAccuracy::Exact, + }; + _geocoderResolveTimer.callOnce(kResolveAddressDelay); } }); }); @@ -714,6 +752,12 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { raw->navigateToData("location/picker.html"); } +void LocationPicker::resolveAddressByTimer() { + if (const auto location = base::take(_geocoderResolvePostponed)) { + resolveAddress(location); + } +} + void LocationPicker::resolveAddress(Core::GeoLocation location) { if (_geocoderResolvingFor == location) { return; @@ -730,8 +774,14 @@ void LocationPicker::resolveAddress(Core::GeoLocation location) { .arg(location.point.y(), 0, 'f'); } }; + const auto baseLangId = Lang::GetInstance().baseId(); + const auto langId = baseLangId.isEmpty() + ? Lang::GetInstance().id() + : baseLangId; + const auto nonEmptyId = langId.isEmpty() ? u"en"_q : langId; Core::ResolveLocationAddress( location, + langId, GeocodingProviderToken, crl::guard(this, done)); } diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index 9a4ca56d5..e9c0d9801 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/invoke_queued.h" +#include "base/timer.h" #include "base/weak_ptr.h" #include "core/current_geo_location.h" #include "mtproto/sender.h" @@ -106,6 +107,7 @@ private: void setupWebview(const Descriptor &descriptor); void processKey(const QString &key, const QString &modifier); void resolveCurrentLocation(); + void resolveAddressByTimer(); void resolveAddress(Core::GeoLocation location); void mapReady(); @@ -124,6 +126,8 @@ private: SingleQueuedInvokation _updateStyles; bool _subscribedToColors = false; + base::Timer _geocoderResolveTimer; + Core::GeoLocation _geocoderResolvePostponed; Core::GeoLocation _geocoderResolvingFor; rpl::variable<QString> _geocoderAddress; From a130bb1be6d3c3fbae888fd64f062b794afcd56e Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 11 Jul 2024 18:15:17 +0200 Subject: [PATCH 040/163] Search for venues by location. --- Telegram/Resources/picker_html/picker.css | 58 ++++++++++++- Telegram/Resources/picker_html/picker.js | 82 ++++++++++++++++++- .../SourceFiles/core/current_geo_location.cpp | 27 +++++- .../SourceFiles/core/current_geo_location.h | 6 +- .../ui/controls/location_picker.cpp | 75 +++++++++++------ .../SourceFiles/ui/controls/location_picker.h | 1 + 6 files changed, 213 insertions(+), 36 deletions(-) diff --git a/Telegram/Resources/picker_html/picker.css b/Telegram/Resources/picker_html/picker.css index 4d8f378ae..ac3d5912b 100644 --- a/Telegram/Resources/picker_html/picker.css +++ b/Telegram/Resources/picker_html/picker.css @@ -1,7 +1,5 @@ :root { --font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif; - --font-serif: Iowan Old Style, Apple Garamond, Baskerville, Georgia, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; - --font-mono: Menlo, Cascadia Code, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; } html { @@ -13,8 +11,6 @@ html { body { font-family: var(--font-sans); - font-size: 17px; - line-height: 25px; width: 100%; height: 100%; padding: 0; @@ -68,3 +64,57 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover { #marker_shadow { position: absolute; } +#search_venues { + position: absolute; + left: 50%; + transform: translateX(-50%); + z-index: 2; + top: -30px; + transition: top 200ms ease-in-out; +} +#search_venues.shown { + top: 6px; +} +#search_venues_inner { + position: relative; + overflow: hidden; + font-size: 13px; + font-weight: 500; + background: var(--td-window-bg); + color: var(--td-window-active-text-fg); + cursor: pointer; + border-radius: 14px; + padding: 5px 12px 6px; + box-shadow: 0 0 3px 0px var(--td-history-to-down-shadow); +} +#search_venues_inner:hover { + background: var(--td-window-bg-over); +} +#search_venues_content { + position: relative; + z-index: 2; +} +#search_venues_content:before { + content: var(--td-lng-maps-places-in-area); +} +#search_venues_inner .ripple .inner { + position: absolute; + border-radius: 50%; + transform: scale(0); + opacity: 1; + animation: ripple 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards; + background-color: var(--td-window-bg-ripple); +} +#search_venues_inner .ripple.hiding { + animation: fadeOut 200ms linear forwards; +} +@keyframes ripple { + to { + transform: scale(2); + } +} +@keyframes fadeOut { + to { + opacity: 0; + } +} diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js index 27ffd3b6c..e44fd51a9 100644 --- a/Telegram/Resources/picker_html/picker.js +++ b/Telegram/Resources/picker_html/picker.js @@ -72,6 +72,7 @@ var LocationPicker = { LocationPicker.map = new mapboxgl.Map(options); LocationPicker.createMarker(center); LocationPicker.trackMovement(); + LocationPicker.initSearchVenueRipple(); }, marker: function() { return document.getElementById('marker_drop'); @@ -93,13 +94,14 @@ var LocationPicker = { LocationPicker.map.on('movestart', function() { LocationPicker.marker().classList.add('moving'); LocationPicker.clearMovingTimer(); - LocationPicker.notify({ event: 'movestart' }); + LocationPicker.toggleSearchVenues(false); + LocationPicker.notify({ event: 'move_start' }); }); LocationPicker.map.on('moveend', function() { LocationPicker.startMovingTimer(function() { LocationPicker.marker().classList.remove('moving'); LocationPicker.notify({ - event: 'moveend', + event: 'move_end', latitude: LocationPicker.map.getCenter().lat, longitude: LocationPicker.map.getCenter().lng }); @@ -119,5 +121,79 @@ var LocationPicker = { latitude: LocationPicker.map.getCenter().lat, longitude: LocationPicker.map.getCenter().lng }); - } + }, + addRipple: function (button, x, y) { + const ripple = document.createElement('span'); + ripple.classList.add('ripple'); + + const inner = document.createElement('span'); + inner.classList.add('inner'); + + var rect = button.getBoundingClientRect(); + x -= rect.x; + y -= rect.y; + + const mx = button.clientWidth - x; + const my = button.clientHeight - y; + const sq1 = x * x + y * y; + const sq2 = mx * mx + y * y; + const sq3 = x * x + my * my; + const sq4 = mx * mx + my * my; + const radius = Math.sqrt(Math.max(sq1, sq2, sq3, sq4)); + + inner.style.width = inner.style.height = `${2 * radius}px`; + inner.style.left = `${x - radius}px`; + inner.style.top = `${y - radius}px`; + inner.classList.add('inner'); + + ripple.addEventListener('animationend', function (e) { + if (e.animationName === 'fadeOut') { + ripple.remove(); + } + }); + + ripple.appendChild(inner); + button.appendChild(ripple); + }, + stopRipples: function (button) { + const id = button.id ? button.id : button; + button = document.getElementById(id); + const ripples = button.getElementsByClassName('ripple'); + for (var i = 0; i < ripples.length; ++i) { + const ripple = ripples[i]; + if (!ripple.classList.contains('hiding')) { + ripple.classList.add('hiding'); + } + } + }, + initSearchVenueRipple: function() { + var button = document.getElementById('search_venues_inner'); + button.addEventListener('mousedown', function (e) { + LocationPicker.addRipple(e.currentTarget, e.clientX, e.clientY); + LocationPicker.searchVenuesPressed = true; + }); + button.addEventListener('mouseup', function (e) { + const id = e.currentTarget.id; + setTimeout(function () { + LocationPicker.stopRipples(id); + }, 0); + if (LocationPicker.searchVenuesPressed) { + LocationPicker.searchVenuesPressed = false; + LocationPicker.toggleSearchVenues(false); + LocationPicker.notify({ + event: 'search_venues', + latitude: LocationPicker.map.getCenter().lat, + longitude: LocationPicker.map.getCenter().lng + }); + } + }); + button.addEventListener('mouseleave', function (e) { + LocationPicker.stopRipples(e.currentTarget); + LocationPicker.searchVenuesPressed = false; + }); + }, + toggleSearchVenues: function(shown) { + var button = document.getElementById('search_venues'); + button.classList.toggle('shown', shown); + }, }; diff --git a/Telegram/SourceFiles/core/current_geo_location.cpp b/Telegram/SourceFiles/core/current_geo_location.cpp index 818ea361a..2ec4a9cfe 100644 --- a/Telegram/SourceFiles/core/current_geo_location.cpp +++ b/Telegram/SourceFiles/core/current_geo_location.cpp @@ -148,7 +148,7 @@ void ResolveLocationAddressGeneric( } } }; - add({ u"address"_q, u"street"_q, u"neighborhood"_q }); + add({ /*u"address"_q, u"street"_q, */u"neighborhood"_q }); add({ u"place"_q, u"region"_q }); add({ u"country"_q }); finishWith({ .name = names.join(", ") }); @@ -215,4 +215,29 @@ void ResolveLocationAddress( Platform::ResolveLocationAddress(location, language, std::move(done)); } +bool AreTheSame(const GeoLocation &a, const GeoLocation &b) { + if (a.accuracy != GeoLocationAccuracy::Exact + || b.accuracy != GeoLocationAccuracy::Exact) { + return false; + } + const auto normalize = [](float64 value) { + value = std::fmod(value + 180., 360.); + return (value + (value < 0. ? 360. : 0.)) - 180.; + }; + constexpr auto kEpsilon = 0.0001; + const auto lon1 = normalize(a.point.y()); + const auto lon2 = normalize(b.point.y()); + const auto diffLat = std::abs(a.point.x() - b.point.x()); + if (std::abs(a.point.x()) >= (90. - kEpsilon) + || std::abs(b.point.x()) >= (90. - kEpsilon)) { + return diffLat <= kEpsilon; + } + auto diffLon = std::abs(lon1 - lon2); + if (diffLon > 180.) { + diffLon = 360. - diffLon; + } + + return diffLat <= kEpsilon && diffLon <= kEpsilon; +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/current_geo_location.h b/Telegram/SourceFiles/core/current_geo_location.h index dab4ffd00..3b495f115 100644 --- a/Telegram/SourceFiles/core/current_geo_location.h +++ b/Telegram/SourceFiles/core/current_geo_location.h @@ -33,12 +33,10 @@ struct GeoLocation { explicit operator bool() const { return !failed(); } - - friend inline bool operator==( - const GeoLocation&, - const GeoLocation&) = default; }; +[[nodiscard]] bool AreTheSame(const GeoLocation &a, const GeoLocation &b); + struct GeoAddress { QString name; diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 2eef9d698..b5920e131 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -102,6 +102,7 @@ VenueRow::VenueRow( void VenueRow::update(const VenueData &data) { _data = data; setCustomStatus(data.address); + refreshName(st::pickLocationVenueItem); } VenueData VenueRow::data() const { @@ -128,12 +129,12 @@ PaintRoundImageCallback VenueRow::generatePaintUserpicCallback( }; } -class LinksController final +class VenuesController final : public PeerListController , public VenueRowDelegate , public base::has_weak_ptr { public: - LinksController( + VenuesController( not_null<Main::Session*> session, rpl::producer<std::vector<VenueData>> content); @@ -176,21 +177,21 @@ private: return query.trimmed().toLower(); } -LinksController::LinksController( +VenuesController::VenuesController( not_null<Main::Session*> session, rpl::producer<std::vector<VenueData>> content) : _session(session) , _rows(std::move(content)) { } -void LinksController::prepare() { +void VenuesController::prepare() { _rows.value( ) | rpl::start_with_next([=](const std::vector<VenueData> &rows) { rebuild(rows); }, _lifetime); } -void LinksController::rebuild(const std::vector<VenueData> &rows) { +void VenuesController::rebuild(const std::vector<VenueData> &rows) { auto i = 0; auto count = delegate()->peerListFullRowsCount(); while (i < rows.size()) { @@ -209,24 +210,24 @@ void LinksController::rebuild(const std::vector<VenueData> &rows) { delegate()->peerListRefreshRows(); } -void LinksController::rowClicked(not_null<PeerListRow*> row) { +void VenuesController::rowClicked(not_null<PeerListRow*> row) { const auto venue = static_cast<VenueRow*>(row.get())->data(); venue; } -void LinksController::rowRightActionClicked(not_null<PeerListRow*> row) { +void VenuesController::rowRightActionClicked(not_null<PeerListRow*> row) { delegate()->peerListShowRowMenu(row, true); } -Main::Session &LinksController::session() const { +Main::Session &VenuesController::session() const { return *_session; } -void LinksController::appendRow(const VenueData &data) { +void VenuesController::appendRow(const VenueData &data) { delegate()->peerListAppendRow(std::make_unique<VenueRow>(this, data)); } -void LinksController::rowPaintIcon( +void VenuesController::rowPaintIcon( QPainter &p, int x, int y, @@ -331,6 +332,7 @@ void LinksController::rowPaintIcon( { "window-bg-over", &st::windowBgOver }, { "window-bg-ripple", &st::windowBgRipple }, { "window-active-text-fg", &st::windowActiveTextFg }, + { "history-to-down-shadow", &st::historyToDownShadow }, }; static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{ { "maps-places-in-area", tr::lng_maps_places_in_area }, @@ -358,6 +360,9 @@ void LinksController::rowPaintIcon( <link href='https://api.mapbox.com/mapbox-gl-js/v3.4.0/mapbox-gl.css' rel='stylesheet' /> </head> <body> + <div id="search_venues"> + <div id="search_venues_inner"><span id="search_venues_content"></span></div> + </div> <div id="marker"> <div id="marker_shadow" style="transform: translate(0px, -14px);"> <svg display="block" height="41px" width="27px" viewBox="0 0 27 41"> @@ -467,7 +472,7 @@ void SetupVenues( auto &lifetime = container->lifetime(); const auto delegate = lifetime.make_state<PeerListContentDelegateShow>( show); - const auto controller = lifetime.make_state<LinksController>( + const auto controller = lifetime.make_state<VenuesController>( &show->session(), std::move(value)); controller->setStyleOverrides(&st::pickLocationVenueList); @@ -687,17 +692,40 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { const auto lon = object.value("longitude").toDouble(); _callback({ lat, lon }); close(); - } else if (event == u"movestart"_q) { - _geocoderAddress = QString(); + } else if (event == u"move_start"_q) { + if (const auto now = _geocoderAddress.current() + ; !now.isEmpty()) { + _geocoderSavedAddress = now; + _geocoderAddress = QString(); + } + base::take(_geocoderResolvePostponed); _geocoderResolveTimer.cancel(); - } else if (event == u"moveend"_q) { + } else if (event == u"move_end"_q) { const auto lat = object.value("latitude").toDouble(); const auto lon = object.value("longitude").toDouble(); - _geocoderResolvePostponed = Core::GeoLocation{ + const auto location = Core::GeoLocation{ .point = { lat, lon }, .accuracy = Core::GeoLocationAccuracy::Exact, }; - _geocoderResolveTimer.callOnce(kResolveAddressDelay); + if (AreTheSame(_geocoderResolvingFor, location) + && !_geocoderSavedAddress.isEmpty()) { + _geocoderAddress = base::take(_geocoderSavedAddress); + _geocoderResolveTimer.cancel(); + } else { + _geocoderResolvePostponed = location; + _geocoderResolveTimer.callOnce(kResolveAddressDelay); + } + if (!AreTheSame(_venuesRequestLocation, location)) { + _webview->eval( + "LocationPicker.toggleSearchVenues(true);"); + } + } else if (event == u"search_venues"_q) { + const auto lat = object.value("latitude").toDouble(); + const auto lon = object.value("longitude").toDouble(); + venuesRequest({ + .point = { lat, lon }, + .accuracy = Core::GeoLocationAccuracy::Exact, + }); } }); }); @@ -759,12 +787,12 @@ void LocationPicker::resolveAddressByTimer() { } void LocationPicker::resolveAddress(Core::GeoLocation location) { - if (_geocoderResolvingFor == location) { + if (AreTheSame(_geocoderResolvingFor, location)) { return; } _geocoderResolvingFor = location; const auto done = [=](Core::GeoAddress address) { - if (_geocoderResolvingFor != location) { + if (!AreTheSame(_geocoderResolvingFor, location)) { return; } else if (address) { _geocoderAddress = address.name; @@ -809,14 +837,13 @@ void LocationPicker::venuesRequest( QString query) { query = NormalizeVenuesQuery(query); auto &cache = _venuesCache[query]; - const auto i = ranges::find( - cache, - location, - &VenuesCacheEntry::location); + const auto i = ranges::find_if(cache, [&](const VenuesCacheEntry &v) { + return AreTheSame(v.location, location); + }); if (i != end(cache)) { _venueState = i->result; return; - } else if (_venuesRequestLocation == location + } else if (AreTheSame(_venuesRequestLocation, location) && _venuesRequestQuery == query) { return; } else if (const auto oldRequestId = base::take(_venuesRequestId)) { @@ -888,7 +915,7 @@ void LocationPicker::resolveCurrentLocation() { using namespace Core; const auto window = _window.get(); ResolveCurrentGeoLocation(crl::guard(window, [=](GeoLocation location) { - const auto changed = (LastExactLocation != location); + const auto changed = !AreTheSame(LastExactLocation, location); if (location.accuracy != GeoLocationAccuracy::Exact || !changed) { return; } diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index e9c0d9801..0adfc166f 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -129,6 +129,7 @@ private: base::Timer _geocoderResolveTimer; Core::GeoLocation _geocoderResolvePostponed; Core::GeoLocation _geocoderResolvingFor; + QString _geocoderSavedAddress; rpl::variable<QString> _geocoderAddress; rpl::variable<PickerVenueState> _venueState; From 8ce10d55038e2d025e5008c4d84e2cb3210b1acc Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 12 Jul 2024 12:23:47 +0200 Subject: [PATCH 041/163] Send chosen venues. --- Telegram/SourceFiles/data/data_location.h | 4 ++ .../inline_bots/bot_attach_web_view.cpp | 33 ++++++++++------ Telegram/SourceFiles/main/main_app_config.cpp | 26 +++++-------- Telegram/SourceFiles/main/main_app_config.h | 11 +++--- .../ui/controls/location_picker.cpp | 39 ++++++++++--------- .../SourceFiles/ui/controls/location_picker.h | 24 ++++++------ 6 files changed, 73 insertions(+), 64 deletions(-) diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index 10f05adfb..5157f79a1 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -54,6 +54,10 @@ struct InputVenue { QString id; QString venueType; + [[nodiscard]] bool justLocation() const { + return id.isEmpty() && title.isEmpty() && address.isEmpty(); + } + friend inline bool operator==( const InputVenue &, const InputVenue &) = default; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index b9233313c..d52e13b82 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -158,12 +158,16 @@ constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); return result; } -[[nodiscard]] QString ResolveMapsToken(not_null<Main::Session*> session) { - return u""_q; -} - -[[nodiscard]] QString ResolveGeocodingToken(not_null<Main::Session*> session) { - return u""_q; +[[nodiscard]] Ui::LocationPickerConfig ResolveMapsConfig( + not_null<Main::Session*> session) { + const auto &appConfig = session->appConfig(); + auto map = appConfig.get<base::flat_map<QString, QString>>( + u"tdesktop_config_map"_q, + base::flat_map<QString, QString>()); + return { + .mapsToken = map[u"maps"_q], + .geoToken = map[u"geo"_q], + }; } void ShowChooseBox( @@ -1806,12 +1810,18 @@ void AttachWebView::toggleInMenu( void ChooseAndSendLocation( not_null<Window::SessionController*> controller, + const Ui::LocationPickerConfig &config, Api::SendAction action) { - const auto callback = [=](Ui::LocationInfo info) { - Api::SendLocation(action, info.lat, info.lon); + const auto callback = [=](Data::InputVenue venue) { + if (venue.justLocation()) { + Api::SendLocation(action, venue.lat, venue.lon); + } else { + Api::SendVenue(action, venue); + } }; Ui::LocationPicker::Show({ .parent = controller->widget(), + .config = config, .session = &controller->session(), .callback = crl::guard(controller, callback), .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); }, @@ -1876,12 +1886,11 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu( } const auto session = &controller->session(); const auto locationType = ChatRestriction::SendOther; + const auto config = ResolveMapsConfig(session); if (Data::CanSendAnyOf(peer, locationType) - && Ui::LocationPicker::Available( - ResolveMapsToken(session), - ResolveGeocodingToken(session))) { + && Ui::LocationPicker::Available(config)) { raw->addAction(tr::lng_maps_point(tr::now), [=] { - ChooseAndSendLocation(controller, actionFactory()); + ChooseAndSendLocation(controller, config, actionFactory()); }, &st::menuIconAddress); } for (const auto &bot : bots->attachBots()) { diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 2507dc8f1..6a4c63c35 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -144,28 +144,22 @@ std::vector<QString> AppConfig::getStringArray( }); } -std::vector<std::map<QString, QString>> AppConfig::getStringMapArray( +base::flat_map<QString, QString> AppConfig::getStringMap( const QString &key, - std::vector<std::map<QString, QString>> &&fallback) const { + base::flat_map<QString, QString> &&fallback) const { return getValue(key, [&](const MTPJSONValue &value) { - return value.match([&](const MTPDjsonArray &data) { - auto result = std::vector<std::map<QString, QString>>(); + return value.match([&](const MTPDjsonObject &data) { + auto result = base::flat_map<QString, QString>(); result.reserve(data.vvalue().v.size()); for (const auto &entry : data.vvalue().v) { - if (entry.type() != mtpc_jsonObject) { + const auto &data = entry.data(); + const auto &value = data.vvalue(); + if (value.type() != mtpc_jsonString) { return std::move(fallback); } - auto element = std::map<QString, QString>(); - for (const auto &field : entry.c_jsonObject().vvalue().v) { - const auto &data = field.c_jsonObjectValue(); - if (data.vvalue().type() != mtpc_jsonString) { - return std::move(fallback); - } - element.emplace( - qs(data.vkey()), - qs(data.vvalue().c_jsonString().vvalue())); - } - result.push_back(std::move(element)); + result.emplace( + qs(data.vkey()), + qs(value.c_jsonString().vvalue())); } return result; }, [&](const auto &data) { diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 67b985348..f09d5f120 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -35,12 +35,11 @@ public: return getString(key, fallback); } else if constexpr (std::is_same_v<Type, std::vector<QString>>) { return getStringArray(key, std::move(fallback)); + } else if constexpr ( + std::is_same_v<Type, base::flat_map<QString, QString>>) { + return getStringMap(key, std::move(fallback)); } else if constexpr (std::is_same_v<Type, std::vector<int>>) { return getIntArray(key, std::move(fallback)); - } else if constexpr (std::is_same_v< - Type, - std::vector<std::map<QString, QString>>>) { - return getStringMapArray(key, std::move(fallback)); } else if constexpr (std::is_same_v<Type, bool>) { return getBool(key, fallback); } @@ -78,9 +77,9 @@ private: [[nodiscard]] std::vector<QString> getStringArray( const QString &key, std::vector<QString> &&fallback) const; - [[nodiscard]] std::vector<std::map<QString, QString>> getStringMapArray( + [[nodiscard]] base::flat_map<QString, QString> getStringMap( const QString &key, - std::vector<std::map<QString, QString>> &&fallback) const; + base::flat_map<QString, QString> &&fallback) const; [[nodiscard]] std::vector<int> getIntArray( const QString &key, std::vector<int> &&fallback) const; diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index b5920e131..9d926f980 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -56,8 +56,6 @@ const auto kProtocolOverride = ""; #endif // Q_OS_MAC Core::GeoLocation LastExactLocation; -QString MapsProviderToken; -QString GeocodingProviderToken; using VenueData = Data::InputVenue; @@ -136,7 +134,8 @@ class VenuesController final public: VenuesController( not_null<Main::Session*> session, - rpl::producer<std::vector<VenueData>> content); + rpl::producer<std::vector<VenueData>> content, + Fn<void(VenueData)> callback); void prepare() override; void rowClicked(not_null<PeerListRow*> row) override; @@ -165,6 +164,7 @@ private: void rebuild(const std::vector<VenueData> &rows); const not_null<Main::Session*> _session; + const Fn<void(VenueData)> _callback; rpl::variable<std::vector<VenueData>> _rows; base::flat_map<QString, VenueIcon> _icons; @@ -179,8 +179,10 @@ private: VenuesController::VenuesController( not_null<Main::Session*> session, - rpl::producer<std::vector<VenueData>> content) + rpl::producer<std::vector<VenueData>> content, + Fn<void(VenueData)> callback) : _session(session) +, _callback(std::move(callback)) , _rows(std::move(content)) { } @@ -211,8 +213,7 @@ void VenuesController::rebuild(const std::vector<VenueData> &rows) { } void VenuesController::rowClicked(not_null<PeerListRow*> row) { - const auto venue = static_cast<VenueRow*>(row.get())->data(); - venue; + _callback(static_cast<VenueRow*>(row.get())->data()); } void VenuesController::rowRightActionClicked(not_null<PeerListRow*> row) { @@ -468,13 +469,15 @@ void VenuesController::rowPaintIcon( void SetupVenues( not_null<VerticalLayout*> container, std::shared_ptr<Main::SessionShow> show, - rpl::producer<std::vector<VenueData>> value) { + rpl::producer<std::vector<VenueData>> value, + Fn<void(VenueData)> callback) { auto &lifetime = container->lifetime(); const auto delegate = lifetime.make_state<PeerListContentDelegateShow>( show); const auto controller = lifetime.make_state<VenuesController>( &show->session(), - std::move(value)); + std::move(value), + std::move(callback)); controller->setStyleOverrides(&st::pickLocationVenueList); const auto content = container->add(object_ptr<PeerListContent>( container, @@ -520,7 +523,8 @@ void SetupVenues( } // namespace LocationPicker::LocationPicker(Descriptor &&descriptor) -: _callback(std::move(descriptor.callback)) +: _config(std::move(descriptor.config)) +, _callback(std::move(descriptor.callback)) , _quit(std::move(descriptor.quit)) , _window(std::make_unique<SeparatePanel>()) , _body((_window->setInnerSize(st::pickLocationWindow) @@ -550,13 +554,9 @@ std::shared_ptr<Main::SessionShow> LocationPicker::uiShow() { return Main::MakeSessionShow(nullptr, _session); } -bool LocationPicker::Available( - const QString &mapsToken, - const QString &geocodingToken) { +bool LocationPicker::Available(const LocationPickerConfig &config) { static const auto Supported = Webview::NavigateToDataSupported(); - MapsProviderToken = mapsToken; - GeocodingProviderToken = geocodingToken; - return Supported && !MapsProviderToken.isEmpty(); + return Supported && !config.mapsToken.isEmpty(); } void LocationPicker::setup(const Descriptor &descriptor) { @@ -606,7 +606,10 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { return v::is<PickerVenueList>(state); }) | rpl::map([=](PickerVenueState &&state) { return std::move(v::get<PickerVenueList>(state).list); - })); + }), [=](VenueData info) { + _callback(std::move(info)); + close(); + }); rpl::combine( _body->sizeValue(), @@ -810,14 +813,14 @@ void LocationPicker::resolveAddress(Core::GeoLocation location) { Core::ResolveLocationAddress( location, langId, - GeocodingProviderToken, + _config.geoToken, crl::guard(this, done)); } void LocationPicker::mapReady() { Expects(_scroll != nullptr); - const auto token = MapsProviderToken.toUtf8(); + const auto token = _config.mapsToken.toUtf8(); const auto center = DefaultCenter(); const auto bounds = DefaultBounds(); const auto protocol = *kProtocolOverride diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index 0adfc166f..b7996ee50 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -33,11 +33,6 @@ class SeparatePanel; class RpWidget; class ScrollArea; -struct LocationInfo { - float64 lat = 0.; - float64 lon = 0.; -}; - struct PickerVenueLoading { friend inline bool operator==( PickerVenueLoading, @@ -72,20 +67,24 @@ using PickerVenueState = std::variant< PickerVenueWaitingForLocation, PickerVenueList>; +struct LocationPickerConfig { + QString mapsToken; + QString geoToken; +}; + class LocationPicker final : public base::has_weak_ptr { public: struct Descriptor { RpWidget *parent = nullptr; + LocationPickerConfig config; not_null<Main::Session*> session; - Fn<void(LocationInfo)> callback; + Fn<void(Data::InputVenue)> callback; Fn<void()> quit; Webview::StorageId storageId; rpl::producer<> closeRequests; }; - [[nodiscard]] static bool Available( - const QString &mapsToken, - const QString &geocodingToken); + [[nodiscard]] static bool Available(const LocationPickerConfig &config); static not_null<LocationPicker*> Show(Descriptor &&descriptor); void close(); @@ -114,9 +113,8 @@ private: void venuesRequest(Core::GeoLocation location, QString query = {}); void venuesSendRequest(); - rpl::lifetime _lifetime; - - Fn<void(LocationInfo)> _callback; + LocationPickerConfig _config; + Fn<void(Data::InputVenue)> _callback; Fn<void()> _quit; std::unique_ptr<SeparatePanel> _window; not_null<RpWidget*> _body; @@ -143,6 +141,8 @@ private: QString _venuesRequestQuery; base::flat_map<QString, std::vector<VenuesCacheEntry>> _venuesCache; + rpl::lifetime _lifetime; + }; } // namespace Ui From 917d1841c1ce1a6ca8afb78c14e891f4034d16a7 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 12 Jul 2024 15:07:08 +0200 Subject: [PATCH 042/163] Try better webview focusing. --- Telegram/SourceFiles/iv/iv_controller.cpp | 15 ++++++------- .../ui/chat/attach/attach_bot_webview.cpp | 21 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 4161fe876..e72298f6c 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/invoke_queued.h" -#include "base/qt_signal_producer.h" #include "base/qthelp_url.h" #include "iv/iv_data.h" #include "lang/lang_keys.h" @@ -281,12 +280,14 @@ void Controller::createWindow() { _window = std::make_unique<Ui::RpWindow>(); const auto window = _window.get(); - base::qt_signal_producer( - window->window()->windowHandle(), - &QWindow::activeChanged - ) | rpl::filter([=] { - return _webview && window->window()->windowHandle()->isActive(); - }) | rpl::start_with_next([=] { + window->windowActiveValue( + ) | rpl::map([=] { + const auto handle = window->window()->windowHandle(); + return (_shareFocus || _webview) && handle && handle->isActive(); + }) | rpl::distinct_until_changed( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::start_with_next([=] { setInnerFocus(); }, window->lifetime()); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index d367e3382..cbc27c0b8 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -739,15 +739,18 @@ postEvent: function(eventType, eventData) { setupProgressGeometry(); - base::qt_signal_producer( - _widget->window()->windowHandle(), - &QWindow::activeChanged - ) | rpl::filter([=] { - return _webview && _widget->window()->windowHandle()->isActive(); - }) | rpl::start_with_next([=] { - if (_webview && !_webview->window.widget()->isHidden()) { - _webview->window.focus(); - } + _widget->windowActiveValue( + ) | rpl::map([=] { + const auto handle = _widget->window()->windowHandle(); + return _webview + && !_webview->window.widget()->isHidden() + && handle + && handle->isActive(); + }) | rpl::distinct_until_changed( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::start_with_next([=] { + _webview->window.focus(); }, _webview->lifetime); return true; From b4dfc25df5609adf0b140eaccbda347c2d8e2569 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 12 Jul 2024 15:07:21 +0200 Subject: [PATCH 043/163] Implement venues search. --- .../ui/controls/location_picker.cpp | 140 ++++++++++++++---- .../SourceFiles/ui/controls/location_picker.h | 14 ++ Telegram/lib_ui | 2 +- 3 files changed, 127 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 9d926f980..d363bbe6d 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/widgets/separate_panel.h" #include "ui/widgets/buttons.h" +#include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/painter.h" #include "ui/vertical_list.h" @@ -48,6 +49,7 @@ namespace Ui { namespace { constexpr auto kResolveAddressDelay = 3 * crl::time(1000); +constexpr auto kSearchDebounceDelay = crl::time(900); #ifdef Q_OS_MAC const auto kProtocolOverride = "mapboxapihelper"; @@ -539,6 +541,12 @@ LocationPicker::LocationPicker(Descriptor &&descriptor) , _geocoderResolveTimer([=] { resolveAddressByTimer(); }) , _venueState(PickerVenueLoading()) , _session(descriptor.session) +, _venuesSearchDebounceTimer([=] { + Expects(_venuesSearchLocation.has_value()); + Expects(_venuesSearchQuery.has_value()); + + venuesRequest(*_venuesSearchLocation, *_venuesSearchQuery); +}) , _api(&_session->mtp()) { std::move( descriptor.closeRequests @@ -565,6 +573,7 @@ void LocationPicker::setup(const Descriptor &descriptor) { if (LastExactLocation) { venuesRequest(LastExactLocation); resolveAddress(LastExactLocation); + venuesSearchEnableAt(LastExactLocation); } } @@ -588,18 +597,27 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { _scroll = CreateChild<ScrollArea>(_body.get()); const auto controls = _scroll->setOwnedWidget( object_ptr<VerticalLayout>(_scroll)); - const auto toppad = controls->add(object_ptr<RpWidget>(controls)); - const auto button = controls->add( - MakeSendLocationButton(controls, _geocoderAddress.value()), + _mapControlsWrap = controls->add( + object_ptr<SlideWrap<VerticalLayout>>( + controls, + object_ptr<VerticalLayout>(controls)) + )->setDuration(0); + _mapControlsWrap->show(anim::type::instant); + const auto mapControls = _mapControlsWrap->entity(); + + const auto toppad = mapControls->add(object_ptr<RpWidget>(controls)); + + const auto button = mapControls->add( + MakeSendLocationButton(mapControls, _geocoderAddress.value()), { 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip }); button->setClickedCallback([=] { _webview->eval("LocationPicker.send();"); }); - AddDivider(controls); - AddSkip(controls); - AddSubsectionTitle(controls, tr::lng_maps_or_choose()); + AddDivider(mapControls); + AddSkip(mapControls); + AddSubsectionTitle(mapControls, tr::lng_maps_or_choose()); SetupVenues(controls, uiShow(), _venueState.value( ) | rpl::filter([=](const PickerVenueState &state) { @@ -613,17 +631,19 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { rpl::combine( _body->sizeValue(), - _scroll->scrollTopValue() - ) | rpl::start_with_next([=](QSize size, int scrollTop) { + _scroll->scrollTopValue(), + _venuesSearchShown.value() + ) | rpl::start_with_next([=](QSize size, int scrollTop, bool search) { const auto width = size.width(); const auto height = size.height(); const auto sub = std::min( (st::pickLocationMapHeight - st::pickLocationCollapsedHeight), scrollTop); const auto mapHeight = st::pickLocationMapHeight - sub; - const auto scrollHeight = height - mapHeight; _container->setGeometry(0, 0, width, mapHeight); - _scroll->setGeometry(0, mapHeight, width, scrollHeight); + const auto scrollWidgetTop = search ? 0 : mapHeight; + const auto scrollHeight = height - scrollWidgetTop; + _scroll->setGeometry(0, scrollWidgetTop, width, scrollHeight); controls->resizeToWidth(width); toppad->resize(width, sub); }, _container->lifetime()); @@ -662,7 +682,7 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { close(); } else if (e->type() == QEvent::KeyPress) { const auto event = static_cast<QKeyEvent*>(e.get()); - if (event->key() == Qt::Key_Escape) { + if (event->key() == Qt::Key_Escape && !_venuesSearchQuery) { close(); } } @@ -722,6 +742,7 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { _webview->eval( "LocationPicker.toggleSearchVenues(true);"); } + venuesSearchEnableAt(location); } else if (event == u"search_venues"_q) { const auto lat = object.value("latitude").toDouble(); const auto lon = object.value("longitude").toDouble(); @@ -835,26 +856,38 @@ void LocationPicker::mapReady() { _scroll->show(); } -void LocationPicker::venuesRequest( +bool LocationPicker::venuesFromCache( Core::GeoLocation location, QString query) { - query = NormalizeVenuesQuery(query); - auto &cache = _venuesCache[query]; + const auto normalized = NormalizeVenuesQuery(query); + auto &cache = _venuesCache[normalized]; const auto i = ranges::find_if(cache, [&](const VenuesCacheEntry &v) { return AreTheSame(v.location, location); }); - if (i != end(cache)) { - _venueState = i->result; - return; - } else if (AreTheSame(_venuesRequestLocation, location) - && _venuesRequestQuery == query) { + if (i == end(cache)) { + return false; + } + _venuesRequestLocation = location; + _venuesRequestQuery = normalized; + _venuesInitialQuery = query; + venuesApplyResults(i->result); + return true; +} + +void LocationPicker::venuesRequest( + Core::GeoLocation location, + QString query) { + const auto normalized = NormalizeVenuesQuery(query); + if (AreTheSame(_venuesRequestLocation, location) + && _venuesRequestQuery == normalized) { return; } else if (const auto oldRequestId = base::take(_venuesRequestId)) { _api.request(oldRequestId).cancel(); } _venueState = PickerVenueLoading(); _venuesRequestLocation = location; - _venuesRequestQuery = query; + _venuesRequestQuery = normalized; + _venuesInitialQuery = query; if (_venuesBot) { venuesSendRequest(); } else if (_venuesBotRequestId) { @@ -904,16 +937,61 @@ void LocationPicker::venuesSendRequest() { .location = _venuesRequestLocation, .result = parsed, }); - if (parsed.list.empty()) { - _venueState = PickerVenueNothingFound{ _venuesRequestQuery }; - } else { - _venueState = std::move(parsed); - } + venuesApplyResults(std::move(parsed)); }).fail([=] { - _venueState = PickerVenueNothingFound{ _venuesRequestQuery }; + venuesApplyResults({}); }).send(); } +void LocationPicker::venuesApplyResults(PickerVenueList venues) { + _venuesRequestId = 0; + if (venues.list.empty()) { + _venueState = PickerVenueNothingFound{ _venuesInitialQuery }; + } else { + _venueState = std::move(venues); + } +} + +void LocationPicker::venuesSearchEnableAt(Core::GeoLocation location) { + if (!_venuesSearchLocation) { + _window->setSearchAllowed( + tr::lng_dlg_filter(), + [=](std::optional<QString> query) { + venuesSearchChanged(query); + }); + } + _venuesSearchLocation = location; +} + +void LocationPicker::venuesSearchChanged( + const std::optional<QString> &query) { + _venuesSearchQuery = query; + + const auto shown = query && !query->trimmed().isEmpty(); + _venuesSearchShown = shown; + if (_container->isHidden() != shown) { + _container->setVisible(!shown); + _mapControlsWrap->toggle(!shown, anim::type::instant); + if (shown) { + _venuesNoSearchLocation = _venuesRequestLocation; + _venueState = PickerVenueLoading(); + } else if (_venuesNoSearchLocation) { + if (!venuesFromCache(_venuesNoSearchLocation)) { + venuesRequest(_venuesNoSearchLocation); + } + } + } + + if (shown + && !venuesFromCache( + *_venuesSearchLocation, + *_venuesSearchQuery)) { + _venuesSearchDebounceTimer.callOnce(kSearchDebounceDelay); + } else { + _venuesSearchDebounceTimer.cancel(); + } +} + void LocationPicker::resolveCurrentLocation() { using namespace Core; const auto window = _window.get(); @@ -924,7 +1002,9 @@ void LocationPicker::resolveCurrentLocation() { } LastExactLocation = location; if (location) { - venuesRequest(location); + if (_venuesSearchQuery.value_or(QString()).isEmpty()) { + venuesRequest(location); + } resolveAddress(location); } if (_webview) { @@ -940,7 +1020,11 @@ void LocationPicker::processKey( const QString &key, const QString &modifier) { const auto ctrl = ::Platform::IsMac() ? u"cmd"_q : u"ctrl"_q; - if (key == u"escape"_q || (key == u"w"_q && modifier == ctrl)) { + if (key == u"escape"_q) { + if (!_window->closeSearch()) { + close(); + } + } else if (key == u"w"_q && modifier == ctrl) { close(); } else if (key == u"m"_q && modifier == ctrl) { minimize(); diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index b7996ee50..6014bd06b 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -32,6 +32,9 @@ namespace Ui { class SeparatePanel; class RpWidget; class ScrollArea; +class VerticalLayout; +template <typename Widget> +class SlideWrap; struct PickerVenueLoading { friend inline bool operator==( @@ -110,8 +113,12 @@ private: void resolveAddress(Core::GeoLocation location); void mapReady(); + bool venuesFromCache(Core::GeoLocation location, QString query = {}); void venuesRequest(Core::GeoLocation location, QString query = {}); void venuesSendRequest(); + void venuesApplyResults(PickerVenueList venues); + void venuesSearchEnableAt(Core::GeoLocation location); + void venuesSearchChanged(const std::optional<QString> &query); LocationPickerConfig _config; Fn<void(Data::InputVenue)> _callback; @@ -119,6 +126,7 @@ private: std::unique_ptr<SeparatePanel> _window; not_null<RpWidget*> _body; RpWidget *_container = nullptr; + SlideWrap<VerticalLayout> *_mapControlsWrap = nullptr; ScrollArea *_scroll = nullptr; std::unique_ptr<Webview::Window> _webview; SingleQueuedInvokation _updateStyles; @@ -133,13 +141,19 @@ private: rpl::variable<PickerVenueState> _venueState; const not_null<Main::Session*> _session; + std::optional<Core::GeoLocation> _venuesSearchLocation; + std::optional<QString> _venuesSearchQuery; + base::Timer _venuesSearchDebounceTimer; MTP::Sender _api; UserData *_venuesBot = nullptr; mtpRequestId _venuesBotRequestId = 0; mtpRequestId _venuesRequestId = 0; Core::GeoLocation _venuesRequestLocation; QString _venuesRequestQuery; + QString _venuesInitialQuery; base::flat_map<QString, std::vector<VenuesCacheEntry>> _venuesCache; + Core::GeoLocation _venuesNoSearchLocation; + rpl::variable<bool> _venuesSearchShown = false; rpl::lifetime _lifetime; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 9b69f3855..a95caea1a 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 9b69f3855ac9feb760aef92ce98e874129303d4d +Subproject commit a95caea1adac69ca6d95b55fe920eac33cdf9580 From 8aac07b3c0f21d317af684f6852924bdf7fe8811 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 12 Jul 2024 16:34:14 +0200 Subject: [PATCH 044/163] Show empty venue search states. --- Telegram/Resources/langs/lang.strings | 1 + .../chat_helpers/chat_helpers.style | 5 + .../ui/controls/location_picker.cpp | 104 ++++++++++++++++-- Telegram/lib_ui | 2 +- 4 files changed, 100 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b15436b2b..08368d748 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3198,6 +3198,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_maps_or_choose" = "Or choose a venue"; "lng_maps_places_in_area" = "Places in this area"; "lng_maps_no_places" = "No places found"; +"lng_maps_choose_to_search" = "Choose location to see places nearby."; "lng_live_location" = "Live Location"; "lng_live_location_now" = "updated just now"; "lng_live_location_minutes#one" = "updated {count} minute ago"; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 452a9f89b..d31d2919a 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1476,3 +1476,8 @@ pickLocationVenueList: PeerList(defaultPeerList) { padding: margins(0px, 0px, 0px, 0px); } pickLocationIconSkip: 6px; +pickLocationLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + size: size(56px, 56px); + color: windowSubTextFg; + thickness: 4px; +} diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index d363bbe6d..ed9d84f7a 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -17,11 +17,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_location.h" #include "data/data_session.h" #include "data/data_user.h" +#include "dialogs/ui/chat_search_empty.h" // Dialogs::SearchEmpty. #include "lang/lang_instance.h" #include "lang/lang_keys.h" #include "main/session/session_show.h" #include "main/main_session.h" #include "mtproto/mtproto_config.h" +#include "ui/effects/radial_animation.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/separate_panel.h" #include "ui/widgets/buttons.h" @@ -468,17 +471,95 @@ void VenuesController::rowPaintIcon( return result; } +void SetupLoadingView(not_null<RpWidget*> container) { + class Loading final : public RpWidget { + public: + explicit Loading(QWidget *parent) + : RpWidget(parent) + , animation( + [=] { if (!anim::Disabled()) update(); }, + st::pickLocationLoading) { + animation.start(st::pickLocationLoading.sineDuration); + } + + private: + void paintEvent(QPaintEvent *e) override { + auto p = QPainter(this); + const auto size = st::pickLocationLoading.size; + const auto inner = QRect(QPoint(), size); + const auto positioned = style::centerrect(rect(), inner); + animation.draw(p, positioned.topLeft(), size, width()); + } + + InfiniteRadialAnimation animation; + + }; + + const auto view = CreateChild<Loading>(container); + view->resize(container->width(), st::recentPeersEmptyHeightMin); + view->show(); + + ResizeFitChild(container, view); +} + +void SetupEmptyView( + not_null<RpWidget*> container, + std::optional<QString> query) { + using Icon = Dialogs::SearchEmptyIcon; + const auto view = CreateChild<Dialogs::SearchEmpty>( + container, + (query ? Icon::NoResults : Icon::Search), + (query + ? tr::lng_maps_no_places + : tr::lng_maps_choose_to_search)(Ui::Text::WithEntities)); + view->setMinimalHeight(st::recentPeersEmptyHeightMin); + view->show(); + + ResizeFitChild(container, view); + + InvokeQueued(view, [=] { view->animate(); }); +} + void SetupVenues( not_null<VerticalLayout*> container, std::shared_ptr<Main::SessionShow> show, - rpl::producer<std::vector<VenueData>> value, + rpl::producer<PickerVenueState> value, Fn<void(VenueData)> callback) { + const auto otherWrap = container->add(object_ptr<SlideWrap<RpWidget>>( + container, + object_ptr<RpWidget>(container))); + const auto other = otherWrap->entity(); + rpl::duplicate( + value + ) | rpl::start_with_next([=](const PickerVenueState &state) { + while (!other->children().isEmpty()) { + delete other->children()[0]; + } + if (v::is<PickerVenueList>(state)) { + otherWrap->hide(anim::type::instant); + return; + } else if (v::is<PickerVenueLoading>(state)) { + SetupLoadingView(other); + } else { + const auto n = std::get_if<PickerVenueNothingFound>(&state); + SetupEmptyView(other, n ? n->query : std::optional<QString>()); + } + otherWrap->show(anim::type::instant); + }, otherWrap->lifetime()); + auto &lifetime = container->lifetime(); + auto venuesList = rpl::duplicate( + value + ) | rpl::map([=](PickerVenueState &&state) { + return v::is<PickerVenueList>(state) + ? std::move(v::get<PickerVenueList>(state).list) + : std::vector<VenueData>(); + }); const auto delegate = lifetime.make_state<PeerListContentDelegateShow>( show); const auto controller = lifetime.make_state<VenuesController>( &show->session(), - std::move(value), + std::move(venuesList), std::move(callback)); controller->setStyleOverrides(&st::pickLocationVenueList); const auto content = container->add(object_ptr<PeerListContent>( @@ -601,8 +682,7 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { _mapControlsWrap = controls->add( object_ptr<SlideWrap<VerticalLayout>>( controls, - object_ptr<VerticalLayout>(controls)) - )->setDuration(0); + object_ptr<VerticalLayout>(controls))); _mapControlsWrap->show(anim::type::instant); const auto mapControls = _mapControlsWrap->entity(); @@ -619,12 +699,8 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { AddSkip(mapControls); AddSubsectionTitle(mapControls, tr::lng_maps_or_choose()); - SetupVenues(controls, uiShow(), _venueState.value( - ) | rpl::filter([=](const PickerVenueState &state) { - return v::is<PickerVenueList>(state); - }) | rpl::map([=](PickerVenueState &&state) { - return std::move(v::get<PickerVenueList>(state).list); - }), [=](VenueData info) { + auto state = _venueState.value(); + SetupVenues(controls, uiShow(), std::move(state), [=](VenueData info) { _callback(std::move(info)); close(); }); @@ -706,6 +782,9 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { if (event == u"ready"_q) { mapReady(); resolveCurrentLocation(); + if (_webview) { + _webview->focus(); + } } else if (event == u"keydown"_q) { const auto key = object.value("key").toString(); const auto modifier = object.value("modifier").toString(); @@ -974,7 +1053,6 @@ void LocationPicker::venuesSearchChanged( _mapControlsWrap->toggle(!shown, anim::type::instant); if (shown) { _venuesNoSearchLocation = _venuesRequestLocation; - _venueState = PickerVenueLoading(); } else if (_venuesNoSearchLocation) { if (!venuesFromCache(_venuesNoSearchLocation)) { venuesRequest(_venuesNoSearchLocation); @@ -986,6 +1064,7 @@ void LocationPicker::venuesSearchChanged( && !venuesFromCache( *_venuesSearchLocation, *_venuesSearchQuery)) { + _venueState = PickerVenueLoading(); _venuesSearchDebounceTimer.callOnce(kSearchDebounceDelay); } else { _venuesSearchDebounceTimer.cancel(); @@ -998,6 +1077,9 @@ void LocationPicker::resolveCurrentLocation() { ResolveCurrentGeoLocation(crl::guard(window, [=](GeoLocation location) { const auto changed = !AreTheSame(LastExactLocation, location); if (location.accuracy != GeoLocationAccuracy::Exact || !changed) { + if (!_venuesSearchLocation) { + _venueState = PickerVenueWaitingForLocation(); + } return; } LastExactLocation = location; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index a95caea1a..f046725ec 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit a95caea1adac69ca6d95b55fe920eac33cdf9580 +Subproject commit f046725ecde41bf5779d69393cc592786ee0440c From b83b403b7581d7c691f367cf7cc9e2180616be5c Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 12 Jul 2024 17:00:16 +0200 Subject: [PATCH 045/163] Pass correct peer to venue search. --- .../chat_helpers/chat_helpers.style | 35 ++++++++----------- .../inline_bots/bot_attach_web_view.cpp | 1 + .../ui/controls/location_picker.cpp | 7 ++-- .../SourceFiles/ui/controls/location_picker.h | 2 ++ 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index d31d2919a..a40d1e0ad 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1428,21 +1428,6 @@ pickLocationWindow: size(364px, 680px); pickLocationMapHeight: 220px; pickLocationCollapsedHeight: 92px; pickLocationRowHeight: 52px; -pickLocationVenue: PeerListItem(defaultPeerListItem) { - height: pickLocationRowHeight; - photoSize: 42px; - photoPosition: point(18px, 5px); - namePosition: point(70px, 9px); - statusPosition: point(70px, 29px); - button: OutlineButton(defaultPeerListButton) { - textBg: contactsBg; - textBgOver: contactsBgOver; - ripple: defaultRippleAnimation; - } - statusFg: contactsStatusFg; - statusFgOver: contactsStatusFgOver; - statusFgActive: contactsStatusFgOnline; -} pickLocationButton: FlatButton { height: pickLocationRowHeight; bgColor: contactsBg; @@ -1451,25 +1436,33 @@ pickLocationButton: FlatButton { } pickLocationButtonText: FlatLabel(defaultFlatLabel) { minWidth: 128px; + maxHeight: 20px; style: semiboldTextStyle; textFg: windowBoldFg; } pickLocationButtonStatus: FlatLabel(defaultFlatLabel) { minWidth: 128px; + maxHeight: 20px; textFg: windowSubTextFg; } pickLocationButtonSkip: 6px; pickLocationSendIcon: icon{{ "chat/filled_location", windowFgActive }}; pickLocationVenueItem: PeerListItem(defaultPeerListItem) { - button: OutlineButton(defaultPeerListButton) { - font: normalFont; - padding: margins(11px, 5px, 11px, 5px); - } - height: 52px; + height: pickLocationRowHeight; + photoSize: 42px; photoPosition: point(18px, 5px); namePosition: point(70px, 7px); statusPosition: point(70px, 27px); - photoSize: 42px; + button: OutlineButton(defaultPeerListButton) { + textBg: contactsBg; + textBgOver: contactsBgOver; + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); + ripple: defaultRippleAnimation; + } + statusFg: contactsStatusFg; + statusFgOver: contactsStatusFgOver; + statusFgActive: contactsStatusFgOnline; } pickLocationVenueList: PeerList(defaultPeerList) { item: pickLocationVenueItem; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index d52e13b82..4dbd94e1d 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1822,6 +1822,7 @@ void ChooseAndSendLocation( Ui::LocationPicker::Show({ .parent = controller->widget(), .config = config, + .recipient = action.history->peer, .session = &controller->session(), .callback = crl::guard(controller, callback), .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); }, diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index ed9d84f7a..5438d00d4 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -409,7 +409,7 @@ void VenuesController::rowPaintIcon( st::pickLocationButton); const auto raw = result.data(); - const auto st = &st::pickLocationVenue; + const auto st = &st::pickLocationVenueItem; const auto icon = CreateChild<RpWidget>(raw); icon->setGeometry( st->photoPosition.x(), @@ -628,7 +628,8 @@ LocationPicker::LocationPicker(Descriptor &&descriptor) venuesRequest(*_venuesSearchLocation, *_venuesSearchQuery); }) -, _api(&_session->mtp()) { +, _api(&_session->mtp()) +, _venueRecipient(descriptor.recipient) { std::move( descriptor.closeRequests ) | rpl::start_with_next([=] { @@ -1002,7 +1003,7 @@ void LocationPicker::venuesSendRequest() { _venuesRequestId = _api.request(MTPmessages_GetInlineBotResults( MTP_flags(MTPmessages_GetInlineBotResults::Flag::f_geo_point), _venuesBot->inputUser, - MTP_inputPeerEmpty(), + (_venueRecipient ? _venueRecipient->input : MTP_inputPeerEmpty()), MTP_inputGeoPoint( MTP_flags(0), MTP_double(_venuesRequestLocation.point.x()), diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index 6014bd06b..756e6b162 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -80,6 +80,7 @@ public: struct Descriptor { RpWidget *parent = nullptr; LocationPickerConfig config; + PeerData *recipient = nullptr; not_null<Main::Session*> session; Fn<void(Data::InputVenue)> callback; Fn<void()> quit; @@ -145,6 +146,7 @@ private: std::optional<QString> _venuesSearchQuery; base::Timer _venuesSearchDebounceTimer; MTP::Sender _api; + PeerData *_venueRecipient = nullptr; UserData *_venuesBot = nullptr; mtpRequestId _venuesBotRequestId = 0; mtpRequestId _venuesRequestId = 0; From 8a92c89f39bc58e0192f5cb808d52724d7e95f10 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Mon, 15 Jul 2024 09:32:52 +0200 Subject: [PATCH 046/163] Add fsqr promo footer. --- Telegram/Resources/langs/lang.strings | 1 + .../chat_helpers/chat_helpers.style | 1 + .../ui/controls/location_picker.cpp | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 08368d748..fc725ff1d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3199,6 +3199,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_maps_places_in_area" = "Places in this area"; "lng_maps_no_places" = "No places found"; "lng_maps_choose_to_search" = "Choose location to see places nearby."; +"lng_maps_venues_source" = "Powered by Foursquare"; "lng_live_location" = "Live Location"; "lng_live_location_now" = "updated just now"; "lng_live_location_minutes#one" = "updated {count} minute ago"; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index a40d1e0ad..9b92f4815 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1474,3 +1474,4 @@ pickLocationLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { color: windowSubTextFg; thickness: 4px; } +pickLocationPromoHeight: 32px; diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 5438d00d4..b461a902b 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/separate_panel.h" +#include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -182,6 +183,27 @@ private: return query.trimmed().toLower(); } +[[nodiscard]] object_ptr<RpWidget> MakeFoursquarePromo() { + auto result = object_ptr<RpWidget>((QWidget*)nullptr); + const auto raw = result.data(); + raw->resize(0, st::pickLocationPromoHeight); + const auto shadow = CreateChild<PlainShadow>(raw); + raw->widthValue() | rpl::start_with_next([=](int width) { + shadow->setGeometry(0, 0, width, st::lineWidth); + }, raw->lifetime()); + raw->paintRequest() | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(raw); + p.fillRect(clip, st::windowBg); + p.setPen(st::windowSubTextFg); + p.setFont(st::normalFont); + p.drawText( + raw->rect(), + tr::lng_maps_venues_source(tr::now), + style::al_center); + }, raw->lifetime()); + return result; +} + VenuesController::VenuesController( not_null<Main::Session*> session, rpl::producer<std::vector<VenueData>> content, @@ -214,6 +236,11 @@ void VenuesController::rebuild(const std::vector<VenueData> &rows) { delegate()->peerListRemoveRow(delegate()->peerListRowAt(i)); --count; } + if (i > 0) { + delegate()->peerListSetBelowWidget(MakeFoursquarePromo()); + } else { + delegate()->peerListSetBelowWidget({ nullptr }); + } delegate()->peerListRefreshRows(); } From 03454ca3b4abf7e5e28e7758f2313811eacaa673 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Mon, 15 Jul 2024 11:35:01 +0200 Subject: [PATCH 047/163] Fix sticker quote reply layout. --- .../history/view/media/history_view_media_unwrapped.cpp | 3 --- 1 file changed, 3 deletions(-) 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 237c30b87..8f8b15ecf 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -125,9 +125,6 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { if (via) { via->resize(availw); } - if (reply) { - [[maybe_unused]] int height = reply->resizeToWidth(availw); - } } return { newWidth, newHeight }; } From 2c3ef13b017e0f7111f0401daf29014973701cf2 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 16 Jul 2024 16:03:15 +0200 Subject: [PATCH 048/163] Update tg_owt to M123. --- .../SourceFiles/platform/win/specific_win.cpp | 17 +++++++++++++++++ .../SourceFiles/platform/win/windows_dlls.cpp | 3 +++ .../SourceFiles/platform/win/windows_dlls.h | 8 ++++++++ Telegram/ThirdParty/tgcalls | 2 +- Telegram/build/docker/centos_env/Dockerfile | 2 +- Telegram/build/prepare/prepare.py | 12 ++++++++---- Telegram/cmake/lib_tgcalls.cmake | 11 +++++++---- Telegram/lib_webrtc | 2 +- cmake | 2 +- snap/snapcraft.yaml | 2 +- 10 files changed, 48 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index a35cfd3c4..9b8b959e0 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -58,6 +58,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <guiddef.h> #include <locale.h> +#include <ShellScalingApi.h> + #ifndef DCX_USESTYLE #define DCX_USESTYLE 0x00010000 #endif @@ -695,3 +697,18 @@ bool psLaunchMaps(const Data::LocationPoint &point) { return QDesktopServices::openUrl( url.arg(point.latAsString()).arg(point.lonAsString())); } + +// Stub while we still support Windows 7. +extern "C" { + +STDAPI GetDpiForMonitor( + _In_ HMONITOR hmonitor, + _In_ MONITOR_DPI_TYPE dpiType, + _Out_ UINT *dpiX, + _Out_ UINT *dpiY) { + return Dlls::GetDpiForMonitor + ? Dlls::GetDpiForMonitor(hmonitor, dpiType, dpiX, dpiY) + : E_FAIL; +} + +} // extern "C" diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.cpp b/Telegram/SourceFiles/platform/win/windows_dlls.cpp index 0e03904f7..ded5dd0d1 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.cpp +++ b/Telegram/SourceFiles/platform/win/windows_dlls.cpp @@ -63,6 +63,9 @@ SafeIniter::SafeIniter() { const auto LibUser32 = LoadLibrary(L"user32.dll"); LOAD_SYMBOL(LibUser32, SetWindowCompositionAttribute); + + const auto LibShCore = LoadLibrary(L"Shcore.dll"); + LOAD_SYMBOL(LibShCore, GetDpiForMonitor); } SafeIniter kSafeIniter; diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.h b/Telegram/SourceFiles/platform/win/windows_dlls.h index 26f478376..9ebed92fc 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.h +++ b/Telegram/SourceFiles/platform/win/windows_dlls.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <windows.h> #include <shellapi.h> +#include <ShellScalingApi.h> #include <dwmapi.h> #include <RestartManager.h> #include <psapi.h> @@ -124,5 +125,12 @@ inline BOOL(__stdcall *SetWindowCompositionAttribute)( HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*); +// SHCORE.DLL +inline HRESULT(__stdcall *GetDpiForMonitor)( + _In_ HMONITOR hmonitor, + _In_ MONITOR_DPI_TYPE dpiType, + _Out_ UINT *dpiX, + _Out_ UINT *dpiY); + } // namespace Dlls } // namespace Platform diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index b9fa8b84d..0c23e3803 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit b9fa8b84d8abe741183f157218ac038c596a54a5 +Subproject commit 0c23e3803bc29e115f886308275bf0e3a313bc58 diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index e4f7d1fb1..00a3dc3b7 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -753,7 +753,7 @@ COPY --link --from=pipewire {{ LibrariesPath }}/pipewire-cache / RUN git init tg_owt \ && cd tg_owt \ && git remote add origin {{ GIT }}/desktop-app/tg_owt.git \ - && git fetch --depth=1 origin c9cc4390ab951f2cbc103ff783a11f398b27660b \ + && git fetch --depth=1 origin 996dbe2c83b5a71d9045ce47960b8432e223dbb3 \ && git reset --hard FETCH_HEAD \ && git submodule update --init --recursive --depth=1 \ && rm -rf .git \ diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 6704bdbd2..2f5986864 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1,4 +1,4 @@ -import os, sys, pprint, re, json, pathlib, hashlib, subprocess, glob +import os, sys, pprint, re, json, pathlib, hashlib, subprocess, glob, tempfile executePath = os.getcwd() sys.dont_write_bytecode = True @@ -435,8 +435,12 @@ if customRunCommand: modifiedEnv['PROMPT'] = '(prepare) $P$G' subprocess.run("cmd.exe", shell=True, env=modifiedEnv) else: - modifiedEnv['PS1'] = '(prepare) \\w \\$ ' - subprocess.run("bash --noprofile --norc", env=modifiedEnv) + prompt = '(prepare) %~ %# ' + with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_zshrc: + tmp_zshrc.write(f'export PS1="{prompt}"\n') + tmp_zshrc_path = tmp_zshrc.name + subprocess.run(['zsh', '--rcs', tmp_zshrc_path], env=modifiedEnv) + os.remove(tmp_zshrc_path) elif not run(command): print('FAILED :(') finish(1) @@ -1616,7 +1620,7 @@ win: stage('tg_owt', """ git clone https://github.com/desktop-app/tg_owt.git cd tg_owt - git checkout afd9d5d317 + git checkout 996dbe2c83 git submodule init git submodule update win: diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 881ad9952..3f6938e72 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -88,10 +88,6 @@ PRIVATE v2/SignalingEncryption.h v2/SignalingSctpConnection.cpp v2/SignalingSctpConnection.h - v2_4_0_0/InstanceV2_4_0_0Impl.cpp - v2_4_0_0/InstanceV2_4_0_0Impl.h - v2_4_0_0/Signaling_4_0_0.cpp - v2_4_0_0/Signaling_4_0_0.h # Desktop capturer desktop_capturer/DesktopCaptureSource.h @@ -152,10 +148,17 @@ PRIVATE platform/darwin/GLVideoView.mm platform/darwin/GLVideoViewMac.h platform/darwin/GLVideoViewMac.mm + platform/darwin/h265_nalu_rewriter.cc + platform/darwin/h265_nalu_rewriter.h platform/darwin/objc_video_encoder_factory.h platform/darwin/objc_video_encoder_factory.mm platform/darwin/objc_video_decoder_factory.h platform/darwin/objc_video_decoder_factory.mm + platform/darwin/RTCCodecSpecificInfoH265+Private.h + platform/darwin/RTCCodecSpecificInfoH265.h + platform/darwin/RTCCodecSpecificInfoH265.mm + platform/darwin/RTCH265ProfileLevelId.h + platform/darwin/RTCH265ProfileLevelId.mm platform/darwin/TGCMIOCapturer.h platform/darwin/TGCMIOCapturer.m platform/darwin/TGCMIODevice.h diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index eb9496540..81d01697c 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit eb9496540356945e2c9fb700bcfa51444fd36f41 +Subproject commit 81d01697c2ee2b46c5e5c41ce1176c230264e4cb diff --git a/cmake b/cmake index 78b441c9c..d50ff1a71 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 78b441c9c6ad8a14a8f97a28825babcadc6bf781 +Subproject commit d50ff1a71e080e63f71c451f500a2f03f9d48539 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 033424e1d..b3c8d1905 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -433,7 +433,7 @@ parts: webrtc: source: https://github.com/desktop-app/tg_owt.git source-depth: 1 - source-commit: c9cc4390ab951f2cbc103ff783a11f398b27660b + source-commit: 996dbe2c83b5a71d9045ce47960b8432e223dbb3 plugin: cmake build-environment: - LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s From 8c55364afad0ba598f53b4fd592d0b86db82addf Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 10:37:14 +0200 Subject: [PATCH 049/163] Allow editing business location in Settings. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/calls/calls_call.cpp | 2 - .../data/business/data_business_info.cpp | 36 +++ .../data/business/data_business_info.h | 1 + Telegram/SourceFiles/data/data_location.cpp | 5 + Telegram/SourceFiles/data/data_location.h | 7 +- .../inline_bots/bot_attach_web_view.cpp | 1 + .../settings/business/settings_chat_intro.cpp | 1 - .../settings/business/settings_location.cpp | 208 ++++++++++++++++-- .../ui/controls/location_picker.cpp | 33 ++- .../SourceFiles/ui/controls/location_picker.h | 3 + 11 files changed, 266 insertions(+), 33 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fc725ff1d..c692195bc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2387,6 +2387,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_location_title" = "Location"; "lng_location_about" = "Display the location of your business on your account."; "lng_location_address" = "Enter Address"; +"lng_location_set_map" = "Set Location on Map"; "lng_location_fallback" = "You can set your location on the map from your mobile device."; "lng_hours_title" = "Business Hours"; @@ -3195,6 +3196,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_maps_point" = "Location"; "lng_maps_point_send" = "Send This Location"; +"lng_maps_point_set" = "Set This Location"; "lng_maps_or_choose" = "Or choose a venue"; "lng_maps_places_in_area" = "Places in this area"; "lng_maps_no_places" = "No places found"; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 9f1272859..0ca37c629 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -39,7 +39,6 @@ namespace tgcalls { class InstanceImpl; class InstanceV2Impl; class InstanceV2ReferenceImpl; -class InstanceV2_4_0_0Impl; class InstanceImplLegacy; void SetLegacyGlobalServerConfig(const std::string &serverConfig); } // namespace tgcalls @@ -56,7 +55,6 @@ const auto kDefaultVersion = "2.4.4"_q; const auto Register = tgcalls::Register<tgcalls::InstanceImpl>(); const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>(); const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>(); -const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>(); const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>(); [[nodiscard]] base::flat_set<int64> CollectEndpointIds( diff --git a/Telegram/SourceFiles/data/business/data_business_info.cpp b/Telegram/SourceFiles/data/business/data_business_info.cpp index e158c7a3a..d9bb6ec7a 100644 --- a/Telegram/SourceFiles/data/business/data_business_info.cpp +++ b/Telegram/SourceFiles/data/business/data_business_info.cpp @@ -130,6 +130,42 @@ void BusinessInfo::saveChatIntro(ChatIntro data, Fn<void(QString)> fail) { session->user()->setBusinessDetails(std::move(details)); } +void BusinessInfo::saveLocation( + BusinessLocation data, + Fn<void(QString)> fail) { + const auto session = &_owner->session(); + auto details = session->user()->businessDetails(); + const auto &was = details.location; + if (was == data) { + return; + } else { + const auto session = &_owner->session(); + using Flag = MTPaccount_UpdateBusinessLocation::Flag; + session->api().request(MTPaccount_UpdateBusinessLocation( + MTP_flags((data.point ? Flag::f_geo_point : Flag()) + | (data.address.isEmpty() ? Flag() : Flag::f_address)), + (data.point + ? MTP_inputGeoPoint( + MTP_flags(0), + MTP_double(data.point->lat()), + MTP_double(data.point->lon()), + MTPint()) // accuracy_radius + : MTP_inputGeoPointEmpty()), + MTP_string(data.address) + )).fail([=](const MTP::Error &error) { + auto details = session->user()->businessDetails(); + details.location = was; + session->user()->setBusinessDetails(std::move(details)); + if (fail) { + fail(error.type()); + } + }).send(); + } + + details.location = std::move(data); + session->user()->setBusinessDetails(std::move(details)); +} + void BusinessInfo::applyAwaySettings(AwaySettings data) { if (_awaySettings == data) { return; diff --git a/Telegram/SourceFiles/data/business/data_business_info.h b/Telegram/SourceFiles/data/business/data_business_info.h index 7b99e4c8f..01993f223 100644 --- a/Telegram/SourceFiles/data/business/data_business_info.h +++ b/Telegram/SourceFiles/data/business/data_business_info.h @@ -22,6 +22,7 @@ public: void saveWorkingHours(WorkingHours data, Fn<void(QString)> fail); void saveChatIntro(ChatIntro data, Fn<void(QString)> fail); + void saveLocation(BusinessLocation data, Fn<void(QString)> fail); void saveAwaySettings(AwaySettings data, Fn<void(QString)> fail); void applyAwaySettings(AwaySettings data); diff --git a/Telegram/SourceFiles/data/data_location.cpp b/Telegram/SourceFiles/data/data_location.cpp index 80727b3ba..457bb6411 100644 --- a/Telegram/SourceFiles/data/data_location.cpp +++ b/Telegram/SourceFiles/data/data_location.cpp @@ -26,6 +26,11 @@ LocationPoint::LocationPoint(const MTPDgeoPoint &point) , _access(point.vaccess_hash().v) { } +LocationPoint::LocationPoint(float64 lat, float64 lon, IgnoreAccessHash) +: _lat(lat) +, _lon(lon) { +} + QString LocationPoint::latAsString() const { return AsString(_lat); } diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index 5157f79a1..114d21c08 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -16,6 +16,11 @@ public: LocationPoint() = default; explicit LocationPoint(const MTPDgeoPoint &point); + enum IgnoreAccessHash { + NoAccessHash, + }; + LocationPoint(float64 lat, float64 lon, IgnoreAccessHash); + [[nodiscard]] QString latAsString() const; [[nodiscard]] QString lonAsString() const; [[nodiscard]] MTPGeoPoint toMTP() const; @@ -55,7 +60,7 @@ struct InputVenue { QString venueType; [[nodiscard]] bool justLocation() const { - return id.isEmpty() && title.isEmpty() && address.isEmpty(); + return id.isEmpty(); } friend inline bool operator==( diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 4dbd94e1d..c42ecb3b9 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1822,6 +1822,7 @@ void ChooseAndSendLocation( Ui::LocationPicker::Show({ .parent = controller->widget(), .config = config, + .chooseLabel = tr::lng_maps_point_send(), .recipient = action.history->peer, .session = &controller->session(), .callback = crl::guard(controller, callback), diff --git a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp index e9bc2f73e..688a6940d 100644 --- a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp +++ b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp @@ -632,7 +632,6 @@ void ChatIntro::setupContent( } void ChatIntro::save() { - const auto show = controller()->uiShow(); const auto fail = [=](QString error) { }; controller()->session().data().businessInfo().saveChatIntro( diff --git a/Telegram/SourceFiles/settings/business/settings_location.cpp b/Telegram/SourceFiles/settings/business/settings_location.cpp index 4a2f14e73..e5bb0f8e3 100644 --- a/Telegram/SourceFiles/settings/business/settings_location.cpp +++ b/Telegram/SourceFiles/settings/business/settings_location.cpp @@ -8,16 +8,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/business/settings_location.h" #include "core/application.h" +#include "core/shortcuts.h" +#include "data/business/data_business_info.h" +#include "data/data_file_origin.h" #include "data/data_session.h" +#include "data/data_user.h" #include "lang/lang_keys.h" +#include "main/main_app_config.h" #include "main/main_session.h" +#include "mainwidget.h" +#include "mainwindow.h" #include "settings/business/settings_recipients_helper.h" +#include "settings/settings_common.h" +#include "storage/storage_account.h" +#include "ui/controls/location_picker.h" #include "ui/text/text_utilities.h" #include "ui/widgets/fields/input_field.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/vertical_list.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" namespace Settings { @@ -40,16 +54,37 @@ private: void setupContent(not_null<Window::SessionController*> controller); void save(); - [[nodiscard]] bool mapSupported() const; + void setupPicker(not_null<Ui::VerticalLayout*> content); + void setupUnsupported(not_null<Ui::VerticalLayout*> content); + [[nodiscard]] bool mapSupported() const; + void chooseOnMap(); + + const Ui::LocationPickerConfig _config; + rpl::variable<Data::BusinessLocation> _data; + rpl::variable<Data::CloudImage*> _map = nullptr; + std::shared_ptr<QImage> _view; Ui::RoundRect _bottomSkipRounding; }; +[[nodiscard]] Ui::LocationPickerConfig ResolveBusinessMapsConfig( + not_null<Main::Session*> session) { + const auto &appConfig = session->appConfig(); + auto map = appConfig.get<base::flat_map<QString, QString>>( + u"tdesktop_config_map"_q, + base::flat_map<QString, QString>()); + return { + .mapsToken = map[u"bmaps"_q], + .geoToken = map[u"bgeo"_q], + }; +} + Location::Location( QWidget *parent, not_null<Window::SessionController*> controller) : BusinessSection(parent, controller) +, _config(ResolveBusinessMapsConfig(&controller->session())) , _bottomSkipRounding(st::boxRadius, st::boxDividerBg) { setupContent(controller); } @@ -65,12 +100,23 @@ rpl::producer<QString> Location::title() { } void Location::setupContent( - not_null<Window::SessionController*> controller) { + not_null<Window::SessionController*> controller) { using namespace rpl::mappers; const auto content = Ui::CreateChild<Ui::VerticalLayout>(this); -#if 0 // #TODO location choosing + if (mapSupported()) { + setupPicker(content); + } else { + setupUnsupported(content); + } + + Ui::ResizeFitChild(this, content); +} + +void Location::setupPicker(not_null<Ui::VerticalLayout*> content) { + _data = controller()->session().user()->businessDetails().location; + AddDividerTextWithLottie(content, { .lottie = u"location"_q, .lottieSize = st::settingsCloudPasswordIconSize, @@ -86,36 +132,158 @@ void Location::setupContent( st::settingsLocationAddress, Ui::InputField::Mode::MultiLine, tr::lng_location_address(), - QString()), + _data.current().address), st::settingsChatbotsUsernameMargins); + _data.value( + ) | rpl::start_with_next([=](const Data::BusinessLocation &location) { + address->setText(location.address); + }, address->lifetime()); + + address->changes() | rpl::start_with_next([=] { + auto copy = _data.current(); + copy.address = address->getLastText(); + _data = std::move(copy); + }, address->lifetime()); + + AddDivider(content); + AddSkip(content); + + const auto maptoggle = AddButtonWithIcon( + content, + tr::lng_location_set_map(), + st::settingsButton, + { &st::menuIconAddress } + )->toggleOn(_data.value( + ) | rpl::map([](const Data::BusinessLocation &location) { + return location.point.has_value(); + })); + + maptoggle->toggledValue() | rpl::start_with_next([=](bool toggled) { + if (!toggled) { + auto copy = _data.current(); + if (copy.point.has_value()) { + copy.point = std::nullopt; + _data = std::move(copy); + } + } else if (!_data.current().point.has_value()) { + _data.force_assign(_data.current()); + chooseOnMap(); + } + }, maptoggle->lifetime()); + + const auto mapSkip = st::defaultVerticalListSkip; + const auto mapWrap = content->add( + object_ptr<Ui::SlideWrap<Ui::AbstractButton>>( + content, + object_ptr<Ui::AbstractButton>(content), + st::boxRowPadding + QMargins(0, mapSkip, 0, mapSkip))); + mapWrap->toggle(_data.current().point.has_value(), anim::type::instant); + + const auto map = mapWrap->entity(); + map->resize(map->width(), st::locationSize.height()); + + _data.value( + ) | rpl::start_with_next([=](const Data::BusinessLocation &location) { + const auto image = location.point.has_value() + ? controller()->session().data().location(*location.point).get() + : nullptr; + if (image) { + image->load(&controller()->session(), {}); + _view = image->createView(); + } + mapWrap->toggle(image != nullptr, anim::type::normal); + _map = image; + }, mapWrap->lifetime()); + + map->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(map); + + const auto left = (map->width() - st::locationSize.width()) / 2; + const auto rect = QRect(QPoint(left, 0), st::locationSize); + const auto &image = _view ? *_view : QImage(); + if (!image.isNull()) { + p.drawImage(rect, image); + } + + const auto paintMarker = [&](const style::icon &icon) { + icon.paint( + p, + rect.x() + ((rect.width() - icon.width()) / 2), + rect.y() + (rect.height() / 2) - icon.height(), + width()); + }; + paintMarker(st::historyMapPoint); + paintMarker(st::historyMapPointInner); + }, map->lifetime()); + + controller()->session().downloaderTaskFinished( + ) | rpl::start_with_next([=] { + map->update(); + }, map->lifetime()); + + map->setClickedCallback([=] { + chooseOnMap(); + }); + showFinishes() | rpl::start_with_next([=] { address->setFocus(); }, address->lifetime()); -#endif +} - if (!mapSupported()) { - AddDividerTextWithLottie(content, { - .lottie = u"phone"_q, - .lottieSize = st::settingsCloudPasswordIconSize, - .lottieMargins = st::peerAppearanceIconPadding, - .showFinished = showFinishes(), - .about = tr::lng_location_fallback(Ui::Text::WithEntities), - .aboutMargins = st::peerAppearanceCoverLabelMargin, - .parts = RectPart::Top, - }); - } else { +void Location::chooseOnMap() { + const auto callback = [=](Data::InputVenue venue) { + auto copy = _data.current(); + copy.point = Data::LocationPoint( + venue.lat, + venue.lon, + Data::LocationPoint::NoAccessHash); + copy.address = venue.address; + _data = std::move(copy); + }; + const auto session = &controller()->session(); + const auto current = _data.current().point; + const auto initial = current + ? Core::GeoLocation{ + .point = { current->lat(), current->lon() }, + .accuracy = Core::GeoLocationAccuracy::Exact, + } + : Core::GeoLocation(); + Ui::LocationPicker::Show({ + .parent = controller()->widget(), + .config = _config, + .chooseLabel = tr::lng_maps_point_set(), + .session = session, + .initial = initial, + .callback = crl::guard(this, callback), + .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); }, + .storageId = session->local().resolveStorageIdBots(), + .closeRequests = controller()->content()->death(), + }); +} - } - - Ui::ResizeFitChild(this, content); +void Location::setupUnsupported(not_null<Ui::VerticalLayout*> content) { + AddDividerTextWithLottie(content, { + .lottie = u"phone"_q, + .lottieSize = st::settingsCloudPasswordIconSize, + .lottieMargins = st::peerAppearanceIconPadding, + .showFinished = showFinishes(), + .about = tr::lng_location_fallback(Ui::Text::WithEntities), + .aboutMargins = st::peerAppearanceCoverLabelMargin, + .parts = RectPart::Top, + }); } void Location::save() { + const auto fail = [=](QString error) { + }; + auto value = _data.current(); + value.address = value.address.trimmed(); + controller()->session().data().businessInfo().saveLocation(value, fail); } bool Location::mapSupported() const { - return false; + return Ui::LocationPicker::Available(_config); } } // namespace diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index b461a902b..3f1c74fe0 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -427,8 +427,9 @@ void VenuesController::rowPaintIcon( )"_q; } -[[nodiscard]] object_ptr<AbstractButton> MakeSendLocationButton( +[[nodiscard]] object_ptr<AbstractButton> MakeChooseLocationButton( QWidget *parent, + rpl::producer<QString> label, rpl::producer<QString> address) { auto result = object_ptr<FlatButton>( parent, @@ -465,7 +466,7 @@ void VenuesController::rowPaintIcon( }); const auto name = CreateChild<FlatLabel>( raw, - tr::lng_maps_point_send(tr::now), + std::move(label), st::pickLocationButtonText); name->show(); const auto status = CreateChild<FlatLabel>( @@ -679,10 +680,15 @@ bool LocationPicker::Available(const LocationPickerConfig &config) { void LocationPicker::setup(const Descriptor &descriptor) { setupWindow(descriptor); setupWebview(descriptor); - if (LastExactLocation) { - venuesRequest(LastExactLocation); - resolveAddress(LastExactLocation); - venuesSearchEnableAt(LastExactLocation); + + _initialProvided = descriptor.initial.exact(); + const auto initial = _initialProvided + ? descriptor.initial + : LastExactLocation; + if (initial) { + venuesRequest(initial); + resolveAddress(initial); + venuesSearchEnableAt(initial); } } @@ -717,7 +723,10 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { const auto toppad = mapControls->add(object_ptr<RpWidget>(controls)); const auto button = mapControls->add( - MakeSendLocationButton(mapControls, _geocoderAddress.value()), + MakeChooseLocationButton( + mapControls, + std::move(descriptor.chooseLabel), + _geocoderAddress.value()), { 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip }); button->setClickedCallback([=] { _webview->eval("LocationPicker.send();"); @@ -809,7 +818,9 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { const auto event = object.value("event").toString(); if (event == u"ready"_q) { mapReady(); - resolveCurrentLocation(); + if (!_initialProvided) { + resolveCurrentLocation(); + } if (_webview) { _webview->focus(); } @@ -820,7 +831,11 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { } else if (event == u"send"_q) { const auto lat = object.value("latitude").toDouble(); const auto lon = object.value("longitude").toDouble(); - _callback({ lat, lon }); + _callback({ + .lat = lat, + .lon = lon, + .address = _geocoderAddress.current(), + }); close(); } else if (event == u"move_start"_q) { if (const auto now = _geocoderAddress.current() diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index 756e6b162..0a1485157 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -80,8 +80,10 @@ public: struct Descriptor { RpWidget *parent = nullptr; LocationPickerConfig config; + rpl::producer<QString> chooseLabel; PeerData *recipient = nullptr; not_null<Main::Session*> session; + Core::GeoLocation initial; Fn<void(Data::InputVenue)> callback; Fn<void()> quit; Webview::StorageId storageId; @@ -132,6 +134,7 @@ private: std::unique_ptr<Webview::Window> _webview; SingleQueuedInvokation _updateStyles; bool _subscribedToColors = false; + bool _initialProvided = false; base::Timer _geocoderResolveTimer; Core::GeoLocation _geocoderResolvePostponed; From c22698084f169d662871b86315ade067a0376725 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 16 Jul 2024 16:14:15 +0200 Subject: [PATCH 050/163] Location search cancel by X button. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index f046725ec..58329bfe1 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit f046725ecde41bf5779d69393cc592786ee0440c +Subproject commit 58329bfe19c031ec6065a3a098743bb3d7732a64 From 6f86acf7128fcad9c9da1be1d9043997c5c4a8ee Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 16 Jul 2024 18:11:59 +0200 Subject: [PATCH 051/163] Use system reverse geocoding on macOS. --- .../platform/mac/current_geo_location_mac.mm | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm index 1a5382f19..0ea554463 100644 --- a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm +++ b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/current_geo_location_mac.h" +#include "base/platform/mac/base_utilities_mac.h" #include "core/current_geo_location.h" #include <CoreLocation/CoreLocation.h> @@ -120,7 +121,34 @@ void ResolveLocationAddress( const Core::GeoLocation &location, const QString &language, Fn<void(Core::GeoAddress)> callback) { - callback({}); + CLGeocoder *geocoder = [[CLGeocoder alloc] init]; + CLLocation *request = [[CLLocation alloc] + initWithLatitude:location.point.x() + longitude:location.point.y()]; + [geocoder reverseGeocodeLocation:request completionHandler:^( + NSArray<CLPlacemark*> * __nullable placemarks, + NSError * __nullable error) { + if (placemarks && [placemarks count] > 0) { + CLPlacemark *placemark = [placemarks firstObject]; + auto list = QStringList(); + const auto push = [&](NSString *text) { + if (text) { + const auto qt = NS2QString(text); + if (!qt.isEmpty()) { + list.push_back(qt); + } + } + }; + push([placemark thoroughfare]); + push([placemark locality]); + push([placemark country]); + callback({ .name = list.join(u", "_q) }); + } else { + callback({}); + } + [geocoder release]; + }]; + [request release]; } } // namespace Platform From e83704982f9c0c81793de2d6eb6035731104a374 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 16 Jul 2024 18:13:17 +0200 Subject: [PATCH 052/163] Set initial location correctly. --- .../ui/controls/location_picker.cpp | 21 ++++++++++++------- .../SourceFiles/ui/controls/location_picker.h | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 3f1c74fe0..8cce44571 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -332,14 +332,15 @@ void VenuesController::rowPaintIcon( p.drawImage(x, y, data.image); } -[[nodiscard]] QByteArray DefaultCenter() { - if (!LastExactLocation) { +[[nodiscard]] QByteArray DefaultCenter(Core::GeoLocation initial) { + const auto &use = initial.exact() ? initial : LastExactLocation; + if (!use) { return "null"; } return "["_q - + QByteArray::number(LastExactLocation.point.x()) + + QByteArray::number(use.point.x()) + ","_q - + QByteArray::number(LastExactLocation.point.y()) + + QByteArray::number(use.point.y()) + "]"_q; } @@ -496,6 +497,10 @@ void VenuesController::rowPaintIcon( status->moveToLeft(statusPosition.x(), statusPosition.y(), width); }, name->lifetime()); + icon->setAttribute(Qt::WA_TransparentForMouseEvents); + name->setAttribute(Qt::WA_TransparentForMouseEvents); + status->setAttribute(Qt::WA_TransparentForMouseEvents); + return result; } @@ -681,9 +686,9 @@ void LocationPicker::setup(const Descriptor &descriptor) { setupWindow(descriptor); setupWebview(descriptor); - _initialProvided = descriptor.initial.exact(); - const auto initial = _initialProvided - ? descriptor.initial + _initialProvided = descriptor.initial; + const auto initial = _initialProvided.exact() + ? _initialProvided : LastExactLocation; if (initial) { venuesRequest(initial); @@ -964,7 +969,7 @@ void LocationPicker::mapReady() { Expects(_scroll != nullptr); const auto token = _config.mapsToken.toUtf8(); - const auto center = DefaultCenter(); + const auto center = DefaultCenter(_initialProvided); const auto bounds = DefaultBounds(); const auto protocol = *kProtocolOverride ? "'"_q + kProtocolOverride + "'" diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index 0a1485157..97ce724f3 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -133,8 +133,8 @@ private: ScrollArea *_scroll = nullptr; std::unique_ptr<Webview::Window> _webview; SingleQueuedInvokation _updateStyles; + Core::GeoLocation _initialProvided; bool _subscribedToColors = false; - bool _initialProvided = false; base::Timer _geocoderResolveTimer; Core::GeoLocation _geocoderResolvePostponed; From 2412183b835b98a8d5991a2bcf9016c3da79fe68 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 10:24:13 +0200 Subject: [PATCH 053/163] Build most dependencies for Windows on ARM. --- Telegram/CMakeLists.txt | 9 +- Telegram/build/prepare/prepare.py | 152 +++++++++++++++++++++++++----- Telegram/lib_crl | 2 +- cmake | 2 +- 4 files changed, 138 insertions(+), 27 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 592f1329f..48cf60eff 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1847,9 +1847,14 @@ if (WIN32) /DELAYLOAD:propsys.dll ) if (QT_VERSION GREATER 6) + if (NOT build_winarm) + target_link_options(Telegram PRIVATE + /DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll + ) + endif() + target_link_options(Telegram PRIVATE - /DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-Console-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-0.dll /DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-1.dll @@ -1866,7 +1871,7 @@ if (WIN32) /DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll /DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll - /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll + # /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll # We shadowed GetDpiForMonitor /DELAYLOAD:authz.dll # Authz.lib /DELAYLOAD:comdlg32.dll /DELAYLOAD:dwrite.dll # DWrite.lib diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 2f5986864..6300390d3 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -26,7 +26,7 @@ if win and not 'Platform' in os.environ: win32 = win and (os.environ['Platform'] == 'x86') win64 = win and (os.environ['Platform'] == 'x64') -winarm = win and (os.environ['Platform'] == 'arm') +winarm = win and (os.environ['Platform'] == 'arm64') arch = '' if win32: @@ -114,6 +114,12 @@ elif (win64): 'X8664': 'x64', 'WIN32X64': 'x64', }) +elif (winarm): + environment.update({ + 'SPECIAL_TARGET': 'winarm', + 'X8664': 'ARM64', + 'WIN32X64': 'ARM64', + }) elif (mac): environment.update({ 'SPECIAL_TARGET': 'mac', @@ -239,6 +245,8 @@ def filterByPlatform(commands): inscope = True if win64 and 'win64' in scopes: inscope = True + if winarm and 'winarm' in scopes: + inscope = True if mac and 'mac' in scopes: inscope = True # if linux and 'linux' in scopes: @@ -449,7 +457,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout e0cdca2e79 + git checkout 514332f808 """) stage('msys64', """ @@ -502,9 +510,9 @@ mac: if not mac or 'build-stackwalk' in options: stage('gyp', """ win: - git clone https://chromium.googlesource.com/external/gyp + git clone https://github.com/desktop-app/gyp.git cd gyp - git checkout 9d09418933 + git checkout 618958fdbe mac: python3 -m pip install \\ --ignore-installed \\ @@ -610,6 +618,8 @@ win32: perl Configure no-shared no-tests debug-VC-WIN32 /FS win64: perl Configure no-shared no-tests debug-VC-WIN64A /FS +winarm: + perl Configure no-shared no-tests debug-VC-WIN64-ARM /FS win: jom -j%NUMBER_OF_PROCESSORS% mkdir out.dbg @@ -624,6 +634,8 @@ win32_release: perl Configure no-shared no-tests VC-WIN32 /FS win64_release: perl Configure no-shared no-tests VC-WIN64A /FS +winarm_release: + perl Configure no-shared no-tests VC-WIN64-ARM /FS win_release: jom -j%NUMBER_OF_PROCESSORS% mkdir out @@ -720,18 +732,32 @@ mac: make install """) +stage('gas-preprocessor', """ +winarm: + git clone https://github.com/FFmpeg/gas-preprocessor + cd gas-preprocessor + echo @echo off > cpp.bat + echo cl %%%%%%** >> cpp.bat +""") + # Somehow in x86 Debug build dav1d crashes on AV1 10bpc videos. stage('dav1d', """ git clone -b 1.4.1 https://code.videolan.org/videolan/dav1d.git cd dav1d +win32: + SET "TARGET=x86" + SET "DAV1D_ASM_DISABLE=-Denable_asm=false" +win64: + SET "TARGET=x86_64" + SET "DAV1D_ASM_DISABLE=" +winarm: + SET "TARGET=aarch64" + SET "DAV1D_ASM_DISABLE=" + SET "PATH_BACKUP_=%PATH%" + SET "PATH=%LIBS_DIR%\\gas-preprocessor;%PATH%" + echo armasm64 fails with 'syntax error in expression: tbnz x14, #4, 8f' as if this instruction is unknown/unsupported. + git revert --no-edit d503bb0ccaf104b2f13da0f092e09cc9411b3297 win: - if "%X8664%" equ "x64" ( - SET "TARGET=x86_64" - SET "DAV1D_ASM_DISABLE=" - ) else ( - SET "TARGET=x86" - SET "DAV1D_ASM_DISABLE=-Denable_asm=false" - ) set FILE=cross-file.txt echo [binaries] > %FILE% echo c = 'cl' >> %FILE% @@ -756,6 +782,8 @@ release: win: copy %LIBS_DIR%\\local\\lib\\libdav1d.a %LIBS_DIR%\\local\\lib\\dav1d.lib deactivate +winarm: + SET "PATH=%PATH_BACKUP_%" mac: buildOneArch() { arch=$1 @@ -781,6 +809,67 @@ mac: lipo -create build.arm64/libdav1d.a build/libdav1d.a -output ${USED_PREFIX}/lib/libdav1d.a """) +stage('openh264', """ + git clone -b v2.4.1 https://github.com/cisco/openh264.git + cd openh264 +win32: + SET "TARGET=x86" +win64: + SET "TARGET=x86_64" +winarm: + SET "TARGET=aarch64" + SET "PATH_BACKUP_=%PATH%" + SET "PATH=%LIBS_DIR%\\gas-preprocessor;%PATH%" +win: + set FILE=cross-file.txt + echo [binaries] > %FILE% + echo c = 'cl' >> %FILE% + echo cpp = 'cl' >> %FILE% + echo ar = 'lib' >> %FILE% + echo windres = 'rc' >> %FILE% + echo [host_machine] >> %FILE% + echo system = 'windows' >> %FILE% + echo cpu_family = '%TARGET%' >> %FILE% + echo cpu = '%TARGET%' >> %FILE% + echo endian = 'little' >> %FILE% + +depends:python/Scripts/activate.bat + %THIRDPARTY_DIR%\\python\\Scripts\\activate.bat + meson setup --cross-file %FILE% --prefix %LIBS_DIR%/local --default-library=static --buildtype=debug -Db_vscrt=mtd builddir-debug + meson compile -C builddir-debug + meson install -C builddir-debug +release: + meson setup --cross-file %FILE% --prefix %LIBS_DIR%/local --default-library=static --buildtype=release -Db_vscrt=mt builddir-release + meson compile -C builddir-release + meson install -C builddir-release +win: + copy %LIBS_DIR%\\local\\lib\\libopenh264.a %LIBS_DIR%\\local\\lib\\openh264.lib + deactivate +winarm: + SET "PATH=%PATH_BACKUP_%" +mac: + buildOneArch() { + arch=$1 + folder=`pwd`/$2 + + meson setup \\ + --cross-file ../patches/macos_meson_${arch}.txt \\ + --prefix ${USED_PREFIX} \\ + --default-library=static \\ + --buildtype=minsize \\ + ${folder} + meson compile -C ${folder} + meson install -C ${folder} + + mv ${USED_PREFIX}/lib/libopenh264.a ${folder}/libopenh264.a + } + + buildOneArch arm64 build.arm64 + buildOneArch x86_64 build + + lipo -create build.arm64/libopenh264.a build/libopenh264.a -output ${USED_PREFIX}/lib/libopenh264.a +""") + stage('libavif', """ git clone -b v1.0.4 https://github.com/AOMediaCodec/libavif.git cd libavif @@ -850,7 +939,7 @@ mac: """) stage('libwebp', """ - git clone -b v1.3.2 https://github.com/webmproject/libwebp.git + git clone -b v1.4.0 https://github.com/webmproject/libwebp.git cd libwebp win: nmake /f Makefile.vc CFG=debug-static OBJDIR=out RTLIBCFG=static all @@ -1005,12 +1094,13 @@ win: SET CHERE_INVOKING=enabled_from_arguments SET MSYS2_PATH_TYPE=inherit - if "%X8664%" equ "x64" ( - SET "TOOLCHAIN=x86_64-win64-vs17" - ) else ( - SET "TOOLCHAIN=x86-win32-vs17" - ) - +win32: + SET "TOOLCHAIN=x86-win32-vs17" +win64: + SET "TOOLCHAIN=x86_64-win64-vs17" +winarm: + SET "TOOLCHAIN=arm64-win64-vs17" +win: depends:patches/build_libvpx_win.sh bash --login ../patches/build_libvpx_win.sh @@ -1097,12 +1187,19 @@ stage('ffmpeg', """ git clone -b n6.1.1 https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg win: +depends:patches/ffmpeg.patch + git apply ../patches/ffmpeg.patch + SET PATH_BACKUP_=%PATH% SET PATH=%ROOT_DIR%\\ThirdParty\\msys64\\usr\\bin;%PATH% SET CHERE_INVOKING=enabled_from_arguments SET MSYS2_PATH_TYPE=inherit + SET "ARCH_PARAM=" +winarm: + SET "ARCH_PARAM=--arch=aarch64" +win: depends:patches/build_ffmpeg_win.sh bash --login ../patches/build_ffmpeg_win.sh @@ -1319,11 +1416,12 @@ depends:patches/breakpad.diff git clone -b release-1.11.0 https://github.com/google/googletest src/testing win: SET "PYTHONUTF8=1" - if "%X8664%" equ "x64" ( - SET "FolderPostfix=_x64" - ) else ( - SET "FolderPostfix=" - ) + SET "FolderPostfix=" +win64: + SET "FolderPostfix=_x64" +winarm: + SET "FolderPostfix=_ARM64" +win: depends:python/Scripts/activate.bat %THIRDPARTY_DIR%\\python\\Scripts\\activate.bat cd src\\client\\windows @@ -1628,6 +1726,7 @@ win: SET OPUS_PATH=$USED_PREFIX/include/opus SET OPENSSL_PATH=$LIBS_DIR/openssl3/include SET LIBVPX_PATH=$USED_PREFIX/include + SET OPENH264_PATH=$USED_PREFIX/include SET FFMPEG_PATH=$LIBS_DIR/ffmpeg mkdir out cd out @@ -1641,6 +1740,7 @@ win: -DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PATH \ -DTG_OWT_OPUS_INCLUDE_PATH=$OPUS_PATH \ -DTG_OWT_LIBVPX_INCLUDE_PATH=$LIBVPX_PATH \ + -DTG_OWT_OPENH264_INCLUDE_PATH=$OPENH264_PATH \ -DTG_OWT_FFMPEG_INCLUDE_PATH=$FFMPEG_PATH ../.. ninja release: @@ -1655,12 +1755,14 @@ release: -DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PATH \ -DTG_OWT_OPUS_INCLUDE_PATH=$OPUS_PATH \ -DTG_OWT_LIBVPX_INCLUDE_PATH=$LIBVPX_PATH \ + -DTG_OWT_OPENH264_INCLUDE_PATH=$OPENH264_PATH \ -DTG_OWT_FFMPEG_INCLUDE_PATH=$FFMPEG_PATH ../.. ninja mac: MOZJPEG_PATH=$USED_PREFIX/include OPUS_PATH=$USED_PREFIX/include/opus LIBVPX_PATH=$USED_PREFIX/include + OPENH264_PATH=$USED_PREFIX/include FFMPEG_PATH=$USED_PREFIX/include mkdir out cd out @@ -1675,6 +1777,7 @@ mac: -DTG_OWT_OPENSSL_INCLUDE_PATH=$LIBS_DIR/openssl3/include \ -DTG_OWT_OPUS_INCLUDE_PATH=$OPUS_PATH \ -DTG_OWT_LIBVPX_INCLUDE_PATH=$LIBVPX_PATH \ + -DTG_OWT_OPENH264_INCLUDE_PATH=$OPENH264_PATH \ -DTG_OWT_FFMPEG_INCLUDE_PATH=$FFMPEG_PATH ../.. ninja cd .. @@ -1689,6 +1792,7 @@ mac: -DTG_OWT_OPENSSL_INCLUDE_PATH=$LIBS_DIR/openssl3/include \ -DTG_OWT_OPUS_INCLUDE_PATH=$OPUS_PATH \ -DTG_OWT_LIBVPX_INCLUDE_PATH=$LIBVPX_PATH \ + -DTG_OWT_OPENH264_INCLUDE_PATH=$OPENH264_PATH \ -DTG_OWT_FFMPEG_INCLUDE_PATH=$FFMPEG_PATH ../.. ninja cd .. @@ -1705,6 +1809,7 @@ release: -DTG_OWT_OPENSSL_INCLUDE_PATH=$LIBS_DIR/openssl3/include \ -DTG_OWT_OPUS_INCLUDE_PATH=$OPUS_PATH \ -DTG_OWT_LIBVPX_INCLUDE_PATH=$LIBVPX_PATH \ + -DTG_OWT_OPENH264_INCLUDE_PATH=$OPENH264_PATH \ -DTG_OWT_FFMPEG_INCLUDE_PATH=$FFMPEG_PATH ../.. ninja cd .. @@ -1718,6 +1823,7 @@ release: -DTG_OWT_OPENSSL_INCLUDE_PATH=$LIBS_DIR/openssl3/include \ -DTG_OWT_OPUS_INCLUDE_PATH=$OPUS_PATH \ -DTG_OWT_LIBVPX_INCLUDE_PATH=$LIBVPX_PATH \ + -DTG_OWT_OPENH264_INCLUDE_PATH=$OPENH264_PATH \ -DTG_OWT_FFMPEG_INCLUDE_PATH=$FFMPEG_PATH ../.. ninja cd .. diff --git a/Telegram/lib_crl b/Telegram/lib_crl index 078006d29..c1d6b0273 160000 --- a/Telegram/lib_crl +++ b/Telegram/lib_crl @@ -1 +1 @@ -Subproject commit 078006d29af0002e6cd8c61a405cdeaf65b37142 +Subproject commit c1d6b0273653095b10b4d0f4f7c30b614b690fd5 diff --git a/cmake b/cmake index d50ff1a71..37d42a722 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit d50ff1a71e080e63f71c451f500a2f03f9d48539 +Subproject commit 37d42a72255aabde49deafb02904f58f06f4a97d From ee9f99a754c3daea037bd37df6e3d2a0956cf6a9 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 12:25:27 +0200 Subject: [PATCH 054/163] Add some Windows on ARM special cases. --- Telegram/SourceFiles/boxes/about_box.cpp | 4 ++++ Telegram/SourceFiles/core/crash_reports.cpp | 6 +++++- Telegram/lib_base | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/boxes/about_box.cpp b/Telegram/SourceFiles/boxes/about_box.cpp index 6d001d407..75674299f 100644 --- a/Telegram/SourceFiles/boxes/about_box.cpp +++ b/Telegram/SourceFiles/boxes/about_box.cpp @@ -100,6 +100,8 @@ void AboutBox::showVersionHistory() { url += u"win/%1.zip"_q; } else if (Platform::IsWindows64Bit()) { url += u"win64/%1.zip"_q; + } else if (Platform::IsWindowsARM64()) { + url += u"winarm/%1.zip"_q; } else if (Platform::IsMac()) { url += u"mac/%1.zip"_q; } else if (Platform::IsLinux()) { @@ -155,6 +157,8 @@ QString currentVersionText() { } if (Platform::IsWindows64Bit()) { result += " x64"; + } else if (Platform::IsWindowsARM64()) { + result += " arm64"; } return result; } diff --git a/Telegram/SourceFiles/core/crash_reports.cpp b/Telegram/SourceFiles/core/crash_reports.cpp index 6f4e2d466..1e38d721a 100644 --- a/Telegram/SourceFiles/core/crash_reports.cpp +++ b/Telegram/SourceFiles/core/crash_reports.cpp @@ -296,13 +296,17 @@ bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, QString PlatformString() { if (Platform::IsWindowsStoreBuild()) { - return Platform::IsWindows64Bit() + return Platform::IsWindowsARM64() + ? u"WinStoreARM64"_q + : Platform::IsWindows64Bit() ? u"WinStore64Bit"_q : u"WinStore32Bit"_q; } else if (Platform::IsWindows32Bit()) { return u"Windows32Bit"_q; } else if (Platform::IsWindows64Bit()) { return u"Windows64Bit"_q; + } else if (Platform::IsWindowsARM64()) { + return u"WindowsARM64"_q; } else if (Platform::IsMacStoreBuild()) { return u"MacAppStore"_q; } else if (Platform::IsMac()) { diff --git a/Telegram/lib_base b/Telegram/lib_base index 1a50fd230..54639131b 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 1a50fd2300da3198e751a22bf728d33822180e15 +Subproject commit 54639131bf1e5dce87c10dba45062cfec804e343 From 08ec9e6bfd15bfae304776a0611b800c8d661349 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 13:52:57 +0200 Subject: [PATCH 055/163] Fix build on macOS with new dependencies. --- Telegram/build/prepare/prepare.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 6300390d3..e1e31ddfb 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -852,11 +852,11 @@ mac: arch=$1 folder=`pwd`/$2 - meson setup \\ - --cross-file ../patches/macos_meson_${arch}.txt \\ - --prefix ${USED_PREFIX} \\ - --default-library=static \\ - --buildtype=minsize \\ + meson setup \ + --cross-file ../patches/macos_meson_${arch}.txt \ + --prefix ${USED_PREFIX} \ + --default-library=static \ + --buildtype=minsize \ ${folder} meson compile -C ${folder} meson install -C ${folder} @@ -864,10 +864,10 @@ mac: mv ${USED_PREFIX}/lib/libopenh264.a ${folder}/libopenh264.a } - buildOneArch arm64 build.arm64 - buildOneArch x86_64 build + buildOneArch aarch64 build.aarch64 + buildOneArch x86_64 build.x86_64 - lipo -create build.arm64/libopenh264.a build/libopenh264.a -output ${USED_PREFIX}/lib/libopenh264.a + lipo -create build.aarch64/libopenh264.a build.x86_64/libopenh264.a -output ${USED_PREFIX}/lib/libopenh264.a """) stage('libavif', """ From 28a6aa45b9a97c4db635d1e01d5fdd71b9973b3b Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 14:03:36 +0200 Subject: [PATCH 056/163] Update msys2 and patches. --- Telegram/build/docker/centos_env/Dockerfile | 2 +- Telegram/build/prepare/prepare.py | 6 +++--- snap/snapcraft.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 00a3dc3b7..b5f7c86cd 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -43,7 +43,7 @@ FROM builder AS patches RUN git init patches \ && cd patches \ && git remote add origin {{ GIT }}/desktop-app/patches.git \ - && git fetch --depth=1 origin 20a7c5ffd8265fc6e45203ea2536f7b1965be19a \ + && git fetch --depth=1 origin 6898f0d215f249917c076f00d3fc954a43f35e6a \ && git reset --hard FETCH_HEAD \ && rm -rf .git diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index e1e31ddfb..0658703b7 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -457,7 +457,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 514332f808 + git checkout 6898f0d215 """) stage('msys64', """ @@ -468,12 +468,12 @@ win: SET CHERE_INVOKING=enabled_from_arguments SET MSYS2_PATH_TYPE=inherit - powershell -Command "iwr -OutFile ./msys64.exe https://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20221028.sfx.exe" + powershell -Command "iwr -OutFile ./msys64.exe https://github.com/msys2/msys2-installer/releases/download/2024-05-07/msys2-base-x86_64-20240507.sfx.exe" msys64.exe del msys64.exe bash -c "pacman-key --init; pacman-key --populate; pacman -Syu --noconfirm" - pacman -Syu --noconfirm mingw-w64-x86_64-perl mingw-w64-x86_64-nasm mingw-w64-x86_64-yasm mingw-w64-x86_64-ninja + pacman -Syu --noconfirm mingw-w64-x86_64-perl mingw-w64-x86_64-nasm mingw-w64-x86_64-yasm mingw-w64-x86_64-ninja msys/make diffutils pkg-config SET PATH=%PATH_BACKUP_% """, 'ThirdParty') diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b3c8d1905..0a3d3f855 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -165,7 +165,7 @@ parts: patches: source: https://github.com/desktop-app/patches.git source-depth: 1 - source-commit: 20a7c5ffd8265fc6e45203ea2536f7b1965be19a + source-commit: 6898f0d215f249917c076f00d3fc954a43f35e6a plugin: dump override-pull: | craftctl default From 675ee9088ff2a7f0bc3c6b0aa472204b358cce23 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 15:24:29 +0200 Subject: [PATCH 057/163] Fix build on Windows. --- Telegram/build/prepare/prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 0658703b7..ea305575e 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -621,7 +621,7 @@ win64: winarm: perl Configure no-shared no-tests debug-VC-WIN64-ARM /FS win: - jom -j%NUMBER_OF_PROCESSORS% + jom -j%NUMBER_OF_PROCESSORS% build_libs mkdir out.dbg move libcrypto.lib out.dbg move libssl.lib out.dbg @@ -637,7 +637,7 @@ win64_release: winarm_release: perl Configure no-shared no-tests VC-WIN64-ARM /FS win_release: - jom -j%NUMBER_OF_PROCESSORS% + jom -j%NUMBER_OF_PROCESSORS% build_libs mkdir out move libcrypto.lib out move libssl.lib out @@ -733,7 +733,7 @@ mac: """) stage('gas-preprocessor', """ -winarm: +win: git clone https://github.com/FFmpeg/gas-preprocessor cd gas-preprocessor echo @echo off > cpp.bat From 61ca619db40be13c7725e00069f78ff17e4c8bad Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 16:20:18 +0200 Subject: [PATCH 058/163] Fix build on macOS. --- .../info/statistics/info_statistics_list_controllers.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index a9649fb4d..9c2a692e7 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -948,7 +948,6 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { delegate()->peerListUpdateRow(row); }, }; - using Type = Data::CreditsHistoryEntry::PeerType; if (const auto peerId = PeerId(item.barePeerId)) { const auto peer = session().data().peer(peerId); return std::make_unique<CreditsRow>(peer, descriptor); From 2cd6bfef067dcf351db1dbc472ed780ea99aaa1c Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 16:20:45 +0200 Subject: [PATCH 059/163] Fix H265 in webrtc. --- Telegram/cmake/lib_tgcalls.cmake | 1 + cmake | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 3f6938e72..018a7cfb0 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -221,6 +221,7 @@ PUBLIC TGCALLS_USE_STD_OPTIONAL PRIVATE WEBRTC_APP_TDESKTOP + RTC_ENABLE_H265 RTC_ENABLE_VP9 ) diff --git a/cmake b/cmake index 37d42a722..3fe312335 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 37d42a72255aabde49deafb02904f58f06f4a97d +Subproject commit 3fe312335453073028a7bc5e7b3d65f42011e34a From 11c45b03420f6d7c803c36d8b1d98359a75bd7ae Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 17:11:42 +0200 Subject: [PATCH 060/163] Update submodules. --- Telegram/ThirdParty/tgcalls | 2 +- Telegram/lib_webrtc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 0c23e3803..2dc886d05 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 0c23e3803bc29e115f886308275bf0e3a313bc58 +Subproject commit 2dc886d05e4b7848dd7964b8e89bafdce905ba4f diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 81d01697c..839d9f0aa 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 81d01697c2ee2b46c5e5c41ce1176c230264e4cb +Subproject commit 839d9f0aade5e20a03d36caceec22ed6c8c7a63f From f123a9e16cff1bb17390b1d3dcb3f484f9fb2242 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 23:32:12 +0800 Subject: [PATCH 061/163] Add openh264 to Dockerfile. --- Telegram/build/docker/centos_env/Dockerfile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index b5f7c86cd..d60e6d403 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -147,6 +147,17 @@ RUN git clone -b 1.4.1 --depth=1 {{ GIT }}/videolan/dav1d.git \ && cd .. \ && rm -rf dav1d +FROM builder AS openh264 +RUN git clone -b v2.4.1 --depth=1 {{ GIT }}/cisco/openh264.git \ + && cd openh264 \ + && meson build \ + --buildtype=plain \ + --default-library=both \ + && meson compile -C build \ + && DESTDIR="{{ LibrariesPath }}/openh264-cache" meson install -C build \ + && cd .. \ + && rm -rf openh264 + FROM builder AS libde265 RUN git clone -b v1.0.15 --depth=1 {{ GIT }}/strukturag/libde265.git \ && cd libde265 \ @@ -742,6 +753,7 @@ RUN git clone -b v2023.06.01 --depth=1 https://chromium.googlesource.com/breakpa FROM builder AS webrtc COPY --link --from=opus {{ LibrariesPath }}/opus-cache / +COPY --link --from=openh264 {{ LibrariesPath }}/openh264-cache / COPY --link --from=libvpx {{ LibrariesPath }}/libvpx-cache / COPY --link --from=libjxl {{ LibrariesPath }}/libjxl-cache / COPY --link --from=ffmpeg {{ LibrariesPath }}/ffmpeg-cache / @@ -767,6 +779,7 @@ RUN git init tg_owt \ -DTG_OWT_OPENSSL_INCLUDE_PATH=/usr/local/include \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ -DTG_OWT_LIBVPX_INCLUDE_PATH=/usr/local/include \ + -DTG_OWT_OPENH264_INCLUDE_PATH=/usr/local/include \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include WORKDIR tg_owt @@ -791,6 +804,7 @@ COPY --link --from=brotli {{ LibrariesPath }}/brotli-cache / COPY --link --from=highway {{ LibrariesPath }}/highway-cache / COPY --link --from=opus {{ LibrariesPath }}/opus-cache / COPY --link --from=dav1d {{ LibrariesPath }}/dav1d-cache / +COPY --link --from=openh264 {{ LibrariesPath }}/openh264-cache / COPY --link --from=libde265 {{ LibrariesPath }}/libde265-cache / COPY --link --from=libvpx {{ LibrariesPath }}/libvpx-cache / COPY --link --from=libavif {{ LibrariesPath }}/libavif-cache / From 2f22a8f46b6aabd45308ca54acd36cfb92215e90 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 18 Jul 2024 10:33:58 +0200 Subject: [PATCH 062/163] Add Windows on ARM to build scripts. --- Telegram/SourceFiles/_other/packer.cpp | 4 +- Telegram/SourceFiles/core/update_checker.cpp | 1 + Telegram/build/build.bat | 67 ++++++++++++++------ Telegram/build/deploy.sh | 35 +++++++++- Telegram/build/release.py | 14 ++++ Telegram/build/setup.iss | 9 ++- Telegram/lib_base | 2 +- 7 files changed, 108 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 156ba726a..e563e1675 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -155,6 +155,7 @@ int main(int argc, char *argv[]) QString remove; int version = 0; [[maybe_unused]] bool targetwin64 = false; + [[maybe_unused]] bool targetwinarm = false; [[maybe_unused]] bool targetarmac = false; QFileInfoList files; for (int i = 0; i < argc; ++i) { @@ -165,6 +166,7 @@ int main(int argc, char *argv[]) if (remove.isEmpty()) remove = info.canonicalPath() + "/"; } else if (string("-target") == argv[i] && i + 1 < argc) { targetwin64 = (string("win64") == argv[i + 1]); + targetwinarm = (string("winarm") == argv[i + 1]); } else if (string("-arch") == argv[i] && i + 1 < argc) { targetarmac = (string("arm64") == argv[i + 1]); if (!targetarmac && string("x86_64") != argv[i + 1]) { @@ -493,7 +495,7 @@ int main(int argc, char *argv[]) cout << "Signature verified!\n"; RSA_free(pbKey); #ifdef Q_OS_WIN - QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version)); + QString outName((targetwinarm ? QString("tarm64upd%1") : targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version)); #elif defined Q_OS_MAC QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version)); #else diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index fe95f60db..777dde80c 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -245,6 +245,7 @@ QString FindUpdateFile() { "^(" "tupdate|" "tx64upd|" + "tarm64upd|" "tmacupd|" "tarmacupd|" "tlinuxupd|" diff --git a/Telegram/build/build.bat b/Telegram/build/build.bat index 3f3b65caa..6dc539c61 100644 --- a/Telegram/build/build.bat +++ b/Telegram/build/build.bat @@ -14,42 +14,54 @@ if not exist "%FullScriptPath%..\..\..\DesktopPrivate" ( FOR /F "tokens=1* delims= " %%i in (%FullScriptPath%target) do set "BuildTarget=%%i" -if "%BuildTarget%" equ "uwp" ( - set "BuildUWP=1" -) else if "%BuildTarget%" equ "uwp64" ( - set "BuildUWP=1" -) else ( - set "BuildUWP=0" -) - +set "Build64=0" +set "BuildARM=0" +set "BuildUWP=0" if "%BuildTarget%" equ "win64" ( set "Build64=1" +) else if "%BuildTarget%" equ "winarm" ( + set "BuildARM=1" +) else if "%BuildTarget%" equ "uwp" ( + set "BuildUWP=1" ) else if "%BuildTarget%" equ "uwp64" ( set "Build64=1" -) else ( - set "Build64=0" + set "BuildUWP=1" +) else if "%BuildTarget%" equ "uwparm" ( + set "BuildARM=1" + set "BuildUWP=1" ) if %Build64% neq 0 ( if "%Platform%" neq "x64" ( - echo Bad environment. Make sure to run from 'x64 Native Tools Command Prompt for VS 2019'. + echo Bad environment. Make sure to run from 'x64 Native Tools Command Prompt for VS 2022'. exit /b ) else if "%VSCMD_ARG_HOST_ARCH%" neq "x64" ( - echo Bad environment. Make sure to run from 'x64 Native Tools Command Prompt for VS 2019'. + echo Bad environment. Make sure to run from 'x64 Native Tools Command Prompt for VS 2022'. exit /b ) else if "%VSCMD_ARG_TGT_ARCH%" neq "x64" ( - echo Bad environment. Make sure to run from 'x64 Native Tools Command Prompt for VS 2019'. + echo Bad environment. Make sure to run from 'x64 Native Tools Command Prompt for VS 2022'. + exit /b + ) +) else if %BuildARM% neq 0 ( + if "%Platform%" neq "arm64" ( + echo Bad environment. Make sure to run from 'ARM64 Native Tools Command Prompt for VS 2022'. + exit /b + ) else if "%VSCMD_ARG_HOST_ARCH%" neq "arm64" ( + echo Bad environment. Make sure to run from 'ARM64 Native Tools Command Prompt for VS 2022'. + exit /b + ) else if "%VSCMD_ARG_TGT_ARCH%" neq "arm64" ( + echo Bad environment. Make sure to run from 'ARM64 Native Tools Command Prompt for VS 2022'. exit /b ) ) else ( if "%Platform%" neq "x86" ( - echo Bad environment. Make sure to run from 'x86 Native Tools Command Prompt for VS 2019'. + echo Bad environment. Make sure to run from 'x86 Native Tools Command Prompt for VS 2022'. exit /b ) else if "%VSCMD_ARG_HOST_ARCH%" neq "x86" ( - echo Bad environment. Make sure to run from 'x86 Native Tools Command Prompt for VS 2019'. + echo Bad environment. Make sure to run from 'x86 Native Tools Command Prompt for VS 2022'. exit /b ) else if "%VSCMD_ARG_TGT_ARCH%" neq "x86" ( - echo Bad environment. Make sure to run from 'x86 Native Tools Command Prompt for VS 2019'. + echo Bad environment. Make sure to run from 'x86 Native Tools Command Prompt for VS 2022'. exit /b ) ) @@ -76,12 +88,16 @@ echo. if %BuildUWP% neq 0 ( if %Build64% neq 0 ( echo Building version %AppVersionStrFull% for UWP 64 bit.. + ) else if %BuildARM% neq 0 ( + echo Building version %AppVersionStrFull% for UWP ARM.. ) else ( echo Building version %AppVersionStrFull% for UWP.. ) ) else ( if %Build64% neq 0 ( echo Building version %AppVersionStrFull% for Windows 64 bit.. + ) else if %BuildARM% neq 0 ( + echo Building version %AppVersionStrFull% for Windows on ARM.. ) else ( echo Building version %AppVersionStrFull% for Windows.. ) @@ -96,6 +112,11 @@ if %Build64% neq 0 ( set "SetupFile=tsetup-x64.%AppVersionStrFull%.exe" set "PortableFile=tportable-x64.%AppVersionStrFull%.zip" set "DumpSymsPath=%SolutionPath%\..\..\Libraries\win64\breakpad\src\tools\windows\dump_syms\Release\dump_syms.exe" +) else if %BuildARM% neq 0 ( + set "UpdateFile=tarm64upd%AppVersion%" + set "SetupFile=tsetup-arm64.%AppVersionStrFull%.exe" + set "PortableFile=tportable-arm64.%AppVersionStrFull%.zip" + set "DumpSymsPath=%SolutionPath%\..\..\Libraries\breakpad\src\tools\windows\dump_syms\Release\dump_syms.exe" ) else ( set "UpdateFile=tupdate%AppVersion%" set "SetupFile=tsetup.%AppVersionStrFull%.exe" @@ -210,7 +231,11 @@ if %BuildUWP% equ 0 ( if not exist "%SetupFile%" goto error ) - call Packer.exe -version %VersionForPacker% -path %BinaryName%.exe -path Updater.exe -path "modules\%Platform%\d3d\d3dcompiler_47.dll" -target %BuildTarget% %AlphaBetaParam% + if %BuildARM% neq 0 ( + call Packer.exe -version %VersionForPacker% -path %BinaryName%.exe -path Updater.exe -target %BuildTarget% %AlphaBetaParam% + ) else ( + call Packer.exe -version %VersionForPacker% -path %BinaryName%.exe -path Updater.exe -path "modules\%Platform%\d3d\d3dcompiler_47.dll" -target %BuildTarget% %AlphaBetaParam% + ) if %errorlevel% neq 0 goto error if %AlphaVersion% neq 0 ( @@ -309,10 +334,12 @@ if %BuildUWP% neq 0 ( if %errorlevel% neq 0 goto error ) -if %Build64% equ 0 ( - set "FinalDeployPath=%FinalReleasePath%\%AppVersionStrMajor%\%AppVersionStrFull%\tsetup" -) else ( +if %Build64% neq 0 ( set "FinalDeployPath=%FinalReleasePath%\%AppVersionStrMajor%\%AppVersionStrFull%\tx64" +) else if %BuildARM% neq 0 ( + set "FinalDeployPath=%FinalReleasePath%\%AppVersionStrMajor%\%AppVersionStrFull%\tarm64" +) else ( + set "FinalDeployPath=%FinalReleasePath%\%AppVersionStrMajor%\%AppVersionStrFull%\tsetup" ) if %BuildUWP% equ 0 ( diff --git a/Telegram/build/deploy.sh b/Telegram/build/deploy.sh index 23e8143e3..21183fa38 100755 --- a/Telegram/build/deploy.sh +++ b/Telegram/build/deploy.sh @@ -49,6 +49,7 @@ HomePath="$FullScriptPath/.." DeployMac="0" DeployWin="0" DeployWin64="0" +DeployWinArm="0" DeployLinux="0" if [ "$DeployTarget" == "mac" ]; then DeployMac="1" @@ -59,6 +60,9 @@ elif [ "$DeployTarget" == "win" ]; then elif [ "$DeployTarget" == "win64" ]; then DeployWin64="1" echo "Deploying version $AppVersionStrFull for Windows 64 bit.." +elif [ "$DeployTarget" == "winarm" ]; then + DeployWinArm="1" + echo "Deploying version $AppVersionStrFull for Windows on ARM.." elif [ "$DeployTarget" == "linux" ]; then DeployLinux="1" echo "Deploying version $AppVersionStrFull for Linux 64 bit.." @@ -66,8 +70,9 @@ else DeployMac="1" DeployWin="1" DeployWin64="1" + DeployWinArm="1" DeployLinux="1" - echo "Deploying four versions of $AppVersionStrFull: for Windows 32 bit, Windows 64 bit, macOS and Linux 64 bit.." + echo "Deploying five versions of $AppVersionStrFull: for Windows 32 bit, Windows 64 bit, Windows on ARM, macOS and Linux 64 bit.." fi if [ "$BuildTarget" == "mac" ]; then BackupPath="$HOME/Projects/backup/tdesktop" @@ -94,6 +99,11 @@ Win64UpdateFile="tx64upd$AppVersion" Win64SetupFile="tsetup-x64.$AppVersionStrFull.exe" Win64PortableFile="tportable-x64.$AppVersionStrFull.zip" Win64RemoteFolder="tx64" +WinArmDeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tarm64" +WinArmUpdateFile="tarm64upd$AppVersion" +WinArmSetupFile="tsetup-arm64.$AppVersionStrFull.exe" +WinArmPortablefile="tportable-arm64.$AppVersionStrFull.zip" +WinArmRemoteFolder="tarm64" LinuxDeployPath="$BackupPath/$AppVersionStrMajor/$AppVersionStrFull/tlinux" LinuxUpdateFile="tlinuxupd$AppVersion" LinuxSetupFile="tsetup.$AppVersionStrFull.tar.xz" @@ -105,6 +115,8 @@ if [ "$AlphaVersion" != "0" ]; then AlphaFilePath="$WinDeployPath/$AlphaKeyFile" elif [ "$DeployTarget" == "win64" ]; then AlphaFilePath="$Win64DeployPath/$AlphaKeyFile" + elif [ "$DeployTarget" == "winarm" ]; then + AlphaFilePath="$WinArmDeployPath/$AlphaKeyFile" elif [ "$DeployTarget" == "linux" ]; then AlphaFilePath="$LinuxDeployPath/$AlphaKeyFile" else @@ -125,6 +137,8 @@ if [ "$AlphaVersion" != "0" ]; then WinPortableFile="talpha${AlphaVersion}_${AlphaSignature}.zip" Win64UpdateFile="${Win64UpdateFile}_${AlphaSignature}" Win64PortableFile="talpha${AlphaVersion}_${AlphaSignature}.zip" + WinArmUpdateFile="${WinArmUpdateFile}_${AlphaSignature}" + WinArmPortablefile="talpha${AlphaVersion}_${AlphaSignature}.zip" LinuxUpdateFile="${LinuxUpdateFile}_${AlphaSignature}" LinuxSetupFile="talpha${AlphaVersion}_${AlphaSignature}.tar.xz" fi @@ -166,6 +180,19 @@ if [ "$DeployWin64" == "1" ]; then Error "$Win64PortableFile not found!" fi fi +if [ "$DeployWinArm" == "1" ]; then + if [ ! -f "$WinArmDeployPath/$WinArmUpdateFile" ]; then + Error "$WinArmUpdateFile not found!" + fi + if [ "$AlphaVersion" == "0" ]; then + if [ ! -f "$WinArmDeployPath/$WinArmSetupFile" ]; then + Error "$WinArmSetupFile not found!" + fi + fi + if [ ! -f "$WinArmDeployPath/$WinArmPortableFile" ]; then + Error "$WinArmPortableFile not found!" + fi +fi if [ "$DeployLinux" == "1" ]; then if [ ! -f "$LinuxDeployPath/$LinuxUpdateFile" ]; then Error "$LinuxDeployPath/$LinuxUpdateFile not found!" @@ -193,6 +220,12 @@ if [ "$DeployWin64" == "1" ]; then Files+=("tx64/$Win64SetupFile") fi fi +if [ "$DeployWinArm" == "1" ]; then + Files+=("tarm64/$WinArmUpdateFile" "tarm64/$WinArmPortableFile") + if [ "$AlphaVersion" == "0" ]; then + Files+=("tarm64/$WinArmSetupFile") + fi +fi if [ "$DeployLinux" == "1" ]; then Files+=("tlinux/$LinuxUpdateFile" "tlinux/$LinuxSetupFile") fi diff --git a/Telegram/build/release.py b/Telegram/build/release.py index 4bcf93fe9..4e6f38352 100644 --- a/Telegram/build/release.py +++ b/Telegram/build/release.py @@ -220,6 +220,20 @@ files.append({ 'mime': 'application/zip', 'label': 'Windows 64 bit: Portable', }) +files.append({ + 'local': 'tsetup-arm64.' + version_full + '.exe', + 'remote': 'tsetup-arm64.' + version_full + '.exe', + 'backup_folder': 'tarm64', + 'mime': 'application/octet-stream', + 'label': 'Windows on ARM: Installer', +}) +files.append({ + 'local': 'tportable-arm64.' + version_full + '.zip', + 'remote': 'tportable-arm64.' + version_full + '.zip', + 'backup_folder': 'tarm64', + 'mime': 'application/zip', + 'label': 'Windows on ARM: Portable', +}) files.append({ 'local': 'tsetup.' + version_full + '.dmg', 'remote': 'tsetup.' + version_full + '.dmg', diff --git a/Telegram/build/setup.iss b/Telegram/build/setup.iss index 5c5ee7635..b0dd43f65 100644 --- a/Telegram/build/setup.iss +++ b/Telegram/build/setup.iss @@ -36,7 +36,12 @@ DisableProgramGroupPage=no WizardStyle=modern SignTool=sha256 -#if MyBuildTarget == "win64" +#if MyBuildTarget == "winarm" + ArchitecturesAllowed="arm64" + OutputBaseFilename=tsetup-arm64.{#MyAppVersionFull} + #define ArchModulesFolder "arm64" + AppVerName={#MyAppName} {#MyAppVersion} arm64 +#elif MyBuildTarget == "win64" ArchitecturesAllowed="x64 arm64" ArchitecturesInstallIn64BitMode="x64 arm64" OutputBaseFilename=tsetup-x64.{#MyAppVersionFull} @@ -68,7 +73,9 @@ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescrip [Files] Source: "{#ReleasePath}\Telegram.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#ReleasePath}\Updater.exe"; DestDir: "{app}"; Flags: ignoreversion +#if MyBuildTarget != "winarm" Source: "{#ReleasePath}\{#ModulesFolder}\d3d\d3dcompiler_47.dll"; DestDir: "{app}\{#ModulesFolder}\d3d"; Flags: ignoreversion +#endif ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] diff --git a/Telegram/lib_base b/Telegram/lib_base index 54639131b..4ac8cf9d6 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 54639131bf1e5dce87c10dba45062cfec804e343 +Subproject commit 4ac8cf9d65e47efa9d2022939c6d0c38f32d9c7a From 8a6a749296e58b684b14b2a7117cb70bbb10a5de Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 18 Jul 2024 10:47:50 +0200 Subject: [PATCH 063/163] Show error on attempt to scan QR in bot app. Fixes #26886. --- Telegram/Resources/langs/lang.strings | 1 + .../ui/chat/attach/attach_bot_webview.cpp | 15 +++++++++++++++ .../ui/chat/attach/attach_bot_webview.h | 1 + 3 files changed, 17 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c692195bc..d3427dce9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3160,6 +3160,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_close_warning_sure" = "Close anyway"; "lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time."; "lng_bot_add_to_side_menu_done" = "Bot added to the main menu."; +"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps."; "lng_typing" = "typing"; "lng_user_typing" = "{user} is typing"; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index cbc27c0b8..cb922a4ff 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -694,6 +694,8 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { openInvoice(arguments); } else if (command == "web_app_open_popup") { openPopup(arguments); + } else if (command == "web_app_open_scan_qr_popup") { + openScanQrPopup(arguments); } else if (command == "web_app_request_write_access") { requestWriteAccess(); } else if (command == "web_app_request_phone") { @@ -918,6 +920,19 @@ void Panel::openPopup(const QJsonObject &args) { } } +void Panel::openScanQrPopup(const QJsonObject &args) { + const auto widget = _webview->window.widget(); + [[maybe_unused]] const auto ok = Webview::ShowBlockingPopup({ + .parent = widget ? widget->window() : nullptr, + .text = tr::lng_bot_no_scan_qr(tr::now), + .buttons = { { + .id = "ok", + .text = tr::lng_box_ok(tr::now), + .type = Webview::PopupArgs::Button::Type::Ok, + }}, + }); +} + void Panel::requestWriteAccess() { if (_inBlockingRequest) { replyRequestWriteAccess(false); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index d9071c846..e25483b89 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -132,6 +132,7 @@ private: void openExternalLink(const QJsonObject &args); void openInvoice(const QJsonObject &args); void openPopup(const QJsonObject &args); + void openScanQrPopup(const QJsonObject &args); void requestWriteAccess(); void replyRequestWriteAccess(bool allowed); void requestPhone(); From 730c968b1efb245871eba5b2e771ebb969ec395d Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 18 Jul 2024 13:21:14 +0200 Subject: [PATCH 064/163] Use always 1:1 ratio in limits boxes. Otherwise "Free" label doesn't fit in 2:20 shareable folders. --- Telegram/SourceFiles/boxes/premium_limits_box.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp index 451957c21..8febb5f42 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp @@ -418,7 +418,9 @@ void SimpleLimitBox( BoxShowFinishes(box), 0, descriptor.current, - descriptor.premiumLimit, + (descriptor.complexRatio + ? descriptor.premiumLimit + : 2 * descriptor.current), premiumPossible, descriptor.phrase, descriptor.icon); @@ -769,7 +771,7 @@ void FilterLinksLimitBox( premiumLimit, &st::premiumIconChats, std::nullopt, - true }); + /*true */}); // Don't use real ratio, "Free" doesn't fit. } @@ -856,7 +858,7 @@ void ShareableFiltersLimitBox( premiumLimit, &st::premiumIconFolders, std::nullopt, - true }); + /*true*/ }); // Don't use real ratio, "Free" doesn't fit. } void FilterPinsLimitBox( From 484c647b5b22ad344fe59d6309905dc324a35914 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 18 Jul 2024 13:55:13 +0200 Subject: [PATCH 065/163] Fix bot about text layout on wide windows. --- Telegram/SourceFiles/history/view/history_view_message.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 0d93ee0b6..4c6d4970d 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -929,9 +929,6 @@ QSize Message::performCountOptimalSize() { - st::msgPadding.left() - st::msgPadding.right(); if (withVisibleText) { - if (botTop) { - minHeight += botTop->height; - } if (maxWidth < textualWidth) { minHeight -= text().minHeight(); minHeight += text().countHeight(innerWidth); From f8b756d4470749a9a07d6112a118ee5e48e4e380 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 18 Jul 2024 16:41:18 +0200 Subject: [PATCH 066/163] Improve bot checkout error messages. --- Telegram/Resources/langs/lang.strings | 4 + .../SourceFiles/boxes/send_credits_box.cpp | 30 +++- .../view/media/history_view_media_common.cpp | 21 +-- .../inline_bots/bot_attach_web_view.cpp | 34 +++- .../inline_bots/bot_attach_web_view.h | 7 + .../payments/payments_checkout_process.cpp | 10 ++ .../payments/payments_checkout_process.h | 11 +- .../payments/payments_non_panel_process.cpp | 159 ++++++++++-------- .../payments/payments_non_panel_process.h | 17 ++ .../SourceFiles/settings/settings_credits.cpp | 2 +- .../settings/settings_credits_graphics.cpp | 32 ++-- .../settings/settings_credits_graphics.h | 8 +- .../ui/chat/attach/attach_bot_webview.cpp | 6 +- 13 files changed, 221 insertions(+), 120 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d3427dce9..6c9828189 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3161,6 +3161,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time."; "lng_bot_add_to_side_menu_done" = "Bot added to the main menu."; "lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps."; +"lng_bot_click_to_start" = "Click here to use this bot."; "lng_typing" = "typing"; "lng_user_typing" = "{user} is typing"; @@ -3757,6 +3758,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_card_declined" = "Your card was declined."; "lng_payments_payment_failed" = "Payment failed. Your card has not been billed."; "lng_payments_precheckout_failed" = "The bot couldn't process your payment. Your card has not been billed."; +"lng_payments_precheckout_timeout" = "The bot didn't respond in time. Your card has not been billed."; +"lng_payments_precheckout_stars_failed" = "The bot couldn't process your payment."; +"lng_payments_precheckout_stars_timeout" = "The bot didn't respond in time."; "lng_payments_already_paid" = "You have already paid for this item."; "lng_payments_terms_title" = "Terms of Service"; diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 39d7983d7..a8771cc17 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" #include "settings/settings_credits_graphics.h" +#include "ui/boxes/confirm_box.h" #include "ui/controls/userpic_button.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg. @@ -257,6 +258,8 @@ void SendCreditsBox( if (state->confirmButtonBusy.current()) { return; } + const auto show = box->uiShow(); + const auto weak = MakeWeak(box.get()); state->confirmButtonBusy = true; session->api().request( MTPpayments_SendStarsForm( @@ -264,12 +267,31 @@ void SendCreditsBox( MTP_long(form->formId), form->inputInvoice) ).done([=](auto result) { - state->confirmButtonBusy = false; - box->closeBox(); + if (weak) { + state->confirmButtonBusy = false; + box->closeBox(); + } sent(); }).fail([=](const MTP::Error &error) { - state->confirmButtonBusy = false; - box->uiShow()->showToast(error.type()); + if (weak) { + state->confirmButtonBusy = false; + } + const auto id = error.type(); + if (id == u"BOT_PRECHECKOUT_FAILED"_q) { + auto error = ::Ui::MakeInformBox( + tr::lng_payments_precheckout_stars_failed(tr::now)); + error->boxClosing() | rpl::start_with_next([=] { + if (const auto paybox = weak.data()) { + paybox->closeBox(); + } + }, error->lifetime()); + show->showBox(std::move(error)); + } else if (id == u"BOT_PRECHECKOUT_TIMEOUT"_q) { + show->showToast( + tr::lng_payments_precheckout_stars_timeout(tr::now)); + } else { + show->showToast(id); + } }).send(); }); { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index dd32fa00f..71b01fbf8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -241,19 +241,20 @@ ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item) { } } }); + const auto reactivate = controller + ? crl::guard( + controller, + [=](auto) { controller->widget()->activate(); }) + : Fn<void(Payments::CheckoutResult)>(); + const auto credits = Payments::IsCreditsInvoice(item); + const auto nonPanelPaymentFormProcess = (controller && credits) + ? Payments::ProcessNonPanelPaymentFormFactory(controller, done) + : nullptr; Payments::CheckoutProcess::Start( item, Payments::Mode::Payment, - (controller - ? crl::guard( - controller, - [=](auto) { controller->widget()->activate(); }) - : Fn<void(Payments::CheckoutResult)>()), - ((controller && Payments::IsCreditsInvoice(item)) - ? Payments::ProcessNonPanelPaymentFormFactory( - controller, - done) - : nullptr)); + reactivate, + nonPanelPaymentFormProcess); }); } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index c42ecb3b9..c8e88aed7 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -621,16 +621,38 @@ void AttachWebView::botHandleInvoice(QString slug) { }()); } }; - _panel->hideForPayment(); Payments::CheckoutProcess::Start( &_bot->session(), slug, reactivate, - _context - ? Payments::ProcessNonPanelPaymentFormFactory( - _context->controller.get(), - reactivate) - : nullptr); + nonPanelPaymentFormFactory(reactivate)); +} + +auto AttachWebView::nonPanelPaymentFormFactory( + Fn<void(Payments::CheckoutResult)> reactivate) +-> Fn<void(Payments::NonPanelPaymentForm)> { + using namespace Payments; + const auto panel = base::make_weak(_panel.get()); + const auto weak = _context ? _context->controller : nullptr; + return [=](Payments::NonPanelPaymentForm form) { + using CreditsFormDataPtr = std::shared_ptr<CreditsFormData>; + using CreditsReceiptPtr = std::shared_ptr<CreditsReceiptData>; + v::match(form, [&](const CreditsFormDataPtr &form) { + if (const auto strong = panel.get()) { + ProcessCreditsPayment( + uiShow(), + strong->toastParent().get(), + form, + reactivate); + } + }, [&](const CreditsReceiptPtr &receipt) { + if (const auto controller = weak.get()) { + ProcessCreditsReceipt(controller, receipt, reactivate); + } + }, [&](RealFormPresentedNotification) { + _panel->hideForPayment(); + }); + }; } void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) { diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 87648b07e..f9f59f1ae 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -40,6 +40,11 @@ namespace Data { class DocumentMedia; } // namespace Data +namespace Payments { +struct NonPanelPaymentForm; +enum class CheckoutResult; +} // namespace Payments + namespace InlineBots { enum class PeerType : uint8 { @@ -246,6 +251,8 @@ private: void showToast( const QString &text, Window::SessionController *controller = nullptr); + Fn<void(Payments::NonPanelPaymentForm)> nonPanelPaymentFormFactory( + Fn<void(Payments::CheckoutResult)> reactivate); const not_null<Main::Session*> _session; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index cbb435389..9a5daf9a4 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -536,6 +536,8 @@ void CheckoutProcess::handleError(const Error &error) { showToast({ tr::lng_payments_payment_failed(tr::now) }); } else if (id == u"BOT_PRECHECKOUT_FAILED"_q) { showToast({ tr::lng_payments_precheckout_failed(tr::now) }); + } else if (id == u"BOT_PRECHECKOUT_TIMEOUT"_q) { + showToast({ tr::lng_payments_precheckout_timeout(tr::now) }); } else if (id == u"REQUESTED_INFO_INVALID"_q || id == u"SHIPPING_OPTION_INVALID"_q || id == u"PAYMENT_CREDENTIALS_INVALID"_q @@ -764,6 +766,14 @@ void CheckoutProcess::showForm() { _form->information(), _form->paymentMethod().ui, _form->shippingOptions()); + if (_nonPanelPaymentFormProcess && !_realFormNotified) { + _realFormNotified = true; + const auto weak = base::make_weak(_panel.get()); + _nonPanelPaymentFormProcess(RealFormPresentedNotification()); + if (weak) { + requestActivate(); + } + } } void CheckoutProcess::showEditInformation(Ui::InformationField field) { diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 3c7f112ca..e783bba58 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -55,9 +55,13 @@ enum class CheckoutResult { Failed, }; -struct NonPanelPaymentForm : std::variant< - std::shared_ptr<CreditsFormData>, - std::shared_ptr<CreditsReceiptData>> { +struct RealFormPresentedNotification { +}; +struct NonPanelPaymentForm + : std::variant< + std::shared_ptr<CreditsFormData>, + std::shared_ptr<CreditsReceiptData>, + RealFormPresentedNotification> { using variant::variant; }; @@ -183,6 +187,7 @@ private: Fn<void(NonPanelPaymentForm)> _nonPanelPaymentFormProcess; SubmitState _submitState = SubmitState::None; bool _initialSilentValidation = false; + bool _realFormNotified = false; bool _sendFormPending = false; bool _sendFormFailed = false; diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 69cf7abde..b62987a82 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/boost_box.h" // Ui::StartFireworks. #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" +#include "window/window_controller.h" #include "window/window_session_controller.h" namespace Payments { @@ -37,84 +38,98 @@ bool IsCreditsInvoice(not_null<HistoryItem*> item) { return invoice && (invoice->currency == Ui::kCreditsCurrency); } +void ProcessCreditsPayment( + std::shared_ptr<Main::SessionShow> show, + QPointer<QWidget> fireworks, + std::shared_ptr<CreditsFormData> form, + Fn<void(CheckoutResult)> maybeReturnToBot) { + const auto lifetime = std::make_shared<rpl::lifetime>(); + const auto api = lifetime->make_state<Api::CreditsStatus>( + show->session().user()); + const auto sendBox = [=] { + const auto unsuccessful = std::make_shared<bool>(true); + const auto box = show->show(Box( + Ui::SendCreditsBox, + form, + [=] { + *unsuccessful = false; + if (const auto widget = fireworks.data()) { + Ui::StartFireworks(widget); + } + if (maybeReturnToBot) { + maybeReturnToBot(CheckoutResult::Paid); + } + })); + box->boxClosing() | rpl::start_with_next([=] { + crl::on_main([=] { + if ((*unsuccessful) && maybeReturnToBot) { + maybeReturnToBot(CheckoutResult::Cancelled); + } + }); + }, box->lifetime()); + }; + api->request({}, [=](Data::CreditsStatusSlice slice) { + show->session().setCredits(slice.balance); + const auto creditsNeeded = int64(form->invoice.credits) + - int64(slice.balance); + if (creditsNeeded <= 0) { + sendBox(); + } else if (show->session().premiumPossible()) { + show->show(Box( + Settings::SmallBalanceBox, + show, + creditsNeeded, + form->botId, + sendBox)); + } else { + show->showToast( + tr::lng_credits_purchase_blocked(tr::now)); + if (maybeReturnToBot) { + maybeReturnToBot(CheckoutResult::Failed); + } + } + lifetime->destroy(); + }); +} + +void ProcessCreditsReceipt( + not_null<Window::SessionController*> controller, + std::shared_ptr<CreditsReceiptData> receipt, + Fn<void(CheckoutResult)> maybeReturnToBot) { + const auto entry = Data::CreditsHistoryEntry{ + .id = receipt->id, + .title = receipt->title, + .description = receipt->description, + .date = base::unixtime::parse(receipt->date), + .photoId = receipt->photo ? receipt->photo->id : 0, + .credits = receipt->credits, + .bareMsgId = uint64(), + .barePeerId = receipt->peerId.value, + .peerType = Data::CreditsHistoryEntry::PeerType::Peer, + }; + controller->uiShow()->show(Box( + Settings::ReceiptCreditsBox, + controller, + nullptr, + entry)); + controller->window().activate(); +} + Fn<void(NonPanelPaymentForm)> ProcessNonPanelPaymentFormFactory( not_null<Window::SessionController*> controller, Fn<void(CheckoutResult)> maybeReturnToBot) { return [=](NonPanelPaymentForm form) { using CreditsFormDataPtr = std::shared_ptr<CreditsFormData>; using CreditsReceiptPtr = std::shared_ptr<CreditsReceiptData>; - if (const auto creditsData = std::get_if<CreditsFormDataPtr>(&form)) { - const auto form = *creditsData; - const auto lifetime = std::make_shared<rpl::lifetime>(); - const auto api = lifetime->make_state<Api::CreditsStatus>( - controller->session().user()); - const auto sendBox = [=, weak = base::make_weak(controller)] { - if (const auto strong = weak.get()) { - const auto unsuccessful = std::make_shared<bool>(true); - const auto box = controller->uiShow()->show(Box( - Ui::SendCreditsBox, - form, - crl::guard(strong, [=] { - *unsuccessful = false; - Ui::StartFireworks(strong->content()); - if (maybeReturnToBot) { - maybeReturnToBot(CheckoutResult::Paid); - } - }))); - box->boxClosing() | rpl::start_with_next([=] { - crl::on_main([=] { - if ((*unsuccessful) && maybeReturnToBot) { - maybeReturnToBot(CheckoutResult::Cancelled); - } - }); - }, box->lifetime()); - } - }; - const auto weak = base::make_weak(controller); - api->request({}, [=](Data::CreditsStatusSlice slice) { - if (const auto strong = weak.get()) { - strong->session().setCredits(slice.balance); - const auto creditsNeeded = int64(form->invoice.credits) - - int64(slice.balance); - if (creditsNeeded <= 0) { - sendBox(); - } else if (strong->session().premiumPossible()) { - strong->uiShow()->show(Box( - Settings::SmallBalanceBox, - strong, - creditsNeeded, - form->botId, - sendBox)); - } else { - strong->uiShow()->showToast( - tr::lng_credits_purchase_blocked(tr::now)); - if (maybeReturnToBot) { - maybeReturnToBot(CheckoutResult::Failed); - } - } - } - lifetime->destroy(); - }); - } - if (const auto r = std::get_if<CreditsReceiptPtr>(&form)) { - const auto receipt = *r; - const auto entry = Data::CreditsHistoryEntry{ - .id = receipt->id, - .title = receipt->title, - .description = receipt->description, - .date = base::unixtime::parse(receipt->date), - .photoId = receipt->photo ? receipt->photo->id : 0, - .credits = receipt->credits, - .bareMsgId = uint64(), - .barePeerId = receipt->peerId.value, - .peerType = Data::CreditsHistoryEntry::PeerType::Peer, - }; - controller->uiShow()->show(Box( - Settings::ReceiptCreditsBox, - controller, - nullptr, - entry)); - } + v::match(form, [&](const CreditsFormDataPtr &form) { + ProcessCreditsPayment( + controller->uiShow(), + controller->content().get(), + form, + maybeReturnToBot); + }, [&](const CreditsReceiptPtr &receipt) { + ProcessCreditsReceipt(controller, receipt, maybeReturnToBot); + }, [](RealFormPresentedNotification) {}); }; } diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.h b/Telegram/SourceFiles/payments/payments_non_panel_process.h index e8ab9375c..53a31f81c 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.h +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryItem; +namespace Main { +class SessionShow; +} // namespace Main + namespace Window { class SessionController; } // namespace Window @@ -16,10 +20,23 @@ class SessionController; namespace Payments { enum class CheckoutResult; +struct CreditsFormData; +struct CreditsReceiptData; struct NonPanelPaymentForm; [[nodiscard]] bool IsCreditsInvoice(not_null<HistoryItem*> item); +void ProcessCreditsPayment( + std::shared_ptr<Main::SessionShow> show, + QPointer<QWidget> fireworks, + std::shared_ptr<CreditsFormData> form, + Fn<void(CheckoutResult)> maybeReturnToBot = nullptr); + +void ProcessCreditsReceipt( + not_null<Window::SessionController*> controller, + std::shared_ptr<CreditsReceiptData> receipt, + Fn<void(CheckoutResult)> maybeReturnToBot = nullptr); + Fn<void(NonPanelPaymentForm)> ProcessNonPanelPaymentFormFactory( not_null<Window::SessionController*> controller, Fn<void(Payments::CheckoutResult)> maybeReturnToBot = nullptr); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 5088ce62a..e80f82a62 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -289,7 +289,7 @@ void Credits::setupContent() { Ui::StartFireworks(_parent); } }; - FillCreditOptions(_controller, content, 0, paid); + FillCreditOptions(_controller->uiShow(), content, 0, paid); setupHistory(content); Ui::ResizeFitChild(this, content); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 282945d9e..1eaef4568 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -225,7 +225,7 @@ void AddViewMediaHandler( } // namespace void FillCreditOptions( - not_null<Window::SessionController*> controller, + std::shared_ptr<Main::SessionShow> show, not_null<Ui::VerticalLayout*> container, int minimumCredits, Fn<void()> paid) { @@ -302,7 +302,7 @@ void FillCreditOptions( }, button->lifetime()); button->setClickedCallback([=] { const auto invoice = Payments::InvoiceCredits{ - .session = &controller->session(), + .session = &show->session(), .randomId = UniqueIdFromOption(option), .credits = option.credits, .product = option.product, @@ -348,18 +348,18 @@ void FillCreditOptions( using ApiOptions = Api::CreditsTopupOptions; const auto apiCredits = content->lifetime().make_state<ApiOptions>( - controller->session().user()); + show->session().user()); - if (controller->session().premiumPossible()) { + if (show->session().premiumPossible()) { apiCredits->request( ) | rpl::start_with_error_done([=](const QString &error) { - controller->showToast(error); + show->showToast(error); }, [=] { fill(apiCredits->options()); }, content->lifetime()); } - controller->session().premiumPossibleValue( + show->session().premiumPossibleValue( ) | rpl::start_with_next([=](bool premiumPossible) { if (!premiumPossible) { fill({}); @@ -739,7 +739,7 @@ object_ptr<Ui::RpWidget> PaidMediaThumbnail( void SmallBalanceBox( not_null<Ui::GenericBox*> box, - not_null<Window::SessionController*> controller, + std::shared_ptr<Main::SessionShow> show, int creditsNeeded, UserId botId, Fn<void()> paid) { @@ -750,21 +750,13 @@ void SmallBalanceBox( paid(); }; - const auto bot = controller->session().data().user(botId).get(); + const auto bot = show->session().data().user(botId).get(); const auto content = [&]() -> Ui::Premium::TopBarAbstract* { - const auto weak = base::make_weak(controller); - const auto clickContextOther = [=] { - return QVariant::fromValue(ClickHandlerContext{ - .sessionWindow = weak, - .botStartAutoSubmit = true, - }); - }; return box->setPinnedToTopContent(object_ptr<Ui::Premium::TopBar>( box, st::creditsLowBalancePremiumCover, Ui::Premium::TopBarDescriptor{ - .clickContextOther = clickContextOther, .title = tr::lng_credits_small_balance_title( lt_count, rpl::single(creditsNeeded) | tr::to_count()), @@ -777,7 +769,7 @@ void SmallBalanceBox( })); }(); - FillCreditOptions(controller, box->verticalLayout(), creditsNeeded, done); + FillCreditOptions(show, box->verticalLayout(), creditsNeeded, done); content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight); content->setMinimumHeight(st::infoLayerTopBarHeight); @@ -796,12 +788,12 @@ void SmallBalanceBox( { const auto balance = AddBalanceWidget( content, - controller->session().creditsValue(), + show->session().creditsValue(), true); const auto api = balance->lifetime().make_state<Api::CreditsStatus>( - controller->session().user()); + show->session().user()); api->request({}, [=](Data::CreditsStatusSlice slice) { - controller->session().setCredits(slice.balance); + show->session().setCredits(slice.balance); }); rpl::combine( balance->sizeValue(), diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 406bfabca..47d5323c7 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -16,6 +16,10 @@ namespace Data { struct CreditsHistoryEntry; } // namespace Data +namespace Main { +class SessionShow; +} // namespace Main + namespace Window { class SessionController; } // namespace Window @@ -29,7 +33,7 @@ class VerticalLayout; namespace Settings { void FillCreditOptions( - not_null<Window::SessionController*> controller, + std::shared_ptr<Main::SessionShow> show, not_null<Ui::VerticalLayout*> container, int minCredits, Fn<void()> paid); @@ -77,7 +81,7 @@ void ShowRefundInfoBox( void SmallBalanceBox( not_null<Ui::GenericBox*> box, - not_null<Window::SessionController*> controller, + std::shared_ptr<Main::SessionShow> show, int creditsNeeded, UserId botId, Fn<void()> paid); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index cb922a4ff..ae27e4938 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -1344,8 +1344,10 @@ void Panel::invoiceClosed(const QString &slug, const QString &status) { { u"slug"_q, slug }, { u"status"_q, status }, }); - _widget->showAndActivate(); - _hiddenForPayment = false; + if (_hiddenForPayment) { + _hiddenForPayment = false; + _widget->showAndActivate(); + } } void Panel::hideForPayment() { From 4d647e64b79ffab9d2d7708c84b86cfe6036375f Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 18 Jul 2024 17:14:47 +0200 Subject: [PATCH 067/163] Fix chat links with '-' in slug. Fixes https://bugs.telegram.org/c/41902. --- Telegram/SourceFiles/core/local_url_handlers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 9f4860da3..675bb5edc 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -1268,7 +1268,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() { ResolveBoost, }, { - u"^message/?\\?slug=([a-zA-Z0-9\\.\\_]+)(&|$)"_q, + u"^message/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q, ResolveChatLink }, { From a847969e9c88bcd37e9ea31ed881980531003544 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 18 Jul 2024 17:59:12 +0200 Subject: [PATCH 068/163] Attempt to fix bad input field layout on Qt 6. --- Telegram/SourceFiles/chat_helpers/message_field.cpp | 2 +- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 5d8be6144..8ee5170fb 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -525,7 +525,7 @@ void InitMessageFieldGeometry(not_null<Ui::InputField*> field) { st::historySendSize.height() - 2 * st::historySendPadding); field->setMaxHeight(st::historyComposeFieldMaxHeight); - field->document()->setDocumentMargin(4.); + field->setDocumentMargin(4.); field->setAdditionalMargin(style::ConvertScale(4) - 4); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 58329bfe1..007e3a519 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 58329bfe19c031ec6065a3a098743bb3d7732a64 +Subproject commit 007e3a519ffbad0bd889de6cb4b19f34959afbce From a2fa1a52e233b15c9e6095a0e266243ed029fa27 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 19 Jul 2024 11:53:49 +0200 Subject: [PATCH 069/163] Pass VideoCaptureOptions on Linux. --- Telegram/ThirdParty/tgcalls | 2 +- Telegram/lib_webrtc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 2dc886d05..ce592ecab 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 2dc886d05e4b7848dd7964b8e89bafdce905ba4f +Subproject commit ce592ecab26770fecca683a05f4611d1c00113ef diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 839d9f0aa..5d44a8acc 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 839d9f0aade5e20a03d36caceec22ed6c8c7a63f +Subproject commit 5d44a8acc341d5f45c4150f07929484d15b9c5ba From 9461095c88c4f3e3188427ba4e6054eafdb29041 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 19 Jul 2024 13:00:51 +0300 Subject: [PATCH 070/163] Fix build with GCC. --- Telegram/SourceFiles/api/api_sending.cpp | 6 ----- .../inline_bots/bot_attach_web_view.cpp | 2 +- Telegram/build/docker/centos_env/Dockerfile | 22 +++++++++++++------ Telegram/cmake/lib_tgcalls.cmake | 1 + cmake | 2 +- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index bf4d472fe..c05a42e95 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -80,18 +80,12 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } - const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); InnerFillMessagePostFlags(action.options, peer, flags); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } const auto sendAs = action.options.sendAs; - const auto messageFromId = sendAs - ? sendAs->id - : anonymousPost - ? 0 - : session->userPeerId(); if (sendAs) { sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index c8e88aed7..b701fe21a 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1684,7 +1684,7 @@ std::shared_ptr<Main::SessionShow> AttachWebView::uiShow() { using UniqueLayer = std::unique_ptr<Ui::LayerWidget>; using ObjectBox = object_ptr<Ui::BoxContent>; const auto panel = _that ? _that->_panel.get() : nullptr; - if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) { + if (v::is<UniqueLayer>(layer)) { Unexpected("Layers in AttachWebView are not implemented."); } else if (auto box = std::get_if<ObjectBox>(&layer)) { if (panel) { diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index d60e6d403..0091881ed 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -1,5 +1,6 @@ {%- set GIT = "https://github.com" -%} {%- set GIT_FREEDESKTOP = GIT ~ "/gitlab-freedesktop-mirrors" -%} +{%- set GIT_UPDATE_M4 = "git submodule set-url m4 https://gitlab.freedesktop.org/xorg/util/xcb-util-m4 && git config -f .gitmodules submodule.m4.shallow true && git submodule init && git submodule update" -%} {%- set QT = "6.7.2" -%} {%- set QT_TAG = "v" ~ QT -%} {%- set CFLAGS_DEBUG = "-g -pipe -fPIC -fstack-protector-all -fstack-clash-protection -fcf-protection -D_GLIBCXX_ASSERTIONS" -%} @@ -313,8 +314,9 @@ RUN git clone -b libxcb-1.16 --depth=1 {{ GIT_FREEDESKTOP }}/libxcb.git \ && rm -rf libxcb FROM builder AS xcb-wm -RUN git clone -b xcb-util-wm-0.4.2 --depth=1 --recursive --shallow-submodules {{ GIT_FREEDESKTOP }}/libxcb-wm.git \ +RUN git clone -b xcb-util-wm-0.4.2 --depth=1 {{ GIT_FREEDESKTOP }}/libxcb-wm.git \ && cd libxcb-wm \ + && {{ GIT_UPDATE_M4 }} \ && ./autogen.sh --enable-static \ && make -j$(nproc) \ && make DESTDIR="{{ LibrariesPath }}/xcb-wm-cache" install \ @@ -322,8 +324,9 @@ RUN git clone -b xcb-util-wm-0.4.2 --depth=1 --recursive --shallow-submodules {{ && rm -rf libxcb-wm FROM builder AS xcb-util -RUN git clone -b xcb-util-0.4.1 --depth=1 --recursive --shallow-submodules {{ GIT_FREEDESKTOP }}/libxcb-util.git \ +RUN git clone -b xcb-util-0.4.1 --depth=1 {{ GIT_FREEDESKTOP }}/libxcb-util.git \ && cd libxcb-util \ + && {{ GIT_UPDATE_M4 }} \ && ./autogen.sh --enable-static \ && make -j$(nproc) \ && make DESTDIR="{{ LibrariesPath }}/xcb-util-cache" install \ @@ -333,8 +336,9 @@ RUN git clone -b xcb-util-0.4.1 --depth=1 --recursive --shallow-submodules {{ GI FROM builder AS xcb-image COPY --link --from=xcb-util {{ LibrariesPath }}/xcb-util-cache / -RUN git clone -b xcb-util-image-0.4.1 --depth=1 --recursive --shallow-submodules {{ GIT_FREEDESKTOP }}/libxcb-image.git \ +RUN git clone -b xcb-util-image-0.4.1 --depth=1 {{ GIT_FREEDESKTOP }}/libxcb-image.git \ && cd libxcb-image \ + && {{ GIT_UPDATE_M4 }} \ && ./autogen.sh --enable-static \ && make -j$(nproc) \ && make DESTDIR="{{ LibrariesPath }}/xcb-image-cache" install \ @@ -342,8 +346,9 @@ RUN git clone -b xcb-util-image-0.4.1 --depth=1 --recursive --shallow-submodules && rm -rf libxcb-image FROM builder AS xcb-keysyms -RUN git clone -b xcb-util-keysyms-0.4.1 --depth=1 --recursive --shallow-submodules {{ GIT_FREEDESKTOP }}/libxcb-keysyms.git \ +RUN git clone -b xcb-util-keysyms-0.4.1 --depth=1 {{ GIT_FREEDESKTOP }}/libxcb-keysyms.git \ && cd libxcb-keysyms \ + && {{ GIT_UPDATE_M4 }} \ && ./autogen.sh --enable-static \ && make -j$(nproc) \ && make DESTDIR="{{ LibrariesPath }}/xcb-keysyms-cache" install \ @@ -351,8 +356,9 @@ RUN git clone -b xcb-util-keysyms-0.4.1 --depth=1 --recursive --shallow-submodul && rm -rf libxcb-keysyms FROM builder AS xcb-render-util -RUN git clone -b xcb-util-renderutil-0.3.10 --depth=1 --recursive --shallow-submodules {{ GIT_FREEDESKTOP }}/libxcb-render-util.git \ +RUN git clone -b xcb-util-renderutil-0.3.10 --depth=1 {{ GIT_FREEDESKTOP }}/libxcb-render-util.git \ && cd libxcb-render-util \ + && {{ GIT_UPDATE_M4 }} \ && ./autogen.sh --enable-static \ && make -j$(nproc) \ && make DESTDIR="{{ LibrariesPath }}/xcb-render-util-cache" install \ @@ -364,8 +370,9 @@ COPY --link --from=xcb-util {{ LibrariesPath }}/xcb-util-cache / COPY --link --from=xcb-image {{ LibrariesPath }}/xcb-image-cache / COPY --link --from=xcb-render-util {{ LibrariesPath }}/xcb-render-util-cache / -RUN git clone -b xcb-util-cursor-0.1.4 --depth=1 --recursive --shallow-submodules {{ GIT_FREEDESKTOP }}/libxcb-cursor.git \ +RUN git clone -b xcb-util-cursor-0.1.4 --depth=1 {{ GIT_FREEDESKTOP }}/libxcb-cursor.git \ && cd libxcb-cursor \ + && {{ GIT_UPDATE_M4 }} \ && ./autogen.sh --enable-static \ && make -j$(nproc) \ && make DESTDIR="{{ LibrariesPath }}/xcb-cursor-cache" install \ @@ -613,12 +620,13 @@ RUN git clone -b n6.1.1 --depth=1 {{ GIT }}/FFmpeg/FFmpeg.git \ && rm -rf ffmpeg FROM builder AS pipewire -RUN git clone -b 0.3.25 --depth=1 {{ GIT }}/PipeWire/pipewire.git \ +RUN git clone -b 0.3.62 --depth=1 {{ GIT }}/PipeWire/pipewire.git \ && cd pipewire \ && meson build \ --buildtype=plain \ -Dtests=disabled \ -Dexamples=disabled \ + -Dsession-managers=media-session \ -Dspa-plugins=disabled \ && meson compile -C build \ && DESTDIR="{{ LibrariesPath }}/pipewire-cache" meson install -C build \ diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 018a7cfb0..44539b54b 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -259,6 +259,7 @@ PRIVATE -Wno-ambiguous-reversed-operator -Wno-deprecated-declarations -Wno-unqualified-std-cast-call + -Wno-unused-function ) remove_target_sources(lib_tgcalls ${tgcalls_loc} diff --git a/cmake b/cmake index 3fe312335..b3871c4ef 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 3fe312335453073028a7bc5e7b3d65f42011e34a +Subproject commit b3871c4efde37827263053d95e6f16fe34d4952e From fd982b90dbb43b1dd623a67e2e9d9adccf48cc81 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 19 Jul 2024 14:06:30 +0200 Subject: [PATCH 071/163] Implement separate instances for web apps. --- Telegram/SourceFiles/api/api_bot.cpp | 21 +- Telegram/SourceFiles/core/application.cpp | 6 +- .../SourceFiles/core/click_handler_types.cpp | 10 +- .../SourceFiles/core/click_handler_types.h | 6 +- .../SourceFiles/core/local_url_handlers.cpp | 11 +- .../SourceFiles/history/history_widget.cpp | 9 +- .../inline_bots/bot_attach_web_view.cpp | 1713 ++++++++--------- .../inline_bots/bot_attach_web_view.h | 388 ++-- .../inline_bots/inline_results_inner.cpp | 12 +- Telegram/SourceFiles/mainwindow.cpp | 2 +- .../ui/chat/attach/attach_bot_webview.cpp | 21 + .../window/window_main_menu_helpers.cpp | 9 +- .../window/window_session_controller.cpp | 69 +- .../window_session_controller_link_info.h | 9 +- 14 files changed, 1202 insertions(+), 1084 deletions(-) diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 929051261..7514121eb 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -488,20 +488,23 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::WebView: { if (const auto bot = item->getMessageBot()) { - bot->session().attachWebView().request( - controller, - Api::SendAction(bot->owner().history(bot)), - bot, - { .text = button->text, .url = button->data }); + bot->session().attachWebView().open({ + .bot = bot, + .context = { .controller = controller }, + .button = { .text = button->text, .url = button->data }, + .source = InlineBots::WebViewSourceButton{ .simple = false }, + }); } } break; case ButtonType::SimpleWebView: { if (const auto bot = item->getMessageBot()) { - bot->session().attachWebView().requestSimple( - controller, - bot, - { .text = button->text, .url = button->data }); + bot->session().attachWebView().open({ + .bot = bot, + .context = { .controller = controller }, + .button = {.text = button->text, .url = button->data }, + .source = InlineBots::WebViewSourceButton{ .simple = true }, + }); } } break; } diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 79dfd0977..19f488b0b 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -231,7 +231,11 @@ Application::~Application() { // For example Domain::removeRedundantAccounts() is called from // Domain::finish() and there is a violation on Ensures(started()). Payments::CheckoutProcess::ClearAll(); - InlineBots::AttachWebView::ClearAll(); + for (const auto &[index, account] : _domain->accounts()) { + if (account->sessionExists()) { + account->session().attachWebView().closeAll(); + } + } _iv->closeAll(); _domain->finish(); diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index eadb16181..915b12f4f 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -191,11 +191,13 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const { const auto title = game->title; const auto itemId = my.itemId; const auto openGame = [=] { - bot->session().attachWebView().showGame({ + bot->session().attachWebView().open({ .bot = bot, - .context = itemId, - .url = url, - .title = title, + .button = {.url = url.toUtf8() }, + .source = InlineBots::WebViewSourceGame{ + .messageId = itemId, + .title = title, + }, }); }; if (_bot->isVerified() diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index a63594195..b3aa0bae0 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -21,6 +21,10 @@ namespace Ui { class Show; } // namespace Ui +namespace InlineBots { +struct WebViewContext; +} // namespace InlineBots + namespace Main { class Session; } // namespace Main @@ -38,10 +42,10 @@ class SessionController; class PeerData; struct ClickHandlerContext { FullMsgId itemId; - QString attachBotWebviewUrl; // Is filled from sections. Fn<HistoryView::ElementDelegate*()> elementDelegate; base::weak_ptr<Window::SessionController> sessionWindow; + std::shared_ptr<InlineBots::WebViewContext> botWebviewContext; std::shared_ptr<Ui::Show> show; bool mayShowConfirmation = false; bool skipBotAutoLogin = false; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 675bb5edc..223cec043 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -573,8 +573,11 @@ bool ResolveUsernameOrPhone( : (appname.isEmpty() && params.contains(u"startapp"_q)) ? params.value(u"startapp"_q) : std::optional<QString>()), - .attachBotMenuOpen = (appname.isEmpty() + .attachBotMainOpen = (appname.isEmpty() && params.contains(u"startapp"_q)), + .attachBotMainCompact = (appname.isEmpty() + && params.contains(u"startapp"_q) + && (params.value(u"mode"_q) == u"compact"_q)), .attachBotChooseTypes = InlineBots::ParseChooseTypes( params.value(u"choose"_q)), .voicechatHash = (params.contains(u"livestream"_q) @@ -585,7 +588,7 @@ bool ResolveUsernameOrPhone( ? std::make_optional(params.value(u"voicechat"_q)) : std::nullopt), .clickFromMessageId = myContext.itemId, - .clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl, + .clickFromBotWebviewContext = myContext.botWebviewContext, }); return true; } @@ -626,7 +629,7 @@ bool ResolvePrivatePost( } : Window::RepliesByLinkInfo{ v::null }, .clickFromMessageId = my.itemId, - .clickFromAttachBotWebviewUrl = my.attachBotWebviewUrl, + .clickFromBotWebviewContext = my.botWebviewContext, }); controller->window().activate(); return true; @@ -1178,7 +1181,7 @@ bool ResolveChatLink( controller->showPeerByLink(Window::PeerByLinkInfo{ .chatLinkSlug = match->captured(1), .clickFromMessageId = myContext.itemId, - .clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl, + .clickFromBotWebviewContext = myContext.botWebviewContext, }); return true; } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 0a3c5a064..789390bbf 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4940,7 +4940,14 @@ bool HistoryWidget::updateCmdStartShown() { const auto user = _peer ? _peer->asUser() : nullptr; const auto bot = (user && user->isBot()) ? user : nullptr; if (bot && !bot->botInfo->botMenuButtonUrl.isEmpty()) { - session().attachWebView().requestMenu(controller(), bot); + session().attachWebView().open({ + .bot = bot, + .context = { .controller = controller() }, + .button = { + .url = bot->botInfo->botMenuButtonUrl.toUtf8(), + }, + .source = InlineBots::WebViewSourceBotMenu(), + }); } else if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->hideAnimated(); } else { diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index b701fe21a..8e993f4f7 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -170,8 +170,24 @@ constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); }; } +[[nodiscard]] Window::SessionController *WindowForThread( + base::weak_ptr<Window::SessionController> weak, + not_null<Data::Thread*> thread) { + if (const auto separate = Core::App().separateWindowFor(thread)) { + return separate->sessionController(); + } + const auto strong = weak.get(); + if (strong && strong->windowId().hasChatsList()) { + strong->showThread(thread); + return strong; + } + const auto window = Core::App().ensureSeparateWindowFor(thread); + return window ? window->sessionController() : nullptr; +} + void ShowChooseBox( - not_null<Window::SessionController*> controller, + std::shared_ptr<Ui::Show> show, + not_null<Main::Session*> session, PeerTypes types, Fn<void(not_null<Data::Thread*>)> callback, rpl::producer<QString> titleOverride = nullptr) { @@ -206,26 +222,36 @@ void ShowChooseBox( box->closeBox(); }); }; - *weak = controller->show(Box<PeerListBox>( + *weak = show->show(Box<PeerListBox>( std::make_unique<ChooseRecipientBoxController>( - &controller->session(), + session, std::move(done), std::move(filter)), std::move(initBox))); } -[[nodiscard]] base::flat_set<not_null<AttachWebView*>> &ActiveWebViews() { - static auto result = base::flat_set<not_null<AttachWebView*>>(); - return result; +void ShowChooseBox( + not_null<Window::SessionController*> controller, + PeerTypes types, + Fn<void(not_null<Data::Thread*>)> callback, + rpl::producer<QString> titleOverride = nullptr) { + ShowChooseBox( + controller->uiShow(), + &controller->session(), + types, + std::move(callback), + std::move(titleOverride)); } -void FillDisclaimerBox(not_null<Ui::GenericBox*> box, Fn<void()> done) { +void FillDisclaimerBox( + not_null<Ui::GenericBox*> box, + Fn<void(bool accepted)> done) { const auto updateCheck = std::make_shared<Fn<void()>>(); const auto validateCheck = std::make_shared<Fn<bool()>>(); const auto callback = [=](Fn<void()> close) { if (validateCheck && (*validateCheck)()) { - done(); + done(true); close(); } }; @@ -236,6 +262,7 @@ void FillDisclaimerBox(not_null<Ui::GenericBox*> box, Fn<void()> done) { tr::now, Ui::Text::RichLangValue), .confirmed = callback, + .cancelled = [=](Fn<void()> close) { done(false); close(); }, .confirmText = tr::lng_box_ok(), .labelPadding = QMargins(padding.left(), 0, padding.right(), 0), .title = tr::lng_mini_apps_disclaimer_title(), @@ -291,10 +318,43 @@ void FillDisclaimerBox(not_null<Ui::GenericBox*> box, Fn<void()> done) { }; } +WebViewContext ResolveContext( + not_null<UserData*> bot, + WebViewContext context) { + if (!context.dialogsEntryState.key) { + if (const auto strong = context.controller.get()) { + context.dialogsEntryState = strong->currentDialogsEntryState(); + } + } + if (!context.action) { + const auto &state = context.dialogsEntryState; + if (const auto thread = state.key.thread()) { + context.action = Api::SendAction(thread); + context.action->replyTo = state.currentReplyTo; + } else { + context.action = Api::SendAction(bot->owner().history(bot)); + } + } + if (!context.dialogsEntryState.key) { + using namespace Dialogs; + using Section = EntryState::Section; + const auto history = context.action->history; + const auto topicId = context.action->replyTo.topicRootId; + const auto topic = history->peer->forumTopicFor(topicId); + context.dialogsEntryState = EntryState{ + .key = (topic ? Key{ topic } : Key{ history }), + .section = (topic ? Section::Replies : Section::History), + .currentReplyTo = context.action->replyTo, + }; + } + return context; +} + class BotAction final : public Ui::Menu::ItemBase { public: BotAction( not_null<Ui::RpWidget*> parent, + std::shared_ptr<Ui::Show> show, const style::Menu &st, const AttachWebViewBot &bot, Fn<void()> callback); @@ -317,6 +377,7 @@ private: void prepare(); void paint(Painter &p); + const std::shared_ptr<Ui::Show> _show; const not_null<QAction*> _dummyAction; const style::Menu &_st; const AttachWebViewBot _bot; @@ -334,10 +395,12 @@ private: BotAction::BotAction( not_null<Ui::RpWidget*> parent, + std::shared_ptr<Ui::Show> show, const style::Menu &st, const AttachWebViewBot &bot, Fn<void()> callback) : ItemBase(parent, st) +, _show(std::move(show)) , _dummyAction(new QAction(parent)) , _st(st) , _bot(bot) @@ -409,7 +472,8 @@ void BotAction::contextMenuEvent(QContextMenuEvent *e) { this, st::popupMenuWithIcons); _menu->addAction(tr::lng_bot_remove_from_menu(tr::now), [=] { - _bot.user->session().attachWebView().removeFromMenu(_bot.user); + const auto bot = _bot.user; + bot->session().attachWebView().removeFromMenu(_show, bot); }, &st::menuIconDelete); QObject::connect(_menu, &QObject::destroyed, [=] { @@ -449,6 +513,8 @@ void BotAction::handleKeyPress(not_null<QKeyEvent*> e) { } // namespace +base::weak_ptr<WebViewInstance> WebViewInstance::PendingActivation; + MenuBotIcon::MenuBotIcon( QWidget *parent, std::shared_ptr<Data::DocumentMedia> media) @@ -528,82 +594,514 @@ PeerTypes ParseChooseTypes(QStringView choose) { return result; } -struct AttachWebView::Context { - base::weak_ptr<Window::SessionController> controller; - Dialogs::EntryState dialogsEntryState; - Api::SendAction action; - bool fromSwitch = false; - bool fromMainMenu = false; - bool fromBotApp = false; -}; - -AttachWebView::AttachWebView(not_null<Main::Session*> session) -: _session(session) -, _refreshTimer([=] { requestBots(); }) { - _refreshTimer.callEach(kRefreshBotsTimeout); -} - -AttachWebView::~AttachWebView() { - ActiveWebViews().remove(this); -} - -void AttachWebView::request( - not_null<Window::SessionController*> controller, - const Api::SendAction &action, - const QString &botUsername, - const QString &startCommand) { - if (botUsername.isEmpty()) { - return; - } - const auto username = _bot ? _bot->username() : _botUsername; - const auto context = LookupContext(controller, action); - if (IsSame(_context, context) - && username.toLower() == botUsername.toLower() - && _startCommand == startCommand) { - if (_panel) { - _panel->requestActivate(); - } - return; - } - cancel(); - - _context = std::make_unique<Context>(context); - _botUsername = botUsername; - _startCommand = startCommand; +WebViewInstance::WebViewInstance(WebViewDescriptor &&descriptor) +: _parentShow(descriptor.parentShow + ? std::move(descriptor.parentShow) + : descriptor.context.controller + ? descriptor.context.controller.get()->uiShow() + : nullptr) +, _session(&descriptor.bot->session()) +, _bot(descriptor.bot) +, _context(ResolveContext(_bot, std::move(descriptor.context))) +, _button(std::move(descriptor.button)) +, _source(std::move(descriptor.source)) { resolve(); } -Webview::ThemeParams AttachWebView::botThemeParams() { +WebViewInstance::~WebViewInstance() { + _session->api().request(base::take(_requestId)).cancel(); + _session->api().request(base::take(_prolongId)).cancel(); + base::take(_panel); +} + +Main::Session &WebViewInstance::session() const { + return *_session; +} + +not_null<UserData*> WebViewInstance::bot() const { + return _bot; +} + +WebViewSource WebViewInstance::source() const { + return _source; +} + +void WebViewInstance::activate() { + if (_panel) { + _panel->requestActivate(); + } else { + PendingActivation = this; + } +} + +void WebViewInstance::resolve() { + v::match(_source, [&](WebViewSourceButton data) { + confirmOpen([=] { + if (data.simple) { + requestSimple(); + } else { + requestButton(); + } + }); + }, [&](WebViewSourceSwitch) { + confirmOpen([=] { + requestSimple(); + }); + }, [&](WebViewSourceLinkApp data) { + resolveApp(data.appname, data.token, !_context.maySkipConfirmation); + }, [&](WebViewSourceLinkBotProfile) { + requestWithMenuAdd(); + }, [&](WebViewSourceLinkAttachMenu data) { + requestWithMenuAdd(); + }, [&](WebViewSourceMainMenu) { + requestWithMainMenuDisclaimer(); + }, [&](WebViewSourceAttachMenu) { + requestWithMenuAdd(); + }, [&](WebViewSourceBotMenu) { + if (!openAppFromBotMenuLink()) { + confirmOpen([=] { + requestButton(); + }); + } + }, [&](WebViewSourceGame game) { + showGame(); + }, [&](WebViewSourceBotProfile) { + requestWithMenuAdd(); + }); +} + +bool WebViewInstance::openAppFromBotMenuLink() { + const auto url = QString::fromUtf8(_button.url); + const auto local = Core::TryConvertUrlToLocal(url); + const auto prefix = u"tg://resolve?"_q; + if (!local.startsWith(prefix)) { + return false; + } + const auto params = qthelp::url_parse_params( + local.mid(prefix.size()), + qthelp::UrlParamNameTransform::ToLower); + const auto domainParam = params.value(u"domain"_q); + const auto appnameParam = params.value(u"appname"_q); + const auto webChannelPreviewLink = (domainParam == u"s"_q) + && !appnameParam.isEmpty(); + const auto appname = webChannelPreviewLink ? QString() : appnameParam; + if (appname.isEmpty()) { + return false; + } + resolveApp(appname, params.value(u"startapp"_q), true); + return true; +} + +void WebViewInstance::resolveApp( + const QString &appname, + const QString &startparam, + bool forceConfirmation) { + const auto already = _session->data().findBotApp(_bot->id, appname); + _requestId = _session->api().request(MTPmessages_GetBotApp( + MTP_inputBotAppShortName( + _bot->inputUser, + MTP_string(appname)), + MTP_long(already ? already->hash : 0) + )).done([=](const MTPmessages_BotApp &result) { + _requestId = 0; + const auto &data = result.data(); + const auto received = _session->data().processBotApp( + _bot->id, + data.vapp()); + _app = received ? received : already; + _appStartParam = startparam; + if (!_app) { + _parentShow->showToast(tr::lng_username_app_not_found(tr::now)); + close(); + return; + } + const auto confirm = data.is_inactive() || forceConfirmation; + const auto writeAccess = result.data().is_request_write_access(); + + // Check if this app can be added to main menu. + // On fail it'll still be opened. + using Result = AttachWebView::AddToMenuResult; + const auto done = crl::guard(this, [=](Result value, auto) { + if (value == Result::Cancelled) { + close(); + } else if (value != Result::Unsupported) { + requestApp(true); + } else if (confirm) { + confirmAppOpen(writeAccess, [=](bool allowWrite) { + requestApp(allowWrite); + }); + } else { + requestApp(false); + } + }); + _session->attachWebView().requestAddToMenu(_bot, done); + }).fail([=] { + _parentShow->showToast(tr::lng_username_app_not_found(tr::now)); + close(); + }).send(); +} + +void WebViewInstance::confirmOpen(Fn<void()> done) { + if (_bot->isVerified() + || _session->local().isBotTrustedOpenWebView(_bot->id)) { + done(); + return; + } + const auto callback = [=](Fn<void()> close) { + _session->local().markBotTrustedOpenWebView(_bot->id); + close(); + done(); + }; + _parentShow->show(Ui::MakeConfirmBox({ + .text = tr::lng_allow_bot_webview( + tr::now, + lt_bot_name, + Ui::Text::Bold(_bot->name()), + Ui::Text::RichLangValue), + .confirmed = crl::guard(this, callback), + .cancelled = crl::guard(this, [=] { botClose(); }), + .confirmText = tr::lng_box_ok(), + })); +} + +void WebViewInstance::confirmAppOpen( + bool writeAccess, + Fn<void(bool allowWrite)> done) { + _parentShow->show(Box([=](not_null<Ui::GenericBox*> box) { + const auto allowed = std::make_shared<Ui::Checkbox*>(); + const auto callback = [=](Fn<void()> close) { + done((*allowed) && (*allowed)->checked()); + close(); + }; + Ui::ConfirmBox(box, { + tr::lng_allow_bot_webview( + tr::now, + lt_bot_name, + Ui::Text::Bold(_bot->name()), + Ui::Text::RichLangValue), + crl::guard(this, callback), + crl::guard(this, [=] { botClose(); }), + }); + if (writeAccess) { + (*allowed) = box->addRow( + object_ptr<Ui::Checkbox>( + box, + tr::lng_url_auth_allow_messages( + tr::now, + lt_bot, + Ui::Text::Bold(_bot->name()), + Ui::Text::WithEntities), + true, + st::urlAuthCheckbox), + style::margins( + st::boxRowPadding.left(), + st::boxPhotoCaptionSkip, + st::boxRowPadding.right(), + st::boxPhotoCaptionSkip)); + (*allowed)->setAllowTextLines(); + } + })); +} + +void WebViewInstance::requestButton() { + Expects(_context.action.has_value()); + + const auto &action = *_context.action; + using Flag = MTPmessages_RequestWebView::Flag; + _requestId = _session->api().request(MTPmessages_RequestWebView( + MTP_flags(Flag::f_theme_params + | (_button.url.isEmpty() ? Flag(0) : Flag::f_url) + | (_button.startCommand.isEmpty() + ? Flag(0) + : Flag::f_start_param) + | (v::is<WebViewSourceBotMenu>(_source) + ? Flag::f_from_bot_menu + : Flag(0)) + | (action.replyTo ? Flag::f_reply_to : Flag(0)) + | (action.options.sendAs ? Flag::f_send_as : Flag(0)) + | (action.options.silent ? Flag::f_silent : Flag(0))), + action.history->peer->input, + _bot->inputUser, + MTP_bytes(_button.url), + MTP_string(_button.startCommand), + MTP_dataJSON(MTP_bytes(botThemeParams().json)), + MTP_string("tdesktop"), + action.mtpReplyTo(), + (action.options.sendAs + ? action.options.sendAs->input + : MTP_inputPeerEmpty()) + )).done([=](const MTPWebViewResult &result) { + const auto &data = result.data(); + show(qs(data.vurl()), data.vquery_id().value_or_empty()); + }).fail([=](const MTP::Error &error) { + _parentShow->showToast(error.type()); + if (error.type() == u"BOT_INVALID"_q) { + _session->attachWebView().requestBots(); + } + close(); + }).send(); +} + +void WebViewInstance::requestSimple() { + using Flag = MTPmessages_RequestSimpleWebView::Flag; + _requestId = _session->api().request(MTPmessages_RequestSimpleWebView( + MTP_flags(Flag::f_theme_params + | (v::is<WebViewSourceSwitch>(_source) + ? (Flag::f_url | Flag::f_from_switch_webview) + : v::is<WebViewSourceMainMenu>(_source) + ? (Flag::f_from_side_menu + | (_button.startCommand.isEmpty() // from LinkMainMenu + ? Flag() + : Flag::f_start_param)) + : Flag::f_url)), + _bot->inputUser, + MTP_bytes(_button.url), + MTP_string(_button.startCommand), + MTP_dataJSON(MTP_bytes(botThemeParams().json)), + MTP_string("tdesktop") + )).done([=](const MTPWebViewResult &result) { + show(qs(result.data().vurl())); + }).fail([=](const MTP::Error &error) { + _parentShow->showToast(error.type()); + close(); + }).send(); +} + +void WebViewInstance::requestApp(bool allowWrite) { + Expects(_app != nullptr); + Expects(_context.action.has_value()); + + using Flag = MTPmessages_RequestAppWebView::Flag; + const auto app = _app; + const auto flags = Flag::f_theme_params + | (_appStartParam.isEmpty() ? Flag(0) : Flag::f_start_param) + | (allowWrite ? Flag::f_write_allowed : Flag(0)); + _requestId = _session->api().request(MTPmessages_RequestAppWebView( + MTP_flags(flags), + _context.action->history->peer->input, + MTP_inputBotAppID(MTP_long(app->id), MTP_long(app->accessHash)), + MTP_string(_appStartParam), + MTP_dataJSON(MTP_bytes(botThemeParams().json)), + MTP_string("tdesktop") + )).done([=](const MTPWebViewResult &result) { + _requestId = 0; + show(qs(result.data().vurl())); + }).fail([=](const MTP::Error &error) { + _requestId = 0; + if (error.type() == u"BOT_INVALID"_q) { + _session->attachWebView().requestBots(); + } + close(); + }).send(); +} + +void WebViewInstance::requestWithMainMenuDisclaimer() { + using Result = AttachWebView::AddToMenuResult; + const auto done = crl::guard(this, [=](Result value, auto) { + if (value == Result::Cancelled) { + close(); + } else if (value == Result::Unsupported) { + _parentShow->showToast(tr::lng_bot_menu_not_supported(tr::now)); + close(); + } else { + requestSimple(); + } + }); + _session->attachWebView().acceptMainMenuDisclaimer( + _parentShow, + _bot, + done); +} + +void WebViewInstance::requestWithMenuAdd() { + using Result = AttachWebView::AddToMenuResult; + const auto done = crl::guard(this, [=](Result value, PeerTypes types) { + if (value == Result::Cancelled) { + close(); + } else if (value == Result::Unsupported) { + _parentShow->showToast(tr::lng_bot_menu_not_supported(tr::now)); + close(); + } else if (v::is<WebViewSourceLinkAttachMenu>(_source)) { + maybeChooseAndRequestButton(types); + } else if (v::is<WebViewSourceAttachMenu>(_source)) { + requestButton(); + } else { + requestSimple(); + } + }); + _session->attachWebView().requestAddToMenu(_bot, done); +} + +void WebViewInstance::maybeChooseAndRequestButton(PeerTypes supported) { + Expects(v::is<WebViewSourceLinkAttachMenu>(_source)); + + const auto link = v::get<WebViewSourceLinkAttachMenu>(_source); + const auto chooseFrom = (link.choose & supported); + if (!chooseFrom) { + requestButton(); + return; + } + const auto bot = _bot; + const auto button = _button; + const auto weak = _context.controller; + const auto done = [=](not_null<Data::Thread*> thread) { + if (const auto controller = WindowForThread(weak, thread)) { + thread->session().attachWebView().open({ + .bot = bot, + .context = { + .controller = controller, + .action = Api::SendAction(thread), + }, + .button = button, + .source = InlineBots::WebViewSourceLinkAttachMenu{ + .thread = thread, + .token = button.startCommand, + }, + }); + } + }; + ShowChooseBox(_parentShow, _session, chooseFrom, done); + close(); +} + +void WebViewInstance::show(const QString &url, uint64 queryId) { + auto title = Info::Profile::NameValue(_bot); + + const auto &bots = _session->attachWebView().attachBots(); + + using Button = Ui::BotWebView::MenuButton; + const auto attached = ranges::find( + bots, + not_null{ _bot }, + &AttachWebViewBot::user); + const auto hasOpenBot = v::is<WebViewSourceMainMenu>(_source) + || (_context.action->history->peer != _bot); + const auto hasRemoveFromMenu = (attached != end(bots)) + && (!attached->inactive || attached->inMainMenu) + && (v::is<WebViewSourceMainMenu>(_source) + || v::is<WebViewSourceAttachMenu>(_source) + || v::is<WebViewSourceLinkAttachMenu>(_source)); + const auto buttons = (hasOpenBot ? Button::OpenBot : Button::None) + | (!hasRemoveFromMenu + ? Button::None + : attached->inMainMenu + ? Button::RemoveFromMainMenu + : Button::RemoveFromMenu); + const auto allowClipboardRead = v::is<WebViewSourceAttachMenu>(_source) + || v::is<WebViewSourceAttachMenu>(_source) + || (attached != end(bots) + && (attached->inAttachMenu || attached->inMainMenu)); + _panelUrl = url; + _panel = Ui::BotWebView::Show({ + .url = url, + .storageId = _session->local().resolveStorageIdBots(), + .title = std::move(title), + .bottom = rpl::single('@' + _bot->username()), + .delegate = static_cast<Ui::BotWebView::Delegate*>(this), + .menuButtons = buttons, + .allowClipboardRead = allowClipboardRead, + }); + started(queryId); + + if (const auto strong = PendingActivation.get()) { + if (strong == this) { + PendingActivation = nullptr; + _panel->requestActivate(); + } + } +} + +void WebViewInstance::showGame() { + Expects(v::is<WebViewSourceGame>(_source)); + + const auto game = v::get<WebViewSourceGame>(_source); + _panelUrl = QString::fromUtf8(_button.url); + _panel = Ui::BotWebView::Show({ + .url = _panelUrl, + .storageId = _session->local().resolveStorageIdBots(), + .title = rpl::single(game.title), + .bottom = rpl::single('@' + _bot->username()), + .delegate = static_cast<Ui::BotWebView::Delegate*>(this), + .menuButtons = Ui::BotWebView::MenuButton::ShareGame, + }); +} + +void WebViewInstance::close() { + _session->attachWebView().close(this); +} + +void WebViewInstance::started(uint64 queryId) { + Expects(_context.action.has_value()); + + if (!queryId) { + return; + } + + _session->data().webViewResultSent( + ) | rpl::filter([=](const Data::Session::WebViewResultSent &sent) { + return (sent.queryId == queryId); + }) | rpl::start_with_next([=] { + close(); + }, _panel->lifetime()); + + const auto action = *_context.action; + base::timer_each( + kProlongTimeout + ) | rpl::start_with_next([=] { + using Flag = MTPmessages_ProlongWebView::Flag; + _session->api().request(base::take(_prolongId)).cancel(); + _prolongId = _session->api().request(MTPmessages_ProlongWebView( + MTP_flags(Flag(0) + | (action.replyTo ? Flag::f_reply_to : Flag(0)) + | (action.options.sendAs ? Flag::f_send_as : Flag(0)) + | (action.options.silent ? Flag::f_silent : Flag(0))), + action.history->peer->input, + _bot->inputUser, + MTP_long(queryId), + action.mtpReplyTo(), + (action.options.sendAs + ? action.options.sendAs->input + : MTP_inputPeerEmpty()) + )).done([=] { + _prolongId = 0; + }).send(); + }, _panel->lifetime()); +} + +Webview::ThemeParams WebViewInstance::botThemeParams() { return Window::Theme::WebViewParams(); } -bool AttachWebView::botHandleLocalUri(QString uri, bool keepOpen) { +bool WebViewInstance::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; } + const auto bot = _bot; + const auto context = std::make_shared<WebViewContext>(_context); if (!keepOpen) { botClose(); } - crl::on_main([=, shownUrl = _lastShownUrl, bot = _bot] { + crl::on_main([=] { if (bot->session().windows().empty()) { Core::App().domain().activate(&bot->session().account()); } const auto window = !bot->session().windows().empty() ? bot->session().windows().front().get() : nullptr; + context->controller = window; const auto variant = QVariant::fromValue(ClickHandlerContext{ - .attachBotWebviewUrl = shownUrl, .sessionWindow = window, + .botWebviewContext = context, }); UrlClickHandler::Open(local, variant); }); return true; } -void AttachWebView::botHandleInvoice(QString slug) { +void WebViewInstance::botHandleInvoice(QString slug) { Expects(_panel != nullptr); using Result = Payments::CheckoutResult; @@ -628,12 +1126,12 @@ void AttachWebView::botHandleInvoice(QString slug) { nonPanelPaymentFormFactory(reactivate)); } -auto AttachWebView::nonPanelPaymentFormFactory( +auto WebViewInstance::nonPanelPaymentFormFactory( Fn<void(Payments::CheckoutResult)> reactivate) -> Fn<void(Payments::NonPanelPaymentForm)> { using namespace Payments; const auto panel = base::make_weak(_panel.get()); - const auto weak = _context ? _context->controller : nullptr; + const auto weak = _context.controller; return [=](Payments::NonPanelPaymentForm form) { using CreditsFormDataPtr = std::shared_ptr<CreditsFormData>; using CreditsReceiptPtr = std::shared_ptr<CreditsReceiptData>; @@ -655,8 +1153,8 @@ auto AttachWebView::nonPanelPaymentFormFactory( }; } -void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) { - Expects(_bot != nullptr); +void WebViewInstance::botHandleMenuButton( + Ui::BotWebView::MenuButton button) { Expects(_panel != nullptr); using Button = Ui::BotWebView::MenuButton; @@ -675,17 +1173,23 @@ void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) { break; case Button::RemoveFromMenu: case Button::RemoveFromMainMenu: + const auto &bots = _session->attachWebView().attachBots(); const auto attached = ranges::find( - _attachBots, - not_null{ _bot }, + bots, + _bot, &AttachWebViewBot::user); - const auto name = (attached != end(_attachBots)) + const auto name = (attached != end(bots)) ? attached->name : _bot->name(); const auto done = crl::guard(this, [=] { - removeFromMenu(bot); + const auto session = _session; + const auto was = _parentShow; botClose(); - if (const auto active = Core::App().activeWindow()) { + + const auto active = Core::App().activeWindow(); + const auto show = active ? active->uiShow() : was; + session->attachWebView().removeFromMenu(show, bot); + if (active) { active->activate(); } }); @@ -704,7 +1208,7 @@ void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) { } } -bool AttachWebView::botValidateExternalLink(QString uri) { +bool WebViewInstance::botValidateExternalLink(QString uri) { const auto lower = uri.toLower(); const auto allowed = _session->appConfig().get<std::vector<QString>>( "web_app_allowed_protocols", @@ -717,8 +1221,8 @@ bool AttachWebView::botValidateExternalLink(QString uri) { return false; } -void AttachWebView::botOpenIvLink(QString uri) { - const auto window = _context ? _context->controller.get() : nullptr; +void WebViewInstance::botOpenIvLink(QString uri) { + const auto window = _context.controller.get(); if (window) { Core::App().iv().openWithIvPreferred(window, uri); } else { @@ -726,33 +1230,32 @@ void AttachWebView::botOpenIvLink(QString uri) { } } -void AttachWebView::botSendData(QByteArray data) { - if (!_context - || _context->fromSwitch - || _context->fromBotApp - || _context->fromMainMenu - || _context->action.history->peer != _bot - || _lastShownQueryId) { +void WebViewInstance::botSendData(QByteArray data) { + Expects(_context.action.has_value()); + + const auto button = std::get_if<WebViewSourceButton>(&_source); + if (!button + || !button->simple + || _context.action->history->peer != _bot + || _dataSent) { return; } - const auto randomId = base::RandomValue<uint64>(); + _dataSent = true; _session->api().request(MTPmessages_SendWebViewData( _bot->inputUser, - MTP_long(randomId), - MTP_string(_lastShownButtonText), + MTP_long(base::RandomValue<uint64>()), + MTP_string(_button.text), MTP_bytes(data) - )).done([=](const MTPUpdates &result) { - _session->api().applyUpdates(result); + )).done([session = _session](const MTPUpdates &result) { + session->api().applyUpdates(result); }).send(); - crl::on_main(this, [=] { cancel(); }); + botClose(); } -void AttachWebView::botSwitchInlineQuery( +void WebViewInstance::botSwitchInlineQuery( std::vector<QString> chatTypes, QString query) { - const auto controller = _context - ? _context->controller.get() - : nullptr; + const auto controller = _context.controller.get(); const auto types = PeerTypesFromNames(chatTypes); if (!_bot || !_bot->isBot() @@ -760,9 +1263,9 @@ void AttachWebView::botSwitchInlineQuery( || !controller) { return; } else if (!types) { - if (_context->dialogsEntryState.key.owningHistory()) { + if (_context.dialogsEntryState.key.owningHistory()) { controller->switchInlineQuery( - _context->dialogsEntryState, + _context.dialogsEntryState, _bot, query); } @@ -777,10 +1280,10 @@ void AttachWebView::botSwitchInlineQuery( done, tr::lng_inline_switch_choose()); } - crl::on_main(this, [=] { cancel(); }); + botClose(); } -void AttachWebView::botCheckWriteAccess(Fn<void(bool allowed)> callback) { +void WebViewInstance::botCheckWriteAccess(Fn<void(bool allowed)> callback) { _session->api().request(MTPbots_CanSendMessage( _bot->inputUser )).done([=](const MTPBool &result) { @@ -790,46 +1293,43 @@ void AttachWebView::botCheckWriteAccess(Fn<void(bool allowed)> callback) { }).send(); } -void AttachWebView::botAllowWriteAccess(Fn<void(bool allowed)> callback) { +void WebViewInstance::botAllowWriteAccess(Fn<void(bool allowed)> callback) { _session->api().request(MTPbots_AllowSendMessage( _bot->inputUser - )).done([=](const MTPUpdates &result) { - _session->api().applyUpdates(result); + )).done([session = _session, callback](const MTPUpdates &result) { + session->api().applyUpdates(result); callback(true); }).fail([=] { callback(false); }).send(); } -void AttachWebView::botSharePhone(Fn<void(bool shared)> callback) { +void WebViewInstance::botSharePhone(Fn<void(bool shared)> callback) { const auto bot = _bot; const auto history = _bot->owner().history(_bot); if (_bot->isBlocked()) { - const auto done = [=](bool success) { - if (success && _bot == bot) { - Assert(!_bot->isBlocked()); + const auto done = crl::guard(this, [=](bool success) { + if (success) { botSharePhone(callback); } else { callback(false); } - }; - _bot->session().api().blockedPeers().unblock( - _bot, - crl::guard(this, done)); + }); + _session->api().blockedPeers().unblock(_bot, done); return; } auto action = Api::SendAction(history); action.clearDraft = false; - history->session().api().shareContact( - _bot->session().user(), + _session->api().shareContact( + _session->user(), action, std::move(callback)); } -void AttachWebView::botInvokeCustomMethod( +void WebViewInstance::botInvokeCustomMethod( Ui::BotWebView::CustomMethodRequest request) { const auto callback = request.callback; - _bot->session().api().request(MTPbots_InvokeWebViewCustomMethod( + _session->api().request(MTPbots_InvokeWebViewCustomMethod( _bot->inputUser, MTP_string(request.method), MTP_dataJSON(MTP_bytes(request.params)) @@ -840,148 +1340,141 @@ void AttachWebView::botInvokeCustomMethod( }).send(); } -void AttachWebView::botShareGameScore() { - if (!_panel || !_gameContext) { +void WebViewInstance::botShareGameScore() { + const auto itemId = v::is<WebViewSourceGame>(_source) + ? v::get<WebViewSourceGame>(_source).messageId + : FullMsgId(); + if (!_panel || !itemId) { return; - } else if (const auto item = _session->data().message(_gameContext)) { + } else if (const auto item = _session->data().message(itemId)) { FastShareMessage(uiShow(), item); } else { _panel->showToast({ tr::lng_message_not_found(tr::now) }); } } -void AttachWebView::botClose() { - crl::on_main(this, [=] { cancel(); }); +void WebViewInstance::botClose() { + crl::on_main(this, [=] { close(); }); } -AttachWebView::Context AttachWebView::LookupContext( - not_null<Window::SessionController*> controller, - const Api::SendAction &action) { - return { - .controller = controller, - .dialogsEntryState = controller->currentDialogsEntryState(), - .action = action, +std::shared_ptr<Main::SessionShow> WebViewInstance::uiShow() { + class Show final : public Main::SessionShow { + public: + explicit Show(not_null<WebViewInstance*> that) : _that(that) { + } + + void showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr<Ui::BoxContent>, + std::unique_ptr<Ui::LayerWidget>> &&layer, + Ui::LayerOptions options, + anim::type animated) const override { + using UniqueLayer = std::unique_ptr<Ui::LayerWidget>; + using ObjectBox = object_ptr<Ui::BoxContent>; + const auto panel = _that ? _that->_panel.get() : nullptr; + if (v::is<UniqueLayer>(layer)) { + Unexpected("Layers in WebView are not implemented."); + } else if (auto box = std::get_if<ObjectBox>(&layer)) { + if (panel) { + panel->showBox(std::move(*box), options, animated); + } + } else if (panel) { + panel->hideLayer(animated); + } + } + [[nodiscard]] not_null<QWidget*> toastParent() const override { + const auto panel = _that ? _that->_panel.get() : nullptr; + + Ensures(panel != nullptr); + return panel->toastParent(); + } + [[nodiscard]] bool valid() const override { + return _that && (_that->_panel != nullptr); + } + operator bool() const override { + return valid(); + } + + [[nodiscard]] Main::Session &session() const override { + Expects(_that.get() != nullptr); + + return *_that->_session; + } + + private: + const base::weak_ptr<WebViewInstance> _that; + }; + return std::make_shared<Show>(this); } -bool AttachWebView::IsSame( - const std::unique_ptr<Context> &a, - const Context &b) { - // Check fields that are sent to API in bot attach webview requests. - return a - && (a->controller == b.controller) - && (a->dialogsEntryState == b.dialogsEntryState) - && (a->fromSwitch == b.fromSwitch) - && (a->fromMainMenu == b.fromMainMenu) - && (a->action.history == b.action.history) - && (a->action.replyTo == b.action.replyTo) - && (a->action.options.sendAs == b.action.options.sendAs) - && (a->action.options.silent == b.action.options.silent); +AttachWebView::AttachWebView(not_null<Main::Session*> session) +: _session(session) +, _refreshTimer([=] { requestBots(); }) { + _refreshTimer.callEach(kRefreshBotsTimeout); } -void AttachWebView::request( +AttachWebView::~AttachWebView() = default; + +void AttachWebView::openByUsername( not_null<Window::SessionController*> controller, const Api::SendAction &action, - not_null<UserData*> bot, - const WebViewButton &button) { - requestWithOptionalConfirm( - bot, - button, - LookupContext(controller, action), - button.fromAttachMenu ? nullptr : controller.get()); -} - -void AttachWebView::requestWithOptionalConfirm( - not_null<UserData*> bot, - const WebViewButton &button, - const Context &context, - Window::SessionController *controllerForConfirm) { - if (IsSame(_context, context) && _bot == bot) { - if (_panel) { - _panel->requestActivate(); - } else if (_requestId) { - return; - } + const QString &botUsername, + const QString &startCommand) { + if (botUsername.isEmpty() + || (_botUsername == botUsername && _startCommand == startCommand)) { + return; } cancel(); - _bot = bot; - _context = std::make_unique<Context>(context); - if (controllerForConfirm) { - confirmOpen(controllerForConfirm, [=] { - request(button); + _botUsername = botUsername; + _startCommand = startCommand; + const auto weak = base::make_weak(controller); + const auto show = controller->uiShow(); + resolveUsername(show, crl::guard(weak, [=](not_null<PeerData*> peer) { + _botUsername = QString(); + const auto token = base::take(_startCommand); + + const auto bot = peer->asUser(); + if (!bot || !bot->isBot()) { + if (const auto strong = weak.get()) { + strong->showToast(tr::lng_bot_menu_not_supported(tr::now)); + } + return; + } + + open({ + .bot = bot, + .context = { + .controller = controller, + .action = action, + }, + .button = { .startCommand = token }, + .source = InlineBots::WebViewSourceLinkAttachMenu{}, }); - } else { - request(button); + })); +} + +void AttachWebView::close(not_null<WebViewInstance*> instance) { + const auto i = ranges::find( + _instances, + instance.get(), + &std::unique_ptr<WebViewInstance>::get); + if (i != end(_instances)) { + const auto taken = base::take(*i); + _instances.erase(i); } } -void AttachWebView::request(const WebViewButton &button) { - Expects(_context != nullptr && _bot != nullptr); - - if (button.fromAttachMenu) { - const auto bot = ranges::find( - _attachBots, - not_null{ _bot }, - &AttachWebViewBot::user); - if (bot == end(_attachBots) || bot->inactive) { - requestAddToMenu(_bot, AddToMenuOpenAttach{ - .startCommand = button.startCommand, - }); - return; - } - } - - _startCommand = button.startCommand; - const auto &action = _context->action; - - using Flag = MTPmessages_RequestWebView::Flag; - const auto flags = Flag::f_theme_params - | (button.url.isEmpty() ? Flag(0) : Flag::f_url) - | (_startCommand.isEmpty() ? Flag(0) : Flag::f_start_param) - | (action.replyTo ? Flag::f_reply_to : Flag(0)) - | (action.options.sendAs ? Flag::f_send_as : Flag(0)) - | (action.options.silent ? Flag::f_silent : Flag(0)); - _requestId = _session->api().request(MTPmessages_RequestWebView( - MTP_flags(flags), - action.history->peer->input, - _bot->inputUser, - MTP_bytes(button.url), - MTP_string(_startCommand), - MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), - MTP_string("tdesktop"), - action.mtpReplyTo(), - (action.options.sendAs - ? action.options.sendAs->input - : MTP_inputPeerEmpty()) - )).done([=](const MTPWebViewResult &result) { - _requestId = 0; - const auto &data = result.data(); - show( - data.vquery_id().value_or_empty(), - qs(data.vurl()), - button.text, - button.fromAttachMenu || button.url.isEmpty()); - }).fail([=](const MTP::Error &error) { - _requestId = 0; - if (error.type() == u"BOT_INVALID"_q) { - requestBots(); - } - }).send(); +void AttachWebView::closeAll() { + cancel(); + base::take(_instances); } void AttachWebView::cancel() { - Expects(!_catchingCancelInShowCall); - - ActiveWebViews().remove(this); _session->api().request(base::take(_requestId)).cancel(); - _session->api().request(base::take(_prolongId)).cancel(); - base::take(_panel); - _lastShownContext = base::take(_context); - _bot = nullptr; - _app = nullptr; _botUsername = QString(); - _botAppName = QString(); _startCommand = QString(); } @@ -1033,190 +1526,83 @@ bool AttachWebView::showMainMenuNewBadge( void AttachWebView::requestAddToMenu( not_null<UserData*> bot, - AddToMenuOpen open) { - requestAddToMenu(bot, open, nullptr, std::nullopt); -} - -void AttachWebView::requestAddToMenu( - not_null<UserData*> bot, - AddToMenuOpen open, - Window::SessionController *controller, - std::optional<Api::SendAction> action) { - Expects(controller != nullptr || _context != nullptr); - - const auto wasController = (controller != nullptr); - _addToMenuChooseController = base::make_weak(controller); - _addToMenuOpen = open; - if (!controller) { - _addToMenuContext = base::take(_context); - } else if (action) { - _addToMenuContext = std::make_unique<Context>( - LookupContext(controller, *action)); + Fn<void(AddToMenuResult, PeerTypes supported)> done) { + auto &process = _addToMenu[bot]; + if (done) { + process.done.push_back(std::move(done)); } - - const auto unsupported = [=] { - auto context = base::take(_addToMenuContext); - const auto open = base::take(_addToMenuOpen); - if (const auto openApp = std::get_if<AddToMenuOpenApp>(&open)) { - _app = openApp->app; - _startCommand = openApp->startCommand; - _context = std::move(context); - if (_appConfirmationRequired) { - confirmAppOpen(_appRequestWriteAccess); - } else { - requestAppView(false); - } - } else { - showToast( - tr::lng_bot_menu_not_supported(tr::now), - _addToMenuChooseController.get()); - } - }; - if (!bot->isBot() || !bot->botInfo->supportsAttachMenu) { - unsupported(); + if (process.requestId) { return; } - if (_addToMenuId) { - if (_addToMenuBot == bot) { + const auto finish = [=](AddToMenuResult result, PeerTypes supported) { + if (auto process = _addToMenu.take(bot)) { + for (const auto &done : process->done) { + done(result, supported); + } + } + }; + if (!bot->isBot() || !bot->botInfo->supportsAttachMenu) { + finish(AddToMenuResult::Unsupported, {}); + return; + } + + process.requestId = _session->api().request( + MTPmessages_GetAttachMenuBot(bot->inputUser) + ).done([=](const MTPAttachMenuBotsBot &result) { + _addToMenu[bot].requestId = 0; + const auto &data = result.data(); + _session->data().processUsers(data.vusers()); + const auto parsed = ParseAttachBot(_session, data.vbot()); + if (!parsed || bot != parsed->user) { + finish(AddToMenuResult::Unsupported, {}); return; } - _session->api().request(base::take(_addToMenuId)).cancel(); - } - _addToMenuBot = bot; - _addToMenuId = _session->api().request(MTPmessages_GetAttachMenuBot( - bot->inputUser - )).done([=](const MTPAttachMenuBotsBot &result) { - _addToMenuId = 0; - const auto bot = base::take(_addToMenuBot); - const auto context = std::shared_ptr(base::take(_addToMenuContext)); - const auto open = base::take(_addToMenuOpen); - const auto chooseController = base::take(_addToMenuChooseController); - const auto launch = [=](PeerTypes types) { - const auto openAttach = v::is<AddToMenuOpenAttach>(open) - ? v::get<AddToMenuOpenAttach>(open) - : AddToMenuOpenAttach(); - const auto chooseTypes = openAttach.chooseTypes; - const auto strong = chooseController.get(); - if (v::is<AddToMenuOpenApp>(open)) { - if (!context) { - return false; - } - const auto &openApp = v::get<AddToMenuOpenApp>(open); - _app = openApp.app; - _startCommand = openApp.startCommand; - _context = std::make_unique<Context>(*context); - requestAppView(true); - return true; - } else if (!strong) { - if (wasController || !v::is<AddToMenuOpenAttach>(open)) { - // Just ignore the click if controller was destroyed. - return true; - } - } else if (v::is<AddToMenuOpenMenu>(open)) { - const auto &openMenu = v::get<AddToMenuOpenMenu>(open); - _bot = bot; - requestSimple(strong, bot, { - .startCommand = openMenu.startCommand, - .fromMainMenu = true, - }); - return true; - } else if (const auto useTypes = chooseTypes & types) { - const auto done = [=](not_null<Data::Thread*> thread) { - strong->showThread(thread); - requestWithOptionalConfirm( - bot, - { .startCommand = openAttach.startCommand }, - LookupContext(strong, Api::SendAction(thread))); - }; - ShowChooseBox(strong, useTypes, done); - return true; - } - if (!context) { - return false; - } - requestWithOptionalConfirm( - bot, - { .startCommand = openAttach.startCommand }, - *context); - return true; - }; - result.match([&](const MTPDattachMenuBotsBot &data) { - _session->data().processUsers(data.vusers()); - if (const auto parsed = ParseAttachBot(_session, data.vbot())) { - if (bot == parsed->user) { - const auto i = ranges::find( - _attachBots, - not_null(bot), - &AttachWebViewBot::user); - if (i != end(_attachBots)) { - // Save flags in our list, like 'inactive'. - *i = *parsed; - } - const auto types = parsed->types; - if (parsed->inactive) { - confirmAddToMenu(*parsed, [=] { - launch(types); - }); - } else { - requestBots(); - if (!launch(types)) { - showToast( - tr::lng_bot_menu_already_added(tr::now)); - } - } - } - } - }); + const auto i = ranges::find( + _attachBots, + not_null(bot), + &AttachWebViewBot::user); + if (i != end(_attachBots)) { + // Save flags in our list, like 'inactive'. + *i = *parsed; + } + const auto types = parsed->types; + if (parsed->inactive) { + confirmAddToMenu(*parsed, [=](bool added) { + const auto result = added + ? AddToMenuResult::Added + : AddToMenuResult::Cancelled; + finish(result, types); + }); + } else { + requestBots(); + finish(AddToMenuResult::AlreadyInMenu, types); + } }).fail([=] { - _addToMenuId = 0; - _addToMenuBot = nullptr; - unsupported(); + finish(AddToMenuResult::Unsupported, {}); }).send(); } -void AttachWebView::removeFromMenu(not_null<UserData*> bot) { - toggleInMenu(bot, ToggledState::Removed, [=] { - showToast(tr::lng_bot_remove_from_menu_done(tr::now)); - }); -} - -std::optional<Api::SendAction> AttachWebView::lookupLastAction( - const QString &url) const { - if (_lastShownUrl == url && _lastShownContext) { - return _lastShownContext->action; - } - return std::nullopt; -} - -void AttachWebView::resolve() { - Expects(!_panel); - - resolveUsername(_botUsername, [=](not_null<PeerData*> bot) { - if (!_context) { - return; +void AttachWebView::removeFromMenu( + std::shared_ptr<Ui::Show> show, + not_null<UserData*> bot) { + toggleInMenu(bot, ToggledState::Removed, [=](bool success) { + if (success) { + show->showToast(tr::lng_bot_remove_from_menu_done(tr::now)); } - _bot = bot->asUser(); - if (!_bot) { - showToast(tr::lng_bot_menu_not_supported(tr::now)); - return; - } - requestAddToMenu(_bot, AddToMenuOpenAttach{ - .startCommand = _startCommand, - }); }); } void AttachWebView::resolveUsername( - const QString &username, + std::shared_ptr<Ui::Show> show, Fn<void(not_null<PeerData*>)> done) { - if (const auto peer = _session->data().peerByUsername(username)) { + if (const auto peer = _session->data().peerByUsername(_botUsername)) { done(peer); return; } _session->api().request(base::take(_requestId)).cancel(); _requestId = _session->api().request(MTPcontacts_ResolveUsername( - MTP_string(username) + MTP_string(_botUsername) )).done([=](const MTPcontacts_ResolvedPeer &result) { _requestId = 0; result.match([&](const MTPDcontacts_resolvedPeer &data) { @@ -1229,519 +1615,64 @@ void AttachWebView::resolveUsername( }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.code() == 400) { - showToast( - tr::lng_username_not_found(tr::now, lt_user, username)); + show->showToast( + tr::lng_username_not_found(tr::now, lt_user, _botUsername)); } }).send(); } -void AttachWebView::requestSimple( - not_null<Window::SessionController*> controller, - not_null<UserData*> bot, - const WebViewButton &button) { - cancel(); - _bot = bot; - _context = std::make_unique<Context>(LookupContext( - controller, - Api::SendAction(bot->owner().history(bot)))); - _context->fromSwitch = button.fromSwitch; - _context->fromMainMenu = button.fromMainMenu; - if (button.fromMainMenu) { - acceptMainMenuDisclaimer(controller, button); - } else { - confirmOpen(controller, [=] { - requestSimple(button); - }); - } -} - -void AttachWebView::requestSimple(const WebViewButton &button) { - using Flag = MTPmessages_RequestSimpleWebView::Flag; - _requestId = _session->api().request(MTPmessages_RequestSimpleWebView( - MTP_flags(Flag::f_theme_params - | (button.fromMainMenu - ? (Flag::f_from_side_menu - | (button.startCommand.isEmpty() - ? Flag() - : Flag::f_start_param)) - : Flag::f_url) - | (button.fromSwitch ? Flag::f_from_switch_webview : Flag())), - _bot->inputUser, - MTP_bytes(button.url), - MTP_string(button.startCommand), - MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), - MTP_string("tdesktop") - )).done([=](const MTPWebViewResult &result) { - _requestId = 0; - const auto &data = result.data(); - const auto queryId = uint64(); - show( - queryId, - qs(data.vurl()), - button.text, - false, - nullptr, - button.fromMainMenu); - }).fail([=](const MTP::Error &error) { - _requestId = 0; - }).send(); -} - -bool AttachWebView::openAppFromMenuLink( - not_null<Window::SessionController*> controller, - not_null<UserData*> bot) { - Expects(bot->botInfo != nullptr); - - const auto &url = bot->botInfo->botMenuButtonUrl; - const auto local = Core::TryConvertUrlToLocal(url); - const auto prefix = u"tg://resolve?"_q; - if (!local.startsWith(prefix)) { - return false; - } - const auto params = qthelp::url_parse_params( - local.mid(prefix.size()), - qthelp::UrlParamNameTransform::ToLower); - const auto domainParam = params.value(u"domain"_q); - const auto appnameParam = params.value(u"appname"_q); - const auto webChannelPreviewLink = (domainParam == u"s"_q) - && !appnameParam.isEmpty(); - const auto appname = webChannelPreviewLink ? QString() : appnameParam; - if (appname.isEmpty()) { - return false; - } - requestApp( - controller, - Api::SendAction(bot->owner().history(bot)), - bot, - appname, - params.value(u"startapp"_q), - true); - return true; -} - -void AttachWebView::requestMenu( - not_null<Window::SessionController*> controller, - not_null<UserData*> bot) { - if (openAppFromMenuLink(controller, bot)) { - return; - } - - cancel(); - _bot = bot; - _context = std::make_unique<Context>(LookupContext( - controller, - Api::SendAction(bot->owner().history(bot)))); - const auto url = bot->botInfo->botMenuButtonUrl; - const auto text = bot->botInfo->botMenuButtonText; - confirmOpen(controller, [=] { - const auto &action = _context->action; - using Flag = MTPmessages_RequestWebView::Flag; - _requestId = _session->api().request(MTPmessages_RequestWebView( - MTP_flags(Flag::f_theme_params - | Flag::f_url - | Flag::f_from_bot_menu - | (action.replyTo? Flag::f_reply_to : Flag(0)) - | (action.options.sendAs ? Flag::f_send_as : Flag(0)) - | (action.options.silent ? Flag::f_silent : Flag(0))), - action.history->peer->input, - _bot->inputUser, - MTP_string(url), - MTPstring(), // start_param - MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), - MTP_string("tdesktop"), - action.mtpReplyTo(), - (action.options.sendAs - ? action.options.sendAs->input - : MTP_inputPeerEmpty()) - )).done([=](const MTPWebViewResult &result) { - _requestId = 0; - const auto &data = result.data(); - show(data.vquery_id().value_or_empty(), qs(data.vurl()), text); - }).fail([=](const MTP::Error &error) { - _requestId = 0; - if (error.type() == u"BOT_INVALID"_q) { - requestBots(); - } - }).send(); - }); -} - -void AttachWebView::requestApp( - not_null<Window::SessionController*> controller, - const Api::SendAction &action, - not_null<UserData*> bot, - const QString &appName, - const QString &startParam, - bool forceConfirmation) { - const auto context = LookupContext(controller, action); - if (_requestId - && _bot == bot - && _startCommand == startParam - && _botAppName == appName - && IsSame(_context, context)) { - return; - } - cancel(); - _bot = bot; - _startCommand = startParam; - _botAppName = appName; - _context = std::make_unique<Context>(context); - _context->fromBotApp = true; - const auto already = _session->data().findBotApp(_bot->id, appName); - _requestId = _session->api().request(MTPmessages_GetBotApp( - MTP_inputBotAppShortName( - bot->inputUser, - MTP_string(appName)), - MTP_long(already ? already->hash : 0) - )).done([=](const MTPmessages_BotApp &result) { - _requestId = 0; - if (!_bot || !_context) { +void AttachWebView::open(WebViewDescriptor &&descriptor) { + for (const auto &instance : _instances) { + if (instance->bot() == descriptor.bot + && instance->source() == descriptor.source) { + instance->activate(); return; } - const auto &data = result.data(); - const auto firstTime = data.is_inactive(); - const auto received = _session->data().processBotApp( - _bot->id, - data.vapp()); - _app = received ? received : already; - if (!_app) { - cancel(); - showToast(tr::lng_username_app_not_found(tr::now)); - return; - } - // Check if this app can be added to main menu. - // On fail it'll still be opened. - _appConfirmationRequired = firstTime || forceConfirmation; - _appRequestWriteAccess = result.data().is_request_write_access(); - requestAddToMenu(_bot, AddToMenuOpenApp{ - .app = _app, - .startCommand = _startCommand, - }); - }).fail([=] { - showToast(tr::lng_username_app_not_found(tr::now)); - cancel(); - }).send(); -} - -void AttachWebView::confirmAppOpen(bool requestWriteAccess) { - const auto controller = _context ? _context->controller.get() : nullptr; - if (!controller || !_bot) { - return; } - controller->show(Box([=](not_null<Ui::GenericBox*> box) { - const auto allowed = std::make_shared<Ui::Checkbox*>(); - const auto done = [=](Fn<void()> close) { - requestAppView((*allowed) && (*allowed)->checked()); - close(); - }; - Ui::ConfirmBox(box, { - tr::lng_allow_bot_webview( - tr::now, - lt_bot_name, - Ui::Text::Bold(_bot->name()), - Ui::Text::RichLangValue), - done, - }); - if (requestWriteAccess) { - (*allowed) = box->addRow( - object_ptr<Ui::Checkbox>( - box, - tr::lng_url_auth_allow_messages( - tr::now, - lt_bot, - Ui::Text::Bold(_bot->name()), - Ui::Text::WithEntities), - true, - st::urlAuthCheckbox), - style::margins( - st::boxRowPadding.left(), - st::boxPhotoCaptionSkip, - st::boxRowPadding.right(), - st::boxPhotoCaptionSkip)); - (*allowed)->setAllowTextLines(); - } - })); -} - -void AttachWebView::requestAppView(bool allowWrite) { - if (!_context || !_app) { - return; - } - using Flag = MTPmessages_RequestAppWebView::Flag; - const auto app = _app; - const auto flags = Flag::f_theme_params - | (_startCommand.isEmpty() ? Flag(0) : Flag::f_start_param) - | (allowWrite ? Flag::f_write_allowed : Flag(0)); - _requestId = _session->api().request(MTPmessages_RequestAppWebView( - MTP_flags(flags), - _context->action.history->peer->input, - MTP_inputBotAppID(MTP_long(app->id), MTP_long(app->accessHash)), - MTP_string(_startCommand), - MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), - MTP_string("tdesktop") - )).done([=](const MTPWebViewResult &result) { - _requestId = 0; - const auto &data = result.data(); - const auto queryId = uint64(); - show(queryId, qs(data.vurl()), QString(), false, app); - }).fail([=](const MTP::Error &error) { - _requestId = 0; - if (error.type() == u"BOT_INVALID"_q) { - requestBots(); - } - }).send(); -} - -void AttachWebView::confirmOpen( - not_null<Window::SessionController*> controller, - Fn<void()> done) { - if (!_bot) { - return; - } else if (_bot->isVerified() - || _bot->session().local().isBotTrustedOpenWebView(_bot->id)) { - done(); - return; - } - const auto callback = [=] { - _bot->session().local().markBotTrustedOpenWebView(_bot->id); - controller->hideLayer(); - done(); - }; - controller->show(Ui::MakeConfirmBox({ - .text = tr::lng_allow_bot_webview( - tr::now, - lt_bot_name, - Ui::Text::Bold(_bot->name()), - Ui::Text::RichLangValue), - .confirmed = callback, - .confirmText = tr::lng_box_ok(), - })); + _instances.push_back( + std::make_unique<WebViewInstance>(std::move(descriptor))); + _instances.back()->activate(); } void AttachWebView::acceptMainMenuDisclaimer( - not_null<Window::SessionController*> controller, - const WebViewButton &button) { - Expects(button.fromMainMenu); - - const auto local = _bot ? &_bot->session().local() : nullptr; - if (!local) { - return; - } - const auto i = ranges::find( - _attachBots, - not_null(_bot), - &AttachWebViewBot::user); + std::shared_ptr<Ui::Show> show, + not_null<UserData*> bot, + Fn<void(AddToMenuResult, PeerTypes supported)> done) { + const auto i = ranges::find(_attachBots, bot, &AttachWebViewBot::user); if (i == end(_attachBots)) { _attachBotsUpdates.fire({}); return; } else if (i->inactive) { - requestAddToMenu(_bot, AddToMenuOpenMenu{ - .startCommand = button.startCommand, - }, controller, {}); + requestAddToMenu(bot, std::move(done)); return; } else if (!i->disclaimerRequired || disclaimerAccepted(*i)) { - requestSimple(button); + done(AddToMenuResult::AlreadyInMenu, i->types); return; } - - const auto weak = base::make_weak(this); - controller->show(Box(FillDisclaimerBox, crl::guard(this, [=] { - _disclaimerAccepted.emplace(_bot); - _attachBotsUpdates.fire({}); - requestSimple(button); + const auto types = i->types; + show->show(Box(FillDisclaimerBox, crl::guard(this, [=](bool accepted) { + if (accepted) { + _disclaimerAccepted.emplace(bot); + _attachBotsUpdates.fire({}); + done(AddToMenuResult::AlreadyInMenu, types); + } else { + done(AddToMenuResult::Cancelled, {}); + } }))); } -void AttachWebView::ClearAll() { - while (!ActiveWebViews().empty()) { - ActiveWebViews().front()->cancel(); - } -} - -void AttachWebView::show( - uint64 queryId, - const QString &url, - const QString &buttonText, - bool allowClipboardRead, - const BotAppData *app, - bool fromMainMenu) { - Expects(_bot != nullptr && _context != nullptr); - - auto title = Info::Profile::NameValue(_bot); - ActiveWebViews().emplace(this); - - using Button = Ui::BotWebView::MenuButton; - const auto attached = ranges::find( - _attachBots, - not_null{ _bot }, - &AttachWebViewBot::user); - const auto hasOpenBot = !_context - || (_bot != _context->action.history->peer) - || fromMainMenu; - const auto hasRemoveFromMenu = !app - && (attached != end(_attachBots)) - && (!attached->inactive || attached->inMainMenu); - const auto buttons = (hasOpenBot ? Button::OpenBot : Button::None) - | (!hasRemoveFromMenu - ? Button::None - : attached->inMainMenu - ? Button::RemoveFromMainMenu - : Button::RemoveFromMenu); - if (attached != end(_attachBots) - && (attached->inAttachMenu || attached->inMainMenu)) { - allowClipboardRead = true; - } - - _lastShownUrl = url; - _lastShownQueryId = queryId; - _lastShownButtonText = buttonText; - _gameContext = {}; - base::take(_panel); - _catchingCancelInShowCall = true; - _panel = Ui::BotWebView::Show({ - .url = url, - .storageId = _session->local().resolveStorageIdBots(), - .title = std::move(title), - .bottom = rpl::single('@' + _bot->username()), - .delegate = static_cast<Ui::BotWebView::Delegate*>(this), - .menuButtons = buttons, - .allowClipboardRead = allowClipboardRead, - }); - _catchingCancelInShowCall = false; - started(queryId); -} - -void AttachWebView::showGame(ShowGameParams &¶ms) { - ActiveWebViews().emplace(this); - - base::take(_panel); - _gameContext = params.context; - - _catchingCancelInShowCall = true; - _panel = Ui::BotWebView::Show({ - .url = params.url, - .storageId = _session->local().resolveStorageIdBots(), - .title = rpl::single(params.title), - .bottom = rpl::single('@' + params.bot->username()), - .delegate = static_cast<Ui::BotWebView::Delegate*>(this), - .menuButtons = Ui::BotWebView::MenuButton::ShareGame, - }); - _catchingCancelInShowCall = false; -} - -void AttachWebView::started(uint64 queryId) { - Expects(_bot != nullptr); - Expects(_context != nullptr); - - if (_context->fromSwitch || !queryId) { - return; - } - - _session->data().webViewResultSent( - ) | rpl::filter([=](const Data::Session::WebViewResultSent &sent) { - return (sent.queryId == queryId); - }) | rpl::start_with_next([=] { - cancel(); - }, _panel->lifetime()); - - const auto action = _context->action; - base::timer_each( - kProlongTimeout - ) | rpl::start_with_next([=] { - using Flag = MTPmessages_ProlongWebView::Flag; - _session->api().request(base::take(_prolongId)).cancel(); - _prolongId = _session->api().request(MTPmessages_ProlongWebView( - MTP_flags(Flag(0) - | (action.replyTo ? Flag::f_reply_to : Flag(0)) - | (action.options.sendAs ? Flag::f_send_as : Flag(0)) - | (action.options.silent ? Flag::f_silent : Flag(0))), - action.history->peer->input, - _bot->inputUser, - MTP_long(queryId), - action.mtpReplyTo(), - (action.options.sendAs - ? action.options.sendAs->input - : MTP_inputPeerEmpty()) - )).done([=] { - _prolongId = 0; - }).send(); - }, _panel->lifetime()); -} - -std::shared_ptr<Main::SessionShow> AttachWebView::uiShow() { - class Show final : public Main::SessionShow { - public: - explicit Show(not_null<AttachWebView*> that) : _that(that) { - } - - void showOrHideBoxOrLayer( - std::variant< - v::null_t, - object_ptr<Ui::BoxContent>, - std::unique_ptr<Ui::LayerWidget>> &&layer, - Ui::LayerOptions options, - anim::type animated) const override { - using UniqueLayer = std::unique_ptr<Ui::LayerWidget>; - using ObjectBox = object_ptr<Ui::BoxContent>; - const auto panel = _that ? _that->_panel.get() : nullptr; - if (v::is<UniqueLayer>(layer)) { - Unexpected("Layers in AttachWebView are not implemented."); - } else if (auto box = std::get_if<ObjectBox>(&layer)) { - if (panel) { - panel->showBox(std::move(*box), options, animated); - } - } else if (panel) { - panel->hideLayer(animated); - } - } - [[nodiscard]] not_null<QWidget*> toastParent() const override { - const auto panel = _that ? _that->_panel.get() : nullptr; - - Ensures(panel != nullptr); - return panel->toastParent(); - } - [[nodiscard]] bool valid() const override { - return _that && (_that->_panel != nullptr); - } - operator bool() const override { - return valid(); - } - - [[nodiscard]] Main::Session &session() const override { - Expects(_that.get() != nullptr); - return *_that->_session; - } - - private: - const base::weak_ptr<AttachWebView> _that; - - }; - return std::make_shared<Show>(this); -} - -void AttachWebView::showToast( - const QString &text, - Window::SessionController *controller) { - const auto strong = controller - ? controller - : _context - ? _context->controller.get() - : _addToMenuContext - ? _addToMenuContext->controller.get() - : nullptr; - if (strong) { - strong->showToast(text); - } -} - void AttachWebView::confirmAddToMenu( AttachWebViewBot bot, - Fn<void()> callback) { + Fn<void(bool added)> callback) { const auto active = Core::App().activeWindow(); if (!active) { + if (callback) { + callback(false); + } return; } - _confirmAddBox = active->show(Box([=](not_null<Ui::GenericBox*> box) { + const auto weak = base::make_weak(active); + active->show(Box([=](not_null<Ui::GenericBox*> box) { const auto allowed = std::make_shared<Ui::Checkbox*>(); const auto disclaimer = !disclaimerAccepted(bot); const auto done = [=](Fn<void()> close) { @@ -1749,21 +1680,27 @@ void AttachWebView::confirmAddToMenu( || ((*allowed) && (*allowed)->checked())) ? ToggledState::AllowedToWrite : ToggledState::Added; - toggleInMenu(bot.user, state, [=] { + toggleInMenu(bot.user, state, [=](bool success) { if (callback) { - callback(); + callback(success); + } + if (const auto strong = weak.get()) { + strong->showToast((bot.inMainMenu + ? tr::lng_bot_add_to_side_menu_done + : tr::lng_bot_add_to_menu_done)(tr::now)); } - showToast((bot.inMainMenu - ? tr::lng_bot_add_to_side_menu_done - : tr::lng_bot_add_to_menu_done)(tr::now)); }); close(); }; if (disclaimer) { - FillDisclaimerBox(box, [=] { - _disclaimerAccepted.emplace(bot.user); - _attachBotsUpdates.fire({}); - done([] {}); + FillDisclaimerBox(box, [=](bool accepted) { + if (accepted) { + _disclaimerAccepted.emplace(bot.user); + _attachBotsUpdates.fire({}); + done([] {}); + } else if (callback) { + callback(false); + } }); box->addRow(object_ptr<Ui::FixedHeightWidget>( box, @@ -1785,6 +1722,9 @@ void AttachWebView::confirmAddToMenu( Ui::Text::Bold(bot.name), Ui::Text::WithEntities), done, + (callback + ? [=](Fn<void()> close) { callback(false); close(); } + : Fn<void(Fn<void()>)>()), }); if (bot.requestWriteAccess) { (*allowed) = box->addRow( @@ -1813,7 +1753,7 @@ void AttachWebView::confirmAddToMenu( void AttachWebView::toggleInMenu( not_null<UserData*> bot, ToggledState state, - Fn<void()> callback) { + Fn<void(bool success)> callback) { using Flag = MTPmessages_ToggleBotInAttachMenu::Flag; _session->api().request(MTPmessages_ToggleBotInAttachMenu( MTP_flags((state == ToggledState::AllowedToWrite) @@ -1824,9 +1764,12 @@ void AttachWebView::toggleInMenu( )).done([=] { _requestId = 0; _session->api().request(base::take(_botsRequestId)).cancel(); - requestBots(std::move(callback)); + requestBots(callback ? [=] { callback(true); } : Fn<void()>()); }).fail([=] { cancel(); + if (callback) { + callback(false); + } }).send(); } @@ -1923,14 +1866,18 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu( continue; } const auto callback = [=] { - bots->request( - controller, - actionFactory(), - bot.user, - { .fromAttachMenu = true }); + bots->open({ + .bot = bot.user, + .context = { + .controller = controller, + .action = actionFactory(), + }, + .source = InlineBots::WebViewSourceAttachMenu(), + }); }; auto action = base::make_unique_q<BotAction>( raw, + controller->uiShow(), raw->menu()->st(), bot, callback); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index f9f59f1ae..6e11c87cd 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -10,15 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/flags.h" #include "base/timer.h" #include "base/weak_ptr.h" +#include "dialogs/dialogs_key.h" +#include "api/api_common.h" #include "mtproto/sender.h" #include "ui/chat/attach/attach_bot_webview.h" #include "ui/rp_widget.h" -namespace Api { -struct SendAction; -} // namespace Api +namespace Data { +class Thread; +} // namespace Data namespace Ui { +class Show; class GenericBox; class DropdownMenu; } // namespace Ui @@ -47,6 +50,8 @@ enum class CheckoutResult; namespace InlineBots { +class WebViewInstance; + enum class PeerType : uint8 { SameBot = 0x01, Bot = 0x02, @@ -86,95 +91,176 @@ struct AddToMenuOpenApp { not_null<BotAppData*> app; QString startCommand; }; -using AddToMenuOpen = std::variant< +struct AddToMenuOpen : std::variant< AddToMenuOpenAttach, AddToMenuOpenMenu, - AddToMenuOpenApp>; + AddToMenuOpenApp> { + using variant::variant; +}; -class AttachWebView final +struct WebViewSourceButton { + bool simple = false; + + friend inline bool operator==( + WebViewSourceButton, + WebViewSourceButton) = default; +}; + +struct WebViewSourceSwitch { + friend inline bool operator==( + const WebViewSourceSwitch &, + const WebViewSourceSwitch &) = default; +}; + +struct WebViewSourceLinkApp { // t.me/botusername/appname + base::weak_ptr<WebViewInstance> from; + QString appname; + QString token; + + friend inline bool operator==( + const WebViewSourceLinkApp &, + const WebViewSourceLinkApp &) = default; +}; + +struct WebViewSourceLinkAttachMenu { // ?startattach + base::weak_ptr<WebViewInstance> from; + base::weak_ptr<Data::Thread> thread; + PeerTypes choose; + QString token; + + friend inline bool operator==( + const WebViewSourceLinkAttachMenu &, + const WebViewSourceLinkAttachMenu &) = default; +}; + +struct WebViewSourceLinkBotProfile { // t.me/botusername?startapp + base::weak_ptr<WebViewInstance> from; + QString token; + bool compact = false; + + friend inline bool operator==( + const WebViewSourceLinkBotProfile &, + const WebViewSourceLinkBotProfile &) = default; +}; + +struct WebViewSourceMainMenu { + friend inline bool operator==( + WebViewSourceMainMenu, + WebViewSourceMainMenu) = default; +}; + +struct WebViewSourceAttachMenu { + base::weak_ptr<Data::Thread> thread; + + friend inline bool operator==( + const WebViewSourceAttachMenu &, + const WebViewSourceAttachMenu &) = default; +}; + +struct WebViewSourceBotMenu { + friend inline bool operator==( + WebViewSourceBotMenu, + WebViewSourceBotMenu) = default; +}; + +struct WebViewSourceGame { + FullMsgId messageId; + QString title; + + friend inline bool operator==( + WebViewSourceGame, + WebViewSourceGame) = default; +}; + +struct WebViewSourceBotProfile { + friend inline bool operator==( + WebViewSourceBotProfile, + WebViewSourceBotProfile) = default; +}; + +struct WebViewSource : std::variant< + WebViewSourceButton, + WebViewSourceSwitch, + WebViewSourceLinkApp, + WebViewSourceLinkAttachMenu, + WebViewSourceLinkBotProfile, + WebViewSourceMainMenu, + WebViewSourceAttachMenu, + WebViewSourceBotMenu, + WebViewSourceGame, + WebViewSourceBotProfile> { + using variant::variant; +}; + +struct WebViewButton { + QString text; + QString startCommand; + QByteArray url; + bool fromAttachMenu = false; + bool fromMainMenu = false; + bool fromSwitch = false; +}; + +struct WebViewContext { + base::weak_ptr<Window::SessionController> controller; + Dialogs::EntryState dialogsEntryState; + std::optional<Api::SendAction> action; + bool maySkipConfirmation = false; +}; + +struct WebViewDescriptor { + not_null<UserData*> bot; + std::shared_ptr<Ui::Show> parentShow; + WebViewContext context; + WebViewButton button; + WebViewSource source; +}; + +class WebViewInstance final : public base::has_weak_ptr , public Ui::BotWebView::Delegate { public: - explicit AttachWebView(not_null<Main::Session*> session); - ~AttachWebView(); + explicit WebViewInstance(WebViewDescriptor &&descriptor); + ~WebViewInstance(); - struct WebViewButton { - QString text; - QString startCommand; - QByteArray url; - bool fromAttachMenu = false; - bool fromMainMenu = false; - bool fromSwitch = false; - }; - void request( - not_null<Window::SessionController*> controller, - const Api::SendAction &action, - const QString &botUsername, - const QString &startCommand); - void request( - not_null<Window::SessionController*> controller, - const Api::SendAction &action, - not_null<UserData*> bot, - const WebViewButton &button); - void requestSimple( - not_null<Window::SessionController*> controller, - not_null<UserData*> bot, - const WebViewButton &button); - void requestMenu( - not_null<Window::SessionController*> controller, - not_null<UserData*> bot); - void requestApp( - not_null<Window::SessionController*> controller, - const Api::SendAction &action, - not_null<UserData*> bot, - const QString &appName, - const QString &startParam, - bool forceConfirmation); + [[nodiscard]] Main::Session &session() const; + [[nodiscard]] not_null<UserData*> bot() const; + [[nodiscard]] WebViewSource source() const; - void cancel(); - - void requestBots(Fn<void()> callback = nullptr); - [[nodiscard]] const std::vector<AttachWebViewBot> &attachBots() const { - return _attachBots; - } - [[nodiscard]] rpl::producer<> attachBotsUpdates() const { - return _attachBotsUpdates.events(); - } - void notifyBotIconLoaded() { - _attachBotsUpdates.fire({}); - } - [[nodiscard]] bool disclaimerAccepted( - const AttachWebViewBot &bot) const; - [[nodiscard]] bool showMainMenuNewBadge( - const AttachWebViewBot &bot) const; - - void requestAddToMenu( - not_null<UserData*> bot, - AddToMenuOpen open); - void requestAddToMenu( - not_null<UserData*> bot, - AddToMenuOpen open, - Window::SessionController *controller, - std::optional<Api::SendAction> action); - void removeFromMenu(not_null<UserData*> bot); - - [[nodiscard]] std::optional<Api::SendAction> lookupLastAction( - const QString &url) const; - - struct ShowGameParams { - not_null<UserData*> bot; - FullMsgId context; - QString url; - QString title; - }; - void showGame(ShowGameParams &¶ms); + void activate(); + void close(); [[nodiscard]] std::shared_ptr<Main::SessionShow> uiShow(); - static void ClearAll(); - private: - struct Context; + void resolve(); + + bool openAppFromBotMenuLink(); + + void requestButton(); + void requestSimple(); + void requestApp(bool allowWrite); + void requestWithMainMenuDisclaimer(); + void requestWithMenuAdd(); + void maybeChooseAndRequestButton(PeerTypes supported); + + void resolveApp( + const QString &appname, + const QString &startparam, + bool forceConfirmation); + void confirmOpen(Fn<void()> done); + void confirmAppOpen(bool writeAccess, Fn<void(bool allowWrite)> done); + + void show(const QString &url, uint64 queryId = 0); + void showGame(); + void started(uint64 queryId); + + [[nodiscard]] Window::SessionController *windowForThread( + not_null<Data::Thread*> thread); + + auto nonPanelPaymentFormFactory( + Fn<void(Payments::CheckoutResult)> reactivate) + -> Fn<void(Payments::NonPanelPaymentForm)>; Webview::ThemeParams botThemeParams() override; bool botHandleLocalUri(QString uri, bool keepOpen) override; @@ -194,35 +280,81 @@ private: void botShareGameScore() override; void botClose() override; - [[nodiscard]] static Context LookupContext( - not_null<Window::SessionController*> controller, - const Api::SendAction &action); - [[nodiscard]] static bool IsSame( - const std::unique_ptr<Context> &a, - const Context &b); + const std::shared_ptr<Ui::Show> _parentShow; + const not_null<Main::Session*> _session; + const not_null<UserData*> _bot; + const WebViewContext _context; + const WebViewButton _button; + const WebViewSource _source; - bool openAppFromMenuLink( + BotAppData *_app = nullptr; + QString _appStartParam; + bool _dataSent = false; + + mtpRequestId _requestId = 0; + mtpRequestId _prolongId = 0; + + QString _panelUrl; + std::unique_ptr<Ui::BotWebView::Panel> _panel; + + static base::weak_ptr<WebViewInstance> PendingActivation; + +}; + +class AttachWebView final : public base::has_weak_ptr { +public: + explicit AttachWebView(not_null<Main::Session*> session); + ~AttachWebView(); + + void open(WebViewDescriptor &&descriptor); + void openByUsername( not_null<Window::SessionController*> controller, + const Api::SendAction &action, + const QString &botUsername, + const QString &startCommand); + + void cancel(); + + void requestBots(Fn<void()> callback = nullptr); + [[nodiscard]] const std::vector<AttachWebViewBot> &attachBots() const { + return _attachBots; + } + [[nodiscard]] rpl::producer<> attachBotsUpdates() const { + return _attachBotsUpdates.events(); + } + void notifyBotIconLoaded() { + _attachBotsUpdates.fire({}); + } + [[nodiscard]] bool disclaimerAccepted( + const AttachWebViewBot &bot) const; + [[nodiscard]] bool showMainMenuNewBadge( + const AttachWebViewBot &bot) const; + + void removeFromMenu( + std::shared_ptr<Ui::Show> show, not_null<UserData*> bot); - void requestWithOptionalConfirm( + + enum class AddToMenuResult { + AlreadyInMenu, + Added, + Unsupported, + Cancelled, + }; + void requestAddToMenu( not_null<UserData*> bot, - const WebViewButton &button, - const Context &context, - Window::SessionController *controllerForConfirm = nullptr); - - void resolve(); - void request(const WebViewButton &button); - void requestSimple(const WebViewButton &button); - void resolveUsername( - const QString &username, - Fn<void(not_null<PeerData*>)> done); - - void confirmOpen( - not_null<Window::SessionController*> controller, - Fn<void()> done); + Fn<void(AddToMenuResult, PeerTypes supported)> done); void acceptMainMenuDisclaimer( - not_null<Window::SessionController*> controller, - const WebViewButton &button); + std::shared_ptr<Ui::Show> show, + not_null<UserData*> bot, + Fn<void(AddToMenuResult, PeerTypes supported)> done); + + void close(not_null<WebViewInstance*> instance); + void closeAll(); + +private: + void resolveUsername( + std::shared_ptr<Ui::Show> show, + Fn<void(not_null<PeerData*>)> done); enum class ToggledState { Removed, @@ -232,67 +364,35 @@ private: void toggleInMenu( not_null<UserData*> bot, ToggledState state, - Fn<void()> callback = nullptr); - - void show( - uint64 queryId, - const QString &url, - const QString &buttonText = QString(), - bool allowClipboardRead = false, - const BotAppData *app = nullptr, - bool fromMainMenu = false); + Fn<void(bool success)> callback = nullptr); void confirmAddToMenu( AttachWebViewBot bot, - Fn<void()> callback = nullptr); - void confirmAppOpen(bool requestWriteAccess); - void requestAppView(bool allowWrite); - void started(uint64 queryId); - - void showToast( - const QString &text, - Window::SessionController *controller = nullptr); - Fn<void(Payments::NonPanelPaymentForm)> nonPanelPaymentFormFactory( - Fn<void(Payments::CheckoutResult)> reactivate); + Fn<void(bool added)> callback = nullptr); const not_null<Main::Session*> _session; base::Timer _refreshTimer; - std::unique_ptr<Context> _context; - std::unique_ptr<Context> _lastShownContext; - QString _lastShownUrl; - uint64 _lastShownQueryId = 0; - QString _lastShownButtonText; - UserData *_bot = nullptr; QString _botUsername; - QString _botAppName; QString _startCommand; - BotAppData *_app = nullptr; - QPointer<Ui::GenericBox> _confirmAddBox; - bool _appConfirmationRequired = false; - bool _appRequestWriteAccess = false; mtpRequestId _requestId = 0; - mtpRequestId _prolongId = 0; uint64 _botsHash = 0; mtpRequestId _botsRequestId = 0; std::vector<Fn<void()>> _botsRequestCallbacks; - std::unique_ptr<Context> _addToMenuContext; - UserData *_addToMenuBot = nullptr; - mtpRequestId _addToMenuId = 0; - AddToMenuOpen _addToMenuOpen; - base::weak_ptr<Window::SessionController> _addToMenuChooseController; + struct AddToMenuProcess { + mtpRequestId requestId = 0; + std::vector<Fn<void(AddToMenuResult, PeerTypes supported)>> done; + }; + base::flat_map<not_null<UserData*>, AddToMenuProcess> _addToMenu; std::vector<AttachWebViewBot> _attachBots; rpl::event_stream<> _attachBotsUpdates; base::flat_set<not_null<UserData*>> _disclaimerAccepted; - FullMsgId _gameContext; - - std::unique_ptr<Ui::BotWebView::Panel> _panel; - bool _catchingCancelInShowCall = false; + std::vector<std::unique_ptr<WebViewInstance>> _instances; }; diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 10c6236bf..2418c0f8a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/path_shift_gradient.h" #include "ui/painter.h" #include "history/view/history_view_cursor_state.h" +#include "history/history.h" #include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" @@ -677,10 +678,13 @@ void Inner::switchPm() { if (!_inlineBot || !_inlineBot->isBot()) { return; } else if (!_switchPmUrl.isEmpty()) { - _inlineBot->session().attachWebView().requestSimple( - _controller, - _inlineBot, - { .url = _switchPmUrl, .fromSwitch = true }); + const auto bot = _inlineBot; + _inlineBot->session().attachWebView().open({ + .bot = bot, + .context = { .controller = _controller }, + .button = { .url = _switchPmUrl }, + .source = InlineBots::WebViewSourceSwitch(), + }); } else { _inlineBot->botInfo->startToken = _switchPmStartToken; _inlineBot->botInfo->inlineReturnTo diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index df1f2ef25..0cfdeece2 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -193,7 +193,7 @@ void MainWindow::setupPasscodeLock() { setInnerFocus(); } if (const auto sessionController = controller().sessionController()) { - sessionController->session().attachWebView().cancel(); + sessionController->session().attachWebView().closeAll(); } } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index ae27e4938..6a50d2590 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -1282,6 +1282,27 @@ void Panel::showBox( } } } + const auto raw = box.data(); + + InvokeQueued(raw, [=] { + if (raw->window()->isActiveWindow()) { + // In case focus is somewhat in a native child window, + // like a webview, Qt glitches here with input fields showing + // focused state, but not receiving any keyboard input: + // + // window()->windowHandle()->isActive() == false. + // + // Steps were: SeparatePanel with a WebView2 child, + // some interaction with mouse inside the WebView2, + // so that WebView2 gets focus and active window state, + // then we call setSearchAllowed() and after animation + // is finished try typing -> nothing happens. + // + // With this workaround it works fine. + _widget->activateWindow(); + } + }); + _widget->showBox( std::move(box), LayerOption::KeepOther, diff --git a/Telegram/SourceFiles/window/window_main_menu_helpers.cpp b/Telegram/SourceFiles/window/window_main_menu_helpers.cpp index b70fb833a..629b6cf16 100644 --- a/Telegram/SourceFiles/window/window_main_menu_helpers.cpp +++ b/Telegram/SourceFiles/window/window_main_menu_helpers.cpp @@ -367,12 +367,15 @@ void SetupMenuBots( (height - icon->height()) / 2); }, button->lifetime()); const auto weak = Ui::MakeWeak(container); + const auto show = controller->uiShow(); button->setAcceptBoth(true); button->clicks( ) | rpl::start_with_next([=](Qt::MouseButton which) { if (which == Qt::LeftButton) { - bots->requestSimple(controller, user, { - .fromMainMenu = true, + bots->open({ + .bot = user, + .context = { .controller = controller }, + .source = InlineBots::WebViewSourceMainMenu(), }); if (weak) { controller->window().hideSettingsAndLayer(); @@ -384,7 +387,7 @@ void SetupMenuBots( st::popupMenuWithIcons); (*menu)->addAction( tr::lng_bot_remove_from_menu(tr::now), - [=] { bots->removeFromMenu(user); }, + [=] { bots->removeFromMenu(show, user); }, &st::menuIconDelete); (*menu)->popup(QCursor::pos()); } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 8380e458a..ebd403da4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -614,17 +614,23 @@ void SessionNavigation::showPeerByLinkResolved( const auto contextPeer = item ? item->history()->peer : bot; - const auto action = bot->session().attachWebView().lookupLastAction( - info.clickFromAttachBotWebviewUrl - ).value_or(Api::SendAction(bot->owner().history(contextPeer))); + const auto action = info.clickFromBotWebviewContext + ? info.clickFromBotWebviewContext->action + : Api::SendAction(bot->owner().history(contextPeer)); crl::on_main(this, [=] { - bot->session().attachWebView().requestApp( - parentController(), - action, - bot, - info.botAppName, - info.startToken, - info.botAppForceConfirmation); + bot->session().attachWebView().open({ + .bot = bot, + .context = { + .controller = parentController(), + .action = action, + .maySkipConfirmation = !info.botAppForceConfirmation, + }, + .button = { .startCommand = info.startToken }, + .source = InlineBots::WebViewSourceLinkApp{ + .appname = info.botAppName, + .token = info.startToken, + }, + }); }); } else if (bot && resolveType == ResolveType::ShareGame) { Window::ShowShareGameBox(parentController(), bot, info.startToken); @@ -672,20 +678,25 @@ void SessionNavigation::showPeerByLinkResolved( crl::on_main(this, [=] { const auto history = peer->owner().history(peer); showPeerHistory(history, params, msgId); - peer->session().attachWebView().request( + + peer->session().attachWebView().openByUsername( parentController(), Api::SendAction(history), attachBotUsername, info.attachBotToggleCommand.value_or(QString())); }); - } else if (bot && info.attachBotMenuOpen) { + } else if (bot && info.attachBotMainOpen) { const auto startCommand = info.attachBotToggleCommand.value_or( QString()); - bot->session().attachWebView().requestAddToMenu( - bot, - InlineBots::AddToMenuOpenMenu{ startCommand }, - parentController(), - std::optional<Api::SendAction>()); + bot->session().attachWebView().open({ + .bot = bot, + .context = { .controller = parentController() }, + .button = { .startCommand = startCommand }, + .source = InlineBots::WebViewSourceLinkBotProfile{ + .token = startCommand, + .compact = info.attachBotMainCompact, + }, + }); } else if (bot && info.attachBotToggleCommand) { const auto itemId = info.clickFromMessageId; const auto item = _session->data().message(itemId); @@ -695,17 +706,21 @@ void SessionNavigation::showPeerByLinkResolved( const auto contextUser = contextPeer ? contextPeer->asUser() : nullptr; - bot->session().attachWebView().requestAddToMenu( - bot, - InlineBots::AddToMenuOpenAttach{ - .startCommand = *info.attachBotToggleCommand, - .chooseTypes = info.attachBotChooseTypes, + bot->session().attachWebView().open({ + .bot = bot, + .context = { + .controller = parentController(), + .action = (contextUser + ? Api::SendAction( + contextUser->owner().history(contextUser)) + : std::optional<Api::SendAction>()), }, - parentController(), - (contextUser - ? Api::SendAction( - contextUser->owner().history(contextUser)) - : std::optional<Api::SendAction>())); + .button = { .startCommand = *info.attachBotToggleCommand }, + .source = InlineBots::WebViewSourceLinkAttachMenu{ + .choose = info.attachBotChooseTypes, + .token = *info.attachBotToggleCommand, + }, + }); } else { const auto draft = info.text; crl::on_main(this, [=] { diff --git a/Telegram/SourceFiles/window/window_session_controller_link_info.h b/Telegram/SourceFiles/window/window_session_controller_link_info.h index 2c7457122..74a783696 100644 --- a/Telegram/SourceFiles/window/window_session_controller_link_info.h +++ b/Telegram/SourceFiles/window/window_session_controller_link_info.h @@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace InlineBots { +struct WebViewContext; +} // namespace InlineBots + namespace Window { enum class ResolveType { @@ -45,11 +49,12 @@ struct PeerByLinkInfo { bool botAppForceConfirmation = false; QString attachBotUsername; std::optional<QString> attachBotToggleCommand; - bool attachBotMenuOpen = false; + bool attachBotMainOpen = false; + bool attachBotMainCompact = false; InlineBots::PeerTypes attachBotChooseTypes; std::optional<QString> voicechatHash; FullMsgId clickFromMessageId; - QString clickFromAttachBotWebviewUrl; + std::shared_ptr<InlineBots::WebViewContext> clickFromBotWebviewContext; }; } // namespace Window From c70866a9956ae5ab2612e10637e7427c0ae29245 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 19 Jul 2024 17:42:08 +0200 Subject: [PATCH 072/163] Fix child native window focus. --- Telegram/SourceFiles/iv/iv_controller.cpp | 15 ++++++++------- .../ui/chat/attach/attach_bot_webview.cpp | 13 ++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index e72298f6c..b3fb82a0d 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/invoke_queued.h" +#include "base/qt_signal_producer.h" #include "base/qthelp_url.h" #include "iv/iv_data.h" #include "lang/lang_keys.h" @@ -35,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <QtCore/QJsonObject> #include <QtCore/QJsonValue> #include <QtCore/QFile> +#include <QtGui/QGuiApplication> #include <QtGui/QPainter> #include <QtGui/QWindow> #include <charconv> @@ -280,14 +282,13 @@ void Controller::createWindow() { _window = std::make_unique<Ui::RpWindow>(); const auto window = _window.get(); - window->windowActiveValue( - ) | rpl::map([=] { + base::qt_signal_producer( + qApp, + &QGuiApplication::focusWindowChanged + ) | rpl::filter([=](QWindow *focused) { const auto handle = window->window()->windowHandle(); - return (_shareFocus || _webview) && handle && handle->isActive(); - }) | rpl::distinct_until_changed( - ) | rpl::filter( - rpl::mappers::_1 - ) | rpl::start_with_next([=] { + return _webview && handle && (focused == handle); + }) | rpl::start_with_next([=] { setInnerFocus(); }, window->lifetime()); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 6a50d2590..a8dee5f76 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -741,17 +741,16 @@ postEvent: function(eventType, eventData) { setupProgressGeometry(); - _widget->windowActiveValue( - ) | rpl::map([=] { + base::qt_signal_producer( + qApp, + &QGuiApplication::focusWindowChanged + ) | rpl::filter([=](QWindow *focused) { const auto handle = _widget->window()->windowHandle(); return _webview && !_webview->window.widget()->isHidden() && handle - && handle->isActive(); - }) | rpl::distinct_until_changed( - ) | rpl::filter( - rpl::mappers::_1 - ) | rpl::start_with_next([=] { + && (focused == handle); + }) | rpl::start_with_next([=] { _webview->window.focus(); }, _webview->lifetime); From 1ebe3255e0a584a014b8ef890e38cf7eedf66363 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 16:07:43 +0200 Subject: [PATCH 073/163] Fix share focus in IV. --- Telegram/SourceFiles/iv/iv_instance.cpp | 29 ++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp index d070c93d2..a49ba8887 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/platform/base_platform_info.h" +#include "base/qt_signal_producer.h" #include "boxes/share_box.h" #include "core/application.h" #include "core/file_utilities.h" @@ -49,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller_link_info.h" #include <QtGui/QGuiApplication> +#include <QtGui/QWindow> namespace Iv { namespace { @@ -298,12 +300,33 @@ ShareBoxResult Shown::shareBox(ShareBoxDescriptor &&descriptor) { state->destroyRequests.fire({}); }, wrap->lifetime()); + const auto waiting = layer->lifetime().make_state<rpl::lifetime>(); const auto focus = crl::guard(layer, [=] { - if (!layer->window()->isActiveWindow()) { - layer->window()->activateWindow(); + const auto set = [=] { layer->window()->setFocus(); + layer->setInnerFocus(); + }; + + const auto handle = layer->window()->windowHandle(); + if (!handle) { + waiting->destroy(); + return; + } else if (QGuiApplication::focusWindow() == handle) { + waiting->destroy(); + set(); + } else { + *waiting = base::qt_signal_producer( + qApp, + &QGuiApplication::focusWindowChanged + ) | rpl::filter([=](QWindow *focused) { + const auto handle = layer->window()->windowHandle(); + return handle && (focused == handle); + }) | rpl::start_with_next([=] { + waiting->destroy(); + set(); + }); + layer->window()->activateWindow(); } - layer->setInnerFocus(); }); auto result = ShareBoxResult{ .focus = focus, From 677fbdd84edb93a6bbb7f4241725abbf7e179d17 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 17:46:39 +0300 Subject: [PATCH 074/163] Fix webrtc camera usage on Linux. --- Telegram/ThirdParty/tgcalls | 2 +- Telegram/lib_webrtc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index ce592ecab..358a7b0ec 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit ce592ecab26770fecca683a05f4611d1c00113ef +Subproject commit 358a7b0ec0e22782b4833b9c0add465133d11632 diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 5d44a8acc..1f0b2531d 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 5d44a8acc341d5f45c4150f07929484d15b9c5ba +Subproject commit 1f0b2531d54a881e9a9f0818ce08f2e403917784 From 889ec0c731aea7e234fdf6e2648ef5edddad3c4d Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 18:25:33 +0300 Subject: [PATCH 075/163] Replace \r\n with \n on paste. Fixes #28181. --- Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp | 1 - Telegram/lib_ui | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 8e993f4f7..27e37053a 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1305,7 +1305,6 @@ void WebViewInstance::botAllowWriteAccess(Fn<void(bool allowed)> callback) { } void WebViewInstance::botSharePhone(Fn<void(bool shared)> callback) { - const auto bot = _bot; const auto history = _bot->owner().history(_bot); if (_bot->isBlocked()) { const auto done = crl::guard(this, [=](bool success) { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 007e3a519..7dd187eee 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 007e3a519ffbad0bd889de6cb4b19f34959afbce +Subproject commit 7dd187eeeb46b68dadc5752c3f3fc34e5e1d7e94 From c81f406759cc3693f2104bb80e7c0e8f48a956ba Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 18:04:36 +0200 Subject: [PATCH 076/163] Use xz repository from GitHub. Fixes #28189. --- Telegram/build/docker/centos_env/Dockerfile | 2 +- Telegram/build/prepare/prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 0091881ed..463472041 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -61,7 +61,7 @@ RUN git init zlib \ && rm -rf zlib FROM builder AS xz -RUN git clone -b v5.4.4 --depth=1 https://git.tukaani.org/xz.git \ +RUN git clone -b v5.4.4 --depth=1 https://github.com/tukaani-project/xz.git \ && cd xz \ && cmake -B build . -DCMAKE_BUILD_TYPE=None \ && cmake --build build -j$(nproc) \ diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index ea305575e..f5a64b413 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -540,7 +540,7 @@ release: stage('xz', """ !win: - git clone -b v5.4.5 https://git.tukaani.org/xz.git + git clone -b v5.4.5 https://github.com/tukaani-project/xz.git cd xz sed -i '' '\\@check_symbol_exists(futimens "sys/types.h;sys/stat.h" HAVE_FUTIMENS)@d' CMakeLists.txt CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake -B build . \\ From 1ef6f462f623b77181c794c4f3489b626553d24f Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 18:05:25 +0200 Subject: [PATCH 077/163] Preserve link preview settings on reschedule. --- Telegram/SourceFiles/api/api_editing.cpp | 4 +--- .../history/view/controls/history_view_webpage_processor.cpp | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index f326275fd..d5f76bf86 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -153,9 +153,7 @@ mtpRequestId EditMessage( const auto &text = item->originalText(); const auto webpage = (!item->media() || !item->media()->webpage()) ? Data::WebPageDraft{ .removed = true } - : Data::WebPageDraft{ - .id = item->media()->webpage()->id, - }; + : Data::WebPageDraft::FromItem(item); return EditMessage( item, text, 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 acf09d845..214de059c 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp @@ -257,6 +257,7 @@ void WebpageProcessor::apply(Data::WebPageDraft draft, bool reparse) { const auto was = _link; if (draft.removed) { _draft = draft; + _parsedLinks = _parser.list().current(); if (_parsedLinks.empty()) { _draft.removed = false; } From ac78ae823c0e7865d69b6cbe133648978f0232fd Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Mon, 22 Jul 2024 02:12:39 +0400 Subject: [PATCH 078/163] Reduce portal autostart dialog modality to parent window --- .../platform/linux/specific_linux.cpp | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6d8a8ec54..32437fb77 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -18,8 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "core/launcher.h" #include "core/sandbox.h" +#include "core/application.h" #include "core/core_settings.h" #include "core/update_checker.h" +#include "window/window_controller.h" #include "webview/platform/linux/webview_linux_webkitgtk.h" #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION @@ -92,10 +94,23 @@ void PortalAutostart(bool enabled, Fn<void(bool)> done) { uniqueName.erase(0, 1); uniqueName.replace(uniqueName.find('.'), 1, 1, '_'); - const auto window = std::make_shared<QWidget>(); - window->setAttribute(Qt::WA_DontShowOnScreen); - window->setWindowModality(Qt::ApplicationModal); - window->show(); + const auto parent = []() -> QPointer<QWidget> { + const auto active = Core::App().activeWindow(); + if (!active) { + return nullptr; + } + + return active->widget().get(); + }(); + + const auto window = std::make_shared<base::unique_qptr<QWidget>>( + std::in_place, + parent); + + auto &raw = **window; + raw.setAttribute(Qt::WA_DontShowOnScreen); + raw.setWindowModality(Qt::WindowModal); + raw.show(); XdpRequest::RequestProxy::new_( proxy->get_connection(), @@ -146,7 +161,6 @@ void PortalAutostart(bool enabled, Fn<void(bool)> done) { }); }); - std::vector<std::string> commandline; commandline.push_back(executable.toStdString()); if (Core::Launcher::Instance().customWorkingDir()) { @@ -156,7 +170,9 @@ void PortalAutostart(bool enabled, Fn<void(bool)> done) { commandline.push_back("-autostart"); interface.call_request_background( - base::Platform::XDP::ParentWindowID(), + base::Platform::XDP::ParentWindowID(parent + ? parent->windowHandle() + : nullptr), GLib::Variant::new_array({ GLib::Variant::new_dict_entry( GLib::Variant::new_string("handle_token"), From 2c7922ce7b40fcc4a1bc0b1a7e40452b841fbf7a Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Fri, 19 Jul 2024 18:07:45 +0400 Subject: [PATCH 079/163] Get release CFLAGS from Dockerfile on Linux --- Telegram/build/docker/centos_env/Dockerfile | 10 ++++------ Telegram/build/docker/centos_env/build.sh | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 463472041..f19f8f1e4 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -3,13 +3,12 @@ {%- set GIT_UPDATE_M4 = "git submodule set-url m4 https://gitlab.freedesktop.org/xorg/util/xcb-util-m4 && git config -f .gitmodules submodule.m4.shallow true && git submodule init && git submodule update" -%} {%- set QT = "6.7.2" -%} {%- set QT_TAG = "v" ~ QT -%} -{%- set CFLAGS_DEBUG = "-g -pipe -fPIC -fstack-protector-all -fstack-clash-protection -fcf-protection -D_GLIBCXX_ASSERTIONS" -%} -{%- set CFLAGS_LTO = "-flto=auto -ffat-lto-objects" -%} +{%- set CFLAGS_DEBUG = "$CFLAGS -O0 -fno-lto -U_FORTIFY_SOURCE" -%} {%- set LibrariesPath = "/usr/src/Libraries" -%} # syntax=docker/dockerfile:1 -FROM rockylinux:8 AS builder-base +FROM rockylinux:8 AS builder ENV LANG C.UTF-8 ENV LIBRARY_PATH /usr/local/lib64:/usr/local/lib:/lib64:/lib:/usr/lib64:/usr/lib ENV LD_LIBRARY_PATH $LIBRARY_PATH @@ -33,11 +32,10 @@ WORKDIR {{ LibrariesPath }} RUN python3 -m pip install meson ninja -FROM builder-base AS builder ENV AR gcc-ar ENV RANLIB gcc-ranlib ENV NM gcc-nm -ENV CFLAGS {% if DEBUG %}-g{% endif %} -O3 {% if LTO %}{{ CFLAGS_LTO }}{% endif %} -pipe -fPIC -fno-omit-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection -DNDEBUG -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS +ENV CFLAGS {% if DEBUG %}-g{% endif %} -O3 {% if LTO %}-flto=auto -ffat-lto-objects{% endif %} -pipe -fPIC -fno-strict-aliasing -fno-omit-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS ENV CXXFLAGS $CFLAGS FROM builder AS patches @@ -803,7 +801,7 @@ RUN cmake --build out --config Debug --parallel \ && find out -mindepth 1 -maxdepth 1 ! -name Debug -exec rm -rf {} \; {%- endif %} -FROM builder-base +FROM builder COPY --link --from=zlib {{ LibrariesPath }}/zlib-cache / COPY --link --from=xz {{ LibrariesPath }}/xz-cache / COPY --link --from=protobuf {{ LibrariesPath }}/protobuf-cache / diff --git a/Telegram/build/docker/centos_env/build.sh b/Telegram/build/docker/centos_env/build.sh index 0bf1aae04..e7a34e6ae 100755 --- a/Telegram/build/docker/centos_env/build.sh +++ b/Telegram/build/docker/centos_env/build.sh @@ -3,4 +3,4 @@ set -e cd Telegram ./configure.sh "$@" -cmake --build ../out --config "${CONFIG:-RelWithDebInfo}" --parallel +cmake --build ../out --config "${CONFIG:-Release}" --parallel From 5e1fb6ebbfa59cf06cf724e4ed52087383fdbc01 Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Sat, 20 Jul 2024 12:45:49 +0400 Subject: [PATCH 080/163] Add -fasynchronous-unwind-tables and -mno-omit-leaf-frame-pointer for better debugging on Linux --- Telegram/build/docker/centos_env/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index f19f8f1e4..143bf072c 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -35,7 +35,7 @@ RUN python3 -m pip install meson ninja ENV AR gcc-ar ENV RANLIB gcc-ranlib ENV NM gcc-nm -ENV CFLAGS {% if DEBUG %}-g{% endif %} -O3 {% if LTO %}-flto=auto -ffat-lto-objects{% endif %} -pipe -fPIC -fno-strict-aliasing -fno-omit-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS +ENV CFLAGS {% if DEBUG %}-g{% endif %} -O3 {% if LTO %}-flto=auto -ffat-lto-objects{% endif %} -pipe -fPIC -fno-strict-aliasing -fasynchronous-unwind-tables -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS ENV CXXFLAGS $CFLAGS FROM builder AS patches From f4afa762d8ccc8de2aed0ba59fde3589f57c8fa6 Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Sat, 20 Jul 2024 12:53:22 +0400 Subject: [PATCH 081/163] Replace -fstack-protector-all with -fstack-protector-strong to avoid slowdown of functions not using stack --- Telegram/build/docker/centos_env/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 143bf072c..ca962d935 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -35,7 +35,7 @@ RUN python3 -m pip install meson ninja ENV AR gcc-ar ENV RANLIB gcc-ranlib ENV NM gcc-nm -ENV CFLAGS {% if DEBUG %}-g{% endif %} -O3 {% if LTO %}-flto=auto -ffat-lto-objects{% endif %} -pipe -fPIC -fno-strict-aliasing -fasynchronous-unwind-tables -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS +ENV CFLAGS {% if DEBUG %}-g{% endif %} -O3 {% if LTO %}-flto=auto -ffat-lto-objects{% endif %} -pipe -fPIC -fno-strict-aliasing -fasynchronous-unwind-tables -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fstack-protector-strong -fstack-clash-protection -fcf-protection -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS ENV CXXFLAGS $CFLAGS FROM builder AS patches From c18e8fd777453c38530e694c3eb724fea69b9489 Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Sat, 20 Jul 2024 12:57:19 +0400 Subject: [PATCH 082/163] Enable exceptions for C on Linux --- Telegram/build/docker/centos_env/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index ca962d935..0f101584b 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -35,7 +35,7 @@ RUN python3 -m pip install meson ninja ENV AR gcc-ar ENV RANLIB gcc-ranlib ENV NM gcc-nm -ENV CFLAGS {% if DEBUG %}-g{% endif %} -O3 {% if LTO %}-flto=auto -ffat-lto-objects{% endif %} -pipe -fPIC -fno-strict-aliasing -fasynchronous-unwind-tables -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fstack-protector-strong -fstack-clash-protection -fcf-protection -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS +ENV CFLAGS {% if DEBUG %}-g{% endif %} -O3 {% if LTO %}-flto=auto -ffat-lto-objects{% endif %} -pipe -fPIC -fno-strict-aliasing -fexceptions -fasynchronous-unwind-tables -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fstack-protector-strong -fstack-clash-protection -fcf-protection -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS ENV CXXFLAGS $CFLAGS FROM builder AS patches From 9b2847a11dbaf50e77168724888c06da77d1de9a Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 18:14:46 +0200 Subject: [PATCH 083/163] Update submodules. --- Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- cmake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 4ac8cf9d6..21a8611ab 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 4ac8cf9d65e47efa9d2022939c6d0c38f32d9c7a +Subproject commit 21a8611ab764acc7cb622859ea4c97bd259570af diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 7dd187eee..45f3a3306 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 7dd187eeeb46b68dadc5752c3f3fc34e5e1d7e94 +Subproject commit 45f3a330679b775f0f56bbea7b40f83ebd8f5639 diff --git a/cmake b/cmake index b3871c4ef..462b85cc6 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit b3871c4efde37827263053d95e6f16fe34d4952e +Subproject commit 462b85cc62fd2422084bf6a66408ac7bec3d795d From 24fabf259079a7e3b3a1fe83532d5ab3a67dfff7 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 18:25:09 +0200 Subject: [PATCH 084/163] Fix build on Windows. --- Telegram/ThirdParty/tgcalls | 2 +- Telegram/lib_webrtc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 358a7b0ec..ce5bc41b2 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 358a7b0ec0e22782b4833b9c0add465133d11632 +Subproject commit ce5bc41b27144dad10756c745f6098ba14beb36b diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 1f0b2531d..70b83ba71 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 1f0b2531d54a881e9a9f0818ce08f2e403917784 +Subproject commit 70b83ba71537016a04ff15d44dc078a22c18eced From fb6445249580ef45a0a528148ae277c94f97f8ce Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 25 Jul 2024 09:32:20 +0300 Subject: [PATCH 085/163] Fix build on Linux. --- Telegram/ThirdParty/tgcalls | 2 +- Telegram/build/deploy.sh | 4 ++-- Telegram/build/docker/centos_env/Dockerfile | 2 +- cmake | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index ce5bc41b2..0d88a47c7 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit ce5bc41b27144dad10756c745f6098ba14beb36b +Subproject commit 0d88a47c7f6dcebf4e48c16d253bb3db5a15e2e6 diff --git a/Telegram/build/deploy.sh b/Telegram/build/deploy.sh index 21183fa38..e3217ca40 100755 --- a/Telegram/build/deploy.sh +++ b/Telegram/build/deploy.sh @@ -70,9 +70,9 @@ else DeployMac="1" DeployWin="1" DeployWin64="1" - DeployWinArm="1" + DeployWinArm="0" DeployLinux="1" - echo "Deploying five versions of $AppVersionStrFull: for Windows 32 bit, Windows 64 bit, Windows on ARM, macOS and Linux 64 bit.." + echo "Deploying four versions of $AppVersionStrFull: for Windows 32 bit, Windows 64 bit, macOS and Linux 64 bit.." fi if [ "$BuildTarget" == "mac" ]; then BackupPath="$HOME/Projects/backup/tdesktop" diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 0f101584b..303aab478 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -59,7 +59,7 @@ RUN git init zlib \ && rm -rf zlib FROM builder AS xz -RUN git clone -b v5.4.4 --depth=1 https://github.com/tukaani-project/xz.git \ +RUN git clone -b v5.4.4 --depth=1 {{ GIT }}/tukaani-project/xz.git \ && cd xz \ && cmake -B build . -DCMAKE_BUILD_TYPE=None \ && cmake --build build -j$(nproc) \ diff --git a/cmake b/cmake index 462b85cc6..17c758e2b 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 462b85cc62fd2422084bf6a66408ac7bec3d795d +Subproject commit 17c758e2b9f09a58f14582bde3561873ce026039 From 77d6e19214781859234e33865a209a8fe36502dc Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 18:54:23 +0200 Subject: [PATCH 086/163] Beta version 5.2.4. - Allow opening several web apps. - Send location marks and venues. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/build/version | 10 +++++----- changelog.txt | 5 +++++ 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 46918013d..27e1bbfe4 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ <Identity Name="TelegramMessengerLLP.TelegramDesktop" ProcessorArchitecture="ARCHITECTURE" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" - Version="5.2.3.0" /> + Version="5.2.4.0" /> <Properties> <DisplayName>Telegram Desktop</DisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index c47677eb2..a256817c4 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,3,0 - PRODUCTVERSION 5,2,3,0 + FILEVERSION 5,2,4,0 + PRODUCTVERSION 5,2,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.2.3.0" + VALUE "FileVersion", "5.2.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.3.0" + VALUE "ProductVersion", "5.2.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 5594959fe..8f3f05d48 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,3,0 - PRODUCTVERSION 5,2,3,0 + FILEVERSION 5,2,4,0 + PRODUCTVERSION 5,2,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.2.3.0" + VALUE "FileVersion", "5.2.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.3.0" + VALUE "ProductVersion", "5.2.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index e1ac92bf4..45302d07a 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 = 5002003; -constexpr auto AppVersionStr = "5.2.3"; -constexpr auto AppBetaVersion = false; +constexpr auto AppVersion = 5002004; +constexpr auto AppVersionStr = "5.2.4"; +constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 4d2b86059..446ec84d7 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5002003 +AppVersion 5002004 AppVersionStrMajor 5.2 -AppVersionStrSmall 5.2.3 -AppVersionStr 5.2.3 -BetaChannel 0 +AppVersionStrSmall 5.2.4 +AppVersionStr 5.2.4 +BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.2.3 +AppVersionOriginal 5.2.4.beta diff --git a/changelog.txt b/changelog.txt index c0298f855..8d5ac75b8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +5.2.4 beta (24.07.24) + +- Allow opening several web apps. +- Send location marks and venues. + 5.2.3 (07.07.24) - Fix crash in bot star stats page. From 517b456670d86a7844bddae5cc2557f269163016 Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Thu, 25 Jul 2024 21:25:55 +0400 Subject: [PATCH 087/163] Add hit test for window context menu --- Telegram/SourceFiles/calls/calls_panel.cpp | 2 +- .../calls/group/calls_group_panel.cpp | 2 +- .../media/view/media_view_overlay_widget.cpp | 25 +++++++++++-------- Telegram/lib_ui | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 4b92ee075..ea204a770 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -215,7 +215,7 @@ void Panel::initWindow() { } const auto shown = _layerBg->topShownLayer(); return (!shown || !shown->geometry().contains(widgetPoint)) - ? (Flag::Move | Flag::FullScreen) + ? (Flag::Move | Flag::Menu | Flag::FullScreen) : Flag::None; }); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 84c336131..927dba8f8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -410,7 +410,7 @@ void Panel::initWindow() { } const auto shown = _layerBg->topShownLayer(); return (!shown || !shown->geometry().contains(widgetPoint)) - ? (Flag::Move | Flag::Maximize) + ? (Flag::Move | Flag::Menu | Flag::Maximize) : Flag::None; }); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index c6a7e9c6e..985bc03f9 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -729,25 +729,27 @@ void OverlayWidget::orderWidgets() { void OverlayWidget::setupWindow() { _window->setBodyTitleArea([=](QPoint widgetPoint) { using Flag = Ui::WindowTitleHitTestFlag; - if (!_windowed - || !_widget->rect().contains(widgetPoint) + Ui::WindowTitleHitTestFlags result; + if (!_widget->rect().contains(widgetPoint) || _helper->skipTitleHitTest(widgetPoint)) { - return Flag::None | Flag(0); + return result; } - const auto inControls = (_over != Over::None) && (_over != Over::Video); + if (widgetPoint.y() <= st::mediaviewTitleButton.height) { + result |= Flag::Menu; + } + const auto inControls = ((_over != Over::None) && (_over != Over::Video)); if (inControls || (_streamed && _streamed->controls && _streamed->controls->dragging())) { - return Flag::None | Flag(0); } else if ((_w > _widget->width() || _h > _maxUsedHeight) && (widgetPoint.y() > st::mediaviewHeaderTop) && QRect(_x, _y, _w, _h).contains(widgetPoint)) { - return Flag::None | Flag(0); } else if (_stories && _stories->ignoreWindowMove(widgetPoint)) { - return Flag::None | Flag(0); + } else if (_windowed) { + result |= Flag::Move; } - return Flag::Move | Flag(0); + return result; }); _window->setAttribute(Qt::WA_NoSystemBackground, true); @@ -5926,8 +5928,11 @@ void OverlayWidget::handleMouseRelease( } bool OverlayWidget::handleContextMenu(std::optional<QPoint> position) { - if (position && !QRect(_x, _y, _w, _h).contains(*position)) { - return false; + if (position) { + if (!QRect(_x, _y, _w, _h).contains(*position) + || position->y() <= st::mediaviewTitleButton.height) { + return false; + } } _menu = base::make_unique_q<Ui::PopupMenu>( _window, diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 45f3a3306..03f250aab 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 45f3a330679b775f0f56bbea7b40f83ebd8f5639 +Subproject commit 03f250aab2e791bfb6a047bffa05273748518816 From 3888e8084acc87e54bdd1af353d539d3fc3f7bf7 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 26 Jul 2024 11:38:09 +0200 Subject: [PATCH 088/163] Update tg_owt, lib_webrtc and patches. --- Telegram/build/docker/centos_env/Dockerfile | 4 ++-- Telegram/build/prepare/prepare.py | 4 ++-- Telegram/lib_webrtc | 2 +- snap/snapcraft.yaml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 303aab478..99816a198 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -42,7 +42,7 @@ FROM builder AS patches RUN git init patches \ && cd patches \ && git remote add origin {{ GIT }}/desktop-app/patches.git \ - && git fetch --depth=1 origin 6898f0d215f249917c076f00d3fc954a43f35e6a \ + && git fetch --depth=1 origin 85a1c4ec327ed390a27e85f2162c31525220a50d \ && git reset --hard FETCH_HEAD \ && rm -rf .git @@ -771,7 +771,7 @@ COPY --link --from=pipewire {{ LibrariesPath }}/pipewire-cache / RUN git init tg_owt \ && cd tg_owt \ && git remote add origin {{ GIT }}/desktop-app/tg_owt.git \ - && git fetch --depth=1 origin 996dbe2c83b5a71d9045ce47960b8432e223dbb3 \ + && git fetch --depth=1 origin c0ba7b391c0fd1d408fc0d58f4fbecb68a6fcd55 \ && git reset --hard FETCH_HEAD \ && git submodule update --init --recursive --depth=1 \ && rm -rf .git \ diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index f5a64b413..b7c447019 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -457,7 +457,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 6898f0d215 + git checkout 85a1c4ec32 """) stage('msys64', """ @@ -1718,7 +1718,7 @@ win: stage('tg_owt', """ git clone https://github.com/desktop-app/tg_owt.git cd tg_owt - git checkout 996dbe2c83 + git checkout c0ba7b391c git submodule init git submodule update win: diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 70b83ba71..d9a08df0c 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 70b83ba71537016a04ff15d44dc078a22c18eced +Subproject commit d9a08df0c0b64e4323e23c9ccefe754fd86d0f44 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 0a3d3f855..12fa23acb 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -165,7 +165,7 @@ parts: patches: source: https://github.com/desktop-app/patches.git source-depth: 1 - source-commit: 6898f0d215f249917c076f00d3fc954a43f35e6a + source-commit: 85a1c4ec327ed390a27e85f2162c31525220a50d plugin: dump override-pull: | craftctl default @@ -433,7 +433,7 @@ parts: webrtc: source: https://github.com/desktop-app/tg_owt.git source-depth: 1 - source-commit: 996dbe2c83b5a71d9045ce47960b8432e223dbb3 + source-commit: c0ba7b391c0fd1d408fc0d58f4fbecb68a6fcd55 plugin: cmake build-environment: - LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s From 86cdda22771748e8cc4f635fc00a6f6f40003b27 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 26 Jul 2024 13:36:05 +0200 Subject: [PATCH 089/163] Fix text with blockquotes geometry counting. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 03f250aab..99d914213 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 03f250aab2e791bfb6a047bffa05273748518816 +Subproject commit 99d9142130025e817fe7f1ac278078c34a509a1c From db0856f71c8673acbab9d2739083e1c38b04b1f2 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 26 Jul 2024 13:45:02 +0200 Subject: [PATCH 090/163] Support t.me/username?profile links. --- Telegram/SourceFiles/core/local_url_handlers.cpp | 4 +++- Telegram/SourceFiles/window/window_session_controller.cpp | 2 ++ .../SourceFiles/window/window_session_controller_link_info.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 223cec043..6777464c1 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -503,7 +503,9 @@ bool ResolveUsernameOrPhone( return false; } using ResolveType = Window::ResolveType; - auto resolveType = ResolveType::Default; + auto resolveType = params.contains(u"profile"_q) + ? ResolveType::Profile + : ResolveType::Default; auto startToken = params.value(u"start"_q); if (!startToken.isEmpty()) { resolveType = ResolveType::BotStart; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index ebd403da4..ef1bddf24 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -577,6 +577,8 @@ void SessionNavigation::showPeerByLinkResolved( info.messageId, commentId->id, params); + } else if (resolveType == ResolveType::Profile) { + showPeerInfo(peer, params); } else if (peer->isForum() && resolveType != ResolveType::Boost) { const auto itemId = info.messageId; if (!itemId) { diff --git a/Telegram/SourceFiles/window/window_session_controller_link_info.h b/Telegram/SourceFiles/window/window_session_controller_link_info.h index 74a783696..d64d82c53 100644 --- a/Telegram/SourceFiles/window/window_session_controller_link_info.h +++ b/Telegram/SourceFiles/window/window_session_controller_link_info.h @@ -22,6 +22,7 @@ enum class ResolveType { ShareGame, Mention, Boost, + Profile, }; struct CommentId { From 3f0f3a3c11966f47ec058e2facee5e32010bc026 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 26 Jul 2024 17:23:41 +0200 Subject: [PATCH 091/163] Focus existing location picker. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_common.h | 8 + .../chat_helpers/chat_helpers.style | 6 + .../data/components/location_pickers.cpp | 44 ++++ .../data/components/location_pickers.h | 39 ++++ .../inline_bots/bot_attach_web_view.cpp | 15 +- Telegram/SourceFiles/main/main_session.cpp | 2 + Telegram/SourceFiles/main/main_session.h | 5 + .../settings/business/settings_location.cpp | 9 +- .../ui/controls/location_picker.cpp | 191 +++++++++++++++--- .../SourceFiles/ui/controls/location_picker.h | 10 +- 12 files changed, 297 insertions(+), 35 deletions(-) create mode 100644 Telegram/SourceFiles/data/components/location_pickers.cpp create mode 100644 Telegram/SourceFiles/data/components/location_pickers.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 48cf60eff..b9ee4785c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -475,6 +475,8 @@ PRIVATE data/business/data_shortcut_messages.h data/components/factchecks.cpp data/components/factchecks.h + data/components/location_pickers.cpp + data/components/location_pickers.h data/components/recent_peers.cpp data/components/recent_peers.h data/components/scheduled_messages.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6c9828189..b902aa4f6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3197,6 +3197,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_unread_bar_some" = "Unread messages"; "lng_maps_point" = "Location"; +"lng_maps_select_on_map" = "Select on the Map"; "lng_maps_point_send" = "Send This Location"; "lng_maps_point_set" = "Set This Location"; "lng_maps_or_choose" = "Or choose a venue"; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index cd8aa54e2..81f098d67 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -30,6 +30,10 @@ struct SendOptions { bool invertCaption = false; bool hideViaBot = false; crl::time ttlSeconds = 0; + + friend inline bool operator==( + const SendOptions &, + const SendOptions &) = default; }; [[nodiscard]] SendOptions DefaultSendWhenOnlineOptions(); @@ -52,6 +56,10 @@ struct SendAction { MsgId replaceMediaOf = 0; [[nodiscard]] MTPInputReplyTo mtpReplyTo() const; + + friend inline bool operator==( + const SendAction &, + const SendAction &) = default; }; struct MessageToSend { diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 9b92f4815..47c1e8b47 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1475,3 +1475,9 @@ pickLocationLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { thickness: 4px; } pickLocationPromoHeight: 32px; +pickLocationChooseOnMap: RoundButton(defaultActiveButton) { + height: 44px; + textTop: 11px; + width: -96px; + font: font(15px semibold); +} diff --git a/Telegram/SourceFiles/data/components/location_pickers.cpp b/Telegram/SourceFiles/data/components/location_pickers.cpp new file mode 100644 index 000000000..4402e4ccf --- /dev/null +++ b/Telegram/SourceFiles/data/components/location_pickers.cpp @@ -0,0 +1,44 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/components/location_pickers.h" + +#include "api/api_common.h" +#include "ui/controls/location_picker.h" + +namespace Data { + +struct LocationPickers::Entry { + Api::SendAction action; + base::weak_ptr<Ui::LocationPicker> picker; +}; + +LocationPickers::LocationPickers() = default; + +LocationPickers::~LocationPickers() = default; + +Ui::LocationPicker *LocationPickers::lookup(const Api::SendAction &action) { + for (auto i = begin(_pickers); i != end(_pickers);) { + if (const auto strong = i->picker.get()) { + if (i->action == action) { + return i->picker.get(); + } + ++i; + } else { + i = _pickers.erase(i); + } + } + return nullptr; +} + +void LocationPickers::emplace( + const Api::SendAction &action, + not_null<Ui::LocationPicker*> picker) { + _pickers.push_back({ action, picker }); +} + +} // namespace Data \ No newline at end of file diff --git a/Telegram/SourceFiles/data/components/location_pickers.h b/Telegram/SourceFiles/data/components/location_pickers.h new file mode 100644 index 000000000..ad1046095 --- /dev/null +++ b/Telegram/SourceFiles/data/components/location_pickers.h @@ -0,0 +1,39 @@ +/* +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" + +namespace Api { +struct SendAction; +} // namespace Api + +namespace Ui { +class LocationPicker; +} // namespace Ui + +namespace Data { + +class LocationPickers final { +public: + LocationPickers(); + ~LocationPickers(); + + Ui::LocationPicker *lookup(const Api::SendAction &action); + void emplace( + const Api::SendAction &action, + not_null<Ui::LocationPicker*> picker); + +private: + struct Entry; + + std::vector<Entry> _pickers; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 27e37053a..e7d842c64 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/share_box.h" #include "core/click_handler_types.h" #include "core/shortcuts.h" +#include "data/components/location_pickers.h" #include "data/data_bot_app.h" #include "data/data_changes.h" #include "data/data_user.h" @@ -1776,6 +1777,11 @@ void ChooseAndSendLocation( not_null<Window::SessionController*> controller, const Ui::LocationPickerConfig &config, Api::SendAction action) { + const auto session = &controller->session(); + if (const auto picker = session->locationPickers().lookup(action)) { + picker->activate(); + return; + } const auto callback = [=](Data::InputVenue venue) { if (venue.justLocation()) { Api::SendLocation(action, venue.lat, venue.lon); @@ -1783,17 +1789,18 @@ void ChooseAndSendLocation( Api::SendVenue(action, venue); } }; - Ui::LocationPicker::Show({ + const auto picker = Ui::LocationPicker::Show({ .parent = controller->widget(), .config = config, .chooseLabel = tr::lng_maps_point_send(), .recipient = action.history->peer, - .session = &controller->session(), - .callback = crl::guard(controller, callback), + .session = session, + .callback = crl::guard(session, callback), .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); }, - .storageId = controller->session().local().resolveStorageIdBots(), + .storageId = session->local().resolveStorageIdBots(), .closeRequests = controller->content()->death(), }); + session->locationPickers().emplace(action, picker); } std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu( diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index c14d49051..cc09dacf5 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_account.h" #include "storage/storage_facade.h" #include "data/components/factchecks.h" +#include "data/components/location_pickers.h" #include "data/components/recent_peers.h" #include "data/components/scheduled_messages.h" #include "data/components/sponsored_messages.h" @@ -111,6 +112,7 @@ Session::Session( , _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this)) , _topPeers(std::make_unique<Data::TopPeers>(this)) , _factchecks(std::make_unique<Data::Factchecks>(this)) +, _locationPickers(std::make_unique<Data::LocationPickers>()) , _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>()) , _supportHelper(Support::Helper::Create(this)) , _saveSettingsTimer([=] { saveSettings(); }) { diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 9581e7cd4..a69373a36 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -36,6 +36,7 @@ class ScheduledMessages; class SponsoredMessages; class TopPeers; class Factchecks; +class LocationPickers; } // namespace Data namespace HistoryView::Reactions { @@ -131,6 +132,9 @@ public: [[nodiscard]] Data::Factchecks &factchecks() const { return *_factchecks; } + [[nodiscard]] Data::LocationPickers &locationPickers() const { + return *_locationPickers; + } [[nodiscard]] Api::Updates &updates() const { return *_updates; } @@ -259,6 +263,7 @@ private: const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages; const std::unique_ptr<Data::TopPeers> _topPeers; const std::unique_ptr<Data::Factchecks> _factchecks; + const std::unique_ptr<Data::LocationPickers> _locationPickers; using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory; const std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory; diff --git a/Telegram/SourceFiles/settings/business/settings_location.cpp b/Telegram/SourceFiles/settings/business/settings_location.cpp index e5bb0f8e3..6b659d044 100644 --- a/Telegram/SourceFiles/settings/business/settings_location.cpp +++ b/Telegram/SourceFiles/settings/business/settings_location.cpp @@ -63,6 +63,7 @@ private: const Ui::LocationPickerConfig _config; rpl::variable<Data::BusinessLocation> _data; rpl::variable<Data::CloudImage*> _map = nullptr; + base::weak_ptr<Ui::LocationPicker> _picker; std::shared_ptr<QImage> _view; Ui::RoundRect _bottomSkipRounding; @@ -232,6 +233,10 @@ void Location::setupPicker(not_null<Ui::VerticalLayout*> content) { } void Location::chooseOnMap() { + if (const auto strong = _picker.get()) { + strong->activate(); + return; + } const auto callback = [=](Data::InputVenue venue) { auto copy = _data.current(); copy.point = Data::LocationPoint( @@ -249,7 +254,7 @@ void Location::chooseOnMap() { .accuracy = Core::GeoLocationAccuracy::Exact, } : Core::GeoLocation(); - Ui::LocationPicker::Show({ + _picker = Ui::LocationPicker::Show({ .parent = controller()->widget(), .config = _config, .chooseLabel = tr::lng_maps_point_set(), @@ -258,7 +263,7 @@ void Location::chooseOnMap() { .callback = crl::guard(this, callback), .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); }, .storageId = session->local().resolveStorageIdBots(), - .closeRequests = controller()->content()->death(), + .closeRequests = death(), }); } diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 8cce44571..b6e3dc441 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/chat_search_empty.h" // Dialogs::SearchEmpty. #include "lang/lang_instance.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" #include "main/session/session_show.h" #include "main/main_session.h" #include "mtproto/mtproto_config.h" @@ -41,6 +42,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" +#include "styles/style_settings.h" // settingsCloudPasswordIconSize +#include "styles/style_layers.h" // boxDividerHeight #include <QtCore/QFile> #include <QtCore/QJsonDocument> @@ -185,11 +188,12 @@ private: [[nodiscard]] object_ptr<RpWidget> MakeFoursquarePromo() { auto result = object_ptr<RpWidget>((QWidget*)nullptr); + const auto skip = st::defaultVerticalListSkip; const auto raw = result.data(); - raw->resize(0, st::pickLocationPromoHeight); + raw->resize(0, skip + st::pickLocationPromoHeight); const auto shadow = CreateChild<PlainShadow>(raw); raw->widthValue() | rpl::start_with_next([=](int width) { - shadow->setGeometry(0, 0, width, st::lineWidth); + shadow->setGeometry(0, skip, width, st::lineWidth); }, raw->lifetime()); raw->paintRequest() | rpl::start_with_next([=](QRect clip) { auto p = QPainter(raw); @@ -197,7 +201,7 @@ private: p.setPen(st::windowSubTextFg); p.setFont(st::normalFont); p.drawText( - raw->rect(), + raw->rect().marginsRemoved({ 0, skip, 0, 0 }), tr::lng_maps_venues_source(tr::now), style::al_center); }, raw->lifetime()); @@ -544,7 +548,7 @@ void SetupEmptyView( (query ? Icon::NoResults : Icon::Search), (query ? tr::lng_maps_no_places - : tr::lng_maps_choose_to_search)(Ui::Text::WithEntities)); + : tr::lng_maps_choose_to_search)(Text::WithEntities)); view->setMinimalHeight(st::recentPeersEmptyHeightMin); view->show(); @@ -636,6 +640,96 @@ void SetupVenues( return result; } +not_null<RpWidget*> SetupMapPlaceholder( + not_null<RpWidget*> parent, + int minHeight, + int maxHeight, + Fn<void()> choose) { + const auto result = CreateChild<RpWidget>(parent); + + const auto top = CreateChild<BoxContentDivider>(result); + const auto bottom = CreateChild<BoxContentDivider>(result); + + const auto icon = CreateChild<RpWidget>(result); + const auto iconSize = st::settingsCloudPasswordIconSize; + auto ownedLottie = Lottie::MakeIcon({ + .name = u"location"_q, + .sizeOverride = { iconSize, iconSize }, + .limitFps = true, + }); + const auto lottie = ownedLottie.get(); + icon->lifetime().add([kept = std::move(ownedLottie)] {}); + + icon->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(icon); + const auto left = (icon->width() - iconSize) / 2; + const auto scale = icon->height() / float64(iconSize); + auto hq = std::optional<PainterHighQualityEnabler>(); + if (scale < 1.) { + const auto center = QPointF( + icon->width() / 2., + icon->height() / 2.); + hq.emplace(p); + p.translate(center); + p.scale(scale, scale); + p.translate(-center); + p.setOpacity(scale); + } + lottie->paint(p, left, 0); + }, icon->lifetime()); + + InvokeQueued(icon, [=] { + const auto till = lottie->framesCount() - 1; + lottie->animate([=] { icon->update(); }, 0, till); + }); + + const auto button = CreateChild<RoundButton>( + result, + tr::lng_maps_select_on_map(), + st::pickLocationChooseOnMap); + button->setFullRadius(true); + button->setTextTransform(RoundButton::TextTransform::NoTransform); + button->setClickedCallback(choose); + + parent->sizeValue() | rpl::start_with_next([=](QSize size) { + result->setGeometry(QRect(QPoint(), size)); + + const auto width = size.width(); + top->setGeometry(0, 0, width, top->height()); + bottom->setGeometry(QRect( + QPoint(0, size.height() - bottom->height()), + QSize(width, bottom->height()))); + const auto dividers = top->height() + bottom->height(); + + const auto ratio = (size.height() - minHeight) + / float64(maxHeight - minHeight); + const auto iconHeight = int(base::SafeRound(ratio * iconSize)); + + const auto available = size.height() - dividers; + const auto maxDelta = (maxHeight + - dividers + - iconSize + - button->height()) / 2; + const auto minDelta = (minHeight - dividers - button->height()) / 2; + + const auto delta = anim::interpolate(minDelta, maxDelta, ratio); + button->move( + (width - button->width()) / 2, + size.height() - bottom->height() - delta - button->height()); + const auto wide = available - delta - button->height(); + const auto skip = (wide - iconHeight) / 2; + icon->setGeometry(0, top->height() + skip, width, iconHeight); + }, result->lifetime()); + + top->show(); + icon->show(); + bottom->show(); + result->show(); + + return result; +} + } // namespace LocationPicker::LocationPicker(Descriptor &&descriptor) @@ -646,6 +740,8 @@ LocationPicker::LocationPicker(Descriptor &&descriptor) , _body((_window->setInnerSize(st::pickLocationWindow) , _window->showInner(base::make_unique_q<RpWidget>(_window.get())) , _window->inner())) +, _chooseButtonLabel(std::move(descriptor.chooseLabel)) +, _webviewStorageId(descriptor.storageId) , _updateStyles([=] { const auto str = EscapeForScriptString(ComputeStyles()); if (_webview) { @@ -684,7 +780,6 @@ bool LocationPicker::Available(const LocationPickerConfig &config) { void LocationPicker::setup(const Descriptor &descriptor) { setupWindow(descriptor); - setupWebview(descriptor); _initialProvided = descriptor.initial; const auto initial = _initialProvided.exact() @@ -695,6 +790,9 @@ void LocationPicker::setup(const Descriptor &descriptor) { resolveAddress(initial); venuesSearchEnableAt(initial); } + if (!_initialProvided) { + resolveCurrentLocation(); + } } void LocationPicker::setupWindow(const Descriptor &descriptor) { @@ -714,6 +812,15 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { parent.y() + (parent.height() - window->height()) / 2); _container = CreateChild<RpWidget>(_body.get()); + _mapPlaceholderAdded = st::pickLocationButtonSkip + + st::pickLocationButton.height + + st::pickLocationButtonSkip + + st::boxDividerHeight; + const auto min = st::pickLocationCollapsedHeight + _mapPlaceholderAdded; + const auto max = st::pickLocationMapHeight + _mapPlaceholderAdded; + _mapPlaceholder = SetupMapPlaceholder(_container, min, max, [=] { + setupWebview(); + }); _scroll = CreateChild<ScrollArea>(_body.get()); const auto controls = _scroll->setOwnedWidget( object_ptr<VerticalLayout>(_scroll)); @@ -727,17 +834,6 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { const auto toppad = mapControls->add(object_ptr<RpWidget>(controls)); - const auto button = mapControls->add( - MakeChooseLocationButton( - mapControls, - std::move(descriptor.chooseLabel), - _geocoderAddress.value()), - { 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip }); - button->setClickedCallback([=] { - _webview->eval("LocationPicker.send();"); - }); - - AddDivider(mapControls); AddSkip(mapControls); AddSubsectionTitle(mapControls, tr::lng_maps_or_choose()); @@ -757,7 +853,9 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { const auto sub = std::min( (st::pickLocationMapHeight - st::pickLocationCollapsedHeight), scrollTop); - const auto mapHeight = st::pickLocationMapHeight - sub; + const auto mapHeight = st::pickLocationMapHeight + - sub + + (_mapPlaceholder ? _mapPlaceholderAdded : 0); _container->setGeometry(0, 0, width, mapHeight); const auto scrollWidgetTop = search ? 0 : mapHeight; const auto scrollHeight = height - scrollWidgetTop; @@ -771,21 +869,52 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { }, _container->lifetime()); _container->show(); - _scroll->hide(); + _scroll->show(); controls->show(); - button->show(); window->show(); } -void LocationPicker::setupWebview(const Descriptor &descriptor) { +void LocationPicker::setupWebview() { Expects(!_webview); + delete base::take(_mapPlaceholder); + + const auto mapControls = _mapControlsWrap->entity(); + mapControls->insert( + 1, + object_ptr<BoxContentDivider>(mapControls) + )->show(); + + _mapButton = mapControls->insert( + 1, + MakeChooseLocationButton( + mapControls, + _chooseButtonLabel.value(), + _geocoderAddress.value()), + { 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip }); + _mapButton->setClickedCallback([=] { + _webview->eval("LocationPicker.send();"); + }); + _mapButton->hide(); + + _scroll->scrollToY(0); + _venuesSearchShown.force_assign(_venuesSearchShown.current()); + + _mapLoading = CreateChild<RpWidget>(_body.get()); + + _container->geometryValue() | rpl::start_with_next([=](QRect rect) { + _mapLoading->setGeometry(rect); + }, _mapLoading->lifetime()); + + SetupLoadingView(_mapLoading); + _mapLoading->show(); + const auto window = _window.get(); _webview = std::make_unique<Webview::Window>( _container, Webview::WindowConfig{ .opaqueBg = st::windowBg->c, - .storageId = descriptor.storageId, + .storageId = _webviewStorageId, .dataProtocolOverride = kProtocolOverride, }); const auto raw = _webview.get(); @@ -823,12 +952,6 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { const auto event = object.value("event").toString(); if (event == u"ready"_q) { mapReady(); - if (!_initialProvided) { - resolveCurrentLocation(); - } - if (_webview) { - _webview->focus(); - } } else if (event == u"keydown"_q) { const auto key = object.value("key").toString(); const auto modifier = object.value("modifier").toString(); @@ -968,6 +1091,8 @@ void LocationPicker::resolveAddress(Core::GeoLocation location) { void LocationPicker::mapReady() { Expects(_scroll != nullptr); + delete base::take(_mapLoading); + const auto token = _config.mapsToken.toUtf8(); const auto center = DefaultCenter(_initialProvided); const auto bounds = DefaultBounds(); @@ -980,7 +1105,11 @@ void LocationPicker::mapReady() { + ", protocol: " + protocol; _webview->eval("LocationPicker.init({ " + params + " });"); - _scroll->show(); + const auto handle = _window->window()->windowHandle(); + if (handle && QGuiApplication::focusWindow() == handle) { + _webview->focus(); + } + _mapButton->show(); } bool LocationPicker::venuesFromCache( @@ -1163,6 +1292,12 @@ void LocationPicker::processKey( } } +void LocationPicker::activate() { + if (_window) { + _window->activateWindow(); + } +} + void LocationPicker::close() { crl::on_main(this, [=] { _window = nullptr; diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index 97ce724f3..943e7ac75 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -29,6 +29,7 @@ class Window; namespace Ui { +class AbstractButton; class SeparatePanel; class RpWidget; class ScrollArea; @@ -93,6 +94,7 @@ public: [[nodiscard]] static bool Available(const LocationPickerConfig &config); static not_null<LocationPicker*> Show(Descriptor &&descriptor); + void activate(); void close(); void minimize(); void quit(); @@ -109,7 +111,7 @@ private: void setup(const Descriptor &descriptor); void setupWindow(const Descriptor &descriptor); - void setupWebview(const Descriptor &descriptor); + void setupWebview(); void processKey(const QString &key, const QString &modifier); void resolveCurrentLocation(); void resolveAddressByTimer(); @@ -129,11 +131,17 @@ private: std::unique_ptr<SeparatePanel> _window; not_null<RpWidget*> _body; RpWidget *_container = nullptr; + RpWidget *_mapPlaceholder = nullptr; + RpWidget *_mapLoading = nullptr; + AbstractButton *_mapButton = nullptr; SlideWrap<VerticalLayout> *_mapControlsWrap = nullptr; + rpl::variable<QString> _chooseButtonLabel; ScrollArea *_scroll = nullptr; + Webview::StorageId _webviewStorageId; std::unique_ptr<Webview::Window> _webview; SingleQueuedInvokation _updateStyles; Core::GeoLocation _initialProvided; + int _mapPlaceholderAdded = 0; bool _subscribedToColors = false; base::Timer _geocoderResolveTimer; From 37907636e618311234fa9ea4ef5204434fe76d57 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 27 Jul 2024 07:43:47 +0200 Subject: [PATCH 092/163] Fix build with Xcode. --- Telegram/ThirdParty/tgcalls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 0d88a47c7..9bf4065ea 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 0d88a47c7f6dcebf4e48c16d253bb3db5a15e2e6 +Subproject commit 9bf4065ea00cbed5e63cec348457ed13143459d0 From ee6edf9caa032f891345facc037c46210329d259 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 26 Jul 2024 19:24:01 +0200 Subject: [PATCH 093/163] Force venue icon format. --- Telegram/SourceFiles/ui/controls/location_picker.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index b6e3dc441..945e5cf8f 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -298,6 +298,10 @@ void VenuesController::rowPaintIcon( QSize(inner, inner) * ratio, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + if (!data.icon.isNull()) { + data.icon = data.icon.convertToFormat( + QImage::Format_ARGB32_Premultiplied); + } } } From 6a000207ee3b29f0f43d0923b1528e77ff1cd4a1 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Sat, 27 Jul 2024 07:48:35 +0200 Subject: [PATCH 094/163] Beta version 5.2.5. - Fix media viewer context menu. - Fix blockquotes layout in 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 | 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 27e1bbfe4..4716432a8 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ <Identity Name="TelegramMessengerLLP.TelegramDesktop" ProcessorArchitecture="ARCHITECTURE" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" - Version="5.2.4.0" /> + Version="5.2.5.0" /> <Properties> <DisplayName>Telegram Desktop</DisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a256817c4..91720d235 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,4,0 - PRODUCTVERSION 5,2,4,0 + FILEVERSION 5,2,5,0 + PRODUCTVERSION 5,2,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.2.4.0" + VALUE "FileVersion", "5.2.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.4.0" + VALUE "ProductVersion", "5.2.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 8f3f05d48..829055b26 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,4,0 - PRODUCTVERSION 5,2,4,0 + FILEVERSION 5,2,5,0 + PRODUCTVERSION 5,2,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.2.4.0" + VALUE "FileVersion", "5.2.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.4.0" + VALUE "ProductVersion", "5.2.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 45302d07a..fcfe70dd8 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 = 5002004; -constexpr auto AppVersionStr = "5.2.4"; +constexpr auto AppVersion = 5002005; +constexpr auto AppVersionStr = "5.2.5"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 446ec84d7..3e6807e8b 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5002004 +AppVersion 5002005 AppVersionStrMajor 5.2 -AppVersionStrSmall 5.2.4 -AppVersionStr 5.2.4 +AppVersionStrSmall 5.2.5 +AppVersionStr 5.2.5 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.2.4.beta +AppVersionOriginal 5.2.5.beta diff --git a/changelog.txt b/changelog.txt index 8d5ac75b8..09adb5835 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +5.2.5 beta (27.07.24) + +- Fix media viewer context menu. +- Fix blockquotes layout in messages. + 5.2.4 beta (24.07.24) - Allow opening several web apps. From a141d01a23c7e0fbdf99f017202e0c3559b3f8fb Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Sat, 27 Jul 2024 13:54:38 +0400 Subject: [PATCH 095/163] Fix macOS packaged action --- .github/workflows/mac_packaged.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml index bd51bb74a..3a37311e6 100644 --- a/.github/workflows/mac_packaged.yml +++ b/.github/workflows/mac_packaged.yml @@ -69,7 +69,7 @@ jobs: run: | brew update brew upgrade || true - brew install autoconf automake boost cmake ffmpeg@6 openal-soft openssl opus ninja pkg-config python qt yasm xz + brew install autoconf automake boost cmake ffmpeg@6 openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz sudo xcode-select -s /Applications/Xcode.app/Contents/Developer xcodebuild -version > CACHE_KEY.txt @@ -108,7 +108,7 @@ jobs: run: | cd $LibrariesPath - git clone --recursive --depth=1 $GIT/desktop-app/tg_owt.git + git clone --depth=1 --recursive --shallow-submodules $GIT/desktop-app/tg_owt.git cd tg_owt cmake -B build . -GNinja -DCMAKE_BUILD_TYPE=Debug From b0981ea8e341ae6d71432a237ab086a9ab6d4b44 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Mon, 29 Jul 2024 08:05:28 +0200 Subject: [PATCH 096/163] Beta version 5.2.6. - Fix launching on X11. (Linux) --- 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/docker/centos_env/Dockerfile | 2 +- Telegram/build/prepare/prepare.py | 2 +- Telegram/build/version | 8 ++++---- changelog.txt | 4 ++++ snap/snapcraft.yaml | 2 +- 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 4716432a8..45ec14889 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ <Identity Name="TelegramMessengerLLP.TelegramDesktop" ProcessorArchitecture="ARCHITECTURE" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" - Version="5.2.5.0" /> + Version="5.2.6.0" /> <Properties> <DisplayName>Telegram Desktop</DisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 91720d235..83eecf0a7 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,5,0 - PRODUCTVERSION 5,2,5,0 + FILEVERSION 5,2,6,0 + PRODUCTVERSION 5,2,6,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.2.5.0" + VALUE "FileVersion", "5.2.6.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.5.0" + VALUE "ProductVersion", "5.2.6.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 829055b26..40bb30f11 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,5,0 - PRODUCTVERSION 5,2,5,0 + FILEVERSION 5,2,6,0 + PRODUCTVERSION 5,2,6,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.2.5.0" + VALUE "FileVersion", "5.2.6.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.5.0" + VALUE "ProductVersion", "5.2.6.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index fcfe70dd8..111881fd4 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 = 5002005; -constexpr auto AppVersionStr = "5.2.5"; +constexpr auto AppVersion = 5002006; +constexpr auto AppVersionStr = "5.2.6"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 99816a198..568d6663a 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -771,7 +771,7 @@ COPY --link --from=pipewire {{ LibrariesPath }}/pipewire-cache / RUN git init tg_owt \ && cd tg_owt \ && git remote add origin {{ GIT }}/desktop-app/tg_owt.git \ - && git fetch --depth=1 origin c0ba7b391c0fd1d408fc0d58f4fbecb68a6fcd55 \ + && git fetch --depth=1 origin 4a60ce1ab9fdb962004c6a959f682ace3db50cbd \ && git reset --hard FETCH_HEAD \ && git submodule update --init --recursive --depth=1 \ && rm -rf .git \ diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index b7c447019..83e16284f 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1718,7 +1718,7 @@ win: stage('tg_owt', """ git clone https://github.com/desktop-app/tg_owt.git cd tg_owt - git checkout c0ba7b391c + git checkout 4a60ce1ab9 git submodule init git submodule update win: diff --git a/Telegram/build/version b/Telegram/build/version index 3e6807e8b..f10750e38 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5002005 +AppVersion 5002006 AppVersionStrMajor 5.2 -AppVersionStrSmall 5.2.5 -AppVersionStr 5.2.5 +AppVersionStrSmall 5.2.6 +AppVersionStr 5.2.6 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.2.5.beta +AppVersionOriginal 5.2.6.beta diff --git a/changelog.txt b/changelog.txt index 09adb5835..5ec213f7e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +5.2.6 beta (29.07.24) + +- Fix launching on X11. (Linux) + 5.2.5 beta (27.07.24) - Fix media viewer context menu. diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 12fa23acb..b5f71d99f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -433,7 +433,7 @@ parts: webrtc: source: https://github.com/desktop-app/tg_owt.git source-depth: 1 - source-commit: c0ba7b391c0fd1d408fc0d58f4fbecb68a6fcd55 + source-commit: 4a60ce1ab9fdb962004c6a959f682ace3db50cbd plugin: cmake build-environment: - LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s From f55584b160329536503804c70095780fc32b8cd8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 29 Jul 2024 10:03:50 +0300 Subject: [PATCH 097/163] Fixed position of click handler for via bot header in message view. --- Telegram/SourceFiles/history/view/history_view_message.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 4c6d4970d..e1d67952e 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2406,10 +2406,10 @@ TextState Message::textState( if (getStateForwardedInfo(point, trect, &result, request)) { return result; } - if (getStateReplyInfo(point, trect, &result)) { + if (getStateViaBotIdInfo(point, trect, &result)) { return result; } - if (getStateViaBotIdInfo(point, trect, &result)) { + if (getStateReplyInfo(point, trect, &result)) { return result; } } From e9650385adc153e8754235b961e9a76fed337ac1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 29 Jul 2024 10:26:47 +0300 Subject: [PATCH 098/163] Added ability to provide custom style to widget with infinite animation. --- .../boosts/giveaway/boost_badge.cpp | 17 +++++++++++------ .../boosts/giveaway/boost_badge.h | 4 +++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/boost_badge.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/boost_badge.cpp index feb6d395e..aea01c592 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/boost_badge.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/boost_badge.cpp @@ -20,12 +20,17 @@ namespace Info::Statistics { not_null<Ui::RpWidget*> InfiniteRadialAnimationWidget( not_null<Ui::RpWidget*> parent, - int size) { + int size, + const style::InfiniteRadialAnimation *st) { class Widget final : public Ui::RpWidget { public: - Widget(not_null<Ui::RpWidget*> p, int size) + Widget( + not_null<Ui::RpWidget*> p, + int size, + const style::InfiniteRadialAnimation *st) : Ui::RpWidget(p) - , _animation([=] { update(); }, st::startGiveawayButtonLoading) { + , _st(st ? st : &st::startGiveawayButtonLoading) + , _animation([=] { update(); }, *_st) { resize(size, size); shownValue() | rpl::start_with_next([=](bool v) { return v @@ -39,17 +44,17 @@ not_null<Ui::RpWidget*> InfiniteRadialAnimationWidget( auto p = QPainter(this); p.setPen(st::activeButtonFg); p.setBrush(st::activeButtonFg); - const auto r = rect() - - Margins(st::startGiveawayButtonLoading.thickness); + const auto r = rect() - Margins(_st->thickness); _animation.draw(p, r.topLeft(), r.size(), width()); } private: + const style::InfiniteRadialAnimation *_st; Ui::InfiniteRadialAnimation _animation; }; - return Ui::CreateChild<Widget>(parent.get(), size); + return Ui::CreateChild<Widget>(parent.get(), size, st); } void AddChildToWidgetCenter( diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/boost_badge.h b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/boost_badge.h index 221f6017f..bc42a9d1c 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/boost_badge.h +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/boost_badge.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once namespace style { +struct InfiniteRadialAnimation; struct TextStyle; } // namespace style @@ -30,7 +31,8 @@ namespace Info::Statistics { [[nodiscard]] not_null<Ui::RpWidget*> InfiniteRadialAnimationWidget( not_null<Ui::RpWidget*> parent, - int size); + int size, + const style::InfiniteRadialAnimation *st = nullptr); void AddChildToWidgetCenter( not_null<Ui::RpWidget*> parent, From caef698e5406bc63bbcc422ee6855643b8123bf7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 30 Jul 2024 13:34:00 +0300 Subject: [PATCH 099/163] Fixed recursive invoking of Application::windowFor. --- Telegram/SourceFiles/core/application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 19f488b0b..b2c2ec4db 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -1341,7 +1341,7 @@ Window::Controller *Application::ensureSeparateWindowFor( Window::Controller *Application::windowFor(Window::SeparateId id) const { if (const auto separate = separateWindowFor(id)) { return separate; - } else if (id && id.primary()) { + } else if (id && !id.primary()) { return windowFor(not_null(id.account)); } return activePrimaryWindow(); From a32b781e4966939c3072d7a1297d05ae4755e82c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 30 Jul 2024 13:51:07 +0300 Subject: [PATCH 100/163] Improved Controller::invokeForSessionController for separate windows. --- Telegram/SourceFiles/window/window_controller.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index 8ef614d21..ec9fa548e 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -499,6 +499,15 @@ void Controller::invokeForSessionController( if (separateSession) { return callback(separateSession); } + const auto accountWindow = account + ? Core::App().separateWindowFor(not_null(account)) + : nullptr; + const auto accountSession = accountWindow + ? accountWindow->sessionController() + : nullptr; + if (accountSession) { + return callback(accountSession); + } _id.account->domain().activate(std::move(account)); if (_sessionController) { callback(_sessionController.get()); From aeb5e57061137c0fddf6601461c49e53612f203e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 30 Jul 2024 13:51:53 +0300 Subject: [PATCH 101/163] Fixed profile opening of group call participant in separate windows. --- .../calls/group/calls_group_members.cpp | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index ce806928c..2853340a5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1195,24 +1195,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu( const auto addVolumeItem = (!muted || isMe(participantPeer)); const auto admin = IsGroupCallAdmin(_peer, participantPeer); const auto session = &_peer->session(); - const auto getCurrentWindow = [=]() -> Window::SessionController* { - if (const auto window = Core::App().windowFor(participantPeer)) { - if (const auto controller = window->sessionController()) { - if (&controller->session() == session) { - return controller; - } - } - } - return nullptr; - }; - const auto getWindow = [=] { - if (const auto current = getCurrentWindow()) { - return current; - } else if (&Core::App().domain().active() != &session->account()) { - Core::App().domain().activate(&session->account()); - } - return getCurrentWindow(); - }; + const auto account = &session->account(); auto result = base::make_unique_q<Ui::PopupMenu>( parent, @@ -1223,7 +1206,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu( : st::groupCallPopupMenu)); const auto weakMenu = Ui::MakeWeak(result.get()); const auto withActiveWindow = [=](auto callback) { - if (const auto window = getWindow()) { + if (const auto window = Core::App().activePrimaryWindow()) { if (const auto menu = weakMenu.data()) { menu->discardParentReActivate(); @@ -1232,8 +1215,13 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu( // PopupMenu::hide activates back the group call panel :( delete weakMenu; } - callback(window); - window->widget()->activate(); + window->invokeForSessionController( + account, + participantPeer, + [&](not_null<Window::SessionController*> newController) { + callback(newController); + newController->widget()->activate(); + }); } }; const auto showProfile = [=] { From 0046bae53f0888d8d739ea627b691f2e519df110 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 26 Jul 2024 00:05:25 +0300 Subject: [PATCH 102/163] Added ability to change name of sticker set from sticker set box. --- Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/boxes/sticker_set_box.cpp | 97 ++++++++++++++++++- .../chat_helpers/chat_helpers.style | 5 + Telegram/lib_ui | 2 +- 4 files changed, 104 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b902aa4f6..47b46c6e9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2929,6 +2929,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_masks_has_been_archived" = "Mask pack has been archived."; "lng_masks_installed" = "Mask pack has been installed."; "lng_emoji_nothing_found" = "No emoji found"; +"lng_stickers_context_edit_name" = "Edit name"; +"lng_stickers_box_edit_name_title" = "Edit Sticker Set Name"; +"lng_stickers_box_edit_name_about" = "Choose a name for your set."; "lng_in_dlg_photo" = "Photo"; "lng_in_dlg_album" = "Album"; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index b3c589be7..afd4d233f 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/gradient_round_button.h" +#include "ui/widgets/fields/input_field.h" #include "ui/image/image.h" #include "ui/image/image_location_factory.h" #include "ui/text/text_utilities.h" @@ -271,6 +272,10 @@ public: : Data::StickersType::Stickers; } + [[nodiscard]] bool amSetCreator() const { + return _amSetCreator; + } + ~Inner(); protected: @@ -366,6 +371,7 @@ private: TimeId _setInstallDate = TimeId(0); StickerType _setThumbnailType = StickerType::Webp; ImageWithLocation _setThumbnail; + bool _amSetCreator = false; const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; mutable StickerPremiumMark _premiumMark; @@ -538,6 +544,70 @@ void StickerSetBox::updateTitleAndButtons() { updateButtons(); } +void ChangeSetNameBox( + not_null<Ui::GenericBox*> box, + not_null<Data::Session*> data, + const StickerSetIdentifier &input) { + box->setTitle(tr::lng_stickers_box_edit_name_title()); + box->addRow( + object_ptr<Ui::FlatLabel>( + box, + tr::lng_stickers_box_edit_name_about(), + st::boxLabel)); + + const auto wasName = [&] { + const auto &sets = data->stickers().sets(); + const auto it = sets.find(input.id); + return (it == sets.end()) ? QString() : it->second->title; + }(); + const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>( + box, + st::editStickerSetNameField.heightMin)); + auto owned = object_ptr<Ui::InputField>( + wrap, + st::editStickerSetNameField, + tr::lng_stickers_context_edit_name(), + wasName); + const auto field = owned.data(); + wrap->widthValue() | rpl::start_with_next([=](int width) { + field->move(0, 0); + field->resize(width, field->height()); + wrap->resize(width, field->height()); + }, wrap->lifetime()); + field->selectAll(); + constexpr auto kMaxSetNameLength = 50; + field->setMaxLength(kMaxSetNameLength); + Ui::AddLengthLimitLabel(field, kMaxSetNameLength, kMaxSetNameLength + 1); + box->setFocusCallback([=] { field->setFocusFast(); }); + const auto close = crl::guard(box, [=] { box->closeBox(); }); + const auto save = [=, show = box->uiShow()] { + const auto text = field->getLastText().trimmed(); + if ((Ui::ComputeRealUnicodeCharactersCount(text) > kMaxSetNameLength) + || text.isEmpty()) { + field->showError(); + return; + } + data->session().api().request( + MTPstickers_RenameStickerSet( + Data::InputStickerSet(input), + MTP_string(text)) + ).done([=](const MTPmessages_StickerSet &result) { + result.match([&](const MTPDmessages_stickerSet &d) { + data->stickers().feedSetFull(d); + data->stickers().notifyUpdated(Data::StickersType::Stickers); + }, [](const auto &) { + }); + close(); + }).fail([=](const MTP::Error &error) { + show->showToast(error.type()); + close(); + }).send(); + }; + + box->addButton(tr::lng_box_done(), save); + box->addButton(tr::lng_cancel(), close); +} + void StickerSetBox::updateButtons() { clearButtons(); if (_inner->loaded()) { @@ -548,6 +618,20 @@ void StickerSetBox::updateButtons() { ? tr::lng_stickers_copied_emoji(tr::now) : tr::lng_stickers_copied(tr::now)); }; + const auto fillSetCreatorMenu = [&] { + using Filler = Fn<void(not_null<Ui::PopupMenu*>)>; + if (!_inner->amSetCreator()) { + return Filler(nullptr); + } + const auto data = &_session->data(); + return Filler([=, show = _show, set = _set]( + not_null<Ui::PopupMenu*> menu) { + menu->addAction( + tr::lng_stickers_context_edit_name(tr::now), + [=] { show->showBox(Box(ChangeSetNameBox, data, set)); }, + &st::menuIconEdit); + }); + }(); if (_inner->notInstalled()) { if (!_session->premium() && _session->premiumPossible() @@ -586,6 +670,9 @@ void StickerSetBox::updateButtons() { *menu = base::make_unique_q<Ui::PopupMenu>( top, st::popupMenuWithIcons); + if (fillSetCreatorMenu) { + fillSetCreatorMenu(*menu); + } (*menu)->addAction( ((type == Data::StickersType::Emoji) ? tr::lng_stickers_share_emoji @@ -636,6 +723,9 @@ void StickerSetBox::updateButtons() { remove, &st::menuIconRemove); } else { + if (fillSetCreatorMenu) { + fillSetCreatorMenu(*menu); + } (*menu)->addAction( (type == Data::StickersType::Masks ? tr::lng_masks_archive_pack(tr::now) @@ -748,7 +838,9 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { } }); } - data.vset().match([&](const MTPDstickerSet &set) { + + { + const auto &set = data.vset().data(); _setTitle = _session->data().stickers().getSetTitle( set); _setShortName = qs(set.vshort_name()); @@ -759,6 +851,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { _setFlags = Data::ParseStickersSetFlags(set); _setInstallDate = set.vinstalled_date().value_or(0); _setThumbnailDocumentId = set.vthumb_document_id().value_or_empty(); + _amSetCreator = set.is_creator(); _setThumbnail = [&] { if (const auto thumbs = set.vthumbs()) { for (const auto &thumb : thumbs->v) { @@ -791,7 +884,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { set->emoji = _emoji; set->setThumbnail(_setThumbnail, _setThumbnailType); } - }); + }; }, [&](const MTPDmessages_stickerSetNotModified &data) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 47c1e8b47..be9d5f8e4 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1409,6 +1409,11 @@ editTagLimit: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; } +editStickerSetNameField: InputField(defaultInputField) { + textMargins: margins(0px, 28px, 26px, 4px); + heightMax: 55px; +} + paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }}; paidStarIconTop: 7px; paidAmountAbout: FlatLabel(defaultFlatLabel) { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 99d914213..97bfa6cef 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 99d9142130025e817fe7f1ac278078c34a509a1c +Subproject commit 97bfa6cef474b3b311a178ff1a1042d09972a7c7 From 06fc813e9509b3abfb33f1710c494aa3f17f7881 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 29 Jul 2024 11:02:17 +0300 Subject: [PATCH 103/163] Added loading state to box for renaming of stickers set. --- .../SourceFiles/boxes/sticker_set_box.cpp | 64 ++++++++++++++++--- .../chat_helpers/chat_helpers.style | 4 ++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index afd4d233f..e1906b0c8 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" #include "menu/menu_send.h" +#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. #include "lang/lang_keys.h" #include "ui/boxes/confirm_box.h" #include "boxes/premium_preview_box.h" @@ -73,6 +74,7 @@ constexpr auto kGrayLockOpacity = 0.3; using Data::StickersSet; using Data::StickersPack; using SetFlag = Data::StickersSetFlag; +using TLStickerSet = MTPmessages_StickerSet; [[nodiscard]] std::optional<QColor> ComputeImageColor( const style::icon &lockIcon, @@ -276,6 +278,8 @@ public: return _amSetCreator; } + void applySet(const TLStickerSet &set); + ~Inner(); protected: @@ -327,7 +331,6 @@ private: void startOverAnimation(int index, float64 from, float64 to); int stickerFromGlobalPos(const QPoint &p) const; - void gotSet(const MTPmessages_StickerSet &set); void installDone(const MTPmessages_StickerSetInstallResult &result); void chosen( @@ -547,13 +550,19 @@ void StickerSetBox::updateTitleAndButtons() { void ChangeSetNameBox( not_null<Ui::GenericBox*> box, not_null<Data::Session*> data, - const StickerSetIdentifier &input) { + const StickerSetIdentifier &input, + Fn<void(TLStickerSet)> done) { + struct State final { + rpl::variable<mtpRequestId> requestId = 0; + Ui::RpWidget* saveButton = nullptr; + }; box->setTitle(tr::lng_stickers_box_edit_name_title()); box->addRow( object_ptr<Ui::FlatLabel>( box, tr::lng_stickers_box_edit_name_about(), st::boxLabel)); + const auto state = box->lifetime().make_state<State>(); const auto wasName = [&] { const auto &sets = data->stickers().sets(); @@ -581,31 +590,59 @@ void ChangeSetNameBox( box->setFocusCallback([=] { field->setFocusFast(); }); const auto close = crl::guard(box, [=] { box->closeBox(); }); const auto save = [=, show = box->uiShow()] { + if (state->requestId.current()) { + return; + } const auto text = field->getLastText().trimmed(); if ((Ui::ComputeRealUnicodeCharactersCount(text) > kMaxSetNameLength) || text.isEmpty()) { field->showError(); return; } - data->session().api().request( + const auto buttonWidth = state->saveButton + ? state->saveButton->width() + : 0; + state->requestId = data->session().api().request( MTPstickers_RenameStickerSet( Data::InputStickerSet(input), MTP_string(text)) - ).done([=](const MTPmessages_StickerSet &result) { + ).done([=](const TLStickerSet &result) { result.match([&](const MTPDmessages_stickerSet &d) { data->stickers().feedSetFull(d); data->stickers().notifyUpdated(Data::StickersType::Stickers); }, [](const auto &) { }); + done(result); close(); }).fail([=](const MTP::Error &error) { show->showToast(error.type()); close(); }).send(); + if (state->saveButton) { + state->saveButton->resizeToWidth(buttonWidth); + } }; - box->addButton(tr::lng_box_done(), save); - box->addButton(tr::lng_cancel(), close); + state->saveButton = box->addButton( + rpl::conditional( + state->requestId.value() | rpl::map(rpl::mappers::_1 > 0), + rpl::single(QString()), + tr::lng_box_done()), + save); + if (const auto saveButton = state->saveButton) { + using namespace Info::Statistics; + const auto loadingAnimation = InfiniteRadialAnimationWidget( + saveButton, + saveButton->height() / 2, + &st::editStickerSetNameLoading); + AddChildToWidgetCenter(saveButton, loadingAnimation); + loadingAnimation->showOn( + state->requestId.value() | rpl::map(rpl::mappers::_1 > 0)); + } + box->addButton(tr::lng_cancel(), [=] { + data->session().api().request(state->requestId.current()).cancel(); + close(); + }); } void StickerSetBox::updateButtons() { @@ -626,9 +663,16 @@ void StickerSetBox::updateButtons() { const auto data = &_session->data(); return Filler([=, show = _show, set = _set]( not_null<Ui::PopupMenu*> menu) { + const auto done = [inner = _inner](const TLStickerSet &set) { + if (const auto raw = inner.data()) { + raw->applySet(set); + } + }; menu->addAction( tr::lng_stickers_context_edit_name(tr::now), - [=] { show->showBox(Box(ChangeSetNameBox, data, set)); }, + [=] { + show->showBox(Box(ChangeSetNameBox, data, set, done)); + }, &st::menuIconEdit); }); }(); @@ -777,8 +821,8 @@ StickerSetBox::Inner::Inner( _api.request(MTPmessages_GetStickerSet( Data::InputStickerSet(_input), MTP_int(0) // hash - )).done([=](const MTPmessages_StickerSet &result) { - gotSet(result); + )).done([=](const TLStickerSet &result) { + applySet(result); }).fail([=] { _loaded = true; _errors.fire(Error::NotFound); @@ -794,7 +838,7 @@ StickerSetBox::Inner::Inner( setMouseTracking(true); } -void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { +void StickerSetBox::Inner::applySet(const TLStickerSet &set) { _pack.clear(); _emoji.clear(); _elements.clear(); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index be9d5f8e4..40ab1d705 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1413,6 +1413,10 @@ editStickerSetNameField: InputField(defaultInputField) { textMargins: margins(0px, 28px, 26px, 4px); heightMax: 55px; } +editStickerSetNameLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + color: lightButtonFg; + thickness: 2px; +} paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }}; paidStarIconTop: 7px; From 54214ff2ad311daaa62982800c60856b7d5b74b9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 27 Jul 2024 11:22:39 +0300 Subject: [PATCH 104/163] Added initial ability to drag stickers in sticker set box. --- Telegram/Resources/langs/lang.strings | 1 + .../SourceFiles/boxes/sticker_set_box.cpp | 249 +++++++++++++++++- Telegram/lib_base | 2 +- 3 files changed, 243 insertions(+), 9 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 47b46c6e9..3aa6ac586 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2929,6 +2929,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_masks_has_been_archived" = "Mask pack has been archived."; "lng_masks_installed" = "Mask pack has been installed."; "lng_emoji_nothing_found" = "No emoji found"; +"lng_stickers_context_reorder" = "Reorder"; "lng_stickers_context_edit_name" = "Edit name"; "lng_stickers_box_edit_name_title" = "Edit Sticker Set Name"; "lng_stickers_box_edit_name_about" = "Choose a name for your set."; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index e1906b0c8..3cc095421 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/path_shift_gradient.h" #include "ui/emoji_config.h" #include "ui/painter.h" +#include "ui/rect.h" #include "ui/power_saving.h" #include "ui/toast/toast.h" #include "ui/widgets/popup_menu.h" @@ -70,6 +71,7 @@ constexpr auto kEmojiPerRow = 8; constexpr auto kMinRepaintDelay = crl::time(33); constexpr auto kMinAfterScrollDelay = crl::time(33); constexpr auto kGrayLockOpacity = 0.3; +constexpr auto kStickerMoveDuration = crl::time(200); using Data::StickersSet; using Data::StickersPack; @@ -262,6 +264,13 @@ public: [[nodiscard]] rpl::producer<uint64> setArchived() const; [[nodiscard]] rpl::producer<> updateControls() const; + void setReorderState(bool enabled) { + _dragging.enabled = enabled; + } + [[nodiscard]] bool reorderState() const { + return _dragging.enabled; + } + [[nodiscard]] rpl::producer<Error> errors() const; void archiveStickers(); @@ -338,6 +347,9 @@ private: not_null<DocumentData*> sticker, Api::SendOptions options); + [[nodiscard]] QPoint posFromIndex(int index) const; + [[nodiscard]] bool isDraggedAnimating() const; + not_null<Lottie::MultiPlayer*> getLottiePlayer(); void showPreview(); @@ -376,6 +388,20 @@ private: ImageWithLocation _setThumbnail; bool _amSetCreator = false; + struct { + bool enabled = false; + int index = -1; + int lastSelected = -1; + QPoint point; + } _dragging; + + struct ShiftAnimation final { + Ui::Animations::Simple animation; + Ui::Animations::Simple yAnimation; + int shift = 0; + }; + base::flat_map<int, ShiftAnimation> _shiftAnimations; + const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; mutable StickerPremiumMark _premiumMark; @@ -647,7 +673,12 @@ void ChangeSetNameBox( void StickerSetBox::updateButtons() { clearButtons(); - if (_inner->loaded()) { + if (_inner->reorderState()) { + addButton(tr::lng_box_done(), [=] { + _inner->setReorderState(false); + updateButtons(); + }); + } else if (_inner->loaded()) { const auto type = _inner->setType(); const auto share = [=] { copyStickersLink(); @@ -674,6 +705,13 @@ void StickerSetBox::updateButtons() { show->showBox(Box(ChangeSetNameBox, data, set, done)); }, &st::menuIconEdit); + menu->addAction( + tr::lng_stickers_context_reorder(tr::now), + [=] { + _inner->setReorderState(true); + updateButtons(); + }, + &st::menuIconManage); }); }(); if (_inner->notInstalled()) { @@ -1069,11 +1107,100 @@ void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) { if (index < 0 || index >= _pack.size()) { return; } + if (_dragging.enabled) { + _previewTimer.cancel(); + if (isDraggedAnimating()) { + return; + } + _dragging.index = index; + _dragging.point = mapFromGlobal(QCursor::pos()) - posFromIndex(index); + return; + } _previewTimer.callOnce(QApplication::startDragTime()); } void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) { updateSelected(); + const auto draggedAnimating = isDraggedAnimating(); + if (_selected >= 0 && !draggedAnimating) { + _dragging.lastSelected = _selected; + } + if (_dragging.index >= 0 + && _dragging.index < _pack.size() + && _dragging.lastSelected >= 0 + && !draggedAnimating) { + for (auto i = 0; i < _pack.size(); i++) { + if (i == _dragging.index) { + continue; + } + auto &entry = _shiftAnimations[i]; + const auto wasShift = entry.shift; + if ((i >= _dragging.index) && (i <= _dragging.lastSelected)) { + if (entry.shift == 0) { + entry.shift = -1; + } else if (entry.shift == 1) { + entry.shift = 0; + } + } else if ((i < _dragging.index) + && (i >= _dragging.lastSelected)) { + if (entry.shift == 0) { + entry.shift = 1; + } else if (entry.shift == -1) { + entry.shift = 0; + } + } + if ((i < std::min(_dragging.index, _dragging.lastSelected)) + || (i > std::max(_dragging.index, _dragging.lastSelected))) { + entry.shift = 0; + } + if (wasShift != entry.shift) { + const auto fromPoint = posFromIndex(i + wasShift); + const auto toPoint = posFromIndex(i + entry.shift); + const auto toX = float64(toPoint.x()); + const auto toY = float64(toPoint.y()); + const auto ratio = [&] { + const auto fromX = entry.animation.value(toX); + const auto ratioX = std::min(toX, fromX) + / std::max(toX, fromX); + const auto fromY = entry.yAnimation.value(toY); + const auto ratioY = std::min(toY, fromY) + / std::max(toY, fromY); + return (ratioX == 1.) + ? ratioY + : (ratioY == 1.) + ? ratioX + : std::max(ratioX, ratioY); + }(); + if (!entry.animation.animating()) { + entry.animation.stop(); + entry.animation.start( + [=] { update(); }, + fromPoint.x(), + toX, + kStickerMoveDuration); + } else { + entry.animation.change( + toX, + kStickerMoveDuration * (1. - ratio), + anim::linear); + } + if (!entry.yAnimation.animating()) { + entry.yAnimation.stop(); + entry.yAnimation.start( + [=] { update(); }, + fromPoint.y(), + toY, + kStickerMoveDuration); + } else { + entry.yAnimation.change( + toY, + kStickerMoveDuration * (1. - ratio), + anim::linear); + } + } + } + update(); + } if (_previewShown >= 0) { showPreviewAt(e->globalPos()); } @@ -1096,6 +1223,46 @@ void StickerSetBox::Inner::leaveEventHook(QEvent *e) { } void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { + if (_dragging.index >= 0 && !isDraggedAnimating()) { + const auto fromPos = mapFromGlobal(e->globalPos()) - _dragging.point; + const auto toPos = posFromIndex(_dragging.lastSelected); + const auto finish = [=] { + base::reorder(_pack, _dragging.index, _dragging.lastSelected); + base::reorder(_elements, _dragging.index, _dragging.lastSelected); + _dragging = {}; + _dragging.enabled = true; + _shiftAnimations.clear(); + }; + auto &entry = _shiftAnimations[_dragging.index]; + entry.animation.stop(); + entry.yAnimation.stop(); + entry.animation.start( + [finish, toPos, this](float64 value) { + const auto index = _dragging.index; + if (value >= toPos.x() + && index >= 0 + && !_shiftAnimations[index].yAnimation.animating()) { + finish(); + } + update(); + }, + fromPos.x(), + toPos.x(), + kStickerMoveDuration); + entry.yAnimation.start( + [finish, toPos, this](float64 value) { + const auto index = _dragging.index; + if (value >= toPos.y() + && index >= 0 + && !_shiftAnimations[index].animation.animating()) { + finish(); + } + update(); + }, + fromPos.y(), + toPos.y(), + kStickerMoveDuration); + } if (_previewShown >= 0) { _previewShown = -1; return; @@ -1216,7 +1383,11 @@ void StickerSetBox::Inner::setSelected(int selected) { startOverAnimation(_selected, 1., 0.); _selected = selected; startOverAnimation(_selected, 0., 1.); - setCursor(_selected >= 0 ? style::cur_pointer : style::cur_default); + setCursor((_selected < 0) + ? style::cur_default + : _dragging.enabled + ? style::cur_sizeall + : style::cur_pointer); } } @@ -1238,6 +1409,24 @@ void StickerSetBox::Inner::showPreview() { showPreviewAt(QCursor::pos()); } +QPoint StickerSetBox::Inner::posFromIndex(int index) const { + return { + _padding.left() + (index % _perRow) * _singleSize.width(), + _padding.top() + (index / _perRow) * _singleSize.height(), + }; +} + +bool StickerSetBox::Inner::isDraggedAnimating() const { + if (_dragging.index < 0) { + return false; + } + const auto it = _shiftAnimations.find(_dragging.index); + return (it == _shiftAnimations.end()) + ? false + : (it->second.animation.animating() + || it->second.yAnimation.animating()); +} + not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() { if (!_lottiePlayer) { _lottiePlayer = std::make_unique<Lottie::MultiPlayer>( @@ -1277,12 +1466,36 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { _pathGradient->startFrame(0, width(), width() / 2); + const auto indexUnderCursor = (_dragging.index >= 0 + && _dragging.index < _elements.size()) + ? stickerFromGlobalPos(QCursor::pos()) + : -2; + const auto lastIndex = indexUnderCursor >= 0 + ? indexUnderCursor + : _dragging.lastSelected; + const auto now = crl::now(); const auto paused = On(PowerSaving::kStickersPanel) || _show->paused(ChatHelpers::PauseReason::Layer); for (int32 i = from; i < to; ++i) { for (int32 j = 0; j < _perRow; ++j) { int32 index = i * _perRow + j; + + if (lastIndex >= 0) { + if (_dragging.index == index) { + continue; + } + const auto it = _shiftAnimations.find(index); + if (it != _shiftAnimations.end()) { + const auto &entry = it->second; + const auto toPos = posFromIndex(index + entry.shift); + const auto pos = QPoint( + entry.animation.value(toPos.x()), + entry.yAnimation.value(toPos.y())); + paintSticker(p, index, pos, paused, now); + continue; + } + } if (index >= _elements.size()) { break; } @@ -1292,6 +1505,14 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { paintSticker(p, index, pos, paused, now); } } + if (_dragging.index >= 0 && _dragging.index < _elements.size()) { + const auto pos = isDraggedAnimating() + ? QPoint( + _shiftAnimations[_dragging.index].animation.value(0), + _shiftAnimations[_dragging.index].yAnimation.value(0)) + : (mapFromGlobal(QCursor::pos()) - _dragging.point); + paintSticker(p, _dragging.index, pos, paused, now); + } if (_lottiePlayer && !paused) { _lottiePlayer->markFrameShown(); @@ -1453,12 +1674,24 @@ void StickerSetBox::Inner::paintSticker( QPoint position, bool paused, crl::time now) const { - if (const auto over = _elements[index].overAnimation.value((index == _selected) ? 1. : 0.)) { - p.setOpacity(over); - auto tl = position; - if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); - Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners); - p.setOpacity(1); + if (_dragging.index != index) { + const auto over = _elements[index].overAnimation.value( + (index == _selected) ? 1. : 0.); + if (over) { + p.setOpacity(over); + Ui::FillRoundRect( + p, + QRect( + rtl() + ? QPoint( + width() - position.x() - _singleSize.width(), + position.y()) + : position, + _singleSize), + st::emojiPanHover, + Ui::StickerHoverCorners); + p.setOpacity(1); + } } const auto &element = _elements[index]; diff --git a/Telegram/lib_base b/Telegram/lib_base index 21a8611ab..ca4503b30 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 21a8611ab764acc7cb622859ea4c97bd259570af +Subproject commit ca4503b3075fcaed5719b6ff1f40e40d14d08d95 From 358e58680190fdc7d94cbf6bc8bee797bb57b8f9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 29 Jul 2024 14:17:29 +0300 Subject: [PATCH 105/163] Added api support to reorder stickers from sticker set box. --- .../SourceFiles/boxes/sticker_set_box.cpp | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 3cc095421..48fe9828b 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -342,6 +342,8 @@ private: void installDone(const MTPmessages_StickerSetInstallResult &result); + void requestReorder(not_null<DocumentData*> document, int index); + void chosen( int index, not_null<DocumentData*> sticker, @@ -394,6 +396,8 @@ private: int lastSelected = -1; QPoint point; } _dragging; + std::deque<Fn<void()>> _reorderRequests; + std::optional<MTP::Sender> _apiReorder; struct ShiftAnimation final { Ui::Animations::Simple animation; @@ -1222,13 +1226,52 @@ void StickerSetBox::Inner::leaveEventHook(QEvent *e) { setSelected(-1); } +void StickerSetBox::Inner::requestReorder( + not_null<DocumentData*> document, + int index) { + if (!_apiReorder) { + _apiReorder.emplace(&_session->mtp()); + } + _reorderRequests.emplace_back([document, index, this] { + _apiReorder->request( + MTPstickers_ChangeStickerPosition( + document->mtpInput(), + MTP_int(index)) + ).done([this, document](const TLStickerSet &result) { + result.match([&](const MTPDmessages_stickerSet &d) { + document->owner().stickers().feedSetFull(d); + document->owner().stickers().notifyUpdated( + Data::StickersType::Stickers); + }, [](const auto &) { + }); + if (!_reorderRequests.empty()) { + _reorderRequests.pop_front(); + } + if (_reorderRequests.empty()) { + // applySet(result); // Causes stickers blink. + } else { + _reorderRequests.front()(); + } + }).fail([show = _show](const MTP::Error &error) { + show->showToast(error.type()); + }).send(); + }); + if (_reorderRequests.size() == 1) { + _reorderRequests.front()(); + } +} + void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (_dragging.index >= 0 && !isDraggedAnimating()) { const auto fromPos = mapFromGlobal(e->globalPos()) - _dragging.point; const auto toPos = posFromIndex(_dragging.lastSelected); - const auto finish = [=] { - base::reorder(_pack, _dragging.index, _dragging.lastSelected); - base::reorder(_elements, _dragging.index, _dragging.lastSelected); + const auto document = _pack[_dragging.index]; + const auto wasPosition = _dragging.index; + const auto nowPosition = _dragging.lastSelected; + const auto finish = [=, this] { + requestReorder(document, nowPosition); + base::reorder(_pack, wasPosition, nowPosition); + base::reorder(_elements, wasPosition, nowPosition); _dragging = {}; _dragging.enabled = true; _shiftAnimations.clear(); From 850155b3be7440ac0e545045f1f9ae32539c17be Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 30 Jul 2024 02:37:30 +0300 Subject: [PATCH 106/163] Added shake animation while reordering to sticker set box. --- .../SourceFiles/boxes/sticker_set_box.cpp | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 48fe9828b..dd5a01183 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image_location_factory.h" #include "ui/text/text_utilities.h" #include "ui/text/custom_emoji_instance.h" +#include "ui/effects/animation_value_f.h" #include "ui/effects/path_shift_gradient.h" #include "ui/emoji_config.h" #include "ui/painter.h" @@ -266,6 +267,13 @@ public: void setReorderState(bool enabled) { _dragging.enabled = enabled; + if (enabled) { + _shakeAnimation.init([=] { update(); }); + _shakeAnimation.start(); + } else { + _shakeAnimation.stop(); + update(); + } } [[nodiscard]] bool reorderState() const { return _dragging.enabled; @@ -324,6 +332,11 @@ private: QPoint position, bool paused, crl::time now) const; + void shakeTransform( + QPainter &p, + int index, + QPoint position, + crl::time now) const; void setupLottie(int index); void setupWebm(int index); void clipCallback( @@ -396,6 +409,7 @@ private: int lastSelected = -1; QPoint point; } _dragging; + Ui::Animations::Basic _shakeAnimation; std::deque<Fn<void()>> _reorderRequests; std::optional<MTP::Sender> _apiReorder; @@ -1711,6 +1725,70 @@ void StickerSetBox::Inner::customEmojiRepaint() { update(); } +void StickerSetBox::Inner::shakeTransform( + QPainter &p, + int index, + QPoint position, + crl::time now) const { + constexpr auto kShakeADuration = crl::time(400); + constexpr auto kShakeXDuration = crl::time(kShakeADuration * 1.2); + constexpr auto kShakeYDuration = kShakeADuration; + const auto diff = ((index % 2) ? 0 : kShakeYDuration / 2) + + (now - _shakeAnimation.started()); + const auto pX = (diff % kShakeXDuration) + / float64(kShakeXDuration); + const auto pY = (diff % kShakeYDuration) + / float64(kShakeYDuration); + const auto pA = (diff % kShakeADuration) + / float64(kShakeADuration); + + constexpr auto kMaxA = 2.; + constexpr auto kMaxTranslation = .5; + constexpr auto kAStep = 1. / 5; + constexpr auto kXStep = 1. / 5; + constexpr auto kYStep = 1. / 4; + + // 0, -kMaxA, 0, kMaxA, 0. + const auto angle = (pA < kAStep) + ? anim::interpolateF(0., -kMaxA, pA / kAStep) + : (pA < kAStep * 2.) + ? anim::interpolateF(-kMaxA, 0, (pA - kAStep) / kAStep) + : (pA < kAStep * 3.) + ? anim::interpolateF(0, kMaxA, (pA - kAStep * 2.) / kAStep) + : (pA < kAStep * 4.) + ? anim::interpolateF(kMaxA, 0, (pA - kAStep * 3.) / kAStep) + : anim::interpolateF(0, 0., (pA - kAStep * 4.) / kAStep); + + // 0, kMaxTranslation, 0, -kMaxTranslation, 0. + const auto x = (pX < kXStep) + ? anim::interpolateF(0., kMaxTranslation, pX / kXStep) + : (pX < kXStep * 2.) + ? anim::interpolateF(kMaxTranslation, 0, (pX - kXStep) / kXStep) + : (pX < kXStep * 3.) + ? anim::interpolateF(0, -kMaxTranslation, (pX - kXStep * 2.) / kXStep) + : (pX < kXStep * 4.) + ? anim::interpolateF(-kMaxTranslation, 0, (pX - kXStep * 3.) / kXStep) + : anim::interpolateF(0, 0., (pX - kXStep * 4.) / kXStep); + + // 0, kMaxTranslation, -kMaxTranslation, 0. + const auto y = (pY < kYStep) + ? anim::interpolateF(0., kMaxTranslation, pY / kYStep) + : (pY < kYStep * 2.) + ? anim::interpolateF(kMaxTranslation, 0, (pY - kYStep) / kYStep) + : (pY < kYStep * 3.) + ? anim::interpolateF(0, -kMaxTranslation, (pY - kYStep * 2.) / kYStep) + : anim::interpolateF(-kMaxTranslation, 0, (pY - kYStep * 3) / kYStep); + + const auto center = position + QPoint( + _singleSize.width() / 2, + _singleSize.height() / 2); + + p.translate(center); + p.rotate(angle); + p.translate(-center); + p.translate(x, y); +} + void StickerSetBox::Inner::paintSticker( Painter &p, int index, @@ -1737,6 +1815,11 @@ void StickerSetBox::Inner::paintSticker( } } + const auto hasShake = _shakeAnimation.animating(); + if (hasShake) { + shakeTransform(p, index, position, now); + } + const auto &element = _elements[index]; const auto document = element.document; const auto &media = element.documentMedia; @@ -1803,6 +1886,9 @@ void StickerSetBox::Inner::paintSticker( _singleSize, width()); } + if (hasShake) { + p.resetTransform(); + } } bool StickerSetBox::Inner::loaded() const { From 6a167b33f5222556d18fd505d258e79158fb31d2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 30 Jul 2024 15:41:43 +0300 Subject: [PATCH 107/163] Added ability to delete sticker from sticker set box. --- Telegram/Resources/langs/lang.strings | 2 + .../SourceFiles/boxes/sticker_set_box.cpp | 205 +++++++++++++++--- 2 files changed, 174 insertions(+), 33 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3aa6ac586..57dbaad87 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2931,6 +2931,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_emoji_nothing_found" = "No emoji found"; "lng_stickers_context_reorder" = "Reorder"; "lng_stickers_context_edit_name" = "Edit name"; +"lng_stickers_context_delete" = "Delete sticker"; +"lng_stickers_context_delete_sure" = "Are you sure you want to delete the sticker from your sticker set?"; "lng_stickers_box_edit_name_title" = "Edit Sticker Set Name"; "lng_stickers_box_edit_name_about" = "Choose a name for your set."; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index dd5a01183..aef0dd941 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -7,54 +7,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/sticker_set_box.h" +#include "api/api_common.h" +#include "api/api_toggling_media.h" +#include "apiwrap.h" +#include "base/unixtime.h" +#include "boxes/premium_preview_box.h" +#include "chat_helpers/compose/compose_show.h" +#include "chat_helpers/stickers_list_widget.h" +#include "chat_helpers/stickers_lottie.h" +#include "core/application.h" #include "data/data_document.h" -#include "data/data_session.h" -#include "data/data_file_origin.h" #include "data/data_document_media.h" +#include "data/data_file_origin.h" #include "data/data_peer_values.h" -#include "data/stickers/data_stickers.h" +#include "data/data_session.h" #include "data/stickers/data_custom_emoji.h" -#include "menu/menu_send.h" +#include "data/stickers/data_stickers.h" +#include "dialogs/ui/dialogs_layout.h" #include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. #include "lang/lang_keys.h" -#include "ui/boxes/confirm_box.h" -#include "boxes/premium_preview_box.h" -#include "core/application.h" +#include "lottie/lottie_animation.h" +#include "lottie/lottie_multi_player.h" +#include "main/main_session.h" +#include "mainwindow.h" +#include "media/clip/media_clip_reader.h" +#include "menu/menu_send.h" #include "mtproto/sender.h" +#include "settings/settings_premium.h" #include "storage/storage_account.h" -#include "dialogs/ui/dialogs_layout.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/scroll_area.h" -#include "ui/widgets/gradient_round_button.h" -#include "ui/widgets/fields/input_field.h" -#include "ui/image/image.h" -#include "ui/image/image_location_factory.h" -#include "ui/text/text_utilities.h" -#include "ui/text/custom_emoji_instance.h" +#include "ui/boxes/confirm_box.h" +#include "ui/cached_round_corners.h" #include "ui/effects/animation_value_f.h" #include "ui/effects/path_shift_gradient.h" #include "ui/emoji_config.h" +#include "ui/image/image.h" +#include "ui/image/image_location_factory.h" #include "ui/painter.h" -#include "ui/rect.h" #include "ui/power_saving.h" +#include "ui/rect.h" +#include "ui/text/custom_emoji_instance.h" +#include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/gradient_round_button.h" +#include "ui/widgets/menu/menu_add_action_callback.h" +#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/popup_menu.h" -#include "ui/cached_round_corners.h" -#include "lottie/lottie_multi_player.h" -#include "lottie/lottie_animation.h" -#include "chat_helpers/compose/compose_show.h" -#include "chat_helpers/stickers_lottie.h" -#include "chat_helpers/stickers_list_widget.h" -#include "media/clip/media_clip_reader.h" -#include "window/window_controller.h" -#include "settings/settings_premium.h" -#include "base/unixtime.h" -#include "main/main_session.h" -#include "apiwrap.h" -#include "api/api_toggling_media.h" -#include "api/api_common.h" -#include "mainwidget.h" -#include "mainwindow.h" +#include "ui/widgets/scroll_area.h" #include "styles/style_layers.h" #include "styles/style_chat_helpers.h" #include "styles/style_info.h" @@ -356,6 +357,7 @@ private: void installDone(const MTPmessages_StickerSetInstallResult &result); void requestReorder(not_null<DocumentData*> document, int index); + void fillDeleteStickerBox(not_null<Ui::GenericBox*> box, int index); void chosen( int index, @@ -1422,6 +1424,20 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) { (isFaved ? &st::menuIconUnfave : &st::menuIconFave)); + if (amSetCreator()) { + const auto addAction = Ui::Menu::CreateAddActionCallback( + _menu.get()); + addAction({ + .text = tr::lng_stickers_context_delete(tr::now), + .handler = [index, this, show = _show] { + show->showBox(Box([=](not_null<Ui::GenericBox*> box) { + fillDeleteStickerBox(box, index); + })); + }, + .icon = &st::menuIconDeleteAttention, + .isAttention = true, + }); + } } if (_menu->empty()) { _menu = nullptr; @@ -1430,6 +1446,129 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) { } } +void StickerSetBox::Inner::fillDeleteStickerBox( + not_null<Ui::GenericBox*> box, + int index) { + Expects(index >= 0 || index < _pack.size()); + const auto document = _pack[index]; + const auto weak = Ui::MakeWeak(this); + const auto show = _show; + + const auto container = box->verticalLayout(); + Ui::AddSkip(container); + Ui::AddSkip(container); + const auto line = container->add(object_ptr<Ui::RpWidget>(container)); + line->resize(line->width(), _singleSize.height()); + + const auto sticker = Ui::CreateChild<Ui::RpWidget>(line); + auto &lifetime = sticker->lifetime(); + struct State final { + rpl::variable<mtpRequestId> requestId = 0; + Ui::RpWidget* saveButton = nullptr; + }; + const auto state = lifetime.make_state<State>(); + sticker->resize(_singleSize); + { + const auto animation = lifetime.make_state<Ui::Animations::Basic>(); + animation->init([=] { sticker->update(); }); + animation->start(); + } + sticker->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(sticker); + if (const auto strong = weak.get()) { + const auto paused = On(PowerSaving::kStickersPanel) + || show->paused(ChatHelpers::PauseReason::Layer); + paintSticker(p, index, QPoint(), paused, crl::now()); + if (_lottiePlayer && !paused) { + _lottiePlayer->markFrameShown(); + } + } + }, sticker->lifetime()); + const auto label = Ui::CreateChild<Ui::FlatLabel>( + line, + tr::lng_stickers_context_delete(), + box->getDelegate()->style().title); + line->widthValue( + ) | rpl::start_with_next([=](int width) { + sticker->moveToLeft(st::boxRowPadding.left(), 0); + const auto skip = st::defaultBoxCheckbox.textPosition.x(); + label->resizeToWidth(width + - rect::right(sticker) + - skip + - st::boxRowPadding.right()); + label->moveToLeft( + rect::right(sticker) + skip, + ((sticker->height() - label->height()) / 2)); + }, label->lifetime()); + + sticker->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + + Ui::AddSkip(container); + Ui::AddSkip(container); + + box->addRow( + object_ptr<Ui::FlatLabel>( + container, + tr::lng_stickers_context_delete_sure(), + st::boxLabel)); + const auto save = [=] { + if (state->requestId.current()) { + return; + } + const auto weakBox = Ui::MakeWeak(box); + const auto buttonWidth = state->saveButton + ? state->saveButton->width() + : 0; + state->requestId = document->owner().session().api().request( + MTPstickers_RemoveStickerFromSet(document->mtpInput() + )).done([=](const TLStickerSet &result) { + result.match([&](const MTPDmessages_stickerSet &d) { + document->owner().stickers().feedSetFull(d); + document->owner().stickers().notifyUpdated( + Data::StickersType::Stickers); + }, [](const auto &) { + }); + if (const auto strong = weak.get()) { + applySet(result); + } + if (const auto strongBox = weakBox.get()) { + strongBox->closeBox(); + } + }).fail([=](const MTP::Error &error) { + if (const auto strongBox = weakBox.get()) { + strongBox->uiShow()->showToast(error.type()); + } + }).send(); + if (state->saveButton) { + state->saveButton->resizeToWidth(buttonWidth); + } + }; + state->saveButton = box->addButton( + rpl::conditional( + state->requestId.value() | rpl::map(rpl::mappers::_1 > 0), + rpl::single(QString()), + tr::lng_selected_delete()), + save, + st::attentionBoxButton); + if (const auto saveButton = state->saveButton) { + using namespace Info::Statistics; + const auto loadingAnimation = InfiniteRadialAnimationWidget( + saveButton, + saveButton->height() / 2, + &st::editStickerSetNameLoading); + AddChildToWidgetCenter(saveButton, loadingAnimation); + loadingAnimation->showOn( + state->requestId.value() | rpl::map(rpl::mappers::_1 > 0)); + } + box->addButton(tr::lng_close(), [=] { + document->owner().session().api().request( + state->requestId.current()).cancel(); + box->closeBox(); + }); +} + void StickerSetBox::Inner::updateSelected() { auto selected = stickerFromGlobalPos(QCursor::pos()); setSelected(setType() == Data::StickersType::Masks ? -1 : selected); From 06f2b23687a9f653ba4e5bc82de15f118afa5acf Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 31 Jul 2024 13:05:29 +0300 Subject: [PATCH 108/163] Added badge to header for owned sticker sets in stickers list. --- Telegram/Resources/langs/lang.strings | 1 + .../chat_helpers/chat_helpers.style | 4 ++ .../chat_helpers/stickers_list_widget.cpp | 42 +++++++++++++++++++ .../data/stickers/data_stickers_set.cpp | 3 +- .../data/stickers/data_stickers_set.h | 1 + 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 57dbaad87..7005a219b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2935,6 +2935,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stickers_context_delete_sure" = "Are you sure you want to delete the sticker from your sticker set?"; "lng_stickers_box_edit_name_title" = "Edit Sticker Set Name"; "lng_stickers_box_edit_name_about" = "Choose a name for your set."; +"lng_stickers_creator_badge" = "edit"; "lng_in_dlg_photo" = "Photo"; "lng_in_dlg_album" = "Album"; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 40ab1d705..d48f32c00 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -284,6 +284,10 @@ stickersTrendingSubheaderFont: normalFont; stickersTrendingSubheaderFg: windowSubTextFg; stickersTrendingSubheaderTop: 31px; +stickersHeaderBadgeFont: font(10px); +stickersHeaderBadgeFontTop: 12px; +stickersHeaderBadgeFontSkip: 12px; + emojiPanButtonRight: 7px; emojiPanButtonTop: 8px; emojiPanButton: RoundButton(defaultActiveButton) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 337658af1..148f4e7d9 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -932,6 +932,9 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { if (sets.empty() && _section == Section::Search) { paintEmptySearchResults(p); } + const auto badgeText = tr::lng_stickers_creator_badge(tr::now); + const auto &badgeFont = st::stickersHeaderBadgeFont; + const auto badgeWidth = badgeFont->width(badgeText); enumerateSections([&](const SectionInfo &info) { if (clip.top() >= info.rowsBottom) { return true; @@ -1050,6 +1053,12 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { widthForTitle -= remove.width(); } + const auto amCreator = (set.flags & Data::StickersSetFlag::AmCreator); + if (amCreator) { + widthForTitle -= badgeWidth + + st::stickersFeaturedUnreadSkip + + st::stickersHeaderBadgeFontSkip; + } if (titleWidth > widthForTitle) { titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle); titleWidth = st::stickersTrendingHeaderFont->width(titleText); @@ -1057,6 +1066,39 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { p.setFont(st::emojiPanHeaderFont); p.setPen(st().headerFg); p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st().headerTop, width(), titleText, titleWidth); + if (amCreator) { + const auto badgeLeft = st().headerLeft + - st().margin.left() + + titleWidth + + st::stickersFeaturedUnreadSkip; + { + auto color = st().headerFg->c; + color.setAlphaF(st().headerFg->c.alphaF() * 0.15); + p.setPen(Qt::NoPen); + p.setBrush(color); + auto hq = PainterHighQualityEnabler(p); + p.drawRoundedRect( + style::rtlrect( + badgeLeft, + info.top + st::stickersHeaderBadgeFontTop, + badgeWidth + badgeFont->height, + badgeFont->height, + width()), + badgeFont->height / 2., + badgeFont->height / 2.); + } + p.setPen(st().headerFg); + p.setBrush(Qt::NoBrush); + p.setFont(badgeFont); + p.drawText( + QRect( + badgeLeft + badgeFont->height / 2, + info.top + st::stickersHeaderBadgeFontTop, + badgeWidth, + badgeFont->height), + badgeText, + style::al_center); + } } if (clip.top() + clip.height() <= info.rowsTop) { return true; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp index 324377242..75d98404e 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp @@ -55,7 +55,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) { | (data.vinstalled_date() ? Flag::Installed : Flag()) //| (data.is_videos() ? Flag::Webm : Flag()) | (data.is_text_color() ? Flag::TextColor : Flag()) - | (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag()); + | (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag()) + | (data.is_creator() ? Flag::AmCreator : Flag()); } StickersSet::StickersSet( diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.h b/Telegram/SourceFiles/data/stickers/data_stickers_set.h index e77ee46b5..c218ce2fa 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.h @@ -59,6 +59,7 @@ enum class StickersSetFlag : ushort { Emoji = (1 << 9), TextColor = (1 << 10), ChannelStatus = (1 << 11), + AmCreator = (1 << 12), }; inline constexpr bool is_flag_type(StickersSetFlag) { return true; }; using StickersSetFlags = base::flags<StickersSetFlag>; From 3cc92e01fe41e24e297a74b700b80c33242223a7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 31 Jul 2024 16:23:51 +0300 Subject: [PATCH 109/163] Added ability to force request favorite stickers. --- Telegram/SourceFiles/api/api_editing.cpp | 2 +- Telegram/SourceFiles/apiwrap.cpp | 46 +++++++++++-------- Telegram/SourceFiles/apiwrap.h | 12 +++-- .../data/stickers/data_stickers.cpp | 4 +- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index d5f76bf86..662bfc980 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -128,7 +128,7 @@ mtpRequestId EditMessage( } if (updateRecentStickers) { - api->requestRecentStickersForce(true); + api->requestSpecialStickersForce(false, false, true); } }).fail([=](const MTP::Error &error, mtpRequestId requestId) { if constexpr (ErrorWithId<FailCallback>) { diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 04a2e25f1..0f0fa264b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2585,7 +2585,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu void ApiWrap::updateStickers() { const auto now = crl::now(); requestStickers(now); - requestRecentStickers(now); + requestRecentStickers(now, false); requestFavedStickers(now); requestFeaturedStickers(now); } @@ -2607,8 +2607,15 @@ void ApiWrap::updateCustomEmoji() { requestFeaturedEmoji(now); } -void ApiWrap::requestRecentStickersForce(bool attached) { - requestRecentStickersWithHash(0, attached); +void ApiWrap::requestSpecialStickersForce( + bool faved, + bool recent, + bool attached) { + if (faved) { + requestFavedStickers(std::nullopt); + } else if (recent || attached) { + requestRecentStickers(std::nullopt, attached); + } } void ApiWrap::setGroupStickerSet( @@ -2761,18 +2768,17 @@ void ApiWrap::requestCustomEmoji(TimeId now) { }).send(); } -void ApiWrap::requestRecentStickers(TimeId now, bool attached) { - const auto needed = attached - ? _session->data().stickers().recentAttachedUpdateNeeded(now) - : _session->data().stickers().recentUpdateNeeded(now); +void ApiWrap::requestRecentStickers( + std::optional<TimeId> now, + bool attached) { + const auto needed = !now + ? true + : attached + ? _session->data().stickers().recentAttachedUpdateNeeded(*now) + : _session->data().stickers().recentUpdateNeeded(*now); if (!needed) { return; } - requestRecentStickersWithHash( - Api::CountRecentStickersHash(_session, attached), attached); -} - -void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) { const auto requestId = [=]() -> mtpRequestId & { return attached ? _recentAttachedStickersUpdateRequest @@ -2795,7 +2801,7 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) { : MTPmessages_getRecentStickers::Flags(0); requestId() = request(MTPmessages_GetRecentStickers( MTP_flags(flags), - MTP_long(hash) + MTP_long(now ? Api::CountRecentStickersHash(_session, attached) : 0) )).done([=](const MTPmessages_RecentStickers &result) { finish(); @@ -2822,13 +2828,15 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) { }).send(); } -void ApiWrap::requestFavedStickers(TimeId now) { - if (!_session->data().stickers().favedUpdateNeeded(now) - || _favedStickersUpdateRequest) { - return; +void ApiWrap::requestFavedStickers(std::optional<TimeId> now) { + if (now) { + if (!_session->data().stickers().favedUpdateNeeded(*now) + || _favedStickersUpdateRequest) { + return; + } } _favedStickersUpdateRequest = request(MTPmessages_GetFavedStickers( - MTP_long(Api::CountFavedStickersHash(_session)) + MTP_long(now ? Api::CountFavedStickersHash(_session) : 0) )).done([=](const MTPmessages_FavedStickers &result) { _session->data().stickers().setLastFavedUpdate(crl::now()); _favedStickersUpdateRequest = 0; @@ -4204,7 +4212,7 @@ void ApiWrap::sendMediaWithRandomId( ), [=](const MTPUpdates &result, const MTP::Response &response) { if (done) done(true); if (updateRecentStickers) { - requestRecentStickersForce(true); + requestRecentStickers(std::nullopt, true); } }, [=](const MTP::Error &error, const MTP::Response &response) { if (done) done(false); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 15c0941c3..7259c410d 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -244,7 +244,10 @@ public: void updateSavedGifs(); void updateMasks(); void updateCustomEmoji(); - void requestRecentStickersForce(bool attached = false); + void requestSpecialStickersForce( + bool faved, + bool recent, + bool attached); void setGroupStickerSet( not_null<ChannelData*> megagroup, const StickerSetIdentifier &set); @@ -477,9 +480,10 @@ private: void requestStickers(TimeId now); void requestMasks(TimeId now); void requestCustomEmoji(TimeId now); - void requestRecentStickers(TimeId now, bool attached = false); - void requestRecentStickersWithHash(uint64 hash, bool attached = false); - void requestFavedStickers(TimeId now); + void requestRecentStickers( + std::optional<TimeId> now, + bool attached); + void requestFavedStickers(std::optional<TimeId> now); void requestFeaturedStickers(TimeId now); void requestFeaturedEmoji(TimeId now); void requestSavedGifs(TimeId now); diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp index 9ed24fb38..8000355c2 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -228,7 +228,7 @@ void Stickers::incrementSticker(not_null<DocumentData*> document) { auto index = set->stickers.indexOf(document); if (index > 0) { if (set->dates.empty()) { - session().api().requestRecentStickersForce(); + session().api().requestSpecialStickersForce(false, true, false); } else { Assert(set->dates.size() == set->stickers.size()); set->dates.erase(set->dates.begin() + index); @@ -260,7 +260,7 @@ void Stickers::incrementSticker(not_null<DocumentData*> document) { set->emoji[emoji].push_front(document); } } else { - session().api().requestRecentStickersForce(); + session().api().requestSpecialStickersForce(false, true, false); } writeRecentStickers = true; From f749616dd8851a8bf2471f6da4de9d6d49ae9282 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 31 Jul 2024 16:48:36 +0300 Subject: [PATCH 110/163] Added additional request of special sticker pack on failing view pack. --- .../chat_helpers/stickers_list_widget.cpp | 29 ++++++++++++++++--- .../chat_helpers/stickers_list_widget.h | 4 ++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 148f4e7d9..4258edbac 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/stickers_list_widget.h" +#include "base/timer_rpl.h" #include "core/application.h" #include "data/data_document.h" #include "data/data_document_media.h" @@ -1717,12 +1718,32 @@ QPoint StickersListWidget::buttonRippleTopLeft(int section) const { + st().removeSet.rippleAreaPosition; } -void StickersListWidget::showStickerSetBox(not_null<DocumentData*> document) { +void StickersListWidget::showStickerSetBox( + not_null<DocumentData*> document, + uint64 setId) { if (document->sticker() && document->sticker()->set) { checkHideWithBox(Box<StickerSetBox>( _show, document->sticker()->set, document->sticker()->setType)); + } else if ((setId == Data::Stickers::FavedSetId) + || (setId == Data::Stickers::RecentSetId)) { + const auto lifetime = std::make_shared<rpl::lifetime>(); + constexpr auto kTimeout = 10000; + rpl::merge( + base::timer_once(kTimeout), + document->owner().stickers().updated( + Data::StickersType::Stickers) + ) | rpl::start_with_next([=, weak = Ui::MakeWeak(this)] { + if (weak.get()) { + showStickerSetBox(document, setId); + } + lifetime->destroy(); + }, *lifetime); + document->owner().session().api().requestSpecialStickersForce( + setId == Data::Stickers::FavedSetId, + setId == Data::Stickers::RecentSetId, + false); } } @@ -1779,8 +1800,8 @@ base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu( isFaved ? &icons->menuUnfave : &icons->menuFave); if (_features.openStickerSets) { - menu->addAction(tr::lng_context_pack_info(tr::now), [=] { - showStickerSetBox(document); + menu->addAction(tr::lng_context_pack_info(tr::now), [=, id = set.id] { + showStickerSetBox(document, id); }, &icons->menuStickerSet); } @@ -1850,7 +1871,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) { const auto document = set.stickers[sticker->index].document; if (_features.openStickerSets && (e->modifiers() & Qt::ControlModifier)) { - showStickerSetBox(document); + showStickerSetBox(document, set.id); } else { _chosen.fire({ .document = document, diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index f1a89b210..c436273fc 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -350,7 +350,9 @@ private: void refreshFooterIcons(); void refreshIcons(ValidateIconAnimations animations); - void showStickerSetBox(not_null<DocumentData*> document); + void showStickerSetBox( + not_null<DocumentData*> document, + uint64 setId); void cancelSetsSearch(); void showSearchResults(); From 5c797d1f316f7e9cca5a3a18f7b9712b22c9df3f Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 17 Jul 2024 10:38:50 +0200 Subject: [PATCH 111/163] Update API scheme to layer 185. --- Telegram/Resources/langs/lang.strings | 5 +++ Telegram/SourceFiles/api/api_media.cpp | 3 +- .../SourceFiles/data/data_media_types.cpp | 5 ++- Telegram/SourceFiles/data/data_media_types.h | 11 +++++- Telegram/SourceFiles/data/data_session.cpp | 3 +- Telegram/SourceFiles/data/data_story.cpp | 32 ++++++++++++++++ Telegram/SourceFiles/data/data_story.h | 11 ++++++ .../export/data/export_data_types.cpp | 7 ++++ .../export/data/export_data_types.h | 10 ++++- .../export/output/export_output_html.cpp | 10 +++++ .../export/output/export_output_json.cpp | 9 +++++ Telegram/SourceFiles/history/history_item.cpp | 32 +++++++++++++++- .../history/view/history_view_fake_items.cpp | 3 +- .../view/media/history_view_premium_gift.cpp | 30 ++++++++++++--- .../view/media/history_view_premium_gift.h | 1 + Telegram/SourceFiles/main/main_account.cpp | 3 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 37 +++++++++++++++---- .../SourceFiles/payments/payments_form.cpp | 19 +++------- .../SourceFiles/storage/localimageloader.cpp | 3 +- .../storage/serialize_document.cpp | 3 +- 20 files changed, 196 insertions(+), 41 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7005a219b..2311102df 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2854,6 +2854,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_link_pending_toast" = "Only the recipient can see the link."; "lng_gift_link_pending_footer" = "This link hasn't been activated yet."; +"lng_gift_stars_title#one" = "{count} Star"; +"lng_gift_stars_title#other" = "{count} Stars"; +"lng_gift_stars_outgoing" = "With Stars, {user} will be able to unlock content and services on Telegram."; +"lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram."; + "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_media.cpp b/Telegram/SourceFiles/api/api_media.cpp index a76cb4b67..46a5b7fa3 100644 --- a/Telegram/SourceFiles/api/api_media.cpp +++ b/Telegram/SourceFiles/api/api_media.cpp @@ -36,7 +36,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes( MTP_double(document->duration() / 1000.), MTP_int(dimensions.width()), MTP_int(dimensions.height()), - MTPint())); // preload_prefix_size + MTPint(), // preload_prefix_size + MTPdouble())); // video_start_ts } else { attributes.push_back(MTP_documentAttributeImageSize( MTP_int(dimensions.width()), diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index da16410b0..70d29f28a 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2303,8 +2303,9 @@ ClickHandlerPtr MediaDice::MakeHandler( MediaGiftBox::MediaGiftBox( not_null<HistoryItem*> parent, not_null<PeerData*> from, - int months) -: MediaGiftBox(parent, from, GiftCode{ .months = months }) { + GiftType type, + int count) +: MediaGiftBox(parent, from, GiftCode{ .count = count, .type = type }) { } MediaGiftBox::MediaGiftBox( diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 424e2c448..b8c943edc 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -125,10 +125,16 @@ struct GiveawayResults { bool all = false; }; +enum class GiftType : uchar { + Premium, // count - months + Stars, // count - stars +}; + struct GiftCode { QString slug; ChannelData *channel = nullptr; - int months = 0; + int count = 0; + GiftType type = GiftType::Premium; bool viaGiveaway = false; bool unclaimed = false; }; @@ -591,7 +597,8 @@ public: MediaGiftBox( not_null<HistoryItem*> parent, not_null<PeerData*> from, - int months); + GiftType type, + int count); MediaGiftBox( not_null<HistoryItem*> parent, not_null<PeerData*> from, diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index e43e6423a..cf772208b 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4533,7 +4533,8 @@ void Session::serviceNotification( MTPVector<MTPUsername>(), MTPint(), // stories_max_id MTPPeerColor(), // color - MTPPeerColor())); // profile_color + MTPPeerColor(), // profile_color + MTPint())); // bot_active_users } const auto history = this->history(PeerData::kServiceNotificationsId); const auto insert = [=] { diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 38ea28aae..edd4670d8 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/download_manager_mtproto.h" #include "storage/file_download.h" // kMaxFileInMemory #include "ui/text/text_utilities.h" +#include "ui/color_int_conversion.h" namespace Data { namespace { @@ -43,6 +44,10 @@ using UpdateFlag = StoryUpdate::Flag; }; } +[[nodiscard]] uint32 ParseMilliKelvin(double celcius) { + return uint32(std::clamp(celcius + 273.15, 0., 1'000'000.) * 1000.); +} + [[nodiscard]] TextWithEntities StripLinks(TextWithEntities text) { const auto link = [&](const EntityInText &entity) { return (entity.type() == EntityType::CustomUrl) @@ -83,6 +88,7 @@ using UpdateFlag = StoryUpdate::Flag; }, [&](const MTPDmediaAreaSuggestedReaction &data) { }, [&](const MTPDmediaAreaChannelPost &data) { }, [&](const MTPDmediaAreaUrl &data) { + }, [&](const MTPDmediaAreaWeather &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -105,6 +111,7 @@ using UpdateFlag = StoryUpdate::Flag; }); }, [&](const MTPDmediaAreaChannelPost &data) { }, [&](const MTPDmediaAreaUrl &data) { + }, [&](const MTPDmediaAreaWeather &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -127,6 +134,7 @@ using UpdateFlag = StoryUpdate::Flag; data.vmsg_id().v), }); }, [&](const MTPDmediaAreaUrl &data) { + }, [&](const MTPDmediaAreaWeather &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -147,6 +155,30 @@ using UpdateFlag = StoryUpdate::Flag; .area = ParseArea(data.vcoordinates()), .url = qs(data.vurl()), }); + }, [&](const MTPDmediaAreaWeather &data) { + }, [&](const MTPDinputMediaAreaChannelPost &data) { + LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); + }, [&](const MTPDinputMediaAreaVenue &data) { + LOG(("API Error: Unexpected inputMediaAreaVenue from API.")); + }); + return result; +} + +[[nodiscard]] auto ParseWeatherArea(const MTPMediaArea &area) +-> std::optional<WeatherArea> { + auto result = std::optional<WeatherArea>(); + area.match([&](const MTPDmediaAreaVenue &data) { + }, [&](const MTPDmediaAreaGeoPoint &data) { + }, [&](const MTPDmediaAreaSuggestedReaction &data) { + }, [&](const MTPDmediaAreaChannelPost &data) { + }, [&](const MTPDmediaAreaUrl &data) { + }, [&](const MTPDmediaAreaWeather &data) { + result.emplace(WeatherArea{ + .area = ParseArea(data.vcoordinates()), + .emoji = qs(data.vemoji()), + .color = Ui::ColorFromSerialized(data.vcolor().v), + .millicelcius = int(data.vtemperature_c().v * 1000.), + }); }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index b318ce9fe..4dfc7a712 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -131,6 +131,17 @@ struct UrlArea { const UrlArea &) = default; }; +struct WeatherArea { + StoryArea area; + QString emoji; + QColor color; + int millicelcius = 0; + + friend inline bool operator==( + const WeatherArea &, + const WeatherArea &) = default; +}; + class Story final { public: Story( diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 9e65eac39..f9c9806d0 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1522,6 +1522,13 @@ ServiceAction ParseServiceAction( content.peerId = ParsePeerId(data.vpeer()); content.transactionId = data.vcharge().data().vid().v; result.content = content; + }, [&](const MTPDmessageActionGiftStars &data) { + auto content = ActionGiftStars(); + content.cost = Ui::FillAmountAndCurrency( + data.vamount().v, + qs(data.vcurrency())).toUtf8(); + content.stars = data.vstars().v; + 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 72824a2ee..9639730e4 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -529,7 +529,7 @@ struct ActionWebViewDataSent { struct ActionGiftPremium { Utf8String cost; - int months; + int months = 0; }; struct ActionTopicCreate { @@ -583,6 +583,11 @@ struct ActionPaymentRefunded { Utf8String transactionId; }; +struct ActionGiftStars { + Utf8String cost; + int stars = 0; +}; + struct ServiceAction { std::variant< v::null_t, @@ -625,7 +630,8 @@ struct ServiceAction { ActionGiveawayLaunch, ActionGiveawayResults, ActionBoostApply, - ActionPaymentRefunded> content; + ActionPaymentRefunded, + ActionGiftStars> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index d988fc00c..4d3fa397c 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1321,6 +1321,16 @@ auto HtmlWriter::Wrap::pushMessage( + " refunded back " + amount; return result; + }, [&](const ActionGiftStars &data) { + if (!data.stars || data.cost.isEmpty()) { + return serviceFrom + " sent you a gift."; + } + return serviceFrom + + " sent you a gift for " + + data.cost + + ": " + + QString::number(data.stars).toUtf8() + + " Telegram Stars."; }, [](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 5d4c7e42d..62aaba5f7 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -632,6 +632,15 @@ QByteArray SerializeMessage( pushBare("peer_name", wrapPeerName(data.peerId)); push("peer_id", data.peerId); push("charge_id", data.transactionId); + }, [&](const ActionGiftStars &data) { + pushActor(); + pushAction("send_stars_gift"); + if (!data.cost.isEmpty()) { + push("cost", data.cost); + } + if (data.stars) { + push("stars", data.stars); + } }, [](v::null_t) {}); if (v::is_null(message.action.content)) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index d2386e7e6..9cee2262e 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4977,6 +4977,27 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareGiftStars = [&]( + const MTPDmessageActionGiftStars &action) { + auto result = PreparedServiceText(); + const auto isSelf = (_from->id == _from->session().userPeerId()); + const auto peer = isSelf ? _history->peer : _from; + _history->session().giftBoxStickersPacks().load(); + const auto amount = action.vamount().v; + const auto currency = qs(action.vcurrency()); + result.links.push_back(peer->createOpenLink()); + result.text = (isSelf + ? tr::lng_action_gift_received_me + : tr::lng_action_gift_received)( + tr::now, + lt_user, + Ui::Text::Link(peer->name(), 1), // Link 1. + lt_cost, + { Ui::FillAmountAndCurrency(amount, currency) }, + Ui::Text::WithEntities); + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -5020,6 +5041,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareGiveawayResults, prepareBoostApply, preparePaymentRefunded, + prepareGiftStars, PrepareEmptyText<MTPDmessageActionRequestedPeerSentMe>, PrepareErrorText<MTPDmessageActionEmpty>)); @@ -5072,6 +5094,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { _media = std::make_unique<Data::MediaGiftBox>( this, _from, + Data::GiftType::Premium, data.vmonths().v); }, [&](const MTPDmessageActionSuggestProfilePhoto &data) { data.vphoto().match([&](const MTPDphoto &photo) { @@ -5106,10 +5129,17 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { .channel = (boostedId ? history()->owner().channel(boostedId).get() : nullptr), - .months = data.vmonths().v, + .count = data.vmonths().v, + .type = Data::GiftType::Premium, .viaGiveaway = data.is_via_giveaway(), .unclaimed = data.is_unclaimed(), }); + }, [&](const MTPDmessageActionGiftStars &data) { + _media = std::make_unique<Data::MediaGiftBox>( + this, + _from, + Data::GiftType::Stars, + data.vstars().v); }, [](const auto &) { }); } diff --git a/Telegram/SourceFiles/history/view/history_view_fake_items.cpp b/Telegram/SourceFiles/history/view/history_view_fake_items.cpp index 5a6538036..8390d09fa 100644 --- a/Telegram/SourceFiles/history/view/history_view_fake_items.cpp +++ b/Telegram/SourceFiles/history/view/history_view_fake_items.cpp @@ -59,7 +59,8 @@ PeerId GenerateUser(not_null<History*> history, const QString &name) { MTPVector<MTPUsername>(), MTPint(), // stories_max_id MTPPeerColor(), // color - MTPPeerColor())); // profile_color + MTPPeerColor(), // profile_color + MTPint())); // bot_active_users return peerId; } 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 b832a6dc3..885e74a62 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "settings/settings_credits.h" // Settings::CreditsId #include "settings/settings_premium.h" // Settings::ShowGiftPremium #include "ui/text/text_utilities.h" #include "window/window_session_controller.h" @@ -45,6 +46,9 @@ QSize PremiumGift::size() { } QString PremiumGift::title() { + if (const auto count = stars()) { + return tr::lng_gift_stars_title(tr::now, lt_count, count); + } return gift() ? tr::lng_premium_summary_title(tr::now) : _data.unclaimed @@ -53,8 +57,16 @@ QString PremiumGift::title() { } TextWithEntities PremiumGift::subtitle() { - if (gift()) { - return { GiftDuration(_data.months) }; + if (const auto count = stars()) { + return outgoingGift() + ? tr::lng_gift_stars_outgoing( + tr::now, + lt_user, + Ui::Text::Bold(_parent->history()->peer->shortName()), + Ui::Text::WithEntities) + : tr::lng_gift_stars_incoming(tr::now, Ui::Text::WithEntities); + } else if (gift()) { + return { GiftDuration(_data.count) }; } const auto name = _data.channel ? _data.channel->name() : "channel"; auto result = (_data.unclaimed @@ -74,7 +86,7 @@ TextWithEntities PremiumGift::subtitle() { : tr::lng_prize_gift_duration)( tr::now, lt_duration, - Ui::Text::Bold(GiftDuration(_data.months)), + Ui::Text::Bold(GiftDuration(_data.count)), Ui::Text::RichLangValue)); return result; } @@ -94,9 +106,11 @@ ClickHandlerPtr PremiumGift::createViewLink() { if (const auto controller = my.sessionWindow.get()) { const auto selfId = controller->session().userPeerId(); const auto self = (from->id == selfId); - if (data.slug.isEmpty()) { + if (data.type == Data::GiftType::Stars) { + controller->showSettings(Settings::CreditsId()); + } else if (data.slug.isEmpty()) { const auto peer = self ? to : from; - const auto months = data.months; + const auto months = data.count; Settings::ShowGiftPremium(controller, peer, months, self); } else { const auto fromId = from->id; @@ -162,12 +176,16 @@ bool PremiumGift::gift() const { return _data.slug.isEmpty() || !_data.channel; } +int PremiumGift::stars() const { + return (_data.type == Data::GiftType::Stars) ? _data.count : 0; +} + void PremiumGift::ensureStickerCreated() const { if (_sticker) { return; } const auto &session = _parent->history()->session(); - const auto months = _gift->data().months; + const auto months = stars() ? 1 : _data.count; auto &packs = session.giftBoxStickersPacks(); if (const auto document = packs.lookup(months)) { if (const auto sticker = document->sticker()) { 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 af5724dfa..7240dd49e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h @@ -49,6 +49,7 @@ private: [[nodiscard]] bool incomingGift() const; [[nodiscard]] bool outgoingGift() const; [[nodiscard]] bool gift() const; + [[nodiscard]] int stars() const; void ensureStickerCreated() const; const not_null<Element*> _parent; diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp index 9e366ca54..bfcab3fc7 100644 --- a/Telegram/SourceFiles/main/main_account.cpp +++ b/Telegram/SourceFiles/main/main_account.cpp @@ -171,7 +171,8 @@ void Account::createSession( MTPVector<MTPUsername>(), MTPint(), // stories_max_id MTPPeerColor(), // color - MTPPeerColor()), // profile_color + MTPPeerColor(), // profile_color + MTPint()), // bot_active_users serialized, streamVersion, std::move(settings)); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 394a50efa..2d35a0c52 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -83,7 +83,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#215c4438 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 contact_require_premium:flags2.10?true bot_business:flags2.11?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<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector<Username> stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor = User; +user#83314fca 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 contact_require_premium:flags2.10?true bot_business:flags2.11?true bot_has_main_app:flags2.13?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<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector<Username> stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor bot_active_users:flags2.12?int = 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; @@ -180,6 +180,7 @@ messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = Me messageActionBoostApply#cc02aa6d boosts:int = MessageAction; messageActionRequestedPeerSentMe#93b31848 button_id:int peers:Vector<RequestedPeer> = MessageAction; messageActionPaymentRefunded#41b3e202 flags:# peer:Peer currency:string total_amount:long payload:flags.0?bytes charge:PaymentCharge = MessageAction; +messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long crypto_currency:flags.0?string crypto_amount:flags.0?long transaction_id:flags.1?string = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -568,7 +569,7 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL; documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute; documentAttributeAnimated#11b58939 = DocumentAttribute; documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute; -documentAttributeVideo#d38ff1c2 flags:# round_message:flags.0?true supports_streaming:flags.1?true nosound:flags.3?true duration:double w:int h:int preload_prefix_size:flags.2?int = DocumentAttribute; +documentAttributeVideo#17399fad flags:# round_message:flags.0?true supports_streaming:flags.1?true nosound:flags.3?true duration:double w:int h:int preload_prefix_size:flags.2?int video_start_ts:flags.4?double = DocumentAttribute; documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; @@ -629,7 +630,7 @@ messages.stickerSetNotModified#d3f924eb = messages.StickerSet; botCommand#c27ac8c7 command:string description:string = BotCommand; -botInfo#8f300b57 flags:# user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector<BotCommand> menu_button:flags.3?BotMenuButton = BotInfo; +botInfo#8f300b57 flags:# has_preview_medias:flags.6?true user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector<BotCommand> menu_button:flags.3?BotMenuButton = BotInfo; keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; @@ -789,6 +790,7 @@ topPeerCategoryChannels#161d9628 = TopPeerCategory; topPeerCategoryPhoneCalls#1e76a78c = TopPeerCategory; topPeerCategoryForwardUsers#a8406ca9 = TopPeerCategory; topPeerCategoryForwardChats#fbeec0f0 = TopPeerCategory; +topPeerCategoryBotsApp#fd9e7bec = TopPeerCategory; topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector<TopPeer> = TopPeerCategoryPeers; @@ -1453,7 +1455,7 @@ attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; inputInvoiceSlug#c326caef slug:string = InputInvoice; inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice; -inputInvoiceStars#1da33ad8 option:StarsTopupOption = InputInvoice; +inputInvoiceStars#65f00ce3 purpose:InputStorePaymentPurpose = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1465,7 +1467,8 @@ inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true upgra inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentPremiumGiftCode#a3805f3f flags:# users:Vector<InputUser> boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentPremiumGiveaway#160544ca flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true boost_peer:InputPeer additional_peers:flags.1?Vector<InputPeer> countries_iso2:flags.2?Vector<string> prize_description:flags.4?string random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; -inputStorePaymentStars#4f0ee8df flags:# stars:long currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentStarsTopup#dddd0f56 stars:long currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentStarsGift#1d741ef7 user_id:InputUser stars:long currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1613,6 +1616,7 @@ mediaAreaSuggestedReaction#14455871 flags:# dark:flags.0?true flipped:flags.1?tr mediaAreaChannelPost#770416af coordinates:MediaAreaCoordinates channel_id:long msg_id:int = MediaArea; inputMediaAreaChannelPost#2271f2bf coordinates:MediaAreaCoordinates channel:InputChannel msg_id:int = MediaArea; mediaAreaUrl#37381085 coordinates:MediaAreaCoordinates url:string = MediaArea; +mediaAreaWeather#49a6549c coordinates:MediaAreaCoordinates emoji:string temperature_c:double color:int = MediaArea; peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector<StoryItem> = PeerStories; @@ -1806,7 +1810,7 @@ starsTransactionPeerAds#60682812 = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#2db5418f flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> = StarsTransaction; +starsTransaction#2db5418f flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> = StarsTransaction; payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector<StarsTransaction> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.StarsStatus; @@ -1826,6 +1830,14 @@ payments.starsRevenueAdsAccountUrl#394e7f21 url:string = payments.StarsRevenueAd inputStarsTransaction#206ae6d1 flags:# refund:flags.0?true id:string = InputStarsTransaction; +starsGiftOption#5e0589f1 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsGiftOption; + +bots.popularAppBots#1991b13b flags:# next_offset:flags.0?string users:Vector<User> = bots.PopularAppBots; + +botPreviewMedia#23e91ba3 date:int media:MessageMedia = BotPreviewMedia; + +bots.previewInfo#ca71d64 media:Vector<BotPreviewMedia> lang_codes:Vector<string> = bots.PreviewInfo; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1992,7 +2004,7 @@ contacts.unblock#b550d328 flags:# my_stories_from:flags.0?true id:InputPeer = Bo contacts.getBlocked#9a868f80 flags:# my_stories_from:flags.0?true offset:int limit:int = contacts.Blocked; contacts.search#11f812d8 q:string limit:int = contacts.Found; contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; -contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:long = contacts.TopPeers; +contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true bots_app:flags.16?true offset:int limit:int hash:long = contacts.TopPeers; contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool; contacts.resetSaved#879537f1 = Bool; contacts.getSaved#82f1e39f = Vector<SavedContact>; @@ -2221,6 +2233,7 @@ messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects; messages.editFactCheck#589ee75 peer:InputPeer msg_id:int text:TextWithEntities = Updates; messages.deleteFactCheck#d1da940c peer:InputPeer msg_id:int = Updates; messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector<int> = Vector<FactCheck>; +messages.requestMainWebView#c9e01e7b flags:# compact:flags.7?true peer:InputPeer bot:InputUser start_param:flags.1?string theme_params:flags.0?DataJSON platform:string = WebViewResult; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; @@ -2349,6 +2362,13 @@ bots.toggleUsername#53ca973 bot:InputUser username:string active:Bool = Bool; bots.canSendMessage#1359f4e6 bot:InputUser = Bool; bots.allowSendMessage#f132e3ef bot:InputUser = Updates; bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON; +bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots; +bots.addPreviewMedia#17aeb75a bot:InputUser lang_code:string media:InputMedia = BotPreviewMedia; +bots.editPreviewMedia#8525606f bot:InputUser lang_code:string media:InputMedia new_media:InputMedia = BotPreviewMedia; +bots.deletePreviewMedia#2d0135b3 bot:InputUser lang_code:string media:Vector<InputMedia> = Bool; +bots.reorderPreviewMedias#b627f3aa bot:InputUser lang_code:string order:Vector<InputMedia> = Bool; +bots.getPreviewInfo#423ab3ad bot:InputUser lang_code:string = bots.PreviewInfo; +bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>; payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; @@ -2375,6 +2395,7 @@ payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true peer:InputPeer payments.getStarsRevenueWithdrawalUrl#13bbe8b3 peer:InputPeer stars:long password:InputCheckPasswordSRP = payments.StarsRevenueWithdrawalUrl; payments.getStarsRevenueAdsAccountUrl#d1d7efc5 peer:InputPeer = payments.StarsRevenueAdsAccountUrl; payments.getStarsTransactionsByID#27842d2e peer:InputPeer id:Vector<InputStarsTransaction> = payments.StarsStatus; +payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector<StarsGiftOption>; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -2494,4 +2515,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 184 +// LAYER 185 diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index c09b41c36..e9dec016b 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -318,20 +318,11 @@ MTPInputInvoice Form::inputInvoice() const { } else if (const auto slug = std::get_if<InvoiceSlug>(&_id.value)) { return MTP_inputInvoiceSlug(MTP_string(slug->slug)); } else if (const auto credits = std::get_if<InvoiceCredits>(&_id.value)) { - using Flag = MTPDstarsTopupOption::Flag; - const auto emptyFlag = MTPDstarsTopupOption::Flags(0); - return MTP_inputInvoiceStars(MTP_starsTopupOption( - MTP_flags(emptyFlag - | (credits->product.isEmpty() - ? Flag::f_store_product - : emptyFlag) - | (credits->extended - ? Flag::f_extended - : emptyFlag)), - MTP_long(credits->credits), - MTP_string(credits->product), - MTP_string(credits->currency), - MTP_long(credits->amount))); + return MTP_inputInvoiceStars( + MTP_inputStorePaymentStarsTopup( + MTP_long(credits->credits), + MTP_string(credits->currency), + MTP_long(credits->amount))); } const auto &giftCode = v::get<InvoicePremiumGiftCode>(_id.value); using Flag = MTPDpremiumGiftCodeOption::Flag; diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 59f2fb505..1895a8703 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -844,7 +844,8 @@ void FileLoadTask::process(Args &&args) { MTP_double(realSeconds), MTP_int(coverWidth), MTP_int(coverHeight), - MTPint())); // preload_prefix_size + MTPint(), // preload_prefix_size + MTPdouble())); // video_start_ts if (args.generateGoodThumbnail) { goodThumbnail = video->thumbnail; diff --git a/Telegram/SourceFiles/storage/serialize_document.cpp b/Telegram/SourceFiles/storage/serialize_document.cpp index dd87b0c5f..efb527506 100644 --- a/Telegram/SourceFiles/storage/serialize_document.cpp +++ b/Telegram/SourceFiles/storage/serialize_document.cpp @@ -207,7 +207,8 @@ DocumentData *Document::readFromStreamHelper( MTP_double(duration / 1000.), MTP_int(width), MTP_int(height), - MTPint())); // preload_prefix_size + MTPint(), // preload_prefix_size + MTPdouble())); // video_start_ts } else { attributes.push_back(MTP_documentAttributeImageSize( MTP_int(width), From 847d66c97349543400133d6339955d22606622cd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 12:13:54 +0300 Subject: [PATCH 112/163] Added initial support of received gifts in list of credits history. --- Telegram/Resources/langs/lang.strings | 6 +++ Telegram/SourceFiles/api/api_credits.cpp | 1 + .../SourceFiles/boxes/gift_premium_box.cpp | 6 ++- Telegram/SourceFiles/data/data_credits.h | 2 +- .../info_statistics_list_controllers.cpp | 4 +- .../settings/settings_credits_graphics.cpp | 38 ++++++++++++++++++- .../ui/effects/credits_graphics.cpp | 4 +- 7 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2311102df..931bbb1c0 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2369,6 +2369,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_play_market" = "Play Market"; "lng_credits_box_history_entry_app_store" = "App Store"; "lng_credits_box_history_entry_fragment" = "Fragment"; +"lng_credits_box_history_entry_anonymous" = "Unknown User"; +"lng_credits_box_history_entry_gift_name" = "Received Gift"; +"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}"; +"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}"; +"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}"; +"lng_credits_box_history_entry_gift_about_url" = "https://telegram.org/blog/telegram-stars"; "lng_credits_box_history_entry_ads" = "Ads Platform"; "lng_credits_box_history_entry_premium_bot" = "Stars Top-Up"; "lng_credits_box_history_entry_via_premium_bot" = "Premium Bot"; diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 356e2cffa..0b35a04ee 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -102,6 +102,7 @@ constexpr auto kTransactionsLimit = 100; : QDateTime(), .successLink = qs(tl.data().vtransaction_url().value_or_empty()), .in = (int64(tl.data().vstars().v) >= 0), + .gift = tl.data().is_gift(), }; } diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index fdf1eda03..5730d9b40 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1695,8 +1695,10 @@ void AddCreditsHistoryEntryTable( AddTableRow( table, tr::lng_credits_box_history_entry_via(), - tr::lng_credits_box_history_entry_fragment( - Ui::Text::RichLangValue)); + (entry.gift + ? tr::lng_credits_box_history_entry_anonymous + : tr::lng_credits_box_history_entry_fragment)( + Ui::Text::RichLangValue)); } else if (entry.peerType == Type::Ads) { AddTableRow( table, diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 75da4db5b..d01becf07 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -57,7 +57,7 @@ struct CreditsHistoryEntry final { QDateTime successDate; QString successLink; bool in = false; - + bool gift = false; }; struct CreditsStatusSlice final { diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 9c2a692e7..86640fe5b 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -800,7 +800,9 @@ void CreditsRow::init() { : _entry.failed ? (joiner + tr::lng_channel_earn_history_failed(tr::now)) : QString()) - + (_entry.title.isEmpty() ? QString() : (joiner + _name))); + + ((_entry.gift && PeerListRow::special()) + ? (joiner + tr::lng_credits_box_history_entry_anonymous(tr::now)) + : (_entry.title.isEmpty() ? QString() : (joiner + _name)))); { constexpr auto kMinus = QChar(0x2212); _rightText.setText( diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 1eaef4568..1f774b499 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -488,6 +488,8 @@ void ReceiptCreditsBox( rpl::single( !e.title.isEmpty() ? e.title + : e.gift + ? tr::lng_credits_box_history_entry_gift_name(tr::now) : peer ? peer->name() : Ui::GenerateEntryName(e).text), @@ -593,7 +595,41 @@ void ReceiptCreditsBox( object_ptr<Ui::FlatLabel>( box, rpl::single(e.description), - st::defaultFlatLabel))); + st::creditsBoxAbout))); + } + if (e.gift) { + Ui::AddSkip(content); + const auto arrow = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::topicButtonArrow, + st::channelEarnLearnArrowMargins, + false)); + auto link = tr::lng_credits_box_history_entry_gift_about_link( + lt_emoji, + rpl::single(arrow), + Ui::Text::RichLangValue + ) | rpl::map([](TextWithEntities text) { + return Ui::Text::Link( + std::move(text), + tr::lng_credits_box_history_entry_gift_about_url(tr::now)); + }); + box->addRow(object_ptr<Ui::CenterWrap<>>( + box, + Ui::CreateLabelWithCustomEmoji( + box, + (!e.in && peer) + ? tr::lng_credits_box_history_entry_gift_out_about( + lt_user, + rpl::single(TextWithEntities{ peer->shortName() }), + lt_link, + std::move(link), + Ui::Text::RichLangValue) + : tr::lng_credits_box_history_entry_gift_in_about( + lt_link, + std::move(link), + Ui::Text::RichLangValue), + { .session = session }, + st::creditsBoxAbout))); } Ui::AddSkip(content); diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index e99a4d4eb..61b43b8fc 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -455,7 +455,9 @@ Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback( } TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { - return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) + return (entry.gift + ? tr::lng_credits_box_history_entry_gift_name + : (entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) ? tr::lng_bot_username_description1_link : (entry.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot) ? tr::lng_credits_box_history_entry_premium_bot From bcb6e9e1af2e56d9d7b5e205e0b716fd4d9c8441 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 12:52:40 +0300 Subject: [PATCH 113/163] Removed unused include directives from settings_common_session. --- .../settings/settings_common_session.cpp | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_common_session.cpp b/Telegram/SourceFiles/settings/settings_common_session.cpp index 8fac1a399..d72c74d07 100644 --- a/Telegram/SourceFiles/settings/settings_common_session.cpp +++ b/Telegram/SourceFiles/settings/settings_common_session.cpp @@ -7,30 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_common_session.h" -#include "api/api_cloud_password.h" -#include "apiwrap.h" -#include "core/application.h" -#include "core/core_cloud_password.h" -#include "lang/lang_keys.h" -#include "main/main_domain.h" -#include "main/main_session.h" #include "settings/cloud_password/settings_cloud_password_email_confirm.h" -#include "settings/settings_advanced.h" -#include "settings/settings_calls.h" #include "settings/settings_chat.h" -#include "settings/settings_experimental.h" -#include "settings/settings_folders.h" -#include "settings/settings_information.h" #include "settings/settings_main.h" -#include "settings/settings_notifications.h" -#include "settings/settings_privacy_security.h" -#include "ui/widgets/menu/menu_add_action_callback.h" -#include "window/themes/window_theme_editor_box.h" -#include "window/window_controller.h" -#include "window/window_session_controller.h" -#include "styles/style_menu_icons.h" - -#include <QAction> namespace Settings { From 3f2cb8f8c94275f46231ccd17e635df6da7178ea Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 14:30:19 +0300 Subject: [PATCH 114/163] Added file origin to sticker pack for gifts. --- .../SourceFiles/chat_helpers/stickers_gift_box_pack.cpp | 6 ++++++ Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp index f54b1f1d3..2e810dd6c 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "data/data_document.h" +#include "data/data_file_origin.h" #include "data/data_session.h" #include "main/main_session.h" @@ -38,6 +39,10 @@ DocumentData *GiftBoxPack::lookup(int months) const { return (index >= _documents.size()) ? fallback : _documents[index]; } +Data::FileOrigin GiftBoxPack::origin() const { + return Data::FileOriginStickerSet(_setId, _accessHash); +} + void GiftBoxPack::load() { if (_requestId || !_documents.empty()) { return; @@ -59,6 +64,7 @@ void GiftBoxPack::load() { void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) { _setId = data.vset().data().vid().v; + _accessHash = data.vset().data().vaccess_hash().v; auto documents = base::flat_map<DocumentId, not_null<DocumentData*>>(); for (const auto &sticker : data.vdocuments().v) { const auto document = _session->data().processDocument(sticker); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h index e722c1979..4c480fba3 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class DocumentData; +namespace Data { +struct FileOrigin; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -22,6 +26,7 @@ public: void load(); [[nodiscard]] DocumentData *lookup(int months) const; + [[nodiscard]] Data::FileOrigin origin() const; private: using SetId = uint64; @@ -32,6 +37,7 @@ private: std::vector<DocumentData*> _documents; SetId _setId = 0; + uint64 _accessHash = 0; mtpRequestId _requestId = 0; }; From e760a0983f7b32ae61f330988106df93f50ccb8f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 14:30:53 +0300 Subject: [PATCH 115/163] Added gift sticker to ReceiptCreditsBox for gifts. --- .../settings/settings_credits_graphics.cpp | 95 +++++++++++++++---- Telegram/SourceFiles/ui/effects/credits.style | 5 + 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 1f774b499..fdb7b74fe 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -12,11 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer_rpl.h" #include "base/unixtime.h" #include "boxes/gift_premium_box.h" +#include "chat_helpers/stickers_gift_box_pack.h" +#include "chat_helpers/stickers_lottie.h" #include "core/click_handler_types.h" +#include "core/click_handler_types.h" // UrlClickHandler #include "core/ui_integration.h" #include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_file_origin.h" -#include "core/click_handler_types.h" // UrlClickHandler #include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_user.h" @@ -27,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. #include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" +#include "lottie/lottie_single_player.h" #include "main/main_app_config.h" #include "main/main_session.h" #include "payments/payments_checkout_process.h" @@ -457,24 +461,82 @@ void ReceiptCreditsBox( content, GenericEntryPhoto(content, callback, stUser.photoSize))); AddViewMediaHandler(thumb->entity(), controller, e); - } else if (peer) { + } else if (peer && !e.gift) { content->add(object_ptr<Ui::CenterWrap<>>( content, object_ptr<Ui::UserpicButton>(content, peer, stUser))); } else { - const auto widget = content->add( - object_ptr<Ui::CenterWrap<>>( + if (e.gift) { + using PlayerPtr = std::unique_ptr<Lottie::SinglePlayer>; + struct State final { + DocumentData *sticker = nullptr; + std::shared_ptr<Data::DocumentMedia> media; + std::unique_ptr<Lottie::SinglePlayer> lottie; + rpl::lifetime downloadLifetime; + }; + Ui::AddSkip( content, - object_ptr<Ui::RpWidget>(content)))->entity(); - using Draw = Fn<void(Painter &, int, int, int, int)>; - const auto draw = widget->lifetime().make_state<Draw>( - Ui::GenerateCreditsPaintUserpicCallback(e)); - widget->resize(Size(stUser.photoSize)); - widget->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(widget); - (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); - }, widget->lifetime()); + st::creditsHistoryEntryGiftStickerSpace); + const auto icon = Ui::CreateChild<Ui::RpWidget>(content); + icon->resize(Size(st::creditsHistoryEntryGiftStickerSize)); + const auto state = icon->lifetime().make_state<State>(); + auto &packs = session->giftBoxStickersPacks(); + const auto document = packs.lookup(1); + if (document && document->sticker()) { + state->sticker = document; + state->media = document->createMediaView(); + state->media->thumbnailWanted(packs.origin()); + state->media->automaticLoad(packs.origin(), nullptr); + session->downloaderTaskFinished( + ) | rpl::start_with_next([=] { + if (state->media->loaded()) { + state->lottie = ChatHelpers::LottiePlayerFromDocument( + state->media.get(), + ChatHelpers::StickerLottieSize::MessageHistory, + icon->size(), + Lottie::Quality::High); + state->lottie->updates() | rpl::start_with_next([=] { + icon->update(); + }, icon->lifetime()); + state->downloadLifetime.destroy(); + } + }, state->downloadLifetime); + } + icon->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(icon); + const auto &lottie = state->lottie; + const auto frame = (lottie && lottie->ready()) + ? lottie->frameInfo({ .box = icon->size() }) + : Lottie::Animation::FrameInfo(); + if (!frame.image.isNull()) { + p.drawImage(0, 0, frame.image); + if (lottie->frameIndex() < lottie->framesCount() - 1) { + lottie->markFrameShown(); + } + } + }, icon->lifetime()); + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + icon->move( + (size.width() - icon->width()) / 2, + st::creditsHistoryEntryGiftStickerSkip); + }, icon->lifetime()); + } else { + const auto widget = content->add( + object_ptr<Ui::CenterWrap<>>( + content, + object_ptr<Ui::RpWidget>(content)))->entity(); + using Draw = Fn<void(Painter &, int, int, int, int)>; + const auto draw = widget->lifetime().make_state<Draw>( + Ui::GenerateCreditsPaintUserpicCallback(e)); + widget->resize(Size(stUser.photoSize)); + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(widget); + (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); + }, widget->lifetime()); + } } Ui::AddSkip(content); @@ -635,10 +697,7 @@ void ReceiptCreditsBox( Ui::AddSkip(content); Ui::AddSkip(content); - AddCreditsHistoryEntryTable( - controller, - box->verticalLayout(), - e); + AddCreditsHistoryEntryTable(controller, content, e); Ui::AddSkip(content); diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index baab38fb7..cebc110c5 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -49,3 +49,8 @@ starIconSmall: icon{{ "payments/small_star", windowFg }}; starIconSmallPadding: margins(0px, -2px, 0px, 0px); creditsHistoryEntryTypeAds: icon {{ "folders/folders_channels", premiumButtonFg }}; + +creditsHistoryEntryGiftStickerSkip: -20px; +creditsHistoryEntryGiftStickerSize: 150px; +creditsHistoryEntryGiftStickerSpace: 105px; + From 127f651d5e14139faf52178fb1f69802c9c420b8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 15:29:05 +0300 Subject: [PATCH 116/163] Added api support to get credits gift options. --- Telegram/SourceFiles/api/api_credits.cpp | 34 +++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 0b35a04ee..3ef1d721f 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -134,12 +134,10 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); - using TLOption = MTPStarsTopupOption; - _api.request(MTPpayments_GetStarsTopupOptions( - )).done([=](const MTPVector<TLOption> &result) { - _options = ranges::views::all( - result.v - ) | ranges::views::transform([](const TLOption &option) { + const auto optionsFromTL = [](const auto &options) { + return ranges::views::all( + options + ) | ranges::views::transform([=](const auto &option) { return Data::CreditTopupOption{ .credits = option.data().vstars().v, .product = qs( @@ -149,10 +147,28 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() { .extended = option.data().is_extended(), }; }) | ranges::to_vector; - consumer.put_done(); - }).fail([=](const MTP::Error &error) { + }; + const auto fail = [=](const MTP::Error &error) { consumer.put_error_copy(error.type()); - }).send(); + }; + + if (_peer->isSelf()) { + using TLOption = MTPStarsTopupOption; + _api.request(MTPpayments_GetStarsTopupOptions( + )).done([=](const MTPVector<TLOption> &result) { + _options = optionsFromTL(result.v); + consumer.put_done(); + }).fail(fail).send(); + } else if (const auto user = _peer->asUser()) { + using TLOption = MTPStarsGiftOption; + _api.request(MTPpayments_GetStarsGiftOptions( + MTP_flags(MTPpayments_GetStarsGiftOptions::Flag::f_user_id), + user->inputUser + )).done([=](const MTPVector<TLOption> &result) { + _options = optionsFromTL(result.v); + consumer.put_done(); + }).fail(fail).send(); + } return lifetime; }; From 24b93a5effe80a66ba86f3db15801054de4fdd81 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 15:32:16 +0300 Subject: [PATCH 117/163] Added support for credits gift options to list of credit options. --- Telegram/SourceFiles/settings/settings_credits.cpp | 3 ++- .../SourceFiles/settings/settings_credits_graphics.cpp | 10 +++++++--- .../SourceFiles/settings/settings_credits_graphics.h | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index e80f82a62..235253652 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -289,7 +289,8 @@ void Credits::setupContent() { Ui::StartFireworks(_parent); } }; - FillCreditOptions(_controller->uiShow(), content, 0, paid); + const auto self = _controller->session().user(); + FillCreditOptions(_controller->uiShow(), content, self, 0, paid); setupHistory(content); Ui::ResizeFitChild(this, content); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index fdb7b74fe..e9e015849 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -231,6 +231,7 @@ void AddViewMediaHandler( void FillCreditOptions( std::shared_ptr<Main::SessionShow> show, not_null<Ui::VerticalLayout*> container, + not_null<PeerData*> peer, int minimumCredits, Fn<void()> paid) { const auto options = container->add( @@ -351,8 +352,7 @@ void FillCreditOptions( }; using ApiOptions = Api::CreditsTopupOptions; - const auto apiCredits = content->lifetime().make_state<ApiOptions>( - show->session().user()); + const auto apiCredits = content->lifetime().make_state<ApiOptions>(peer); if (show->session().premiumPossible()) { apiCredits->request( @@ -864,7 +864,11 @@ void SmallBalanceBox( })); }(); - FillCreditOptions(show, box->verticalLayout(), creditsNeeded, done); + { + const auto content = box->verticalLayout(); + const auto self = show->session().user(); + FillCreditOptions(show, content, self, creditsNeeded, done); + } content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight); content->setMinimumHeight(st::infoLayerTopBarHeight); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 47d5323c7..701c60b7f 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -35,6 +35,7 @@ namespace Settings { void FillCreditOptions( std::shared_ptr<Main::SessionShow> show, not_null<Ui::VerticalLayout*> container, + not_null<PeerData*> peer, int minCredits, Fn<void()> paid); From b8a19b56b667dddff625e8086c3232c6816be0bc Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 16:09:43 +0300 Subject: [PATCH 118/163] Removed window session controller usage from list of credit options. --- .../settings/settings_credits_graphics.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index e9e015849..613bf9169 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -864,11 +864,12 @@ void SmallBalanceBox( })); }(); - { - const auto content = box->verticalLayout(); - const auto self = show->session().user(); - FillCreditOptions(show, content, self, creditsNeeded, done); - } + FillCreditOptions( + show, + box->verticalLayout(), + show->session().user(), + creditsNeeded, + done); content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight); content->setMinimumHeight(st::infoLayerTopBarHeight); From 8ad2d3d39a1539feb0d91abb89f07086470254d8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 17:22:22 +0300 Subject: [PATCH 119/163] Added api support to create invoice for credit gifts. --- Telegram/SourceFiles/api/api_credits.cpp | 5 ++++- Telegram/SourceFiles/data/data_credits.h | 1 + Telegram/SourceFiles/payments/payments_form.cpp | 10 ++++++++++ Telegram/SourceFiles/payments/payments_form.h | 1 + .../SourceFiles/settings/settings_credits_graphics.cpp | 1 + 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 3ef1d721f..727cee565 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -134,7 +134,9 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); - const auto optionsFromTL = [](const auto &options) { + const auto giftBarePeerId = !_peer->isSelf() ? _peer->id.value : 0; + + const auto optionsFromTL = [giftBarePeerId](const auto &options) { return ranges::views::all( options ) | ranges::views::transform([=](const auto &option) { @@ -145,6 +147,7 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() { .currency = qs(option.data().vcurrency()), .amount = option.data().vamount().v, .extended = option.data().is_extended(), + .giftBarePeerId = giftBarePeerId, }; }) | ranges::to_vector; }; diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index d01becf07..ee8d948a7 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -15,6 +15,7 @@ struct CreditTopupOption final { QString currency; uint64 amount = 0; bool extended = false; + uint64 giftBarePeerId = 0; }; using CreditTopupOptions = std::vector<CreditTopupOption>; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index e9dec016b..8c0a44e10 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -318,6 +318,16 @@ MTPInputInvoice Form::inputInvoice() const { } else if (const auto slug = std::get_if<InvoiceSlug>(&_id.value)) { return MTP_inputInvoiceSlug(MTP_string(slug->slug)); } else if (const auto credits = std::get_if<InvoiceCredits>(&_id.value)) { + if (const auto userId = peerToUser(credits->giftPeerId)) { + if (const auto user = _session->data().user(userId)) { + return MTP_inputInvoiceStars( + MTP_inputStorePaymentStarsGift( + user->inputUser, + MTP_long(credits->credits), + MTP_string(credits->currency), + MTP_long(credits->amount))); + } + } return MTP_inputInvoiceStars( MTP_inputStorePaymentStarsTopup( MTP_long(credits->credits), diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index c85d946ed..42b6d00b3 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -167,6 +167,7 @@ struct InvoiceCredits { QString currency; uint64 amount = 0; bool extended = false; + PeerId giftPeerId = PeerId(0); }; struct InvoiceId { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 613bf9169..954c07c7b 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -314,6 +314,7 @@ void FillCreditOptions( .currency = option.currency, .amount = option.amount, .extended = option.extended, + .giftPeerId = PeerId(option.giftBarePeerId), }; const auto weak = Ui::MakeWeak(button); From 0bfb0fd04530af5e40ccde949a5e9ad6da218f84 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 17 Jul 2024 17:22:49 +0300 Subject: [PATCH 120/163] Added initial ability to gift credits to users. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/boxes/gift_credits_box.cpp | 180 ++++++++++++++++++ Telegram/SourceFiles/boxes/gift_credits_box.h | 20 ++ .../SourceFiles/settings/settings_credits.cpp | 14 ++ Telegram/SourceFiles/ui/effects/credits.style | 3 + 6 files changed, 222 insertions(+) create mode 100644 Telegram/SourceFiles/boxes/gift_credits_box.cpp create mode 100644 Telegram/SourceFiles/boxes/gift_credits_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b9ee4785c..8923211da 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -272,6 +272,8 @@ PRIVATE boxes/edit_caption_box.h boxes/edit_privacy_box.cpp boxes/edit_privacy_box.h + boxes/gift_credits_box.cpp + boxes/gift_credits_box.h boxes/gift_premium_box.cpp boxes/gift_premium_box.h boxes/language_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 931bbb1c0..fad51ea32 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2342,6 +2342,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_summary_history_tab_out" = "Outgoing"; "lng_credits_summary_history_entry_inner_in" = "In-App Purchase"; "lng_credits_summary_balance" = "Balance"; +"lng_credits_gift_button" = "Gift Stars to Friends"; "lng_credits_box_out_title" = "Confirm Your Purchase"; "lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?"; "lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?"; @@ -2390,6 +2391,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; +"lng_credits_gift_title" = "Gift Telegram Stars"; + "lng_location_title" = "Location"; "lng_location_about" = "Display the location of your business on your account."; "lng_location_address" = "Enter Address"; diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp new file mode 100644 index 000000000..d03a73641 --- /dev/null +++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp @@ -0,0 +1,180 @@ +/* +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/gift_credits_box.h" + +#include "api/api_credits.h" +#include "boxes/peer_list_controllers.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "data/stickers/data_custom_emoji.h" +#include "lang/lang_keys.h" +#include "main/session/session_show.h" +#include "settings/settings_credits_graphics.h" +#include "ui/controls/userpic_button.h" +#include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_stars_colored.h" +#include "ui/layers/generic_box.h" +#include "ui/rect.h" +#include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" +#include "ui/widgets/label_with_custom_emoji.h" +#include "window/window_session_controller.h" +#include "styles/style_boxes.h" +#include "styles/style_channel_earn.h" +#include "styles/style_chat.h" +#include "styles/style_credits.h" +#include "styles/style_giveaway.h" +#include "styles/style_layers.h" +#include "styles/style_premium.h" + +namespace Ui { + +void GiftCreditsBox( + not_null<Ui::GenericBox*> box, + not_null<PeerData*> peer, + Fn<void()> gifted) { + box->setStyle(st::creditsGiftBox); + box->setNoContentMargin(true); + box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); }); + + const auto content = box->setPinnedToTopContent( + object_ptr<Ui::VerticalLayout>(box)); + + Ui::AddSkip(content); + Ui::AddSkip(content); + const auto &stUser = st::premiumGiftsUserpicButton; + const auto userpicWrap = content->add( + object_ptr<Ui::CenterWrap<>>( + content, + object_ptr<Ui::UserpicButton>(content, peer, stUser))); + userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents); + Ui::AddSkip(content); + Ui::AddSkip(content); + + { + const auto widget = Ui::CreateChild<Ui::RpWidget>(content); + using ColoredMiniStars = Ui::Premium::ColoredMiniStars; + const auto stars = widget->lifetime().make_state<ColoredMiniStars>( + widget, + false, + Ui::Premium::MiniStars::Type::BiStars); + stars->setColorOverride(Ui::Premium::CreditsIconGradientStops()); + widget->resize( + st::boxWidth - stUser.photoSize, + stUser.photoSize * 2); + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + widget->moveToLeft(stUser.photoSize / 2, 0); + const auto starsRect = Rect(widget->size()); + stars->setPosition(starsRect.topLeft()); + stars->setSize(starsRect.size()); + widget->lower(); + }, widget->lifetime()); + widget->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + auto p = QPainter(widget); + p.fillRect(r, Qt::transparent); + stars->paint(p); + }, widget->lifetime()); + } + { + Ui::AddSkip(content); + const auto arrow = Ui::Text::SingleCustomEmoji( + peer->owner().customEmojiManager().registerInternalEmoji( + st::topicButtonArrow, + st::channelEarnLearnArrowMargins, + false)); + auto link = tr::lng_credits_box_history_entry_gift_about_link( + lt_emoji, + rpl::single(arrow), + Ui::Text::RichLangValue + ) | rpl::map([](TextWithEntities text) { + return Ui::Text::Link( + std::move(text), + tr::lng_credits_box_history_entry_gift_about_url(tr::now)); + }); + content->add( + object_ptr<Ui::CenterWrap<>>( + content, + Ui::CreateLabelWithCustomEmoji( + content, + tr::lng_credits_box_history_entry_gift_out_about( + lt_user, + rpl::single(TextWithEntities{ peer->shortName() }), + lt_link, + std::move(link), + Ui::Text::RichLangValue), + { .session = &peer->session() }, + st::creditsBoxAbout)), + st::boxRowPadding); + } + Ui::AddSkip(content); + Ui::AddSkip(box->verticalLayout()); + + Settings::FillCreditOptions( + Main::MakeSessionShow(box->uiShow(), &peer->session()), + box->verticalLayout(), + peer, + 0, + [=] { gifted(); box->uiShow()->hideLayer(); }); + + const auto bottom = box->setPinnedToBottomContent( + object_ptr<Ui::VerticalLayout>(box)); +} + +void ShowGiftCreditsBox( + not_null<Window::SessionController*> controller, + Fn<void()> gifted) { + + class Controller final : public ContactsBoxController { + public: + Controller( + not_null<Main::Session*> session, + Fn<void(not_null<PeerData*>)> choose) + : ContactsBoxController(session) + , _choose(std::move(choose)) { + } + + protected: + std::unique_ptr<PeerListRow> createRow( + not_null<UserData*> user) override { + if (user->isSelf() + || user->isBot() + || user->isServiceUser() + || user->isInaccessible()) { + return nullptr; + } + return ContactsBoxController::createRow(user); + } + + void rowClicked(not_null<PeerListRow*> row) override { + _choose(row->peer()); + } + + private: + const Fn<void(not_null<PeerData*>)> _choose; + + }; + auto initBox = [=](not_null<PeerListBox*> peersBox) { + peersBox->setTitle(tr::lng_credits_gift_title()); + peersBox->addButton(tr::lng_cancel(), [=] { peersBox->closeBox(); }); + }; + + const auto show = controller->uiShow(); + auto listController = std::make_unique<Controller>( + &controller->session(), + [=](not_null<PeerData*> peer) { + show->showBox(Box(GiftCreditsBox, peer, gifted)); + }); + show->showBox( + Box<PeerListBox>(std::move(listController), std::move(initBox)), + Ui::LayerOption::KeepOther); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.h b/Telegram/SourceFiles/boxes/gift_credits_box.h new file mode 100644 index 000000000..43b8556f3 --- /dev/null +++ b/Telegram/SourceFiles/boxes/gift_credits_box.h @@ -0,0 +1,20 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { + +void ShowGiftCreditsBox( + not_null<Window::SessionController*> controller, + Fn<void()> gifted); + +} // namespace Ui diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 235253652..0d7d12490 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits.h" #include "api/api_credits.h" +#include "boxes/gift_credits_box.h" #include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" #include "data/data_file_origin.h" @@ -291,6 +292,19 @@ void Credits::setupContent() { }; const auto self = _controller->session().user(); FillCreditOptions(_controller->uiShow(), content, self, 0, paid); + { + Ui::AddSkip(content); + const auto giftButton = AddButtonWithIcon( + content, + tr::lng_credits_gift_button(), + st::settingsButtonLightNoIcon); + Ui::AddSkip(content); + Ui::AddDivider(content); + giftButton->setClickedCallback([=] { + Ui::ShowGiftCreditsBox(_controller, paid); + }); + } + setupHistory(content); Ui::ResizeFitChild(this, content); diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index cebc110c5..b982687d6 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -54,3 +54,6 @@ creditsHistoryEntryGiftStickerSkip: -20px; creditsHistoryEntryGiftStickerSize: 150px; creditsHistoryEntryGiftStickerSpace: 105px; +creditsGiftBox: Box(defaultBox) { + shadowIgnoreTopSkip: true; +} From 54ce85f8e6d1797dd8816a7fe3db96b7fcb0e3cd Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 23 Jul 2024 07:51:04 +0200 Subject: [PATCH 121/163] Show nice star in stars payments. --- Telegram/SourceFiles/history/history.cpp | 12 +++++- Telegram/SourceFiles/history/history_item.cpp | 38 ++++++++++++++----- .../history/history_item_components.h | 2 +- .../SourceFiles/ui/text/format_values.cpp | 2 +- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 754791e80..35643637e 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_helpers.h" #include "history/history_translation.h" #include "history/history_unread_things.h" +#include "core/ui_integration.h" #include "dialogs/ui/dialogs_layout.h" #include "data/business/data_shortcut_messages.h" #include "data/components/scheduled_messages.h" @@ -1128,14 +1129,23 @@ void History::applyServiceChanges( } if (paid) { // Toast on a current active window. + const auto context = [=](not_null<QWidget*> toast) { + return Core::MarkedTextContext{ + .session = &session(), + .customEmojiRepaint = [=] { toast->update(); }, + }; + }; Ui::Toast::Show({ .text = tr::lng_payments_success( tr::now, lt_amount, - Ui::Text::Bold(payment->amount), + Ui::Text::Wrapped( + payment->amount, + EntityType::Bold), lt_title, Ui::Text::Bold(paid->title), Ui::Text::WithEntities), + .textContext = context, }); } } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 9cee2262e..4a8a413cc 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "base/unixtime.h" #include "base/timer_rpl.h" +#include "boxes/send_credits_box.h" #include "api/api_text_entities.h" #include "api/api_updates.h" #include "data/components/scheduled_messages.h" @@ -137,6 +138,17 @@ template <typename T> return fields; } +[[nodiscard]] TextWithEntities AmountAndStarCurrency( + not_null<Main::Session*> session, + int64 amount, + const QString ¤cy) { + if (currency == Ui::kCreditsCurrency) { + return Ui::CreditsEmojiSmall(session).append( + Lang::FormatCountDecimal(std::abs(amount))); + } + return { Ui::FillAmountAndCurrency(amount, currency) }; +} + } // namespace void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { @@ -3957,7 +3969,10 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { payment->recurringInit = data.is_recurring_init(); payment->recurringUsed = data.is_recurring_used(); payment->isCreditsCurrency = (currency == Ui::kCreditsCurrency); - payment->amount = Ui::FillAmountAndCurrency(amount, currency); + payment->amount = AmountAndStarCurrency( + &_history->session(), + amount, + currency); payment->invoiceLink = std::make_shared<LambdaClickHandler>([=]( ClickContext context) { using namespace Payments; @@ -4692,7 +4707,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { lt_user, Ui::Text::Link(peer->name(), 1), // Link 1. lt_cost, - { Ui::FillAmountAndCurrency(amount, currency) }, + AmountAndStarCurrency(&peer->session(), amount, currency), Ui::Text::WithEntities); return result; }; @@ -4905,9 +4920,10 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { lt_user, Ui::Text::Link(peer->name(), 1), // Link 1. lt_cost, - { Ui::FillAmountAndCurrency( + AmountAndStarCurrency( + &_history->session(), action.vamount().value_or_empty(), - qs(action.vcurrency().value_or_empty())) }, + qs(action.vcurrency().value_or_empty())), Ui::Text::WithEntities); } @@ -4957,7 +4973,6 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { Ui::Text::WithEntities); return result; }; - auto preparePaymentRefunded = [&](const MTPDmessageActionPaymentRefunded &action) { auto result = PreparedServiceText(); const auto refund = Get<HistoryServicePaymentRefund>(); @@ -4972,7 +4987,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { lt_peer, Ui::Text::Link(refund->peer->name(), 1), // Link 1. lt_amount, - { Ui::FillAmountAndCurrency(amount, currency) }, + AmountAndStarCurrency(&_history->session(), amount, currency), Ui::Text::WithEntities); return result; }; @@ -4993,7 +5008,10 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { lt_user, Ui::Text::Link(peer->name(), 1), // Link 1. lt_cost, - { Ui::FillAmountAndCurrency(amount, currency) }, + AmountAndStarCurrency( + &_history->session(), + amount, + currency), Ui::Text::WithEntities); return result; }; @@ -5412,7 +5430,7 @@ PreparedServiceText HistoryItem::preparePaymentSentText() { result.text = tr::lng_action_payment_used_recurring( tr::now, lt_amount, - { .text = payment->amount }, + payment->amount, Ui::Text::WithEntities); } else { result.text = (payment->recurringInit @@ -5420,7 +5438,7 @@ PreparedServiceText HistoryItem::preparePaymentSentText() { : tr::lng_action_payment_done)( tr::now, lt_amount, - { .text = payment->amount }, + payment->amount, lt_user, { .text = _history->peer->name() }, Ui::Text::WithEntities); @@ -5431,7 +5449,7 @@ PreparedServiceText HistoryItem::preparePaymentSentText() { : tr::lng_action_payment_done_for)( tr::now, lt_amount, - { .text = payment->amount }, + payment->amount, lt_user, { .text = _history->peer->name() }, lt_invoice, diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 7e9fb6396..1a5e14014 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -649,7 +649,7 @@ struct HistoryServicePayment : public RuntimeComponent<HistoryServicePayment, HistoryItem> , public HistoryServiceDependentData { QString slug; - QString amount; + TextWithEntities amount; ClickHandlerPtr invoiceLink; bool recurringInit = false; bool recurringUsed = false; diff --git a/Telegram/SourceFiles/ui/text/format_values.cpp b/Telegram/SourceFiles/ui/text/format_values.cpp index d95062313..7ebd961f3 100644 --- a/Telegram/SourceFiles/ui/text/format_values.cpp +++ b/Telegram/SourceFiles/ui/text/format_values.cpp @@ -146,11 +146,11 @@ QString FillAmountAndCurrency( // std::abs doesn't work on that one :/ Expects(amount != std::numeric_limits<int64>::min()); - const auto rule = LookupCurrencyRule(currency); if (currency == kCreditsCurrency) { return QChar(0x2B50) + Lang::FormatCountDecimal(std::abs(amount)); } + const auto rule = LookupCurrencyRule(currency); const auto prefix = (amount < 0) ? QString::fromUtf8("\xe2\x88\x92") : QString(); From 5fdd4eba80d26699749a1ee194aac454d0c3bdf5 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 23 Jul 2024 14:56:06 +0200 Subject: [PATCH 122/163] Implement weather area in stories. --- Telegram/SourceFiles/data/data_story.cpp | 26 +- Telegram/SourceFiles/data/data_story.h | 6 +- .../stories/media_stories_controller.cpp | 42 ++- .../media/stories/media_stories_controller.h | 7 +- .../media/stories/media_stories_reactions.cpp | 298 +++++++++++++++++- .../media/stories/media_stories_reactions.h | 15 +- .../SourceFiles/ui/color_int_conversion.cpp | 8 + .../SourceFiles/ui/color_int_conversion.h | 1 + 8 files changed, 369 insertions(+), 34 deletions(-) diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index edd4670d8..d9c37de4e 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -41,13 +41,10 @@ using UpdateFlag = StoryUpdate::Flag; return { .geometry = { corner / 100., size / 100. }, .rotation = data.vrotation().v, + .radius = data.vradius().value_or_empty(), }; } -[[nodiscard]] uint32 ParseMilliKelvin(double celcius) { - return uint32(std::clamp(celcius + 273.15, 0., 1'000'000.) * 1000.); -} - [[nodiscard]] TextWithEntities StripLinks(TextWithEntities text) { const auto link = [&](const EntityInText &entity) { return (entity.type() == EntityType::CustomUrl) @@ -176,8 +173,11 @@ using UpdateFlag = StoryUpdate::Flag; result.emplace(WeatherArea{ .area = ParseArea(data.vcoordinates()), .emoji = qs(data.vemoji()), - .color = Ui::ColorFromSerialized(data.vcolor().v), - .millicelcius = int(data.vtemperature_c().v * 1000.), + .color = Ui::Color32FromSerialized(data.vcolor().v), + .millicelsius = int(1000. * std::clamp( + data.vtemperature_c().v, + -274., + 1'000'000.)), }); }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); @@ -721,6 +721,10 @@ const std::vector<UrlArea> &Story::urlAreas() const { return _urlAreas; } +const std::vector<WeatherArea> &Story::weatherAreas() const { + return _weatherAreas; +} + void Story::applyChanges( StoryMedia media, const MTPDstoryItem &data, @@ -825,6 +829,7 @@ void Story::applyFields( auto suggestedReactions = std::vector<SuggestedReaction>(); auto channelPosts = std::vector<ChannelPost>(); auto urlAreas = std::vector<UrlArea>(); + auto weatherAreas = std::vector<WeatherArea>(); if (const auto areas = data.vmedia_areas()) { for (const auto &area : areas->v) { if (const auto location = ParseLocation(area)) { @@ -840,6 +845,8 @@ void Story::applyFields( channelPosts.push_back(*post); } else if (auto url = ParseUrlArea(area)) { urlAreas.push_back(*url); + } else if (auto weather = ParseWeatherArea(area)) { + weatherAreas.push_back(*weather); } } } @@ -853,6 +860,7 @@ void Story::applyFields( = (_suggestedReactions != suggestedReactions); const auto channelPostsChanged = (_channelPosts != channelPosts); const auto urlAreasChanged = (_urlAreas != urlAreas); + const auto weatherAreasChanged = (_weatherAreas != weatherAreas); const auto reactionChanged = (_sentReactionId != reaction); _out = out; @@ -881,6 +889,9 @@ void Story::applyFields( if (urlAreasChanged) { _urlAreas = std::move(urlAreas); } + if (weatherAreasChanged) { + _weatherAreas = std::move(weatherAreas); + } if (reactionChanged) { _sentReactionId = reaction; } @@ -891,7 +902,8 @@ void Story::applyFields( || mediaChanged || locationsChanged || channelPostsChanged - || urlAreasChanged; + || urlAreasChanged + || weatherAreasChanged; const auto reactionsChanged = reactionChanged || suggestedReactionsChanged; if (!initial && (changed || reactionsChanged)) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 4dfc7a712..bd508591c 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -81,6 +81,7 @@ struct StoryViews { struct StoryArea { QRectF geometry; float64 rotation = 0; + float64 radius = 0; friend inline bool operator==( const StoryArea &, @@ -135,7 +136,7 @@ struct WeatherArea { StoryArea area; QString emoji; QColor color; - int millicelcius = 0; + int millicelsius = 0; friend inline bool operator==( const WeatherArea &, @@ -219,6 +220,8 @@ public: -> const std::vector<ChannelPost> &; [[nodiscard]] auto urlAreas() const -> const std::vector<UrlArea> &; + [[nodiscard]] auto weatherAreas() const + -> const std::vector<WeatherArea> &; void applyChanges( StoryMedia media, @@ -270,6 +273,7 @@ private: std::vector<SuggestedReaction> _suggestedReactions; std::vector<ChannelPost> _channelPosts; std::vector<UrlArea> _urlAreas; + std::vector<WeatherArea> _weatherAreas; StoryViews _views; StoryViews _channelReactions; const TimeId _date = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index c5fd13992..ca558abfd 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -536,8 +536,9 @@ void Controller::rebuildActiveAreas(const Layout &layout) const { int(base::SafeRound(general.width() * scale.width())), int(base::SafeRound(general.height() * scale.height())) ).translated(origin); - if (const auto reaction = area.reaction.get()) { - reaction->setAreaGeometry(area.geometry); + area.radius = scale.width() * area.radiusOriginal / 100.; + if (const auto view = area.view.get()) { + view->setAreaGeometry(area.geometry, area.radius); } } } @@ -1050,6 +1051,9 @@ void Controller::updateAreas(Data::Story *story) { const auto &urlAreas = story ? story->urlAreas() : std::vector<Data::UrlArea>(); + const auto &weatherAreas = story + ? story->weatherAreas() + : std::vector<Data::WeatherArea>(); if (_locations != locations) { _locations = locations; _areas.clear(); @@ -1062,13 +1066,18 @@ void Controller::updateAreas(Data::Story *story) { _urlAreas = urlAreas; _areas.clear(); } + if (_weatherAreas != weatherAreas) { + _weatherAreas = weatherAreas; + _areas.clear(); + } const auto reactionsCount = int(suggestedReactions.size()); if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) { for (auto i = 0; i != reactionsCount; ++i) { const auto count = suggestedReactions[i].count; if (_suggestedReactions[i].count != count) { _suggestedReactions[i].count = count; - _areas[i + _locations.size()].reaction->updateCount(count); + const auto view = _areas[i + _locations.size()].view.get(); + view->updateReactionsCount(count); } if (_suggestedReactions[i] != suggestedReactions[i]) { _suggestedReactions = suggestedReactions; @@ -1206,7 +1215,8 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { || (_locations.empty() && _suggestedReactions.empty() && _channelPosts.empty() - && _urlAreas.empty())) { + && _urlAreas.empty() + && _weatherAreas.empty())) { return nullptr; } else if (_areas.empty()) { const auto now = story(); @@ -1240,7 +1250,7 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { } } }), - .reaction = std::move(widget), + .view = std::move(widget), }); } if (const auto session = now ? &now->session() : nullptr) { @@ -1261,19 +1271,27 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { .handler = std::make_shared<HiddenUrlClickHandler>(url.url), }); } + for (const auto &weather : _weatherAreas) { + auto widget = _reactions->makeWeatherAreaWidget(weather); + const auto raw = widget.get(); + _areas.push_back({ + .original = weather.area.geometry, + .radiusOriginal = weather.area.radius, + .rotation = weather.area.rotation, + .handler = std::make_shared<LambdaClickHandler>([=] { + raw->toggleMode(); + }), + .view = std::move(widget), + }); + } rebuildActiveAreas(*layout); } - const auto circleContains = [&](QRect circle) { - const auto radius = std::min(circle.width(), circle.height()) / 2; - const auto delta = circle.center() - point; - return QPoint::dotProduct(delta, delta) < (radius * radius); - }; for (const auto &area : _areas) { const auto center = area.geometry.center(); const auto angle = -area.rotation; - const auto contains = area.reaction - ? circleContains(area.geometry) + const auto contains = area.view + ? area.view->contains(point) : area.geometry.contains(Rotated(point, center, angle)); if (contains) { return area.handler; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 3d486fa87..590dee3f6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -68,7 +68,7 @@ struct ContentLayout; class CaptionFullView; class RepostView; enum class ReactionsMode; -class SuggestedReactionView; +class StoryAreaView; struct RepostClickHandler; enum class HeaderLayout { @@ -208,10 +208,12 @@ private: }; struct ActiveArea { QRectF original; + float64 radiusOriginal = 0.; QRect geometry; float64 rotation = 0.; + float64 radius = 0.; ClickHandlerPtr handler; - std::unique_ptr<SuggestedReactionView> reaction; + std::unique_ptr<StoryAreaView> view; }; void initLayout(); @@ -303,6 +305,7 @@ private: std::vector<Data::SuggestedReaction> _suggestedReactions; std::vector<Data::ChannelPost> _channelPosts; std::vector<Data::UrlArea> _urlAreas; + std::vector<Data::WeatherArea> _weatherAreas; mutable std::vector<ActiveArea> _areas; std::vector<CachedSource> _cachedSourcesList; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index af45d9150..f5e2582ef 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "boxes/premium_preview_box.h" #include "chat_helpers/compose/compose_show.h" +#include "chat_helpers/stickers_lottie.h" +#include "chat_helpers/stickers_emoji_pack.h" #include "data/data_changes.h" #include "data/data_document.h" #include "data/data_document_media.h" @@ -20,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/admin_log/history_admin_log_item.h" #include "history/view/media/history_view_custom_emoji.h" #include "history/view/media/history_view_media_unwrapped.h" +#include "history/view/media/history_view_sticker_player.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/history_view_element.h" #include "history/history_item_reply_markup.h" @@ -61,7 +64,7 @@ constexpr auto kStoppingFadeDuration = crl::time(150); class ReactionView final : public Ui::RpWidget - , public SuggestedReactionView + , public StoryAreaView , public HistoryView::DefaultElementDelegate { public: ReactionView( @@ -69,9 +72,11 @@ public: not_null<Main::Session*> session, const Data::SuggestedReaction &reaction); - void setAreaGeometry(QRect geometry) override; - void updateCount(int count) override; + void setAreaGeometry(QRect geometry, float64 radius) override; + void updateReactionsCount(int count) override; void playEffect() override; + void toggleMode() override; + bool contains(QPoint point) override; private: using Element = HistoryView::Element; @@ -108,6 +113,7 @@ private: Ui::Text::String _counter; Ui::Animations::Simple _counterAnimation; QRectF _bubbleGeometry; + QRect _apiGeometry; int _size = 0; int _mediaLeft = 0; int _mediaTop = 0; @@ -126,6 +132,58 @@ private: }; +class WeatherView final : public Ui::RpWidget, public StoryAreaView { +public: + WeatherView( + QWidget *parent, + not_null<Main::Session*> session, + const Data::WeatherArea &data); + + void setAreaGeometry(QRect geometry, float64 radius) override; + void updateReactionsCount(int count) override; + void playEffect() override; + void toggleMode() override; + bool contains(QPoint point) override; + +private: + void paintEvent(QPaintEvent *e) override; + + void cacheBackground(); + void watchForSticker(); + void setStickerFrom(not_null<DocumentData*> document); + [[nodiscard]] QSize stickerSize() const; + + const not_null<Main::Session*> _session; + Data::WeatherArea _data; + EmojiPtr _emoji; + QColor _fg; + QImage _background; + QFont _font; + QRectF _rect; + QRect _wrapped; + float64 _radius = 0.; + int _emojiSize = 0; + int _padding = 0; + bool _celsius = true; + + std::shared_ptr<HistoryView::StickerPlayer> _sticker; + rpl::lifetime _lifetime; + +}; + +[[nodiscard]] QPoint Rotated(QPoint point, QPoint origin, float64 angle) { + if (std::abs(angle) < 1.) { + return point; + } + const auto alpha = angle / 180. * M_PI; + const auto acos = cos(alpha); + const auto asin = sin(alpha); + point -= origin; + return origin + QPoint( + int(base::SafeRound(acos * point.x() - asin * point.y())), + int(base::SafeRound(asin * point.x() + acos * point.y()))); +} + [[nodiscard]] AdminLog::OwnedItem GenerateFakeItem( not_null<HistoryView::ElementDelegate*> delegate, not_null<History*> history) { @@ -140,6 +198,13 @@ private: return AdminLog::OwnedItem(delegate, item); } +[[nodiscard]] QColor ChooseWeatherFg(const QColor &bg) { + const auto luminance = (0.2126 * bg.redF()) + + (0.7152 * bg.greenF()) + + (0.0722 * bg.blueF()); + return (luminance > 0.705) ? QColor(0, 0, 0) : QColor(255, 255, 255); +} + ReactionView::ReactionView( QWidget *parent, not_null<Main::Session*> session, @@ -198,7 +263,7 @@ ReactionView::ReactionView( }, lifetime()); _data.count = 0; - updateCount(reaction.count); + updateReactionsCount(reaction.count); _counterAnimation.stop(); setupCustomChatStylePalette(); @@ -212,7 +277,8 @@ void ReactionView::setupCustomChatStylePalette() { _chatStyle->applyCustomPalette(_chatStyle.get()); } -void ReactionView::setAreaGeometry(QRect geometry) { +void ReactionView::setAreaGeometry(QRect geometry, float64 radius) { + _apiGeometry = geometry; _size = std::min(geometry.width(), geometry.height()); _bubble = _size * kSuggestedBubbleSize; _bigOffset = _bubble * kSuggestedTailBigOffset; @@ -228,7 +294,7 @@ void ReactionView::setAreaGeometry(QRect geometry) { updateEffectGeometry(); } -void ReactionView::updateCount(int count) { +void ReactionView::updateReactionsCount(int count) { if (_data.count == count) { return; } @@ -283,6 +349,17 @@ void ReactionView::playEffect() { } } +void ReactionView::toggleMode() { + Unexpected("ReactionView::toggleMode."); +} + +bool ReactionView::contains(QPoint point) { + const auto circle = _apiGeometry; + const auto radius = std::min(circle.width(), circle.height()) / 2; + const auto delta = circle.center() - point; + return QPoint::dotProduct(delta, delta) < (radius * radius); +} + void ReactionView::paintEffectFrame( QPainter &p, not_null<Ui::ReactionFlyAnimation*> effect, @@ -457,6 +534,205 @@ void ReactionView::cacheBackground() { paintShape(_data.dark ? dark : QColor(255, 255, 255)); } +WeatherView::WeatherView( + QWidget *parent, + not_null<Main::Session*> session, + const Data::WeatherArea &data) +: RpWidget(parent) +, _session(session) +, _data(data) +, _emoji(Ui::Emoji::Find(_data.emoji)) +, _fg(ChooseWeatherFg(_data.color)) { + watchForSticker(); + setAttribute(Qt::WA_TransparentForMouseEvents); + show(); +} + +void WeatherView::watchForSticker() { + if (!_emoji) { + return; + } + const auto emojiStickers = &_session->emojiStickersPack(); + if (const auto sticker = emojiStickers->stickerForEmoji(_emoji)) { + setStickerFrom(sticker.document); + } else { + emojiStickers->refreshed() | rpl::map([=] { + return emojiStickers->stickerForEmoji(_emoji).document; + }) | rpl::filter([=](DocumentData *document) { + return document != nullptr; + }) | rpl::take( + 1 + ) | rpl::start_with_next([=](not_null<DocumentData*> document) { + setStickerFrom(document); + update(); + }, _lifetime); + } +} + +void WeatherView::setAreaGeometry(QRect geometry, float64 radius) { + const auto diagxdiag = (geometry.width() * geometry.width()) + + (geometry.height() * geometry.height()); + const auto diag = std::sqrt(diagxdiag); + const auto topleft = QRectF(geometry).center() + - QPointF(diag / 2., diag / 2.); + const auto bottomright = topleft + QPointF(diag, diag); + const auto left = int(std::floor(topleft.x())); + const auto top = int(std::floor(topleft.y())); + const auto right = int(std::ceil(bottomright.x())); + const auto bottom = int(std::ceil(bottomright.y())); + setGeometry(left, top, right - left, bottom - top); + _rect = QRectF(geometry).translated(-left, -top); + _radius = radius; + + _emojiSize = int(base::SafeRound(_rect.height() * 2 / 3.)); + _font = st::semiboldFont->f; + _font.setPixelSize(_emojiSize); + _background = {}; +} + +void WeatherView::updateReactionsCount(int count) { + Unexpected("WeatherView::updateRactionsCount."); +} + +void WeatherView::playEffect() { + Unexpected("WeatherView::playEffect."); +} + +void WeatherView::toggleMode() { + _celsius = !_celsius; + _background = {}; + update(); +} + +bool WeatherView::contains(QPoint point) { + const auto geometry = _rect.translated(pos()).toRect(); + const auto angle = -_data.area.rotation; + return geometry.contains(Rotated(point, geometry.center(), angle)); +} + +void WeatherView::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + if (_background.size() != size() * style::DevicePixelRatio()) { + cacheBackground(); + } + p.drawImage(0, 0, _background); + if (_sticker && _sticker->ready()) { + auto hq = PainterHighQualityEnabler(p); + const auto rcenter = _wrapped.center(); + p.translate(rcenter); + p.rotate(_data.area.rotation); + p.translate(-rcenter); + + const auto image = _sticker->frame( + stickerSize(), + QColor(0, 0, 0, 0), + false, + crl::now(), + false).image; + const auto size = image.size() / style::DevicePixelRatio(); + const auto rect = QRectF( + _wrapped.x() + _padding + (_emojiSize - size.width()) / 2., + _wrapped.y() + (_wrapped.height() - size.height()) / 2., + size.width(), + size.height()); + const auto scenter = rect.center(); + const auto scale = (_emojiSize * 1.) / stickerSize().width(); + p.translate(scenter); + p.scale(scale, scale); + p.translate(-scenter); + p.drawImage(rect, image); + _sticker->markFrameShown(); + } +} + +QSize WeatherView::stickerSize() const { + return QSize(st::chatIntroStickerSize, st::chatIntroStickerSize); +} + +void WeatherView::setStickerFrom(not_null<DocumentData*> document) { + if (_sticker || !_emoji) { + return; + } + const auto media = document->createMediaView(); + media->checkStickerLarge(); + media->goodThumbnailWanted(); + + rpl::single() | rpl::then( + document->owner().session().downloaderTaskFinished() + ) | rpl::filter([=] { + return media->loaded(); + }) | rpl::take(1) | rpl::start_with_next([=] { + const auto sticker = document->sticker(); + if (sticker->isLottie()) { + _sticker = std::make_shared<HistoryView::LottiePlayer>( + ChatHelpers::LottiePlayerFromDocument( + media.get(), + ChatHelpers::StickerLottieSize::StickerSet, + stickerSize(), + Lottie::Quality::High)); + } else if (sticker->isWebm()) { + _sticker = std::make_shared<HistoryView::WebmPlayer>( + media->owner()->location(), + media->bytes(), + stickerSize()); + } else { + _sticker = std::make_shared<HistoryView::StaticStickerPlayer>( + media->owner()->location(), + media->bytes(), + stickerSize()); + } + _sticker->setRepaintCallback([=] { update(); }); + update(); + }, _lifetime); +} + +void WeatherView::cacheBackground() { + const auto ratio = style::DevicePixelRatio(); + _background = QImage( + size() * ratio, + QImage::Format_ARGB32_Premultiplied); + _background.setDevicePixelRatio(ratio); + _background.fill(Qt::transparent); + + auto p = QPainter(&_background); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(_data.color); + p.setPen(Qt::NoPen); + const auto center = _rect.center(); + p.translate(center); + p.rotate(_data.area.rotation); + p.translate(-center); + + const auto format = [](float64 value) { + return QString::number(int(base::SafeRound(value * 10)) / 10.); + }; + const auto text = [&] { + const auto celsius = _data.millicelsius / 1000.; + if (_celsius) { + return format(celsius); + } + const auto fahrenheit = (celsius * 9.0 / 5.0) + 32; + return format(fahrenheit); + }().append(QChar(0xb0)).append(_celsius ? "C" : "F"); + const auto metrics = QFontMetrics(_font); + const auto textWidth = metrics.horizontalAdvance(text); + _padding = int(_rect.height() / 6); + const auto fullWidth = (_emoji ? _emojiSize : 0) + + textWidth + + (2 * _padding); + const auto left = _rect.x() + (_rect.width() - fullWidth) / 2; + _wrapped = QRect(left, _rect.y(), fullWidth, _rect.height()); + + p.drawRoundedRect(_wrapped, _radius, _radius); + + p.setPen(_fg); + p.setFont(_font); + p.drawText(_wrapped.marginsRemoved( + { _padding + (_emoji ? _emojiSize : 0), 0, _padding, 0 }), + text, + style::al_center); +} + [[nodiscard]] Data::ReactionId HeartReactionId() { return { QString() + QChar(10084) }; } @@ -804,13 +1080,21 @@ auto Reactions::chosen() const -> rpl::producer<Chosen> { auto Reactions::makeSuggestedReactionWidget( const Data::SuggestedReaction &reaction) --> std::unique_ptr<SuggestedReactionView> { +-> std::unique_ptr<StoryAreaView> { return std::make_unique<ReactionView>( _controller->wrap(), &_controller->uiShow()->session(), reaction); } +auto Reactions::makeWeatherAreaWidget(const Data::WeatherArea &data) +-> std::unique_ptr<StoryAreaView> { + return std::make_unique<WeatherView>( + _controller->wrap(), + &_controller->uiShow()->session(), + data); +} + void Reactions::setReplyFieldState( rpl::producer<bool> focused, rpl::producer<bool> hasSendText) { diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index de9f0e0ce..b17e2e19d 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -16,6 +16,7 @@ struct ReactionId; class Session; class Story; struct SuggestedReaction; +struct WeatherArea; } // namespace Data namespace HistoryView::Reactions { @@ -41,13 +42,15 @@ enum class ReactionsMode { Reaction, }; -class SuggestedReactionView { +class StoryAreaView { public: - virtual ~SuggestedReactionView() = default; + virtual ~StoryAreaView() = default; - virtual void setAreaGeometry(QRect geometry) = 0; - virtual void updateCount(int count) = 0; + virtual void setAreaGeometry(QRect geometry, float64 radius) = 0; + virtual void updateReactionsCount(int count) = 0; virtual void playEffect() = 0; + virtual void toggleMode() = 0; + virtual bool contains(QPoint point) = 0; }; class Reactions final { @@ -79,7 +82,9 @@ public: [[nodiscard]] auto makeSuggestedReactionWidget( const Data::SuggestedReaction &reaction) - -> std::unique_ptr<SuggestedReactionView>; + -> std::unique_ptr<StoryAreaView>; + [[nodiscard]] auto makeWeatherAreaWidget(const Data::WeatherArea &data) + -> std::unique_ptr<StoryAreaView>; void setReplyFieldState( rpl::producer<bool> focused, diff --git a/Telegram/SourceFiles/ui/color_int_conversion.cpp b/Telegram/SourceFiles/ui/color_int_conversion.cpp index a1b0bcb45..5c9c57f07 100644 --- a/Telegram/SourceFiles/ui/color_int_conversion.cpp +++ b/Telegram/SourceFiles/ui/color_int_conversion.cpp @@ -22,4 +22,12 @@ std::optional<QColor> MaybeColorFromSerialized(quint32 serialized) { : std::make_optional(ColorFromSerialized(serialized)); } +QColor Color32FromSerialized(quint32 serialized) { + return QColor( + int((serialized >> 24) & 0xFFU), + int((serialized >> 16) & 0xFFU), + int((serialized >> 8) & 0xFFU), + int(serialized & 0xFFU)); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/color_int_conversion.h b/Telegram/SourceFiles/ui/color_int_conversion.h index ed2bb6a18..1102863b2 100644 --- a/Telegram/SourceFiles/ui/color_int_conversion.h +++ b/Telegram/SourceFiles/ui/color_int_conversion.h @@ -12,5 +12,6 @@ namespace Ui { [[nodiscard]] QColor ColorFromSerialized(quint32 serialized); [[nodiscard]] std::optional<QColor> MaybeColorFromSerialized( quint32 serialized); +[[nodiscard]] QColor Color32FromSerialized(quint32 serialized); } // namespace Ui From a5ffd8b7cf7ecbe60f5e413a7698e189b6c302b9 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 24 Jul 2024 13:44:15 +0200 Subject: [PATCH 123/163] Request new main web app. --- .../inline_bots/bot_attach_web_view.cpp | 33 +++++++++++++++++-- .../inline_bots/bot_attach_web_view.h | 1 + 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index e7d842c64..e0a609f7a 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -651,7 +651,9 @@ void WebViewInstance::resolve() { }, [&](WebViewSourceLinkApp data) { resolveApp(data.appname, data.token, !_context.maySkipConfirmation); }, [&](WebViewSourceLinkBotProfile) { - requestWithMenuAdd(); + confirmOpen([=] { + requestMain(); + }); }, [&](WebViewSourceLinkAttachMenu data) { requestWithMenuAdd(); }, [&](WebViewSourceMainMenu) { @@ -667,7 +669,9 @@ void WebViewInstance::resolve() { }, [&](WebViewSourceGame game) { showGame(); }, [&](WebViewSourceBotProfile) { - requestWithMenuAdd(); + confirmOpen([=] { + requestMain(); + }); }); } @@ -868,6 +872,31 @@ void WebViewInstance::requestSimple() { }).send(); } +void WebViewInstance::requestMain() { + using Flag = MTPmessages_RequestMainWebView::Flag; + _requestId = _session->api().request(MTPmessages_RequestMainWebView( + MTP_flags(Flag::f_theme_params + | (_button.startCommand.isEmpty() + ? Flag() + : Flag::f_start_param) + | (v::is<WebViewSourceLinkBotProfile>(_source) + ? (v::get<WebViewSourceLinkBotProfile>(_source).compact + ? Flag::f_compact + : Flag(0)) + : Flag(0))), + _context.action->history->peer->input, + _bot->inputUser, + MTP_string(_button.startCommand), + MTP_dataJSON(MTP_bytes(botThemeParams().json)), + MTP_string("tdesktop") + )).done([=](const MTPWebViewResult &result) { + show(qs(result.data().vurl())); + }).fail([=](const MTP::Error &error) { + _parentShow->showToast(error.type()); + close(); + }).send(); +} + void WebViewInstance::requestApp(bool allowWrite) { Expects(_app != nullptr); Expects(_context.action.has_value()); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 6e11c87cd..ecccdd042 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -239,6 +239,7 @@ private: void requestButton(); void requestSimple(); + void requestMain(); void requestApp(bool allowWrite); void requestWithMainMenuDisclaimer(); void requestWithMenuAdd(); From 992c876930169c1d0fe6dfd1d075a8dd1c71273e Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 25 Jul 2024 10:30:39 +0200 Subject: [PATCH 124/163] Show correct presents in Stars gifts. --- .../chat_helpers/stickers_gift_box_pack.cpp | 10 ++++++++++ .../SourceFiles/chat_helpers/stickers_gift_box_pack.h | 1 + .../history/view/media/history_view_premium_gift.cpp | 3 ++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp index 2e810dd6c..f510540c2 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp @@ -22,6 +22,16 @@ GiftBoxPack::GiftBoxPack(not_null<Main::Session*> session) GiftBoxPack::~GiftBoxPack() = default; +int GiftBoxPack::monthsForStars(int stars) const { + if (stars <= 1000) { + return 3; + } else if (stars < 2500) { + return 6; + } else { + return 12; + } +} + DocumentData *GiftBoxPack::lookup(int months) const { const auto it = ranges::lower_bound(_localMonths, months); const auto fallback = _documents.empty() ? nullptr : _documents[0]; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h index 4c480fba3..1e6c0e934 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h @@ -25,6 +25,7 @@ public: ~GiftBoxPack(); void load(); + [[nodiscard]] int monthsForStars(int stars) const; [[nodiscard]] DocumentData *lookup(int months) const; [[nodiscard]] Data::FileOrigin origin() const; 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 885e74a62..2a7a2f4b1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -185,8 +185,9 @@ void PremiumGift::ensureStickerCreated() const { return; } const auto &session = _parent->history()->session(); - const auto months = stars() ? 1 : _data.count; auto &packs = session.giftBoxStickersPacks(); + const auto count = stars(); + const auto months = count ? packs.monthsForStars(count) : _data.count; if (const auto document = packs.lookup(months)) { if (const auto sticker = document->sticker()) { const auto skipPremiumEffect = false; From 4b09050061ad5783f416d3b25374e46a85d29f19 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 25 Jul 2024 11:40:22 +0200 Subject: [PATCH 125/163] Implement Stars gift view from service messages. --- Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/boxes/gift_premium_box.cpp | 4 +- Telegram/SourceFiles/calls/calls_panel.cpp | 4 +- Telegram/SourceFiles/history/history_item.cpp | 29 ++- .../view/media/history_view_premium_gift.cpp | 22 ++- .../settings/settings_credits_graphics.cpp | 178 ++++++++++-------- .../settings/settings_credits_graphics.h | 7 + .../ui/effects/credits_graphics.cpp | 4 +- 8 files changed, 154 insertions(+), 97 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fad51ea32..fedb32957 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1845,6 +1845,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot."; "lng_action_gift_received" = "{user} sent you a gift for {cost}"; "lng_action_gift_received_me" = "You sent to {user} a gift for {cost}"; +"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}"; "lng_action_suggested_photo_me" = "You suggested {user} to use this profile photo."; "lng_action_suggested_photo" = "{user} suggests you to use this profile photo."; "lng_action_suggested_photo_button" = "View Photo"; @@ -2358,6 +2359,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star"; "lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars"; "lng_credits_box_out_about" = "Review the {link} for Stars."; +"lng_credits_box_out_about_link" = "https://telegram.org/tos/stars"; "lng_credits_media_done_title" = "Media Unlocked"; "lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}."; "lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}."; @@ -2372,6 +2374,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_fragment" = "Fragment"; "lng_credits_box_history_entry_anonymous" = "Unknown User"; "lng_credits_box_history_entry_gift_name" = "Received Gift"; +"lng_credits_box_history_entry_gift_sent" = "Sent Gift"; "lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}"; "lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}"; "lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}"; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 5730d9b40..3b8f6f1b5 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1694,7 +1694,9 @@ void AddCreditsHistoryEntryTable( } else if (entry.peerType == Type::Fragment) { AddTableRow( table, - tr::lng_credits_box_history_entry_via(), + (entry.gift + ? tr::lng_credits_box_history_entry_peer_in + : tr::lng_credits_box_history_entry_via)(), (entry.gift ? tr::lng_credits_box_history_entry_anonymous : tr::lng_credits_box_history_entry_fragment)( diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index ea204a770..6a816af96 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -276,8 +276,8 @@ void Panel::initControls() { _layerBg->showBox(std::move(box)); } } else if (const auto source = env->uniqueDesktopCaptureSource()) { - if (_call->isSharingScreen()) { - _call->toggleScreenSharing(std::nullopt); + if (!chooseSourceActiveDeviceId().isEmpty()) { + chooseSourceStop(); } else { chooseSourceAccepted(*source, false); } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 4a8a413cc..a38b9b33f 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4694,21 +4694,32 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { auto prepareGiftPremium = [&]( const MTPDmessageActionGiftPremium &action) { auto result = PreparedServiceText(); - const auto isSelf = (_from->id == _from->session().userPeerId()); + const auto session = &_history->session(); + const auto isSelf = _from->isSelf(); const auto peer = isSelf ? _history->peer : _from; - _history->session().giftBoxStickersPacks().load(); + session->giftBoxStickersPacks().load(); const auto amount = action.vamount().v; const auto currency = qs(action.vcurrency()); - result.links.push_back(peer->createOpenLink()); - result.text = (isSelf - ? tr::lng_action_gift_received_me - : tr::lng_action_gift_received)( + const auto cost = AmountAndStarCurrency(session, amount, currency); + const auto anonymous = _from->isServiceUser(); + if (anonymous) { + result.text = tr::lng_action_gift_received_anonymous( tr::now, - lt_user, - Ui::Text::Link(peer->name(), 1), // Link 1. lt_cost, - AmountAndStarCurrency(&peer->session(), amount, currency), + cost, Ui::Text::WithEntities); + } else { + result.links.push_back(peer->createOpenLink()); + result.text = (isSelf + ? tr::lng_action_gift_received_me + : tr::lng_action_gift_received)( + tr::now, + lt_user, + Ui::Text::Link(peer->name(), 1), // Link 1. + lt_cost, + cost, + Ui::Text::WithEntities); + } return result; }; 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 2a7a2f4b1..23d483c1f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -12,13 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" // ClickHandlerContext #include "data/data_document.h" #include "data/data_channel.h" +#include "data/data_user.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_credits.h" // Settings::CreditsId +#include "settings/settings_credits_graphics.h" // GiftedCreditsBox #include "settings/settings_premium.h" // Settings::ShowGiftPremium +#include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" @@ -99,22 +102,29 @@ rpl::producer<QString> PremiumGift::button() { ClickHandlerPtr PremiumGift::createViewLink() { const auto from = _gift->from(); - const auto to = _parent->history()->peer; + const auto peer = _parent->history()->peer; + const auto date = _parent->data()->date(); const auto data = _gift->data(); return std::make_shared<LambdaClickHandler>([=](ClickContext context) { const auto my = context.other.value<ClickHandlerContext>(); if (const auto controller = my.sessionWindow.get()) { const auto selfId = controller->session().userPeerId(); - const auto self = (from->id == selfId); + const auto sent = (from->id == selfId); if (data.type == Data::GiftType::Stars) { - controller->showSettings(Settings::CreditsId()); + const auto to = sent ? peer : peer->session().user(); + controller->show(Box( + Settings::GiftedCreditsBox, + controller, + from, + to, + data.count, + date)); } else if (data.slug.isEmpty()) { - const auto peer = self ? to : from; const auto months = data.count; - Settings::ShowGiftPremium(controller, peer, months, self); + Settings::ShowGiftPremium(controller, peer, months, sent); } else { const auto fromId = from->id; - const auto toId = self ? to->id : selfId; + const auto toId = sent ? peer->id : selfId; ResolveGiftCode(controller, data.slug, fromId, toId); } } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 954c07c7b..a8c63b5f8 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -466,84 +466,81 @@ void ReceiptCreditsBox( content->add(object_ptr<Ui::CenterWrap<>>( content, object_ptr<Ui::UserpicButton>(content, peer, stUser))); - } else { - if (e.gift) { - using PlayerPtr = std::unique_ptr<Lottie::SinglePlayer>; - struct State final { - DocumentData *sticker = nullptr; - std::shared_ptr<Data::DocumentMedia> media; - std::unique_ptr<Lottie::SinglePlayer> lottie; - rpl::lifetime downloadLifetime; - }; - Ui::AddSkip( - content, - st::creditsHistoryEntryGiftStickerSpace); - const auto icon = Ui::CreateChild<Ui::RpWidget>(content); - icon->resize(Size(st::creditsHistoryEntryGiftStickerSize)); - const auto state = icon->lifetime().make_state<State>(); - auto &packs = session->giftBoxStickersPacks(); - const auto document = packs.lookup(1); - if (document && document->sticker()) { - state->sticker = document; - state->media = document->createMediaView(); - state->media->thumbnailWanted(packs.origin()); - state->media->automaticLoad(packs.origin(), nullptr); - session->downloaderTaskFinished( - ) | rpl::start_with_next([=] { - if (state->media->loaded()) { - state->lottie = ChatHelpers::LottiePlayerFromDocument( - state->media.get(), - ChatHelpers::StickerLottieSize::MessageHistory, - icon->size(), - Lottie::Quality::High); - state->lottie->updates() | rpl::start_with_next([=] { - icon->update(); - }, icon->lifetime()); - state->downloadLifetime.destroy(); - } - }, state->downloadLifetime); - } - icon->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(icon); - const auto &lottie = state->lottie; - const auto frame = (lottie && lottie->ready()) - ? lottie->frameInfo({ .box = icon->size() }) - : Lottie::Animation::FrameInfo(); - if (!frame.image.isNull()) { - p.drawImage(0, 0, frame.image); - if (lottie->frameIndex() < lottie->framesCount() - 1) { - lottie->markFrameShown(); - } - } - }, icon->lifetime()); - content->sizeValue( - ) | rpl::start_with_next([=](const QSize &size) { - icon->move( - (size.width() - icon->width()) / 2, - st::creditsHistoryEntryGiftStickerSkip); - }, icon->lifetime()); - } else { - const auto widget = content->add( - object_ptr<Ui::CenterWrap<>>( - content, - object_ptr<Ui::RpWidget>(content)))->entity(); - using Draw = Fn<void(Painter &, int, int, int, int)>; - const auto draw = widget->lifetime().make_state<Draw>( - Ui::GenerateCreditsPaintUserpicCallback(e)); - widget->resize(Size(stUser.photoSize)); - widget->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(widget); - (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); - }, widget->lifetime()); + } else if (e.gift) { + struct State final { + DocumentData *sticker = nullptr; + std::shared_ptr<Data::DocumentMedia> media; + std::unique_ptr<Lottie::SinglePlayer> lottie; + rpl::lifetime downloadLifetime; + }; + Ui::AddSkip( + content, + st::creditsHistoryEntryGiftStickerSpace); + const auto icon = Ui::CreateChild<Ui::RpWidget>(content); + icon->resize(Size(st::creditsHistoryEntryGiftStickerSize)); + const auto state = icon->lifetime().make_state<State>(); + auto &packs = session->giftBoxStickersPacks(); + const auto document = packs.lookup(packs.monthsForStars(e.credits)); + if (document && document->sticker()) { + state->sticker = document; + state->media = document->createMediaView(); + state->media->thumbnailWanted(packs.origin()); + state->media->automaticLoad(packs.origin(), nullptr); + rpl::single() | rpl::then( + session->downloaderTaskFinished() + ) | rpl::filter([=] { + return state->media->loaded(); + }) | rpl::start_with_next([=] { + state->lottie = ChatHelpers::LottiePlayerFromDocument( + state->media.get(), + ChatHelpers::StickerLottieSize::MessageHistory, + icon->size(), + Lottie::Quality::High); + state->lottie->updates() | rpl::start_with_next([=] { + icon->update(); + }, icon->lifetime()); + state->downloadLifetime.destroy(); + }, state->downloadLifetime); } + icon->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(icon); + const auto &lottie = state->lottie; + const auto frame = (lottie && lottie->ready()) + ? lottie->frameInfo({ .box = icon->size() }) + : Lottie::Animation::FrameInfo(); + if (!frame.image.isNull()) { + p.drawImage(0, 0, frame.image); + if (lottie->frameIndex() < lottie->framesCount() - 1) { + lottie->markFrameShown(); + } + } + }, icon->lifetime()); + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + icon->move( + (size.width() - icon->width()) / 2, + st::creditsHistoryEntryGiftStickerSkip); + }, icon->lifetime()); + } else { + const auto widget = content->add( + object_ptr<Ui::CenterWrap<>>( + content, + object_ptr<Ui::RpWidget>(content)))->entity(); + using Draw = Fn<void(Painter &, int, int, int, int)>; + const auto draw = widget->lifetime().make_state<Draw>( + Ui::GenerateCreditsPaintUserpicCallback(e)); + widget->resize(Size(stUser.photoSize)); + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(widget); + (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); + }, widget->lifetime()); } Ui::AddSkip(content); Ui::AddSkip(content); - box->addRow(object_ptr<Ui::CenterWrap<>>( box, object_ptr<Ui::FlatLabel>( @@ -565,7 +562,7 @@ void ReceiptCreditsBox( auto &lifetime = content->lifetime(); const auto text = lifetime.make_state<Ui::Text::String>( st::semiboldTextStyle, - (e.in ? QChar('+') : kMinus) + (e.in ? u"+"_q : e.gift ? QString() : QString(kMinus)) + Lang::FormatCountDecimal(std::abs(int64(e.credits)))); const auto roundedText = e.refunded ? tr::lng_channel_earn_history_return(tr::now) @@ -605,6 +602,8 @@ void ReceiptCreditsBox( ? st::creditsStroke : e.in ? st::boxTextFgGood + : e.gift + ? st::windowBoldFg : st::menuIconAttentionColor); const auto x = (amount->width() - fullWidth) / 2; text->draw(p, Ui::Text::PaintContext{ @@ -709,10 +708,9 @@ void ReceiptCreditsBox( tr::lng_credits_box_out_about( lt_link, tr::lng_payments_terms_link( - ) | rpl::map([](const QString &t) { - using namespace Ui::Text; - return Link(t, u"https://telegram.org/tos"_q); - }), + ) | Ui::Text::ToLink( + tr::lng_credits_box_out_about_link(tr::now) + ), Ui::Text::WithEntities), st::creditsBoxAboutDivider))); @@ -757,6 +755,32 @@ void ReceiptCreditsBox( }, button->lifetime()); } +void GiftedCreditsBox( + not_null<Ui::GenericBox*> box, + not_null<Window::SessionController*> controller, + not_null<PeerData*> from, + not_null<PeerData*> to, + int count, + TimeId date) { + const auto received = to->isSelf(); + const auto anonymous = from->isServiceUser(); + const auto peer = received ? from : to; + using PeerType = Data::CreditsHistoryEntry::PeerType; + Settings::ReceiptCreditsBox(box, controller, nullptr, { + .id = QString(), + .title = (received + ? tr::lng_credits_box_history_entry_gift_name + : tr::lng_credits_box_history_entry_gift_sent)(tr::now), + .date = base::unixtime::parse(date), + .credits = uint64(count), + .bareMsgId = uint64(), + .barePeerId = (anonymous ? uint64() : peer->id.value), + .peerType = (anonymous ? PeerType::Fragment : PeerType::Peer), + .in = received, + .gift = true, + }); +} + void ShowRefundInfoBox( not_null<Window::SessionController*> controller, FullMsgId refundItemId) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 701c60b7f..e07262d6d 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -59,6 +59,13 @@ void ReceiptCreditsBox( not_null<Window::SessionController*> controller, PeerData *premiumBot, const Data::CreditsHistoryEntry &e); +void GiftedCreditsBox( + not_null<Ui::GenericBox*> box, + not_null<Window::SessionController*> controller, + not_null<PeerData*> from, + not_null<PeerData*> to, + int count, + TimeId date); void ShowRefundInfoBox( not_null<Window::SessionController*> controller, FullMsgId refundItemId); diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 61b43b8fc..018188cb3 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -217,7 +217,7 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( case Data::CreditsHistoryEntry::PeerType::PlayMarket: return { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 }; case Data::CreditsHistoryEntry::PeerType::Fragment: - return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; + return { st::windowSubTextFg, st::imageBg }; case Data::CreditsHistoryEntry::PeerType::PremiumBot: return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; case Data::CreditsHistoryEntry::PeerType::Ads: @@ -458,7 +458,7 @@ TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { return (entry.gift ? tr::lng_credits_box_history_entry_gift_name : (entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) - ? tr::lng_bot_username_description1_link + ? tr::lng_credits_box_history_entry_fragment : (entry.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot) ? tr::lng_credits_box_history_entry_premium_bot : (entry.peerType == Data::CreditsHistoryEntry::PeerType::Ads) From 031233ea987c924555e5642c7f00f3c8950f6905 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 25 Jul 2024 16:09:46 +0200 Subject: [PATCH 126/163] Remove some code duplication. --- .../dialogs/ui/dialogs_suggestions.cpp | 796 +++++++++--------- .../dialogs/ui/dialogs_suggestions.h | 70 +- 2 files changed, 431 insertions(+), 435 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index a19200d8a..84b3f8b89 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -87,66 +87,6 @@ private: }; -class ControllerWithPreviews - : public PeerListController - , public base::has_weak_ptr { -public: - explicit ControllerWithPreviews( - not_null<Window::SessionController*> window); - - [[nodiscard]] not_null<Window::SessionController*> window() const { - return _window; - } - - bool rowTrackPress(not_null<PeerListRow*> row) override; - void rowTrackPressCancel() override; - bool rowTrackPressSkipMouseSelection() override; - - bool processTouchEvent(not_null<QTouchEvent*> e); - void setupTouchChatPreview(not_null<Ui::ElasticScroll*> scroll); - -private: - const not_null<Window::SessionController*> _window; - - std::optional<QPoint> _chatPreviewTouchGlobal; - rpl::event_stream<> _touchCancelRequests; - -}; - -class RecentsController final : public ControllerWithPreviews { -public: - RecentsController( - not_null<Window::SessionController*> window, - RecentPeersList list); - - [[nodiscard]] rpl::producer<int> count() const { - return _count.value(); - } - [[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const { - return _chosen.events(); - } - - void prepare() override; - void rowClicked(not_null<PeerListRow*> row) override; - base::unique_qptr<Ui::PopupMenu> rowContextMenu( - QWidget *parent, - not_null<PeerListRow*> row) override; - Main::Session &session() const override; - - QString savedMessagesChatStatus() const override; - -private: - void setupDivider(); - void subscribeToEvents(); - [[nodiscard]] Fn<void()> removeAllCallback(); - - RecentPeersList _recent; - rpl::variable<int> _count; - rpl::event_stream<not_null<PeerData*>> _chosen; - rpl::lifetime _lifetime; - -}; - class ChannelRow final : public PeerListRow { public: using PeerListRow::PeerListRow; @@ -161,73 +101,6 @@ private: }; -class MyChannelsController final : public ControllerWithPreviews { -public: - explicit MyChannelsController( - not_null<Window::SessionController*> window); - - [[nodiscard]] rpl::producer<int> count() const { - return _count.value(); - } - [[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const { - return _chosen.events(); - } - - void prepare() override; - void rowClicked(not_null<PeerListRow*> row) override; - base::unique_qptr<Ui::PopupMenu> rowContextMenu( - QWidget *parent, - not_null<PeerListRow*> row) override; - Main::Session &session() const override; - -private: - void setupDivider(); - void appendRow(not_null<ChannelData*> channel); - void fill(bool force = false); - - std::vector<not_null<History*>> _channels; - rpl::variable<Ui::RpWidget*> _toggleExpanded = nullptr; - rpl::variable<int> _count = 0; - rpl::variable<bool> _expanded = false; - rpl::event_stream<not_null<PeerData*>> _chosen; - rpl::lifetime _lifetime; - -}; - -class RecommendationsController final : public ControllerWithPreviews { -public: - explicit RecommendationsController( - not_null<Window::SessionController*> window); - - [[nodiscard]] rpl::producer<int> count() const { - return _count.value(); - } - [[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const { - return _chosen.events(); - } - - void prepare() override; - void rowClicked(not_null<PeerListRow*> row) override; - base::unique_qptr<Ui::PopupMenu> rowContextMenu( - QWidget *parent, - not_null<PeerListRow*> row) override; - Main::Session &session() const override; - - void load(); - -private: - void fill(); - void setupDivider(); - void appendRow(not_null<ChannelData*> channel); - - rpl::variable<int> _count; - History *_activeHistory = nullptr; - bool _requested = false; - rpl::event_stream<not_null<PeerData*>> _chosen; - rpl::lifetime _lifetime; - -}; - struct EntryMenuDescriptor { not_null<Window::SessionController*> controller; not_null<PeerData*> peer; @@ -422,12 +295,131 @@ const style::PeerListItem &ChannelRow::computeSt( return _active ? st::recentPeersItemActive : st::recentPeersItem; } -ControllerWithPreviews::ControllerWithPreviews( +} // namespace + + +class Suggestions::ObjectListController + : public PeerListController + , public base::has_weak_ptr { +public: + explicit ObjectListController( + not_null<Window::SessionController*> window); + + [[nodiscard]] not_null<Window::SessionController*> window() const { + return _window; + } + [[nodiscard]] rpl::producer<int> count() const { + return _count.value(); + } + [[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const { + return _chosen.events(); + } + + bool rowTrackPress(not_null<PeerListRow*> row) override; + void rowTrackPressCancel() override; + bool rowTrackPressSkipMouseSelection() override; + + bool processTouchEvent(not_null<QTouchEvent*> e); + void setupTouchChatPreview(not_null<Ui::ElasticScroll*> scroll); + +protected: + [[nodiscard]] int countCurrent() const; + void setCount(int count); + void choose(not_null<PeerData*> peer); + +private: + const not_null<Window::SessionController*> _window; + + std::optional<QPoint> _chatPreviewTouchGlobal; + rpl::event_stream<> _touchCancelRequests; + rpl::event_stream<not_null<PeerData*>> _chosen; + rpl::variable<int> _count; + +}; + +class RecentsController final : public Suggestions::ObjectListController { +public: + RecentsController( + not_null<Window::SessionController*> window, + RecentPeersList list); + + void prepare() override; + void rowClicked(not_null<PeerListRow*> row) override; + base::unique_qptr<Ui::PopupMenu> rowContextMenu( + QWidget *parent, + not_null<PeerListRow*> row) override; + Main::Session &session() const override; + + QString savedMessagesChatStatus() const override; + +private: + void setupDivider(); + void subscribeToEvents(); + [[nodiscard]] Fn<void()> removeAllCallback(); + + RecentPeersList _recent; + rpl::lifetime _lifetime; + +}; + +class MyChannelsController final + : public Suggestions::ObjectListController { +public: + explicit MyChannelsController( + not_null<Window::SessionController*> window); + + void prepare() override; + void rowClicked(not_null<PeerListRow*> row) override; + base::unique_qptr<Ui::PopupMenu> rowContextMenu( + QWidget *parent, + not_null<PeerListRow*> row) override; + Main::Session &session() const override; + +private: + void setupDivider(); + void appendRow(not_null<ChannelData*> channel); + void fill(bool force = false); + + std::vector<not_null<History*>> _channels; + rpl::variable<Ui::RpWidget*> _toggleExpanded = nullptr; + rpl::variable<bool> _expanded = false; + rpl::lifetime _lifetime; + +}; + +class RecommendationsController final + : public Suggestions::ObjectListController { +public: + explicit RecommendationsController( + not_null<Window::SessionController*> window); + + void prepare() override; + void rowClicked(not_null<PeerListRow*> row) override; + base::unique_qptr<Ui::PopupMenu> rowContextMenu( + QWidget *parent, + not_null<PeerListRow*> row) override; + Main::Session &session() const override; + + void load(); + +private: + void fill(); + void setupDivider(); + void appendRow(not_null<ChannelData*> channel); + + History *_activeHistory = nullptr; + bool _requested = false; + rpl::lifetime _lifetime; + +}; + +Suggestions::ObjectListController::ObjectListController( not_null<Window::SessionController*> window) : _window(window) { } -bool ControllerWithPreviews::rowTrackPress(not_null<PeerListRow*> row) { +bool Suggestions::ObjectListController::rowTrackPress( + not_null<PeerListRow*> row) { const auto peer = row->peer(); const auto history = peer->owner().history(peer); const auto callback = crl::guard(this, [=](bool shown) { @@ -454,16 +446,17 @@ bool ControllerWithPreviews::rowTrackPress(not_null<PeerListRow*> row) { return false; } -void ControllerWithPreviews::rowTrackPressCancel() { +void Suggestions::ObjectListController::rowTrackPressCancel() { _chatPreviewTouchGlobal = {}; _window->cancelScheduledPreview(); } -bool ControllerWithPreviews::rowTrackPressSkipMouseSelection() { +bool Suggestions::ObjectListController::rowTrackPressSkipMouseSelection() { return _chatPreviewTouchGlobal.has_value(); } -bool ControllerWithPreviews::processTouchEvent(not_null<QTouchEvent*> e) { +bool Suggestions::ObjectListController::processTouchEvent( + not_null<QTouchEvent*> e) { const auto point = e->touchPoints().empty() ? std::optional<QPoint>() : e->touchPoints().front().screenPos().toPoint(); @@ -500,7 +493,7 @@ bool ControllerWithPreviews::processTouchEvent(not_null<QTouchEvent*> e) { return false; } -void ControllerWithPreviews::setupTouchChatPreview( +void Suggestions::ObjectListController::setupTouchChatPreview( not_null<Ui::ElasticScroll*> scroll) { _touchCancelRequests.events() | rpl::start_with_next([=] { QTouchEvent ev(QEvent::TouchCancel); @@ -509,10 +502,22 @@ void ControllerWithPreviews::setupTouchChatPreview( }, lifetime()); } +int Suggestions::ObjectListController::countCurrent() const { + return _count.current(); +} + +void Suggestions::ObjectListController::setCount(int count) { + _count = count; +} + +void Suggestions::ObjectListController::choose(not_null<PeerData*> peer) { + _chosen.fire_copy(peer); +} + RecentsController::RecentsController( not_null<Window::SessionController*> window, RecentPeersList list) -: ControllerWithPreviews(window) +: ObjectListController(window) , _recent(std::move(list)) { } @@ -523,13 +528,13 @@ void RecentsController::prepare() { delegate()->peerListAppendRow(std::make_unique<RecentRow>(peer)); } delegate()->peerListRefreshRows(); - _count = _recent.list.size(); + setCount(_recent.list.size()); subscribeToEvents(); } void RecentsController::rowClicked(not_null<PeerListRow*> row) { - _chosen.fire(row->peer()); + choose(row->peer()); } Fn<void()> RecentsController::removeAllCallback() { @@ -537,7 +542,7 @@ Fn<void()> RecentsController::removeAllCallback() { const auto session = &this->session(); return crl::guard(session, [=] { if (weak) { - _count = 0; + setCount(0); while (delegate()->peerListFullRowsCount() > 0) { delegate()->peerListRemoveRow(delegate()->peerListRowAt(0)); } @@ -560,7 +565,7 @@ base::unique_qptr<Ui::PopupMenu> RecentsController::rowContextMenu( if (weak) { const auto rowId = peer->id.value; if (const auto row = delegate()->peerListFindRow(rowId)) { - _count = std::max(0, _count.current() - 1); + setCount(std::max(0, countCurrent() - 1)); delegate()->peerListRemoveRow(row); delegate()->peerListRefreshRows(); } @@ -649,7 +654,7 @@ void RecentsController::subscribeToEvents() { session().data().unreadBadgeChanges( ) | rpl::start_with_next([=] { - for (auto i = 0; i != _count.current(); ++i) { + for (auto i = 0; i != countCurrent(); ++i) { const auto row = delegate()->peerListRowAt(i); if (static_cast<RecentRow*>(row.get())->refreshBadge()) { delegate()->peerListUpdateRow(row); @@ -660,7 +665,7 @@ void RecentsController::subscribeToEvents() { MyChannelsController::MyChannelsController( not_null<Window::SessionController*> window) -: ControllerWithPreviews(window) { +: ObjectListController(window) { } void MyChannelsController::prepare() { @@ -683,7 +688,7 @@ void MyChannelsController::prepare() { if (row) { delegate()->peerListRemoveRow(row); } - _count = int(_channels.size()); + setCount(_channels.size()); fill(true); }, _lifetime); @@ -704,7 +709,7 @@ void MyChannelsController::prepare() { } ranges::sort(_channels, ranges::greater(), &History::chatListTimeId); - _count = int(_channels.size()); + setCount(_channels.size()); _expanded.value() | rpl::start_with_next([=] { fill(); @@ -728,17 +733,17 @@ void MyChannelsController::prepare() { } } } - const auto was = _count.current(); + const auto was = countCurrent(); const auto now = int(_channels.size()); if (was != now) { - _count = now; + setCount(now); fill(); } }, _lifetime); } void MyChannelsController::fill(bool force) { - const auto count = _count.current(); + const auto count = countCurrent(); const auto limit = _expanded.current() ? count : std::min(count, kCollapsedChannelsCount); @@ -772,7 +777,7 @@ void MyChannelsController::appendRow(not_null<ChannelData*> channel) { } void MyChannelsController::rowClicked(not_null<PeerListRow*> row) { - _chosen.fire(row->peer()); + choose(row->peer()); } base::unique_qptr<Ui::PopupMenu> MyChannelsController::rowContextMenu( @@ -806,7 +811,7 @@ void MyChannelsController::setupDivider() { raw, tr::lng_channels_your_title(), st::searchedBarLabel); - _count.value( + count( ) | rpl::map( rpl::mappers::_1 > kCollapsedChannelsCount ) | rpl::distinct_until_changed() | rpl::start_with_next([=](bool more) { @@ -865,7 +870,7 @@ void MyChannelsController::setupDivider() { RecommendationsController::RecommendationsController( not_null<Window::SessionController*> window) -: ControllerWithPreviews(window) { +: ObjectListController(window) { } void RecommendationsController::prepare() { @@ -874,7 +879,7 @@ void RecommendationsController::prepare() { } void RecommendationsController::load() { - if (_requested || _count.current()) { + if (_requested || countCurrent()) { return; } _requested = true; @@ -898,7 +903,7 @@ void RecommendationsController::fill() { } } delegate()->peerListRefreshRows(); - _count = delegate()->peerListFullRowsCount(); + setCount(delegate()->peerListFullRowsCount()); window()->activeChatValue() | rpl::start_with_next([=](const Key &key) { const auto history = key.history(); @@ -936,7 +941,7 @@ void RecommendationsController::appendRow(not_null<ChannelData*> channel) { } void RecommendationsController::rowClicked(not_null<PeerListRow*> row) { - _chosen.fire(row->peer()); + choose(row->peer()); } base::unique_qptr<Ui::PopupMenu> RecommendationsController::rowContextMenu( @@ -972,8 +977,6 @@ void RecommendationsController::setupDivider() { delegate()->peerListSetAboveWidget(std::move(result)); } -} // namespace - Suggestions::Suggestions( not_null<QWidget*> parent, not_null<Window::SessionController*> controller, @@ -990,13 +993,13 @@ Suggestions::Suggestions( this, object_ptr<TopPeersStrip>(this, std::move(topPeers))))) , _topPeers(_topPeersWrap->entity()) -, _recentPeers(_chatsContent->add(setupRecentPeers(std::move(recentPeers)))) +, _recent(setupRecentPeers(std::move(recentPeers))) , _emptyRecent(_chatsContent->add(setupEmptyRecent())) , _channelsScroll(std::make_unique<Ui::ElasticScroll>(this)) , _channelsContent( _channelsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this))) -, _myChannels(_channelsContent->add(setupMyChannels())) -, _recommendations(_channelsContent->add(setupRecommendations())) +, _myChannels(setupMyChannels()) +, _recommendations(setupRecommendations()) , _emptyChannels(_channelsContent->add(setupEmptyChannels())) { setupTabs(); @@ -1032,10 +1035,10 @@ void Suggestions::setupTabs() { } void Suggestions::setupChats() { - _recentCount.value() | rpl::start_with_next([=](int count) { - _recentPeers->toggle(count > 0, anim::type::instant); + _recent->count.value() | rpl::start_with_next([=](int count) { + _recent->wrap->toggle(count > 0, anim::type::instant); _emptyRecent->toggle(count == 0, anim::type::instant); - }, _recentPeers->lifetime()); + }, _recent->wrap->lifetime()); _topPeers->emptyValue() | rpl::start_with_next([=](bool empty) { _topPeersWrap->toggle(!empty, anim::type::instant); @@ -1097,7 +1100,7 @@ void Suggestions::setupChats() { }, _topPeers->lifetime()); _chatsScroll->setVisible(_tab.current() == Tab::Chats); - _chatsScroll->setCustomTouchProcess(_recentProcessTouch); + _chatsScroll->setCustomTouchProcess(_recent->processTouch); } void Suggestions::handlePressForChatPreview( @@ -1115,25 +1118,25 @@ void Suggestions::handlePressForChatPreview( } void Suggestions::setupChannels() { - _myChannelsCount.value() | rpl::start_with_next([=](int count) { - _myChannels->toggle(count > 0, anim::type::instant); - }, _myChannels->lifetime()); + _myChannels->count.value() | rpl::start_with_next([=](int count) { + _myChannels->wrap->toggle(count > 0, anim::type::instant); + }, _myChannels->wrap->lifetime()); - _recommendationsCount.value() | rpl::start_with_next([=](int count) { - _recommendations->toggle(count > 0, anim::type::instant); - }, _recommendations->lifetime()); + _recommendations->count.value() | rpl::start_with_next([=](int count) { + _recommendations->wrap->toggle(count > 0, anim::type::instant); + }, _recommendations->wrap->lifetime()); _emptyChannels->toggleOn( rpl::combine( - _myChannelsCount.value(), - _recommendationsCount.value(), + _myChannels->count.value(), + _recommendations->count.value(), rpl::mappers::_1 + rpl::mappers::_2 == 0), anim::type::instant); _channelsScroll->setVisible(_tab.current() == Tab::Channels); _channelsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) { - const auto myChannels = _myChannelsProcessTouch(e); - const auto recommendations = _recommendationsProcessTouch(e); + const auto myChannels = _myChannels->processTouch(e); + const auto recommendations = _recommendations->processTouch(e); return myChannels || recommendations; }); } @@ -1148,26 +1151,27 @@ void Suggestions::selectJump(Qt::Key direction, int pageSize) { void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) { const auto recentHasSelection = [=] { - return _recentSelectJump({}, 0) == JumpResult::Applied; + return _recent->selectJump({}, 0) == JumpResult::Applied; }; if (pageSize) { if (direction == Qt::Key_Down || direction == Qt::Key_Up) { _topPeers->deselectByKeyboard(); if (!recentHasSelection()) { if (direction == Qt::Key_Down) { - _recentSelectJump(direction, 0); + _recent->selectJump(direction, 0); } else { return; } } - if (_recentSelectJump(direction, pageSize) == JumpResult::AppliedAndOut) { + if (_recent->selectJump(direction, pageSize) + == JumpResult::AppliedAndOut) { if (direction == Qt::Key_Up) { _chatsScroll->scrollTo(0); } } } } else if (direction == Qt::Key_Up) { - if (_recentSelectJump(direction, pageSize) + if (_recent->selectJump(direction, pageSize) == JumpResult::AppliedAndOut) { _topPeers->selectByKeyboard(direction); } else if (_topPeers->selectedByKeyboard()) { @@ -1175,12 +1179,12 @@ void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) { } } else if (direction == Qt::Key_Down) { if (!_topPeersWrap->toggled() || recentHasSelection()) { - _recentSelectJump(direction, pageSize); + _recent->selectJump(direction, pageSize); } else if (_topPeers->selectedByKeyboard()) { if (!_topPeers->selectByKeyboard(direction) - && _recentCount.current() > 0) { + && _recent->count.current() > 0) { _topPeers->deselectByKeyboard(); - _recentSelectJump(direction, pageSize); + _recent->selectJump(direction, pageSize); } } else { _topPeers->selectByKeyboard({}); @@ -1195,62 +1199,62 @@ void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) { void Suggestions::selectJumpChannels(Qt::Key direction, int pageSize) { const auto myChannelsHasSelection = [=] { - return _myChannelsSelectJump({}, 0) == JumpResult::Applied; + return _myChannels->selectJump({}, 0) == JumpResult::Applied; }; const auto recommendationsHasSelection = [=] { - return _recommendationsSelectJump({}, 0) == JumpResult::Applied; + return _recommendations->selectJump({}, 0) == JumpResult::Applied; }; if (pageSize) { if (direction == Qt::Key_Down) { if (recommendationsHasSelection()) { - _recommendationsSelectJump(direction, pageSize); + _recommendations->selectJump(direction, pageSize); } else if (myChannelsHasSelection()) { - if (_myChannelsSelectJump(direction, pageSize) + if (_myChannels->selectJump(direction, pageSize) == JumpResult::AppliedAndOut) { - _recommendationsSelectJump(direction, 0); + _recommendations->selectJump(direction, 0); } - } else if (_myChannelsCount.current()) { - _myChannelsSelectJump(direction, 0); - _myChannelsSelectJump(direction, pageSize); - } else if (_recommendationsCount.current()) { - _recommendationsSelectJump(direction, 0); - _recommendationsSelectJump(direction, pageSize); + } else if (_myChannels->count.current()) { + _myChannels->selectJump(direction, 0); + _myChannels->selectJump(direction, pageSize); + } else if (_recommendations->count.current()) { + _recommendations->selectJump(direction, 0); + _recommendations->selectJump(direction, pageSize); } } else if (direction == Qt::Key_Up) { if (myChannelsHasSelection()) { - if (_myChannelsSelectJump(direction, pageSize) + if (_myChannels->selectJump(direction, pageSize) == JumpResult::AppliedAndOut) { _channelsScroll->scrollTo(0); } } else if (recommendationsHasSelection()) { - if (_recommendationsSelectJump(direction, pageSize) + if (_recommendations->selectJump(direction, pageSize) == JumpResult::AppliedAndOut) { - _myChannelsSelectJump(direction, -1); + _myChannels->selectJump(direction, -1); } } } } else if (direction == Qt::Key_Up) { if (myChannelsHasSelection()) { - _myChannelsSelectJump(direction, 0); - } else if (_recommendationsSelectJump(direction, 0) + _myChannels->selectJump(direction, 0); + } else if (_recommendations->selectJump(direction, 0) == JumpResult::AppliedAndOut) { - _myChannelsSelectJump(direction, -1); + _myChannels->selectJump(direction, -1); } else if (!recommendationsHasSelection()) { - if (_myChannelsSelectJump(direction, 0) + if (_myChannels->selectJump(direction, 0) == JumpResult::AppliedAndOut) { _channelsScroll->scrollTo(0); } } } else if (direction == Qt::Key_Down) { if (recommendationsHasSelection()) { - _recommendationsSelectJump(direction, 0); - } else if (_myChannelsSelectJump(direction, 0) + _recommendations->selectJump(direction, 0); + } else if (_myChannels->selectJump(direction, 0) == JumpResult::AppliedAndOut) { - _recommendationsSelectJump(direction, 0); + _recommendations->selectJump(direction, 0); } else if (!myChannelsHasSelection()) { - if (_recommendationsSelectJump(direction, 0) + if (_recommendations->selectJump(direction, 0) == JumpResult::AppliedAndOut) { - _myChannelsSelectJump(direction, 0); + _myChannels->selectJump(direction, 0); } } } @@ -1258,7 +1262,7 @@ void Suggestions::selectJumpChannels(Qt::Key direction, int pageSize) { void Suggestions::chooseRow() { if (!_topPeers->chooseRow()) { - _recentPeersChoose(); + _recent->choose(); } } @@ -1272,14 +1276,14 @@ Data::Thread *Suggestions::updateFromChatsDrag(QPoint globalPosition) { if (const auto top = _topPeers->updateFromParentDrag(globalPosition)) { return _controller->session().data().history(PeerId(top)); } - return fromListId(_recentUpdateFromParentDrag(globalPosition)); + return fromListId(_recent->updateFromParentDrag(globalPosition)); } Data::Thread *Suggestions::updateFromChannelsDrag(QPoint globalPosition) { - if (const auto id = _myChannelsUpdateFromParentDrag(globalPosition)) { + if (const auto id = _myChannels->updateFromParentDrag(globalPosition)) { return fromListId(id); } - return fromListId(_recommendationsUpdateFromParentDrag(globalPosition)); + return fromListId(_recommendations->updateFromParentDrag(globalPosition)); } Data::Thread *Suggestions::fromListId(uint64 peerListRowId) { @@ -1290,9 +1294,9 @@ Data::Thread *Suggestions::fromListId(uint64 peerListRowId) { void Suggestions::dragLeft() { _topPeers->dragLeft(); - _recentDragLeft(); - _myChannelsDragLeft(); - _recommendationsDragLeft(); + _recent->dragLeft(); + _myChannels->dragLeft(); + _recommendations->dragLeft(); } void Suggestions::show(anim::type animated, Fn<void()> finish) { @@ -1432,36 +1436,25 @@ void Suggestions::resizeEvent(QResizeEvent *e) { _channelsContent->resizeToWidth(w); } -object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers( - RecentPeersList recentPeers) { - auto &lifetime = _chatsContent->lifetime(); - const auto delegate = lifetime.make_state< - PeerListContentDelegateSimple - >(); - const auto controller = lifetime.make_state<RecentsController>( +auto Suggestions::setupRecentPeers(RecentPeersList recentPeers) +-> std::unique_ptr<ObjectList> { + const auto controller = lifetime().make_state<RecentsController>( _controller, std::move(recentPeers)); - controller->setStyleOverrides(&st::recentPeersList); - _recentCount = controller->count(); - _recentProcessTouch = [=](not_null<QTouchEvent*> e) { - return controller->processTouchEvent(e); + const auto addToScroll = [=] { + return _topPeersWrap->toggled() ? _topPeers->height() : 0; }; + auto result = setupObjectList( + _chatsScroll.get(), + _chatsContent, + controller, + addToScroll); + const auto raw = result.get(); + const auto list = raw->wrap->entity(); - controller->chosen( - ) | rpl::start_with_next([=](not_null<PeerData*> peer) { - _controller->session().recentPeers().bump(peer); - _recentPeerChosen.fire_copy(peer); - }, lifetime); - - auto content = object_ptr<PeerListContent>(_chatsContent, controller); - - const auto raw = content.data(); - _recentPeersChoose = [=] { - return raw->submitted(); - }; - _recentSelectJump = [raw](Qt::Key direction, int pageSize) { - const auto had = raw->hasSelection(); + raw->selectJump = [list](Qt::Key direction, int pageSize) { + const auto had = list->hasSelection(); if (direction == Qt::Key()) { return had ? JumpResult::Applied : JumpResult::NotApplied; } else if (direction == Qt::Key_Up && !had) { @@ -1469,11 +1462,11 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers( } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) { const auto delta = (direction == Qt::Key_Down) ? 1 : -1; if (pageSize > 0) { - raw->selectSkipPage(pageSize, delta); + list->selectSkipPage(pageSize, delta); } else { - raw->selectSkip(delta); + list->selectSkip(delta); } - return raw->hasSelection() + return list->hasSelection() ? JumpResult::Applied : had ? JumpResult::AppliedAndOut @@ -1481,23 +1474,13 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers( } return JumpResult::NotApplied; }; - _recentUpdateFromParentDrag = [=](QPoint globalPosition) { - return raw->updateFromParentDrag(globalPosition); - }; - _recentDragLeft = [=] { - raw->dragLeft(); - }; - raw->scrollToRequests( - ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { - const auto add = _topPeersWrap->toggled() ? _topPeers->height() : 0; - _chatsScroll->scrollToY(request.ymin + add, request.ymax + add); - }, lifetime); - delegate->setContent(raw); - controller->setDelegate(delegate); - controller->setupTouchChatPreview(_chatsScroll.get()); + raw->chosen.events( + ) | rpl::start_with_next([=](not_null<PeerData*> peer) { + _controller->session().recentPeers().bump(peer); + }, list->lifetime()); - return object_ptr<Ui::SlideWrap<>>(this, std::move(content)); + return result; } object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent() { @@ -1505,6 +1488,168 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent() { return setupEmpty(_chatsContent, icon, tr::lng_recent_none()); } +auto Suggestions::setupMyChannels() -> std::unique_ptr<ObjectList> { + const auto controller = lifetime().make_state<MyChannelsController>( + _controller); + + auto result = setupObjectList( + _channelsScroll.get(), + _channelsContent, + controller); + const auto raw = result.get(); + const auto list = raw->wrap->entity(); + + raw->selectJump = [=](Qt::Key direction, int pageSize) { + const auto had = list->hasSelection(); + if (direction == Qt::Key()) { + return had ? JumpResult::Applied : JumpResult::NotApplied; + } else if (direction == Qt::Key_Up && !had) { + if (pageSize < 0) { + list->selectLast(); + return list->hasSelection() + ? JumpResult::Applied + : JumpResult::NotApplied; + } + return JumpResult::NotApplied; + } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) { + const auto was = list->selectedIndex(); + const auto delta = (direction == Qt::Key_Down) ? 1 : -1; + if (pageSize > 0) { + list->selectSkipPage(pageSize, delta); + } else { + list->selectSkip(delta); + } + if (had + && delta > 0 + && raw->count.current() + && list->selectedIndex() == was) { + list->clearSelection(); + return JumpResult::AppliedAndOut; + } + return list->hasSelection() + ? JumpResult::Applied + : had + ? JumpResult::AppliedAndOut + : JumpResult::NotApplied; + } + return JumpResult::NotApplied; + }; + + raw->chosen.events( + ) | rpl::start_with_next([=] { + _persist = false; + }, list->lifetime()); + + return result; +} + +auto Suggestions::setupRecommendations() -> std::unique_ptr<ObjectList> { + const auto controller = lifetime().make_state<RecommendationsController>( + _controller); + + const auto addToScroll = [=] { + const auto wrap = _myChannels->wrap; + return wrap->toggled() ? wrap->height() : 0; + }; + auto result = setupObjectList( + _channelsScroll.get(), + _channelsContent, + controller, + addToScroll); + const auto raw = result.get(); + const auto list = raw->wrap->entity(); + + raw->selectJump = [list](Qt::Key direction, int pageSize) { + const auto had = list->hasSelection(); + if (direction == Qt::Key()) { + return had ? JumpResult::Applied : JumpResult::NotApplied; + } else if (direction == Qt::Key_Up && !had) { + return JumpResult::NotApplied; + } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) { + const auto delta = (direction == Qt::Key_Down) ? 1 : -1; + if (pageSize > 0) { + list->selectSkipPage(pageSize, delta); + } else { + list->selectSkip(delta); + } + return list->hasSelection() + ? JumpResult::Applied + : had + ? JumpResult::AppliedAndOut + : JumpResult::NotApplied; + } + return JumpResult::NotApplied; + }; + + raw->chosen.events( + ) | rpl::start_with_next([=] { + _persist = true; + }, list->lifetime()); + + _tab.value() | rpl::filter( + rpl::mappers::_1 == Tab::Channels + ) | rpl::start_with_next([=] { + controller->load(); + }, list->lifetime()); + + return result; +} + +auto Suggestions::setupObjectList( + not_null<Ui::ElasticScroll*> scroll, + not_null<Ui::VerticalLayout*> parent, + not_null<ObjectListController*> controller, + Fn<int()> addToScroll) +-> std::unique_ptr<ObjectList> { + auto &lifetime = parent->lifetime(); + const auto delegate = lifetime.make_state< + PeerListContentDelegateSimple + >(); + controller->setStyleOverrides(&st::recentPeersList); + + auto content = object_ptr<PeerListContent>(parent, controller); + const auto list = content.data(); + + auto result = std::make_unique<ObjectList>(ObjectList{ + .wrap = parent->add(object_ptr<Ui::SlideWrap<PeerListContent>>( + parent, + std::move(content))), + }); + const auto raw = result.get(); + + raw->count = controller->count(); + raw->processTouch = [=](not_null<QTouchEvent*> e) { + return controller->processTouchEvent(e); + }; + + controller->chosen( + ) | rpl::start_with_next([=](not_null<PeerData*> peer) { + raw->chosen.fire_copy(peer); + }, lifetime); + + raw->choose = [=] { + return list->submitted(); + }; + raw->updateFromParentDrag = [=](QPoint globalPosition) { + return list->updateFromParentDrag(globalPosition); + }; + raw->dragLeft = [=] { + list->dragLeft(); + }; + + list->scrollToRequests( + ) | rpl::start_with_next([=](Ui::ScrollToRequest request) { + const auto add = addToScroll ? addToScroll() : 0; + scroll->scrollToY(request.ymin + add, request.ymax + add); + }, list->lifetime()); + + delegate->setContent(list); + controller->setDelegate(delegate); + controller->setupTouchChatPreview(scroll); + + return result; +} + object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyChannels() { const auto icon = SearchEmptyIcon::NoResults; return setupEmpty(_channelsContent, icon, tr::lng_channels_none_about()); @@ -1541,157 +1686,6 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupEmpty( return result; } -object_ptr<Ui::SlideWrap<>> Suggestions::setupMyChannels() { - auto &lifetime = _channelsContent->lifetime(); - const auto delegate = lifetime.make_state< - PeerListContentDelegateSimple - >(); - const auto controller = lifetime.make_state<MyChannelsController>( - _controller); - controller->setStyleOverrides(&st::recentPeersList); - - _myChannelsCount = controller->count(); - _myChannelsProcessTouch = [=](not_null<QTouchEvent*> e) { - return controller->processTouchEvent(e); - }; - - controller->chosen( - ) | rpl::start_with_next([=](not_null<PeerData*> peer) { - _persist = false; - _myChannelChosen.fire_copy(peer); - }, lifetime); - - auto content = object_ptr<PeerListContent>(_channelsContent, controller); - - const auto raw = content.data(); - _myChannelsChoose = [=] { - return raw->submitted(); - }; - _myChannelsSelectJump = [=](Qt::Key direction, int pageSize) { - const auto had = raw->hasSelection(); - if (direction == Qt::Key()) { - return had ? JumpResult::Applied : JumpResult::NotApplied; - } else if (direction == Qt::Key_Up && !had) { - if (pageSize < 0) { - raw->selectLast(); - return raw->hasSelection() - ? JumpResult::Applied - : JumpResult::NotApplied; - } - return JumpResult::NotApplied; - } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) { - const auto was = raw->selectedIndex(); - const auto delta = (direction == Qt::Key_Down) ? 1 : -1; - if (pageSize > 0) { - raw->selectSkipPage(pageSize, delta); - } else { - raw->selectSkip(delta); - } - if (had - && delta > 0 - && _recommendationsCount.current() - && raw->selectedIndex() == was) { - raw->clearSelection(); - return JumpResult::AppliedAndOut; - } - return raw->hasSelection() - ? JumpResult::Applied - : had - ? JumpResult::AppliedAndOut - : JumpResult::NotApplied; - } - return JumpResult::NotApplied; - }; - _myChannelsUpdateFromParentDrag = [=](QPoint globalPosition) { - return raw->updateFromParentDrag(globalPosition); - }; - _myChannelsDragLeft = [=] { - raw->dragLeft(); - }; - raw->scrollToRequests( - ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { - _channelsScroll->scrollToY(request.ymin, request.ymax); - }, lifetime); - - delegate->setContent(raw); - controller->setDelegate(delegate); - controller->setupTouchChatPreview(_channelsScroll.get()); - - return object_ptr<Ui::SlideWrap<>>(this, std::move(content)); -} - -object_ptr<Ui::SlideWrap<>> Suggestions::setupRecommendations() { - auto &lifetime = _channelsContent->lifetime(); - const auto delegate = lifetime.make_state< - PeerListContentDelegateSimple - >(); - const auto controller = lifetime.make_state<RecommendationsController>( - _controller); - controller->setStyleOverrides(&st::recentPeersList); - - _recommendationsCount = controller->count(); - _recommendationsProcessTouch = [=](not_null<QTouchEvent*> e) { - return controller->processTouchEvent(e); - }; - - _tab.value() | rpl::filter( - rpl::mappers::_1 == Tab::Channels - ) | rpl::start_with_next([=] { - controller->load(); - }, lifetime); - - controller->chosen( - ) | rpl::start_with_next([=](not_null<PeerData*> peer) { - _persist = true; - _recommendationChosen.fire_copy(peer); - }, lifetime); - - auto content = object_ptr<PeerListContent>(_channelsContent, controller); - - const auto raw = content.data(); - _recommendationsChoose = [=] { - return raw->submitted(); - }; - _recommendationsSelectJump = [raw](Qt::Key direction, int pageSize) { - const auto had = raw->hasSelection(); - if (direction == Qt::Key()) { - return had ? JumpResult::Applied : JumpResult::NotApplied; - } else if (direction == Qt::Key_Up && !had) { - return JumpResult::NotApplied; - } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) { - const auto delta = (direction == Qt::Key_Down) ? 1 : -1; - if (pageSize > 0) { - raw->selectSkipPage(pageSize, delta); - } else { - raw->selectSkip(delta); - } - return raw->hasSelection() - ? JumpResult::Applied - : had - ? JumpResult::AppliedAndOut - : JumpResult::NotApplied; - } - return JumpResult::NotApplied; - }; - _recommendationsUpdateFromParentDrag = [=](QPoint globalPosition) { - return raw->updateFromParentDrag(globalPosition); - }; - _recommendationsDragLeft = [=] { - raw->dragLeft(); - }; - raw->scrollToRequests( - ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { - const auto add = _myChannels->toggled() ? _myChannels->height() : 0; - _channelsScroll->scrollToY(request.ymin + add, request.ymax + add); - }, lifetime); - - delegate->setContent(raw); - controller->setDelegate(delegate); - controller->setupTouchChatPreview(_channelsScroll.get()); - - return object_ptr<Ui::SlideWrap<>>(this, std::move(content)); -} - bool Suggestions::persist() const { return _persist; } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 8b33718e6..438c0b370 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/rp_widget.h" +class PeerListContent; + namespace Data { class Thread; } // namespace Data @@ -67,17 +69,19 @@ public: } [[nodiscard]] auto recentPeerChosen() const -> rpl::producer<not_null<PeerData*>> { - return _recentPeerChosen.events(); + return _recent->chosen.events(); } [[nodiscard]] auto myChannelChosen() const -> rpl::producer<not_null<PeerData*>> { - return _myChannelChosen.events(); + return _myChannels->chosen.events(); } [[nodiscard]] auto recommendationChosen() const -> rpl::producer<not_null<PeerData*>> { - return _recommendationChosen.events(); + return _recommendations->chosen.events(); } + class ObjectListController; + private: enum class Tab : uchar { Chats, @@ -89,6 +93,17 @@ private: AppliedAndOut, }; + struct ObjectList { + not_null<Ui::SlideWrap<PeerListContent>*> wrap; + rpl::variable<int> count; + Fn<bool()> choose; + Fn<JumpResult(Qt::Key, int)> selectJump; + Fn<uint64(QPoint)> updateFromParentDrag; + Fn<void()> dragLeft; + Fn<bool(not_null<QTouchEvent*>)> processTouch; + rpl::event_stream<not_null<PeerData*>> chosen; + }; + void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; @@ -104,14 +119,22 @@ private: QPoint globalPosition); [[nodiscard]] Data::Thread *fromListId(uint64 peerListRowId); - [[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupRecentPeers( + [[nodiscard]] std::unique_ptr<ObjectList> setupRecentPeers( RecentPeersList recentPeers); - [[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmptyRecent(); - [[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupMyChannels(); - [[nodiscard]] auto setupRecommendations() + [[nodiscard]] auto setupEmptyRecent() -> object_ptr<Ui::SlideWrap<Ui::RpWidget>>; + + [[nodiscard]] std::unique_ptr<ObjectList> setupMyChannels(); + [[nodiscard]] std::unique_ptr<ObjectList> setupRecommendations(); [[nodiscard]] auto setupEmptyChannels() -> object_ptr<Ui::SlideWrap<Ui::RpWidget>>; + + [[nodiscard]] std::unique_ptr<ObjectList> setupObjectList( + not_null<Ui::ElasticScroll*> scroll, + not_null<Ui::VerticalLayout*> parent, + not_null<ObjectListController*> controller, + Fn<int()> addToScroll = nullptr); + [[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmpty( not_null<QWidget*> parent, SearchEmptyIcon icon, @@ -131,44 +154,23 @@ private: const std::unique_ptr<Ui::ElasticScroll> _chatsScroll; const not_null<Ui::VerticalLayout*> _chatsContent; + const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap; const not_null<TopPeersStrip*> _topPeers; + rpl::event_stream<not_null<PeerData*>> _topPeerChosen; + + const std::unique_ptr<ObjectList> _recent; - rpl::variable<int> _recentCount; - Fn<bool()> _recentPeersChoose; - Fn<JumpResult(Qt::Key, int)> _recentSelectJump; - Fn<uint64(QPoint)> _recentUpdateFromParentDrag; - Fn<void()> _recentDragLeft; - Fn<bool(not_null<QTouchEvent*>)> _recentProcessTouch; - const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recentPeers; const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyRecent; const std::unique_ptr<Ui::ElasticScroll> _channelsScroll; const not_null<Ui::VerticalLayout*> _channelsContent; - rpl::variable<int> _myChannelsCount; - Fn<bool()> _myChannelsChoose; - Fn<JumpResult(Qt::Key, int)> _myChannelsSelectJump; - Fn<uint64(QPoint)> _myChannelsUpdateFromParentDrag; - Fn<void()> _myChannelsDragLeft; - Fn<bool(not_null<QTouchEvent*>)> _myChannelsProcessTouch; - const not_null<Ui::SlideWrap<Ui::RpWidget>*> _myChannels; - - rpl::variable<int> _recommendationsCount; - Fn<bool()> _recommendationsChoose; - Fn<JumpResult(Qt::Key, int)> _recommendationsSelectJump; - Fn<uint64(QPoint)> _recommendationsUpdateFromParentDrag; - Fn<void()> _recommendationsDragLeft; - Fn<bool(not_null<QTouchEvent*>)> _recommendationsProcessTouch; - const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recommendations; + const std::unique_ptr<ObjectList> _myChannels; + const std::unique_ptr<ObjectList> _recommendations; const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyChannels; - rpl::event_stream<not_null<PeerData*>> _topPeerChosen; - rpl::event_stream<not_null<PeerData*>> _recentPeerChosen; - rpl::event_stream<not_null<PeerData*>> _myChannelChosen; - rpl::event_stream<not_null<PeerData*>> _recommendationChosen; - Ui::Animations::Simple _shownAnimation; Fn<void()> _showFinished; bool _hidden = false; From 6a8a85e39514242772a4c9b516bd0a21d23ff847 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 25 Jul 2024 18:08:22 +0200 Subject: [PATCH 127/163] Implement recent/popular apps list. --- Telegram/Resources/langs/lang.strings | 8 + .../SourceFiles/data/components/top_peers.cpp | 65 +- .../SourceFiles/data/components/top_peers.h | 9 +- Telegram/SourceFiles/data/data_session.cpp | 2 + Telegram/SourceFiles/data/data_user.h | 2 + .../SourceFiles/dialogs/dialogs_widget.cpp | 33 + Telegram/SourceFiles/dialogs/dialogs_widget.h | 1 + .../dialogs/ui/dialogs_suggestions.cpp | 676 ++++++++++++++---- .../dialogs/ui/dialogs_suggestions.h | 26 +- Telegram/SourceFiles/info/info.style | 6 + .../info/profile/info_profile_actions.cpp | 58 +- .../inline_bots/bot_attach_web_view.cpp | 54 +- .../inline_bots/bot_attach_web_view.h | 12 +- Telegram/SourceFiles/main/main_session.cpp | 4 +- Telegram/SourceFiles/main/main_session.h | 4 + Telegram/SourceFiles/ui/vertical_list.cpp | 17 +- Telegram/SourceFiles/ui/vertical_list.h | 4 +- 17 files changed, 792 insertions(+), 189 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fedb32957..64bbb68d8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1448,6 +1448,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_info_topic_title" = "Topic Info"; "lng_profile_enable_notifications" = "Notifications"; "lng_profile_send_message" = "Send Message"; +"lng_profile_open_app" = "Open App"; +"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}."; +"lng_profile_open_app_terms" = "Terms of Service for Mini Apps"; "lng_info_add_as_contact" = "Add to contacts"; "lng_profile_shared_media" = "Shared media"; "lng_profile_suggest_photo" = "Suggest Profile Photo"; @@ -3186,6 +3189,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_add_to_side_menu_done" = "Bot added to the main menu."; "lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps."; "lng_bot_click_to_start" = "Click here to use this bot."; +"lng_bot_status_users#one" = "{count} user"; +"lng_bot_status_users#other" = "{count} users"; "lng_typing" = "typing"; "lng_user_typing" = "{user} is typing"; @@ -5341,12 +5346,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_recent_none" = "Recent search results\nwill appear here."; "lng_recent_chats" = "Chats"; "lng_recent_channels" = "Channels"; +"lng_recent_apps" = "Apps"; "lng_channels_none_title" = "No channels yet..."; "lng_channels_none_about" = "You are not currently subscribed to any channels."; "lng_channels_your_title" = "Channels you joined"; "lng_channels_your_more" = "Show more"; "lng_channels_your_less" = "Show less"; "lng_channels_recommended" = "Recommended channels"; +"lng_bot_apps_your" = "Apps you use"; +"lng_bot_apps_popular" = "Popular apps"; "lng_font_box_title" = "Choose font family"; "lng_font_default" = "Default"; diff --git a/Telegram/SourceFiles/data/components/top_peers.cpp b/Telegram/SourceFiles/data/components/top_peers.cpp index f476869b6..57974febf 100644 --- a/Telegram/SourceFiles/data/components/top_peers.cpp +++ b/Telegram/SourceFiles/data/components/top_peers.cpp @@ -41,12 +41,36 @@ constexpr auto kRequestTimeLimit = 10 * crl::time(1000); ) / 1'000'000.; } +[[nodiscard]] MTPTopPeerCategory TypeToCategory(TopPeerType type) { + switch (type) { + case TopPeerType::Chat: return MTP_topPeerCategoryCorrespondents(); + case TopPeerType::BotApp: return MTP_topPeerCategoryBotsApp(); + } + Unexpected("Type in TypeToCategory."); +} + +[[nodiscard]] auto TypeToGetFlags(TopPeerType type) { + using Flag = MTPcontacts_GetTopPeers::Flag; + switch (type) { + case TopPeerType::Chat: return Flag::f_correspondents; + case TopPeerType::BotApp: return Flag::f_bots_app; + } + Unexpected("Type in TypeToGetFlags."); +} + } // namespace -TopPeers::TopPeers(not_null<Main::Session*> session) -: _session(session) { +TopPeers::TopPeers(not_null<Main::Session*> session, TopPeerType type) +: _session(session) +, _type(type) { + if (_type == TopPeerType::Chat) { + loadAfterChats(); + } +} + +void TopPeers::loadAfterChats() { using namespace rpl::mappers; - crl::on_main(session, [=] { + crl::on_main(_session, [=] { _session->data().chatsListLoadedEvents( ) | rpl::filter(_1 == nullptr) | rpl::start_with_next([=] { crl::on_main(_session, [=] { @@ -84,7 +108,7 @@ void TopPeers::remove(not_null<PeerData*> peer) { } _requestId = _session->api().request(MTPcontacts_ResetTopPeerRating( - MTP_topPeerCategoryCorrespondents(), + TypeToCategory(_type), peer->input )).send(); } @@ -160,11 +184,13 @@ void TopPeers::request() { } _requestId = _session->api().request(MTPcontacts_GetTopPeers( - MTP_flags(MTPcontacts_GetTopPeers::Flag::f_correspondents), + MTP_flags(TypeToGetFlags(_type)), MTP_int(0), MTP_int(kLimit), MTP_long(countHash()) - )).done([=](const MTPcontacts_TopPeers &result, const MTP::Response &response) { + )).done([=]( + const MTPcontacts_TopPeers &result, + const MTP::Response &response) { _lastReceivedDate = TimeId(response.outerMsgId >> 32); _lastReceived = crl::now(); _requestId = 0; @@ -176,19 +202,22 @@ void TopPeers::request() { owner->processChats(data.vchats()); for (const auto &category : data.vcategories().v) { const auto &data = category.data(); - data.vcategory().match( - [&](const MTPDtopPeerCategoryCorrespondents &) { - _list = ranges::views::all( - data.vpeers().v - ) | ranges::views::transform([&](const MTPTopPeer &top) { - return TopPeer{ - owner->peer(peerFromMTP(top.data().vpeer())), - top.data().vrating().v, - }; - }) | ranges::to_vector; - }, [](const auto &) { + const auto cons = (_type == TopPeerType::Chat) + ? mtpc_topPeerCategoryCorrespondents + : mtpc_topPeerCategoryBotsApp; + if (data.vcategory().type() != cons) { LOG(("API Error: Unexpected top peer category.")); - }); + continue; + } + _list = ranges::views::all( + data.vpeers().v + ) | ranges::views::transform([&]( + const MTPTopPeer &top) { + return TopPeer{ + owner->peer(peerFromMTP(top.data().vpeer())), + top.data().vrating().v, + }; + }) | ranges::to_vector; } updated(); }, [&](const MTPDcontacts_topPeersDisabled &) { diff --git a/Telegram/SourceFiles/data/components/top_peers.h b/Telegram/SourceFiles/data/components/top_peers.h index 5f1250b53..7f834ad84 100644 --- a/Telegram/SourceFiles/data/components/top_peers.h +++ b/Telegram/SourceFiles/data/components/top_peers.h @@ -13,9 +13,14 @@ class Session; namespace Data { +enum class TopPeerType { + Chat, + BotApp, +}; + class TopPeers final { public: - explicit TopPeers(not_null<Main::Session*> session); + TopPeers(not_null<Main::Session*> session, TopPeerType type); ~TopPeers(); [[nodiscard]] std::vector<not_null<PeerData*>> list() const; @@ -36,11 +41,13 @@ private: float64 rating = 0.; }; + void loadAfterChats(); void request(); [[nodiscard]] uint64 countHash() const; void updated(); const not_null<Main::Session*> _session; + const TopPeerType _type = {}; std::vector<TopPeer> _list; rpl::event_stream<> _updates; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index cf772208b..f88fb6abb 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -721,6 +721,8 @@ not_null<UserData*> Session::processUser(const MTPUser &data) { result->botInfo->supportsAttachMenu = data.is_bot_attach_menu(); result->botInfo->supportsBusiness = data.is_bot_business(); result->botInfo->canEditInformation = data.is_bot_can_edit(); + result->botInfo->activeUsers = data.vbot_active_users().value_or_empty(); + result->botInfo->hasMainApp = data.is_bot_has_main_app(); } else { result->setBotInfoVersion(-1); } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 67da9e3f5..8f84555d3 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -40,12 +40,14 @@ struct BotInfo { int version = 0; int descriptionVersion = 0; + int activeUsers = 0; bool inited : 1 = false; bool readsAllHistory : 1 = false; bool cantJoinGroups : 1 = false; bool supportsAttachMenu : 1 = false; bool canEditInformation : 1 = false; bool supportsBusiness : 1 = false; + bool hasMainApp : 1 = false; }; enum class UserDataFlag : uint32 { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 922b673a0..2e7568fc0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -78,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_stories.h" #include "info/downloads/info_downloads_widget.h" #include "info/info_memento.h" +#include "inline_bots/bot_attach_web_view.h" #include "styles/style_dialogs.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" @@ -1265,6 +1266,27 @@ void Widget::updateSuggestions(anim::type animated) { } }, _suggestions->lifetime()); + _suggestions->recentAppChosen( + ) | rpl::start_with_next([=](not_null<PeerData*> peer) { + if (const auto user = peer->asUser()) { + if (const auto info = user->botInfo.get()) { + if (info->hasMainApp) { + openBotMainApp(user); + return; + } + } + } + chosenRow({ + .key = peer->owner().history(peer), + .newWindow = base::IsCtrlPressed(), + }); + }, _suggestions->lifetime()); + + _suggestions->popularAppChosen( + ) | rpl::start_with_next([=](not_null<PeerData*> peer) { + controller()->showPeerInfo(peer); + }, _suggestions->lifetime()); + updateControlsGeometry(); _suggestions->show(animated, [=] { @@ -1276,6 +1298,17 @@ void Widget::updateSuggestions(anim::type animated) { } } +void Widget::openBotMainApp(not_null<UserData*> bot) { + session().attachWebView().open({ + .bot = bot, + .context = { + .controller = controller(), + .maySkipConfirmation = true, + }, + .source = InlineBots::WebViewSourceBotProfile(), + }); +} + void Widget::changeOpenedSubsection( FnMut<void()> change, bool fromRight, diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 347683d0c..a1ee3950d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -214,6 +214,7 @@ private: void refreshTopBars(); void showSearchInTopBar(anim::type animated); void checkUpdateStatus(); + void openBotMainApp(not_null<UserData*> bot); void changeOpenedSubsection( FnMut<void()> change, bool fromRight, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 84b3f8b89..18ae94b37 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "dialogs/ui/chat_search_empty.h" #include "history/history.h" +#include "inline_bots/bot_attach_web_view.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_common.h" @@ -56,6 +57,8 @@ namespace { constexpr auto kCollapsedChannelsCount = 5; constexpr auto kProbablyMaxChannels = 1000; constexpr auto kProbablyMaxRecommendations = 100; +constexpr auto kCollapsedAppsCount = 5; +constexpr auto kProbablyMaxApps = 100; class RecentRow final : public PeerListRow { public: @@ -315,6 +318,11 @@ public: return _chosen.events(); } + Main::Session &session() const override { + return _window->session(); + } + + void rowClicked(not_null<PeerListRow*> row) override; bool rowTrackPress(not_null<PeerListRow*> row) override; void rowTrackPressCancel() override; bool rowTrackPressSkipMouseSelection() override; @@ -325,7 +333,12 @@ public: protected: [[nodiscard]] int countCurrent() const; void setCount(int count); - void choose(not_null<PeerData*> peer); + + [[nodiscard]] bool expandedCurrent() const; + [[nodiscard]] rpl::producer<bool> expanded() const; + + void setupPlainDivider(rpl::producer<QString> title); + void setupExpandDivider(rpl::producer<QString> title); private: const not_null<Window::SessionController*> _window; @@ -334,6 +347,8 @@ private: rpl::event_stream<> _touchCancelRequests; rpl::event_stream<not_null<PeerData*>> _chosen; rpl::variable<int> _count; + rpl::variable<Ui::RpWidget*> _toggleExpanded = nullptr; + rpl::variable<bool> _expanded = false; }; @@ -344,11 +359,9 @@ public: RecentPeersList list); void prepare() override; - void rowClicked(not_null<PeerListRow*> row) override; base::unique_qptr<Ui::PopupMenu> rowContextMenu( QWidget *parent, not_null<PeerListRow*> row) override; - Main::Session &session() const override; QString savedMessagesChatStatus() const override; @@ -369,20 +382,15 @@ public: not_null<Window::SessionController*> window); void prepare() override; - void rowClicked(not_null<PeerListRow*> row) override; base::unique_qptr<Ui::PopupMenu> rowContextMenu( QWidget *parent, not_null<PeerListRow*> row) override; - Main::Session &session() const override; private: - void setupDivider(); void appendRow(not_null<ChannelData*> channel); void fill(bool force = false); std::vector<not_null<History*>> _channels; - rpl::variable<Ui::RpWidget*> _toggleExpanded = nullptr; - rpl::variable<bool> _expanded = false; rpl::lifetime _lifetime; }; @@ -394,17 +402,11 @@ public: not_null<Window::SessionController*> window); void prepare() override; - void rowClicked(not_null<PeerListRow*> row) override; - base::unique_qptr<Ui::PopupMenu> rowContextMenu( - QWidget *parent, - not_null<PeerListRow*> row) override; - Main::Session &session() const override; void load(); private: void fill(); - void setupDivider(); void appendRow(not_null<ChannelData*> channel); History *_activeHistory = nullptr; @@ -413,6 +415,45 @@ private: }; +class RecentAppsController final + : public Suggestions::ObjectListController { +public: + explicit RecentAppsController( + not_null<Window::SessionController*> window); + + void prepare() override; + + void load(); + +private: + void appendRow(not_null<UserData*> bot); + void fill(); + + std::vector<not_null<UserData*>> _bots; + rpl::lifetime _lifetime; + +}; + +class PopularAppsController final + : public Suggestions::ObjectListController { +public: + explicit PopularAppsController( + not_null<Window::SessionController*> window); + + void prepare() override; + + void load(); + +private: + void fill(); + void appendRow(not_null<UserData*> bot); + + History *_activeHistory = nullptr; + bool _requested = false; + rpl::lifetime _lifetime; + +}; + Suggestions::ObjectListController::ObjectListController( not_null<Window::SessionController*> window) : _window(window) { @@ -510,8 +551,108 @@ void Suggestions::ObjectListController::setCount(int count) { _count = count; } -void Suggestions::ObjectListController::choose(not_null<PeerData*> peer) { - _chosen.fire_copy(peer); +bool Suggestions::ObjectListController::expandedCurrent() const { + return _expanded.current(); +} + +rpl::producer<bool> Suggestions::ObjectListController::expanded() const { + return _expanded.value(); +} + +void Suggestions::ObjectListController::rowClicked( + not_null<PeerListRow*> row) { + _chosen.fire(row->peer()); +} + +void Suggestions::ObjectListController::setupPlainDivider( + rpl::producer<QString> title) { + auto result = object_ptr<Ui::FixedHeightWidget>( + (QWidget*)nullptr, + st::searchedBarHeight); + const auto raw = result.data(); + const auto label = Ui::CreateChild<Ui::FlatLabel>( + raw, + std::move(title), + st::searchedBarLabel); + raw->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + const auto x = st::searchedBarPosition.x(); + const auto y = st::searchedBarPosition.y(); + label->resizeToWidth(size.width() - x * 2); + label->moveToLeft(x, y, size.width()); + }, raw->lifetime()); + raw->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(raw).fillRect(clip, st::searchedBarBg); + }, raw->lifetime()); + + delegate()->peerListSetAboveWidget(std::move(result)); +} + +void Suggestions::ObjectListController::setupExpandDivider( + rpl::producer<QString> title) { + auto result = object_ptr<Ui::FixedHeightWidget>( + (QWidget*)nullptr, + st::searchedBarHeight); + const auto raw = result.data(); + const auto label = Ui::CreateChild<Ui::FlatLabel>( + raw, + std::move(title), + st::searchedBarLabel); + count( + ) | rpl::map( + rpl::mappers::_1 > kCollapsedChannelsCount + ) | rpl::distinct_until_changed() | rpl::start_with_next([=](bool more) { + _expanded = false; + if (!more) { + const auto toggle = _toggleExpanded.current(); + _toggleExpanded = nullptr; + delete toggle; + return; + } else if (_toggleExpanded.current()) { + return; + } + const auto toggle = Ui::CreateChild<Ui::LinkButton>( + raw, + tr::lng_channels_your_more(tr::now), + st::searchedBarLink); + toggle->show(); + toggle->setClickedCallback([=] { + const auto expand = !_expanded.current(); + toggle->setText(expand + ? tr::lng_channels_your_less(tr::now) + : tr::lng_channels_your_more(tr::now)); + _expanded = expand; + }); + rpl::combine( + raw->sizeValue(), + toggle->widthValue() + ) | rpl::start_with_next([=](QSize size, int width) { + const auto x = st::searchedBarPosition.x(); + const auto y = st::searchedBarPosition.y(); + toggle->moveToRight(0, 0, size.width()); + label->resizeToWidth(size.width() - x - width); + label->moveToLeft(x, y, size.width()); + }, toggle->lifetime()); + _toggleExpanded = toggle; + }, raw->lifetime()); + + rpl::combine( + raw->sizeValue(), + _toggleExpanded.value() + ) | rpl::filter( + rpl::mappers::_2 == nullptr + ) | rpl::start_with_next([=](QSize size, const auto) { + const auto x = st::searchedBarPosition.x(); + const auto y = st::searchedBarPosition.y(); + label->resizeToWidth(size.width() - x * 2); + label->moveToLeft(x, y, size.width()); + }, raw->lifetime()); + + raw->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(raw).fillRect(clip, st::searchedBarBg); + }, raw->lifetime()); + + delegate()->peerListSetAboveWidget(std::move(result)); } RecentsController::RecentsController( @@ -533,10 +674,6 @@ void RecentsController::prepare() { subscribeToEvents(); } -void RecentsController::rowClicked(not_null<PeerListRow*> row) { - choose(row->peer()); -} - Fn<void()> RecentsController::removeAllCallback() { const auto weak = base::make_weak(this); const auto session = &this->session(); @@ -584,10 +721,6 @@ base::unique_qptr<Ui::PopupMenu> RecentsController::rowContextMenu( return result; } -Main::Session &RecentsController::session() const { - return window()->session(); -} - QString RecentsController::savedMessagesChatStatus() const { return tr::lng_saved_forward_here(tr::now); } @@ -669,7 +802,7 @@ MyChannelsController::MyChannelsController( } void MyChannelsController::prepare() { - setupDivider(); + setupExpandDivider(tr::lng_channels_your_title()); session().changes().peerUpdates( Data::PeerUpdate::Flag::ChannelAmIn @@ -711,7 +844,7 @@ void MyChannelsController::prepare() { ranges::sort(_channels, ranges::greater(), &History::chatListTimeId); setCount(_channels.size()); - _expanded.value() | rpl::start_with_next([=] { + expanded() | rpl::start_with_next([=] { fill(); }, _lifetime); @@ -744,7 +877,7 @@ void MyChannelsController::prepare() { void MyChannelsController::fill(bool force) { const auto count = countCurrent(); - const auto limit = _expanded.current() + const auto limit = expandedCurrent() ? count : std::min(count, kCollapsedChannelsCount); const auto already = delegate()->peerListFullRowsCount(); @@ -776,10 +909,6 @@ void MyChannelsController::appendRow(not_null<ChannelData*> channel) { delegate()->peerListAppendRow(std::move(row)); } -void MyChannelsController::rowClicked(not_null<PeerListRow*> row) { - choose(row->peer()); -} - base::unique_qptr<Ui::PopupMenu> MyChannelsController::rowContextMenu( QWidget *parent, not_null<PeerListRow*> row) { @@ -798,83 +927,13 @@ base::unique_qptr<Ui::PopupMenu> MyChannelsController::rowContextMenu( return result; } -Main::Session &MyChannelsController::session() const { - return window()->session(); -} - -void MyChannelsController::setupDivider() { - auto result = object_ptr<Ui::FixedHeightWidget>( - (QWidget*)nullptr, - st::searchedBarHeight); - const auto raw = result.data(); - const auto label = Ui::CreateChild<Ui::FlatLabel>( - raw, - tr::lng_channels_your_title(), - st::searchedBarLabel); - count( - ) | rpl::map( - rpl::mappers::_1 > kCollapsedChannelsCount - ) | rpl::distinct_until_changed() | rpl::start_with_next([=](bool more) { - _expanded = false; - if (!more) { - const auto toggle = _toggleExpanded.current(); - _toggleExpanded = nullptr; - delete toggle; - return; - } else if (_toggleExpanded.current()) { - return; - } - const auto toggle = Ui::CreateChild<Ui::LinkButton>( - raw, - tr::lng_channels_your_more(tr::now), - st::searchedBarLink); - toggle->show(); - toggle->setClickedCallback([=] { - const auto expand = !_expanded.current(); - toggle->setText(expand - ? tr::lng_channels_your_less(tr::now) - : tr::lng_channels_your_more(tr::now)); - _expanded = expand; - }); - rpl::combine( - raw->sizeValue(), - toggle->widthValue() - ) | rpl::start_with_next([=](QSize size, int width) { - const auto x = st::searchedBarPosition.x(); - const auto y = st::searchedBarPosition.y(); - toggle->moveToRight(0, 0, size.width()); - label->resizeToWidth(size.width() - x - width); - label->moveToLeft(x, y, size.width()); - }, toggle->lifetime()); - _toggleExpanded = toggle; - }, raw->lifetime()); - - rpl::combine( - raw->sizeValue(), - _toggleExpanded.value() - ) | rpl::filter( - rpl::mappers::_2 == nullptr - ) | rpl::start_with_next([=](QSize size, const auto) { - const auto x = st::searchedBarPosition.x(); - const auto y = st::searchedBarPosition.y(); - label->resizeToWidth(size.width() - x * 2); - label->moveToLeft(x, y, size.width()); - }, raw->lifetime()); - - raw->paintRequest() | rpl::start_with_next([=](QRect clip) { - QPainter(raw).fillRect(clip, st::searchedBarBg); - }, raw->lifetime()); - - delegate()->peerListSetAboveWidget(std::move(result)); -} - RecommendationsController::RecommendationsController( not_null<Window::SessionController*> window) : ObjectListController(window) { } void RecommendationsController::prepare() { - setupDivider(); + setupPlainDivider(tr::lng_channels_recommended()); fill(); } @@ -940,41 +999,115 @@ void RecommendationsController::appendRow(not_null<ChannelData*> channel) { delegate()->peerListAppendRow(std::move(row)); } -void RecommendationsController::rowClicked(not_null<PeerListRow*> row) { - choose(row->peer()); +RecentAppsController::RecentAppsController( + not_null<Window::SessionController*> window) +: ObjectListController(window) { } -base::unique_qptr<Ui::PopupMenu> RecommendationsController::rowContextMenu( - QWidget *parent, - not_null<PeerListRow*> row) { - return nullptr; +void RecentAppsController::prepare() { + setupExpandDivider(tr::lng_bot_apps_your()); + + _bots.reserve(kProbablyMaxApps); + rpl::single() | rpl::then( + session().topBotApps().updates() + ) | rpl::start_with_next([=] { + _bots.clear(); + for (const auto &peer : session().topBotApps().list()) { + if (const auto bot = peer->asUser()) { + if (bot->isBot() && !bot->isInaccessible()) { + _bots.push_back(bot); + } + } + } + setCount(_bots.size()); + while (delegate()->peerListFullRowsCount()) { + delegate()->peerListRemoveRow(delegate()->peerListRowAt(0)); + } + fill(); + }, _lifetime); + + expanded() | rpl::skip(1) | rpl::start_with_next([=] { + fill(); + }, _lifetime); } -Main::Session &RecommendationsController::session() const { - return window()->session(); +void RecentAppsController::load() { + session().topBotApps().reload(); } -void RecommendationsController::setupDivider() { - auto result = object_ptr<Ui::FixedHeightWidget>( - (QWidget*)nullptr, - st::searchedBarHeight); - const auto raw = result.data(); - const auto label = Ui::CreateChild<Ui::FlatLabel>( - raw, - tr::lng_channels_recommended(), - st::searchedBarLabel); - raw->sizeValue( - ) | rpl::start_with_next([=](QSize size) { - const auto x = st::searchedBarPosition.x(); - const auto y = st::searchedBarPosition.y(); - label->resizeToWidth(size.width() - x * 2); - label->moveToLeft(x, y, size.width()); - }, raw->lifetime()); - raw->paintRequest() | rpl::start_with_next([=](QRect clip) { - QPainter(raw).fillRect(clip, st::searchedBarBg); - }, raw->lifetime()); +void RecentAppsController::fill() { + const auto count = countCurrent(); + const auto limit = expandedCurrent() + ? count + : std::min(count, kCollapsedAppsCount); + const auto already = delegate()->peerListFullRowsCount(); + const auto delta = limit - already; + if (!delta) { + return; + } else if (delta > 0) { + for (auto i = already; i != limit; ++i) { + appendRow(_bots[i]); + } + } else if (delta < 0) { + for (auto i = already; i != limit;) { + delegate()->peerListRemoveRow(delegate()->peerListRowAt(--i)); + } + } + delegate()->peerListRefreshRows(); +} - delegate()->peerListSetAboveWidget(std::move(result)); +void RecentAppsController::appendRow(not_null<UserData*> bot) { + auto row = std::make_unique<PeerListRow>(bot); + if (const auto count = bot->botInfo->activeUsers) { + row->setCustomStatus( + tr::lng_bot_status_users(tr::now, lt_count_decimal, count)); + } + delegate()->peerListAppendRow(std::move(row)); +} + +PopularAppsController::PopularAppsController( + not_null<Window::SessionController*> window) +: ObjectListController(window) { +} + +void PopularAppsController::prepare() { + setupPlainDivider(tr::lng_bot_apps_popular()); + fill(); +} + +void PopularAppsController::load() { + if (_requested || countCurrent()) { + return; + } + _requested = true; + const auto attachWebView = &session().attachWebView(); + attachWebView->loadPopularAppBots(); + attachWebView->popularAppBotsLoaded( + ) | rpl::take(1) | rpl::start_with_next([=] { + fill(); + }, _lifetime); +} + +void PopularAppsController::fill() { + const auto attachWebView = &session().attachWebView(); + const auto &list = attachWebView->popularAppBots(); + if (list.empty()) { + return; + } + for (const auto &bot : list) { + appendRow(bot); + } + delegate()->peerListRefreshRows(); + setCount(delegate()->peerListFullRowsCount()); +} + +void PopularAppsController::appendRow(not_null<UserData*> bot) { + auto row = std::make_unique<PeerListRow>(bot); + if (const auto count = bot->botInfo->activeUsers) { + row->setCustomStatus( + tr::lng_bot_status_users(tr::now, lt_count_decimal, count)); + } + delegate()->peerListAppendRow(std::move(row)); } Suggestions::Suggestions( @@ -1000,11 +1133,17 @@ Suggestions::Suggestions( _channelsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this))) , _myChannels(setupMyChannels()) , _recommendations(setupRecommendations()) -, _emptyChannels(_channelsContent->add(setupEmptyChannels())) { +, _emptyChannels(_channelsContent->add(setupEmptyChannels())) +, _appsScroll(std::make_unique<Ui::ElasticScroll>(this)) +, _appsContent( + _appsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this))) +, _recentApps(setupRecentApps()) +, _popularApps(setupPopularApps()) { setupTabs(); setupChats(); setupChannels(); + setupApps(); } Suggestions::~Suggestions() = default; @@ -1027,10 +1166,15 @@ void Suggestions::setupTabs() { _tabs->setSections({ tr::lng_recent_chats(tr::now), tr::lng_recent_channels(tr::now), + tr::lng_recent_apps(tr::now), }); _tabs->sectionActivated( ) | rpl::start_with_next([=](int section) { - switchTab(section ? Tab::Channels : Tab::Chats); + switchTab(section == 2 + ? Tab::Apps + : section + ? Tab::Channels + : Tab::Chats); }, _tabs->lifetime()); } @@ -1141,12 +1285,30 @@ void Suggestions::setupChannels() { }); } +void Suggestions::setupApps() { + _recentApps->count.value() | rpl::start_with_next([=](int count) { + _recentApps->wrap->toggle(count > 0, anim::type::instant); + }, _recentApps->wrap->lifetime()); + + _popularApps->count.value() | rpl::start_with_next([=](int count) { + _popularApps->wrap->toggle(count > 0, anim::type::instant); + }, _popularApps->wrap->lifetime()); + + _appsScroll->setVisible(_tab.current() == Tab::Apps); + _appsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) { + const auto recentApps = _recentApps->processTouch(e); + const auto popularApps = _popularApps->processTouch(e); + return recentApps || popularApps; + }); +} + void Suggestions::selectJump(Qt::Key direction, int pageSize) { - if (_tab.current() == Tab::Chats) { - selectJumpChats(direction, pageSize); - } else { - selectJumpChannels(direction, pageSize); + switch (_tab.current()) { + case Tab::Chats: selectJumpChats(direction, pageSize); return; + case Tab::Channels: selectJumpChannels(direction, pageSize); return; + case Tab::Apps: selectJumpApps(direction, pageSize); return; } + Unexpected("Tab in Suggestions::selectJump."); } void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) { @@ -1260,9 +1422,86 @@ void Suggestions::selectJumpChannels(Qt::Key direction, int pageSize) { } } +void Suggestions::selectJumpApps(Qt::Key direction, int pageSize) { + const auto recentAppsHasSelection = [=] { + return _recentApps->selectJump({}, 0) == JumpResult::Applied; + }; + const auto popularAppsHasSelection = [=] { + return _popularApps->selectJump({}, 0) == JumpResult::Applied; + }; + if (pageSize) { + if (direction == Qt::Key_Down) { + if (popularAppsHasSelection()) { + _popularApps->selectJump(direction, pageSize); + } else if (recentAppsHasSelection()) { + if (_recentApps->selectJump(direction, pageSize) + == JumpResult::AppliedAndOut) { + _popularApps->selectJump(direction, 0); + } + } else if (_recentApps->count.current()) { + _recentApps->selectJump(direction, 0); + _recentApps->selectJump(direction, pageSize); + } else if (_popularApps->count.current()) { + _popularApps->selectJump(direction, 0); + _popularApps->selectJump(direction, pageSize); + } + } else if (direction == Qt::Key_Up) { + if (recentAppsHasSelection()) { + if (_recentApps->selectJump(direction, pageSize) + == JumpResult::AppliedAndOut) { + _channelsScroll->scrollTo(0); + } + } else if (popularAppsHasSelection()) { + if (_popularApps->selectJump(direction, pageSize) + == JumpResult::AppliedAndOut) { + _recentApps->selectJump(direction, -1); + } + } + } + } else if (direction == Qt::Key_Up) { + if (recentAppsHasSelection()) { + _recentApps->selectJump(direction, 0); + } else if (_popularApps->selectJump(direction, 0) + == JumpResult::AppliedAndOut) { + _recentApps->selectJump(direction, -1); + } else if (!popularAppsHasSelection()) { + if (_recentApps->selectJump(direction, 0) + == JumpResult::AppliedAndOut) { + _channelsScroll->scrollTo(0); + } + } + } else if (direction == Qt::Key_Down) { + if (popularAppsHasSelection()) { + _popularApps->selectJump(direction, 0); + } else if (_recentApps->selectJump(direction, 0) + == JumpResult::AppliedAndOut) { + _popularApps->selectJump(direction, 0); + } else if (!recentAppsHasSelection()) { + if (_popularApps->selectJump(direction, 0) + == JumpResult::AppliedAndOut) { + _recentApps->selectJump(direction, 0); + } + } + } +} + void Suggestions::chooseRow() { - if (!_topPeers->chooseRow()) { - _recent->choose(); + switch (_tab.current()) { + case Tab::Chats: + if (!_topPeers->chooseRow()) { + _recent->choose(); + } + break; + case Tab::Channels: + if (!_myChannels->choose()) { + _recommendations->choose(); + } + break; + case Tab::Apps: + if (!_recentApps->choose()) { + _popularApps->choose(); + } + break; } } @@ -1286,6 +1525,13 @@ Data::Thread *Suggestions::updateFromChannelsDrag(QPoint globalPosition) { return fromListId(_recommendations->updateFromParentDrag(globalPosition)); } +Data::Thread *Suggestions::updateFromAppsDrag(QPoint globalPosition) { + if (const auto id = _recentApps->updateFromParentDrag(globalPosition)) { + return fromListId(id); + } + return fromListId(_popularApps->updateFromParentDrag(globalPosition)); +} + Data::Thread *Suggestions::fromListId(uint64 peerListRowId) { return peerListRowId ? _controller->session().data().history(PeerId(peerListRowId)).get() @@ -1297,6 +1543,8 @@ void Suggestions::dragLeft() { _recent->dragLeft(); _myChannels->dragLeft(); _recommendations->dragLeft(); + _recentApps->dragLeft(); + _popularApps->dragLeft(); } void Suggestions::show(anim::type animated, Fn<void()> finish) { @@ -1322,7 +1570,8 @@ void Suggestions::hide(anim::type animated, Fn<void()> finish) { } void Suggestions::switchTab(Tab tab) { - if (_tab.current() == tab) { + const auto was = _tab.current(); + if (was == tab) { return; } _tab = tab; @@ -1330,19 +1579,29 @@ void Suggestions::switchTab(Tab tab) { if (_tabs->isHidden()) { return; } - startSlideAnimation(); + startSlideAnimation(was, tab); } -void Suggestions::startSlideAnimation() { +void Suggestions::startSlideAnimation(Tab was, Tab now) { if (!_slideAnimation.animating()) { - _slideLeft = Ui::GrabWidget(_chatsScroll.get()); - _slideRight = Ui::GrabWidget(_channelsScroll.get()); + _slideLeft = (was == Tab::Chats || now == Tab::Chats) + ? Ui::GrabWidget(_chatsScroll.get()) + : Ui::GrabWidget(_channelsScroll.get()); + _slideLeftTop = (was == Tab::Chats || now == Tab::Chats) + ? _chatsScroll->y() + : _channelsScroll->y(); + _slideRight = (was == Tab::Apps || now == Tab::Apps) + ? Ui::GrabWidget(_appsScroll.get()) + : Ui::GrabWidget(_channelsScroll.get()); + _slideRightTop = (was == Tab::Apps || now == Tab::Apps) + ? _appsScroll->y() + : _channelsScroll->y(); _chatsScroll->hide(); _channelsScroll->hide(); + _appsScroll->hide(); } - const auto channels = (_tab.current() == Tab::Channels); - const auto from = channels ? 0. : 1.; - const auto to = channels ? 1. : 0.; + const auto from = (now > was) ? 0. : 1.; + const auto to = (now > was) ? 1. : 0.; _slideAnimation.start([=] { update(); if (!_slideAnimation.animating() && !_shownAnimation.animating()) { @@ -1376,20 +1635,23 @@ void Suggestions::startShownAnimation(bool shown, Fn<void()> finish) { _tabs->hide(); _chatsScroll->hide(); _channelsScroll->hide(); + _appsScroll->hide(); _slideAnimation.stop(); } void Suggestions::finishShow() { _slideAnimation.stop(); _slideLeft = _slideRight = QPixmap(); + _slideLeftTop = _slideRightTop = 0; _shownAnimation.stop(); _cache = QPixmap(); _tabs->show(); - const auto channels = (_tab.current() == Tab::Channels); - _chatsScroll->setVisible(!channels); - _channelsScroll->setVisible(channels); + const auto tab = _tab.current(); + _chatsScroll->setVisible(tab == Tab::Chats); + _channelsScroll->setVisible(tab == Tab::Channels); + _appsScroll->setVisible(tab == Tab::Apps); } float64 Suggestions::shownOpacity() const { @@ -1414,12 +1676,12 @@ void Suggestions::paintEvent(QPaintEvent *e) { p.setOpacity(1. - progress); p.drawPixmap( anim::interpolate(0, -slide, progress), - _chatsScroll->y(), + _slideLeftTop, _slideLeft); p.setOpacity(progress); p.drawPixmap( anim::interpolate(slide, 0, progress), - _channelsScroll->y(), + _slideRightTop, _slideRight); } } @@ -1434,6 +1696,9 @@ void Suggestions::resizeEvent(QResizeEvent *e) { _channelsScroll->setGeometry(0, tabs, w, height() - tabs); _channelsContent->resizeToWidth(w); + + _appsScroll->setGeometry(0, tabs, w, height() - tabs); + _appsContent->resizeToWidth(w); } auto Suggestions::setupRecentPeers(RecentPeersList recentPeers) @@ -1595,6 +1860,115 @@ auto Suggestions::setupRecommendations() -> std::unique_ptr<ObjectList> { return result; } +auto Suggestions::setupRecentApps() -> std::unique_ptr<ObjectList> { + const auto controller = lifetime().make_state<RecentAppsController>( + _controller); + + auto result = setupObjectList( + _appsScroll.get(), + _appsContent, + controller); + const auto raw = result.get(); + const auto list = raw->wrap->entity(); + + raw->selectJump = [=](Qt::Key direction, int pageSize) { + const auto had = list->hasSelection(); + if (direction == Qt::Key()) { + return had ? JumpResult::Applied : JumpResult::NotApplied; + } else if (direction == Qt::Key_Up && !had) { + if (pageSize < 0) { + list->selectLast(); + return list->hasSelection() + ? JumpResult::Applied + : JumpResult::NotApplied; + } + return JumpResult::NotApplied; + } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) { + const auto was = list->selectedIndex(); + const auto delta = (direction == Qt::Key_Down) ? 1 : -1; + if (pageSize > 0) { + list->selectSkipPage(pageSize, delta); + } else { + list->selectSkip(delta); + } + if (had + && delta > 0 + && raw->count.current() + && list->selectedIndex() == was) { + list->clearSelection(); + return JumpResult::AppliedAndOut; + } + return list->hasSelection() + ? JumpResult::Applied + : had + ? JumpResult::AppliedAndOut + : JumpResult::NotApplied; + } + return JumpResult::NotApplied; + }; + + raw->chosen.events( + ) | rpl::start_with_next([=] { + _persist = false; + }, list->lifetime()); + + controller->load(); + + return result; +} + +auto Suggestions::setupPopularApps() -> std::unique_ptr<ObjectList> { + const auto controller = lifetime().make_state<PopularAppsController>( + _controller); + + const auto addToScroll = [=] { + const auto wrap = _recentApps->wrap; + return wrap->toggled() ? wrap->height() : 0; + }; + auto result = setupObjectList( + _appsScroll.get(), + _appsContent, + controller, + addToScroll); + const auto raw = result.get(); + const auto list = raw->wrap->entity(); + + raw->selectJump = [list](Qt::Key direction, int pageSize) { + const auto had = list->hasSelection(); + if (direction == Qt::Key()) { + return had ? JumpResult::Applied : JumpResult::NotApplied; + } else if (direction == Qt::Key_Up && !had) { + return JumpResult::NotApplied; + } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) { + const auto delta = (direction == Qt::Key_Down) ? 1 : -1; + if (pageSize > 0) { + list->selectSkipPage(pageSize, delta); + } else { + list->selectSkip(delta); + } + return list->hasSelection() + ? JumpResult::Applied + : had + ? JumpResult::AppliedAndOut + : JumpResult::NotApplied; + } + return JumpResult::NotApplied; + }; + + raw->chosen.events( + ) | rpl::start_with_next([=] { + _persist = true; + }, list->lifetime()); + + _tab.value() | rpl::filter( + rpl::mappers::_1 == Tab::Apps + ) | rpl::start_with_next([=] { + controller->load(); + }, list->lifetime()); + + return result; +} + auto Suggestions::setupObjectList( not_null<Ui::ElasticScroll*> scroll, not_null<Ui::VerticalLayout*> parent, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 438c0b370..549851d72 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -79,6 +79,14 @@ public: -> rpl::producer<not_null<PeerData*>> { return _recommendations->chosen.events(); } + [[nodiscard]] auto recentAppChosen() const + -> rpl::producer<not_null<PeerData*>> { + return _recentApps->chosen.events(); + } + [[nodiscard]] auto popularAppChosen() const + -> rpl::producer<not_null<PeerData*>> { + return _popularApps->chosen.events(); + } class ObjectListController; @@ -86,6 +94,7 @@ private: enum class Tab : uchar { Chats, Channels, + Apps, }; enum class JumpResult : uchar { NotApplied, @@ -110,13 +119,16 @@ private: void setupTabs(); void setupChats(); void setupChannels(); + void setupApps(); void selectJumpChats(Qt::Key direction, int pageSize); void selectJumpChannels(Qt::Key direction, int pageSize); + void selectJumpApps(Qt::Key direction, int pageSize); [[nodiscard]] Data::Thread *updateFromChatsDrag(QPoint globalPosition); [[nodiscard]] Data::Thread *updateFromChannelsDrag( QPoint globalPosition); + [[nodiscard]] Data::Thread *updateFromAppsDrag(QPoint globalPosition); [[nodiscard]] Data::Thread *fromListId(uint64 peerListRowId); [[nodiscard]] std::unique_ptr<ObjectList> setupRecentPeers( @@ -129,6 +141,9 @@ private: [[nodiscard]] auto setupEmptyChannels() -> object_ptr<Ui::SlideWrap<Ui::RpWidget>>; + [[nodiscard]] std::unique_ptr<ObjectList> setupRecentApps(); + [[nodiscard]] std::unique_ptr<ObjectList> setupPopularApps(); + [[nodiscard]] std::unique_ptr<ObjectList> setupObjectList( not_null<Ui::ElasticScroll*> scroll, not_null<Ui::VerticalLayout*> parent, @@ -142,7 +157,7 @@ private: void switchTab(Tab tab); void startShownAnimation(bool shown, Fn<void()> finish); - void startSlideAnimation(); + void startSlideAnimation(Tab was, Tab now); void finishShow(); void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback); @@ -171,6 +186,12 @@ private: const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyChannels; + const std::unique_ptr<Ui::ElasticScroll> _appsScroll; + const not_null<Ui::VerticalLayout*> _appsContent; + + const std::unique_ptr<ObjectList> _recentApps; + const std::unique_ptr<ObjectList> _popularApps; + Ui::Animations::Simple _shownAnimation; Fn<void()> _showFinished; bool _hidden = false; @@ -181,6 +202,9 @@ private: QPixmap _slideLeft; QPixmap _slideRight; + int _slideLeftTop = 0; + int _slideRightTop = 0; + }; [[nodiscard]] rpl::producer<TopPeersList> TopPeersContent( diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 8e44a7a79..4d5a9bc04 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -469,6 +469,12 @@ infoSharedMediaButtonIconPosition: point(20px, 3px); infoGroupMembersIconPosition: point(20px, 10px); infoChannelMembersIconPosition: point(20px, 19px); +infoOpenApp: RoundButton(defaultActiveButton) { + textTop: 11px; + height: 40px; +} +infoOpenAppMargin: margins(16px, 12px, 16px, 12px); + infoPersonalChannelIconPosition: point(25px, 20px); infoPersonalChannelNameLabel: FlatLabel(infoProfileStatus) { textFg: windowBoldFg; diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 6e17ed549..3c0af7c0d 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_text.h" #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_widget.h" +#include "inline_bots/bot_attach_web_view.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "menu/menu_mute.h" @@ -776,6 +777,7 @@ private: object_ptr<Ui::RpWidget> setupPersonalChannel(not_null<UserData*> user); object_ptr<Ui::RpWidget> setupInfo(); object_ptr<Ui::RpWidget> setupMuteToggle(); + void setupMainApp(); void setupMainButtons(); Ui::MultiSlideTracker fillTopicButtons(); Ui::MultiSlideTracker fillUserButtons( @@ -1209,10 +1211,21 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() { } if (!_peer->isSelf()) { // No notifications toggle for Self => no separator. + + const auto user = _peer->asUser(); + const auto app = user && user->botInfo && user->botInfo->hasMainApp; + const auto padding = app + ? QMargins( + st::infoOpenAppMargin.left(), + st::infoProfileSeparatorPadding.top(), + st::infoOpenAppMargin.right(), + 0) + : st::infoProfileSeparatorPadding; + result->add(object_ptr<Ui::SlideWrap<>>( result, object_ptr<Ui::PlainShadow>(result), - st::infoProfileSeparatorPadding) + padding) )->setDuration( st::infoSlideDuration )->toggleOn( @@ -1548,6 +1561,42 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupMuteToggle() { return result; } +void DetailsFiller::setupMainApp() { + const auto button = _wrap->add( + object_ptr<Ui::RoundButton>( + _wrap, + tr::lng_profile_open_app(), + st::infoOpenApp), + st::infoOpenAppMargin); + button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + + const auto user = _peer->asUser(); + const auto controller = _controller->parentController(); + button->setClickedCallback([=] { + user->session().attachWebView().open({ + .bot = user, + .context = { + .controller = controller, + .maySkipConfirmation = true, + }, + .source = InlineBots::WebViewSourceBotProfile(), + }); + }); + + const auto url = tr::lng_mini_apps_tos_url(tr::now); + Ui::AddDividerText( + _wrap, + tr::lng_profile_open_app_about( + lt_terms, + tr::lng_profile_open_app_terms() | Ui::Text::ToLink(url), + Ui::Text::WithEntities) + )->setClickHandlerFilter([=](const auto &...) { + UrlClickHandler::Open(url); + return false; + }); + Ui::AddSkip(_wrap); +} + void DetailsFiller::setupMainButtons() { auto wrapButtons = [=](auto &&callback) { auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap)); @@ -1737,6 +1786,13 @@ object_ptr<Ui::RpWidget> DetailsFiller::fill() { add(object_ptr<Ui::BoxContentDivider>(_wrap)); add(CreateSkipWidget(_wrap)); add(setupInfo()); + if (const auto user = _peer->asUser()) { + if (const auto info = user->botInfo.get()) { + if (info->hasMainApp) { + setupMainApp(); + } + } + } if (!_peer->isSelf()) { add(setupMuteToggle()); } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index e0a609f7a..5810310fa 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -69,6 +69,7 @@ namespace { constexpr auto kProlongTimeout = 60 * crl::time(1000); constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); +constexpr auto kPopularAppBotsLimit = 100; [[nodiscard]] DocumentData *ResolveIcon( not_null<Main::Session*> session, @@ -669,9 +670,13 @@ void WebViewInstance::resolve() { }, [&](WebViewSourceGame game) { showGame(); }, [&](WebViewSourceBotProfile) { - confirmOpen([=] { + if (_context.maySkipConfirmation) { requestMain(); - }); + } else { + confirmOpen([=] { + requestMain(); + }); + } }); } @@ -757,6 +762,10 @@ void WebViewInstance::confirmOpen(Fn<void()> done) { close(); done(); }; + const auto cancel = [=](Fn<void()> close) { + botClose(); + close(); + }; _parentShow->show(Ui::MakeConfirmBox({ .text = tr::lng_allow_bot_webview( tr::now, @@ -764,7 +773,7 @@ void WebViewInstance::confirmOpen(Fn<void()> done) { Ui::Text::Bold(_bot->name()), Ui::Text::RichLangValue), .confirmed = crl::guard(this, callback), - .cancelled = crl::guard(this, [=] { botClose(); }), + .cancelled = crl::guard(this, cancel), .confirmText = tr::lng_box_ok(), })); } @@ -1444,7 +1453,10 @@ AttachWebView::AttachWebView(not_null<Main::Session*> session) _refreshTimer.callEach(kRefreshBotsTimeout); } -AttachWebView::~AttachWebView() = default; +AttachWebView::~AttachWebView() { + closeAll(); + _session->api().request(_popularAppBotsRequestId).cancel(); +} void AttachWebView::openByUsername( not_null<Window::SessionController*> controller, @@ -1501,6 +1513,40 @@ void AttachWebView::closeAll() { base::take(_instances); } +void AttachWebView::loadPopularAppBots() { + if (_popularAppBotsLoaded.current() || _popularAppBotsRequestId) { + return; + } + _popularAppBotsRequestId = _session->api().request( + MTPbots_GetPopularAppBots( + MTP_string(), + MTP_int(kPopularAppBotsLimit)) + ).done([=](const MTPbots_PopularAppBots &result) { + _popularAppBotsRequestId = 0; + + const auto &list = result.data().vusers().v; + auto parsed = std::vector<not_null<UserData*>>(); + parsed.reserve(list.size()); + for (const auto &user : list) { + const auto bot = _session->data().processUser(user); + if (bot->isBot()) { + parsed.push_back(bot); + } + } + _popularAppBots = std::move(parsed); + _popularAppBotsLoaded = true; + }).send(); +} + +auto AttachWebView::popularAppBots() const +-> const std::vector<not_null<UserData*>> & { + return _popularAppBots; +} + +rpl::producer<> AttachWebView::popularAppBotsLoaded() const { + return _popularAppBotsLoaded.changes() | rpl::to_empty; +} + void AttachWebView::cancel() { _session->api().request(base::take(_requestId)).cancel(); _botUsername = QString(); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index ecccdd042..4a4f5ca08 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -256,9 +256,6 @@ private: void showGame(); void started(uint64 queryId); - [[nodiscard]] Window::SessionController *windowForThread( - not_null<Data::Thread*> thread); - auto nonPanelPaymentFormFactory( Fn<void(Payments::CheckoutResult)> reactivate) -> Fn<void(Payments::NonPanelPaymentForm)>; @@ -352,6 +349,11 @@ public: void close(not_null<WebViewInstance*> instance); void closeAll(); + void loadPopularAppBots(); + [[nodiscard]] auto popularAppBots() const + -> const std::vector<not_null<UserData*>> &; + [[nodiscard]] rpl::producer<> popularAppBotsLoaded() const; + private: void resolveUsername( std::shared_ptr<Ui::Show> show, @@ -395,6 +397,10 @@ private: std::vector<std::unique_ptr<WebViewInstance>> _instances; + std::vector<not_null<UserData*>> _popularAppBots; + mtpRequestId _popularAppBotsRequestId = 0; + rpl::variable<bool> _popularAppBotsLoaded = false; + }; [[nodiscard]] std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu( diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index cc09dacf5..5dd80e988 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -110,7 +110,9 @@ Session::Session( , _recentPeers(std::make_unique<Data::RecentPeers>(this)) , _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this)) , _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this)) -, _topPeers(std::make_unique<Data::TopPeers>(this)) +, _topPeers(std::make_unique<Data::TopPeers>(this, Data::TopPeerType::Chat)) +, _topBotApps( + std::make_unique<Data::TopPeers>(this, Data::TopPeerType::BotApp)) , _factchecks(std::make_unique<Data::Factchecks>(this)) , _locationPickers(std::make_unique<Data::LocationPickers>()) , _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>()) diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index a69373a36..ba5dbcd99 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -129,6 +129,9 @@ public: [[nodiscard]] Data::TopPeers &topPeers() const { return *_topPeers; } + [[nodiscard]] Data::TopPeers &topBotApps() const { + return *_topBotApps; + } [[nodiscard]] Data::Factchecks &factchecks() const { return *_factchecks; } @@ -262,6 +265,7 @@ private: const std::unique_ptr<Data::ScheduledMessages> _scheduledMessages; const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages; const std::unique_ptr<Data::TopPeers> _topPeers; + const std::unique_ptr<Data::TopPeers> _topBotApps; const std::unique_ptr<Data::Factchecks> _factchecks; const std::unique_ptr<Data::LocationPickers> _locationPickers; diff --git a/Telegram/SourceFiles/ui/vertical_list.cpp b/Telegram/SourceFiles/ui/vertical_list.cpp index 11347aa61..6666077dd 100644 --- a/Telegram/SourceFiles/ui/vertical_list.cpp +++ b/Telegram/SourceFiles/ui/vertical_list.cpp @@ -28,31 +28,34 @@ void AddDivider(not_null<Ui::VerticalLayout*> container) { container->add(object_ptr<Ui::BoxContentDivider>(container)); } -void AddDividerText( +not_null<Ui::FlatLabel*> AddDividerText( not_null<Ui::VerticalLayout*> container, rpl::producer<QString> text, const style::margins &margins, RectParts parts) { - AddDividerText( + return AddDividerText( container, std::move(text) | Ui::Text::ToWithEntities(), margins, parts); } -void AddDividerText( +not_null<Ui::FlatLabel*> AddDividerText( not_null<Ui::VerticalLayout*> container, rpl::producer<TextWithEntities> text, const style::margins &margins, RectParts parts) { + auto label = object_ptr<Ui::FlatLabel>( + container, + std::move(text), + st::boxDividerLabel); + const auto result = label.data(); container->add(object_ptr<Ui::DividerLabel>( container, - object_ptr<Ui::FlatLabel>( - container, - std::move(text), - st::boxDividerLabel), + std::move(label), margins, parts)); + return result; } not_null<Ui::FlatLabel*> AddSubsectionTitle( diff --git a/Telegram/SourceFiles/ui/vertical_list.h b/Telegram/SourceFiles/ui/vertical_list.h index 7ab743bd3..82934367f 100644 --- a/Telegram/SourceFiles/ui/vertical_list.h +++ b/Telegram/SourceFiles/ui/vertical_list.h @@ -25,12 +25,12 @@ class VerticalLayout; void AddSkip(not_null<Ui::VerticalLayout*> container); void AddSkip(not_null<Ui::VerticalLayout*> container, int skip); void AddDivider(not_null<Ui::VerticalLayout*> container); -void AddDividerText( +not_null<Ui::FlatLabel*> AddDividerText( not_null<Ui::VerticalLayout*> container, rpl::producer<QString> text, const style::margins &margins = st::defaultBoxDividerLabelPadding, RectParts parts = RectPart::Top | RectPart::Bottom); -void AddDividerText( +not_null<Ui::FlatLabel*> AddDividerText( not_null<Ui::VerticalLayout*> container, rpl::producer<TextWithEntities> text, const style::margins &margins = st::defaultBoxDividerLabelPadding, From 3eeb01be61640c0062715fde3b851ea6b79d9a36 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 26 Jul 2024 14:44:06 +0200 Subject: [PATCH 128/163] Save Celsius/Fahrenheit in Settings. --- Telegram/SourceFiles/core/core_settings.cpp | 12 +++++-- Telegram/SourceFiles/core/core_settings.h | 8 +++++ .../stories/media_stories_controller.cpp | 26 +++++++++++--- .../media/stories/media_stories_controller.h | 2 ++ .../media/stories/media_stories_reactions.cpp | 35 +++++++++---------- .../media/stories/media_stories_reactions.h | 5 +-- 6 files changed, 61 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index efd0ba4d5..16b2595de 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -220,7 +220,7 @@ QByteArray Settings::serialize() const { + Serialize::bytearraySize(ivPosition) + Serialize::stringSize(noWarningExtensions) + Serialize::stringSize(_customFontFamily) - + sizeof(qint32) * 2; + + sizeof(qint32) * 3; auto result = QByteArray(); result.reserve(size); @@ -372,7 +372,8 @@ QByteArray Settings::serialize() const { qRound(_dialogsNoChatWidthRatio.current() * 1000000), 0, 1000000)) - << qint32(_systemUnlockEnabled ? 1 : 0); + << qint32(_systemUnlockEnabled ? 1 : 0) + << qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2); } Ensures(result.size() == size); @@ -493,6 +494,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { QByteArray ivPosition; QString customFontFamily = _customFontFamily; qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0; + qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2; stream >> themesAccentColors; if (!stream.atEnd()) { @@ -793,6 +795,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> systemUnlockEnabled; } + if (!stream.atEnd()) { + stream >> weatherInCelsius; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -1001,6 +1006,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { } _customFontFamily = customFontFamily; _systemUnlockEnabled = (systemUnlockEnabled == 1); + _weatherInCelsius = !weatherInCelsius + ? std::optional<bool>() + : (weatherInCelsius == 1); } QString Settings::getSoundPath(const QString &key) const { diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 3412c4588..0ac4e94a5 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -891,6 +891,13 @@ public: _systemUnlockEnabled = enabled; } + [[nodiscard]] std::optional<bool> weatherInCelsius() const { + return _weatherInCelsius; + } + void setWeatherInCelsius(bool value) { + _weatherInCelsius = value; + } + [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); @@ -1022,6 +1029,7 @@ private: WindowPosition _ivPosition; QString _customFontFamily; bool _systemUnlockEnabled = false; + std::optional<bool> _weatherInCelsius; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index ca558abfd..fc75e366d 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/stories/media_stories_controller.h" +#include "base/platform/base_platform_info.h" #include "base/power_save_blocker.h" #include "base/qt_signal_producer.h" #include "base/unixtime.h" @@ -128,6 +129,13 @@ struct SameDayRange { int(base::SafeRound(asin * point.x() + acos * point.y()))); } +[[nodiscard]] bool ResolveWeatherInCelsius() { + const auto saved = Core::App().settings().weatherInCelsius(); + return saved.value_or(!ranges::contains( + std::array{ u"US"_q, u"BS"_q, u"KY"_q, u"LR"_q, u"BZ"_q }, + Platform::SystemCountry().toUpper())); +} + } // namespace class Controller::PhotoPlayback final { @@ -284,7 +292,8 @@ Controller::Controller(not_null<Delegate*> delegate) , _slider(std::make_unique<Slider>(this)) , _replyArea(std::make_unique<ReplyArea>(this)) , _reactions(std::make_unique<Reactions>(this)) -, _recentViews(std::make_unique<RecentViews>(this)) { +, _recentViews(std::make_unique<RecentViews>(this)) +, _weatherInCelsius(ResolveWeatherInCelsius()){ initLayout(); using namespace rpl::mappers; @@ -1272,16 +1281,16 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { }); } for (const auto &weather : _weatherAreas) { - auto widget = _reactions->makeWeatherAreaWidget(weather); - const auto raw = widget.get(); _areas.push_back({ .original = weather.area.geometry, .radiusOriginal = weather.area.radius, .rotation = weather.area.rotation, .handler = std::make_shared<LambdaClickHandler>([=] { - raw->toggleMode(); + toggleWeatherMode(); }), - .view = std::move(widget), + .view = _reactions->makeWeatherAreaWidget( + weather, + _weatherInCelsius.value()), }); } rebuildActiveAreas(*layout); @@ -1300,6 +1309,13 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { return nullptr; } +void Controller::toggleWeatherMode() const { + const auto now = !_weatherInCelsius.current(); + Core::App().settings().setWeatherInCelsius(now); + Core::App().saveSettingsDelayed(); + _weatherInCelsius = now; +} + void Controller::maybeMarkAsRead(const Player::TrackState &state) { const auto length = state.length; const auto position = Player::IsStoppedAtEnd(state.state) diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 590dee3f6..860908b96 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -229,6 +229,7 @@ private: void updatePlayingAllowed(); void setPlayingAllowed(bool allowed); void rebuildActiveAreas(const Layout &layout) const; + void toggleWeatherMode() const; void hideSiblings(); void showSiblings(not_null<Main::Session*> session); @@ -307,6 +308,7 @@ private: std::vector<Data::UrlArea> _urlAreas; std::vector<Data::WeatherArea> _weatherAreas; mutable std::vector<ActiveArea> _areas; + mutable rpl::variable<bool> _weatherInCelsius; std::vector<CachedSource> _cachedSourcesList; int _cachedSourceIndex = -1; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index f5e2582ef..a8e044cc9 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -75,7 +75,6 @@ public: void setAreaGeometry(QRect geometry, float64 radius) override; void updateReactionsCount(int count) override; void playEffect() override; - void toggleMode() override; bool contains(QPoint point) override; private: @@ -137,12 +136,12 @@ public: WeatherView( QWidget *parent, not_null<Main::Session*> session, - const Data::WeatherArea &data); + const Data::WeatherArea &data, + rpl::producer<bool> weatherInCelsius); void setAreaGeometry(QRect geometry, float64 radius) override; void updateReactionsCount(int count) override; void playEffect() override; - void toggleMode() override; bool contains(QPoint point) override; private: @@ -349,10 +348,6 @@ void ReactionView::playEffect() { } } -void ReactionView::toggleMode() { - Unexpected("ReactionView::toggleMode."); -} - bool ReactionView::contains(QPoint point) { const auto circle = _apiGeometry; const auto radius = std::min(circle.width(), circle.height()) / 2; @@ -537,7 +532,8 @@ void ReactionView::cacheBackground() { WeatherView::WeatherView( QWidget *parent, not_null<Main::Session*> session, - const Data::WeatherArea &data) + const Data::WeatherArea &data, + rpl::producer<bool> weatherInCelsius) : RpWidget(parent) , _session(session) , _data(data) @@ -546,6 +542,12 @@ WeatherView::WeatherView( watchForSticker(); setAttribute(Qt::WA_TransparentForMouseEvents); show(); + + std::move(weatherInCelsius) | rpl::start_with_next([=](bool celsius) { + _celsius = celsius; + _background = {}; + update(); + }, lifetime()); } void WeatherView::watchForSticker() { @@ -565,7 +567,7 @@ void WeatherView::watchForSticker() { ) | rpl::start_with_next([=](not_null<DocumentData*> document) { setStickerFrom(document); update(); - }, _lifetime); + }, lifetime()); } } @@ -598,12 +600,6 @@ void WeatherView::playEffect() { Unexpected("WeatherView::playEffect."); } -void WeatherView::toggleMode() { - _celsius = !_celsius; - _background = {}; - update(); -} - bool WeatherView::contains(QPoint point) { const auto geometry = _rect.translated(pos()).toRect(); const auto angle = -_data.area.rotation; @@ -683,7 +679,7 @@ void WeatherView::setStickerFrom(not_null<DocumentData*> document) { } _sticker->setRepaintCallback([=] { update(); }); update(); - }, _lifetime); + }, lifetime()); } void WeatherView::cacheBackground() { @@ -1087,12 +1083,15 @@ auto Reactions::makeSuggestedReactionWidget( reaction); } -auto Reactions::makeWeatherAreaWidget(const Data::WeatherArea &data) +auto Reactions::makeWeatherAreaWidget( + const Data::WeatherArea &data, + rpl::producer<bool> weatherInCelsius) -> std::unique_ptr<StoryAreaView> { return std::make_unique<WeatherView>( _controller->wrap(), &_controller->uiShow()->session(), - data); + data, + std::move(weatherInCelsius)); } void Reactions::setReplyFieldState( diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index b17e2e19d..11da4c456 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -49,7 +49,6 @@ public: virtual void setAreaGeometry(QRect geometry, float64 radius) = 0; virtual void updateReactionsCount(int count) = 0; virtual void playEffect() = 0; - virtual void toggleMode() = 0; virtual bool contains(QPoint point) = 0; }; @@ -83,7 +82,9 @@ public: [[nodiscard]] auto makeSuggestedReactionWidget( const Data::SuggestedReaction &reaction) -> std::unique_ptr<StoryAreaView>; - [[nodiscard]] auto makeWeatherAreaWidget(const Data::WeatherArea &data) + [[nodiscard]] auto makeWeatherAreaWidget( + const Data::WeatherArea &data, + rpl::producer<bool> weatherInCelsius) -> std::unique_ptr<StoryAreaView>; void setReplyFieldState( From 2dcf40817e39e52e744b423310362c2997c6658c Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 26 Jul 2024 19:25:47 +0200 Subject: [PATCH 129/163] Initial tonsite:// show in IV window. --- Telegram/SourceFiles/core/ui_integration.cpp | 3 + Telegram/SourceFiles/iv/iv_controller.cpp | 20 +++ Telegram/SourceFiles/iv/iv_controller.h | 2 + Telegram/SourceFiles/iv/iv_instance.cpp | 156 +++++++++++++++++-- Telegram/SourceFiles/iv/iv_instance.h | 6 + 5 files changed, 177 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 1bc5a1f85..bf1b49432 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -238,6 +238,9 @@ bool UiIntegration::handleUrlClick( } else if (local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) { Core::App().openLocalUrl(local, context); return true; + } else if (local.startsWith(u"tonsite://"_q, Qt::CaseInsensitive)) { + Core::App().iv().showTonSite(url, context); + return true; } else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) { Core::App().openInternalUrl(local, context); return true; diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index b3fb82a0d..6720f3c93 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -251,6 +251,26 @@ void Controller::update(Prepared page) { } } +void Controller::showTonSite( + const Webview::StorageId &storageId, + QString uri) { + auto part = uri.mid(u"tonsite://"_q.size()); + part = part.replace('-', "-h"); + part = part.replace('.', "-d"); + const auto url = "https://" + part + ".magic.org"; + if (!_webview) { + createWebview(storageId); + } + if (_webview && _webview->widget()) { + _webview->navigate(url); + activate(); + } else { + _events.fire({ Event::Type::Close }); + } + _subtitleText = uri; + _menuToggle->hide(); +} + QByteArray Controller::fillInChannelValuesScript( base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) { auto result = QByteArray(); diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h index 473813fca..1f0ecaf9a 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -76,6 +76,8 @@ public: base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues); void update(Prepared page); + void showTonSite(const Webview::StorageId &storageId, QString uri); + [[nodiscard]] bool active() const; void showJoinedTooltip(); void minimize(); diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp index a49ba8887..00cfaa615 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -171,6 +171,39 @@ private: }; +class TonSite final : public base::has_weak_ptr { +public: + TonSite(not_null<Delegate*> delegate, QString uri); + + [[nodiscard]] bool active() const; + + void moveTo(QString uri); + + void minimize(); + + [[nodiscard]] rpl::producer<Controller::Event> events() const { + return _events.events(); + } + + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + void createController(); + + void showWindowed(); + + const not_null<Delegate*> _delegate; + QString _uri; + std::unique_ptr<Controller> _controller; + + rpl::event_stream<Controller::Event> _events; + + rpl::lifetime _lifetime; + +}; + Shown::Shown( not_null<Delegate*> delegate, not_null<Main::Session*> session, @@ -742,6 +775,50 @@ void Shown::minimize() { } } +TonSite::TonSite(not_null<Delegate*> delegate, QString uri) +: _delegate(delegate) +, _uri(uri) { + showWindowed(); +} + +void TonSite::createController() { + Expects(!_controller); + + const auto showShareBox = [=](ShareBoxDescriptor &&descriptor) { + return ShareBoxResult(); + }; + _controller = std::make_unique<Controller>( + _delegate, + std::move(showShareBox)); + + _controller->events( + ) | rpl::start_to_stream(_events, _controller->lifetime()); +} + +void TonSite::showWindowed() { + if (!_controller) { + createController(); + } + + _controller->showTonSite( + {},//_session->local().resolveStorageIdOther(), + _uri); +} + +bool TonSite::active() const { + return _controller && _controller->active(); +} + +void TonSite::moveTo(QString uri) { + _controller->showTonSite({}, uri); +} + +void TonSite::minimize() { + if (_controller) { + _controller->minimize(); + } +} + Instance::Instance(not_null<Delegate*> delegate) : _delegate(delegate) { } @@ -785,6 +862,7 @@ void Instance::show( const auto lower = event.url.toLower(); const auto urlChecked = lower.startsWith("http://") || lower.startsWith("https://"); + const auto tonsite = lower.startsWith("tonsite://"); switch (event.type) { case Type::Close: _shown = nullptr; @@ -801,8 +879,10 @@ void Instance::show( case Type::OpenLinkExternal: if (urlChecked) { File::OpenUrl(event.url); + closeAll(); + } else if (tonsite) { + showTonSite(event.url); } - closeAll(); break; case Type::OpenMedia: if (const auto window = Core::App().activeWindow()) { @@ -840,7 +920,10 @@ void Instance::show( break; case Type::OpenPage: case Type::OpenLink: { - if (!urlChecked) { + if (tonsite) { + showTonSite(event.url); + break; + } else if (!urlChecked) { break; } const auto session = _shownSession; @@ -990,6 +1073,52 @@ void Instance::openWithIvPreferred( }).send(); } +void Instance::showTonSite( + const QString &uri, + QVariant context) { + if (Platform::IsMac()) { + // Otherwise IV is not visible under the media viewer. + Core::App().hideMediaView(); + } + if (_tonSite) { + _tonSite->moveTo(uri); + return; + } + _tonSite = std::make_unique<TonSite>(_delegate, uri); + _tonSite->events() | rpl::start_with_next([=](Controller::Event event) { + using Type = Controller::Event::Type; + const auto lower = event.url.toLower(); + const auto urlChecked = lower.startsWith("http://") + || lower.startsWith("https://"); + const auto tonsite = lower.startsWith("tonsite://"); + switch (event.type) { + case Type::Close: + _tonSite = nullptr; + break; + case Type::Quit: + Shortcuts::Launch(Shortcuts::Command::Quit); + break; + case Type::OpenLinkExternal: + if (urlChecked) { + File::OpenUrl(event.url); + closeAll(); + } else if (tonsite) { + showTonSite(event.url); + } + break; + case Type::OpenPage: + case Type::OpenLink: + if (urlChecked) { + File::OpenUrl(event.url); + closeAll(); + } else if (tonsite) { + showTonSite(event.url); + } + break; + } + }, _tonSite->lifetime()); +} + void Instance::requestFull( not_null<Main::Session*> session, const QString &id) { @@ -1085,23 +1214,30 @@ bool Instance::hasActiveWindow(not_null<Main::Session*> session) const { } bool Instance::closeActive() { - if (!_shown || !_shown->active()) { - return false; + if (_shown && _shown->active()) { + _shown = nullptr; + return true; + } else if (_tonSite && _tonSite->active()) { + _tonSite = nullptr; + return true; } - _shown = nullptr; - return true; + return false; } bool Instance::minimizeActive() { - if (!_shown || !_shown->active()) { - return false; + if (_shown && _shown->active()) { + _shown->minimize(); + return true; + } else if (_tonSite && _tonSite->active()) { + _tonSite->minimize(); + return true; } - _shown->minimize(); - return true; + return false; } void Instance::closeAll() { _shown = nullptr; + _tonSite = nullptr; } bool PreferForUri(const QString &uri) { diff --git a/Telegram/SourceFiles/iv/iv_instance.h b/Telegram/SourceFiles/iv/iv_instance.h index 9083734ad..c48cf569e 100644 --- a/Telegram/SourceFiles/iv/iv_instance.h +++ b/Telegram/SourceFiles/iv/iv_instance.h @@ -22,6 +22,7 @@ namespace Iv { class Data; class Shown; +class TonSite; class Instance final { public: @@ -50,6 +51,10 @@ public: QString uri, QVariant context = {}); + void showTonSite( + const QString &uri, + QVariant context = {}); + [[nodiscard]] bool hasActiveWindow( not_null<Main::Session*> session) const; @@ -97,6 +102,7 @@ private: QString _ivRequestUri; mtpRequestId _ivRequestId = 0; + std::unique_ptr<TonSite> _tonSite; rpl::lifetime _lifetime; From 4f37343e8b09d59bcb74b803708040c4c5c82d0f Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Mon, 29 Jul 2024 12:26:22 +0200 Subject: [PATCH 130/163] Use special webview storage for tonsite. --- Telegram/SourceFiles/core/core_settings.cpp | 11 ++++++++-- Telegram/SourceFiles/core/core_settings.h | 8 +++++++ Telegram/SourceFiles/iv/iv_controller.cpp | 22 +++++++++++++++---- Telegram/SourceFiles/iv/iv_instance.cpp | 4 +--- .../main/main_session_settings.cpp | 4 +++- .../SourceFiles/storage/storage_account.cpp | 14 ++++++++++++ .../SourceFiles/storage/storage_account.h | 2 ++ 7 files changed, 55 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 16b2595de..782f59156 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -220,7 +220,8 @@ QByteArray Settings::serialize() const { + Serialize::bytearraySize(ivPosition) + Serialize::stringSize(noWarningExtensions) + Serialize::stringSize(_customFontFamily) - + sizeof(qint32) * 3; + + sizeof(qint32) * 3 + + Serialize::bytearraySize(_tonsiteStorageToken); auto result = QByteArray(); result.reserve(size); @@ -373,7 +374,8 @@ QByteArray Settings::serialize() const { 0, 1000000)) << qint32(_systemUnlockEnabled ? 1 : 0) - << qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2); + << qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2) + << _tonsiteStorageToken; } Ensures(result.size() == size); @@ -495,6 +497,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { QString customFontFamily = _customFontFamily; qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0; qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2; + QByteArray tonsiteStorageToken = _tonsiteStorageToken; stream >> themesAccentColors; if (!stream.atEnd()) { @@ -798,6 +801,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> weatherInCelsius; } + if (!stream.atEnd()) { + stream >> tonsiteStorageToken; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -1009,6 +1015,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _weatherInCelsius = !weatherInCelsius ? std::optional<bool>() : (weatherInCelsius == 1); + _tonsiteStorageToken = tonsiteStorageToken; } QString Settings::getSoundPath(const QString &key) const { diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 0ac4e94a5..73f0f5eb0 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -898,6 +898,13 @@ public: _weatherInCelsius = value; } + [[nodiscard]] QByteArray tonsiteStorageToken() const { + return _tonsiteStorageToken; + } + void setTonsiteStorageToken(const QByteArray &value) { + _tonsiteStorageToken = value; + } + [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); @@ -1030,6 +1037,7 @@ private: QString _customFontFamily; bool _systemUnlockEnabled = false; std::optional<bool> _weatherInCelsius; + QByteArray _tonsiteStorageToken; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 6720f3c93..07ddd4e9d 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -254,10 +254,24 @@ void Controller::update(Prepared page) { void Controller::showTonSite( const Webview::StorageId &storageId, QString uri) { - auto part = uri.mid(u"tonsite://"_q.size()); - part = part.replace('-', "-h"); - part = part.replace('.', "-d"); - const auto url = "https://" + part + ".magic.org"; + const auto url = [&] { + auto parsed = QUrl(uri); + if (parsed.isValid()) { + auto host = parsed.host(); + host = host.replace('-', "-h"); + host = host.replace('.', "-d"); + parsed.setHost(host + ".magic.org"); + parsed.setScheme("https"); + return parsed.toString(); + } + auto part = uri.mid(u"tonsite://"_q.size()); + const auto split = part.indexOf('/'); + auto host = (split < 0) ? part : part.left(split); + host = host.replace('-', "-h"); + host = host.replace('.', "-d"); + part = (split < 0) ? QString() : part.mid(split); + return "https://" + host + ".magic.org" + part; + }(); if (!_webview) { createWebview(storageId); } diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp index 00cfaa615..480d73573 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -800,9 +800,7 @@ void TonSite::showWindowed() { createController(); } - _controller->showTonSite( - {},//_session->local().resolveStorageIdOther(), - _uri); + _controller->showTonSite(Storage::TonSiteStorageId(), _uri); } bool TonSite::active() const { diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 6349f6b6a..253116dea 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -34,7 +34,7 @@ SessionSettings::SessionSettings() QByteArray SessionSettings::serialize() const { const auto autoDownload = _autoDownload.serialize(); - auto size = sizeof(qint32) * 4 + const auto size = sizeof(qint32) * 4 + _groupStickersSectionHidden.size() * sizeof(quint64) + sizeof(qint32) * 4 + Serialize::bytearraySize(autoDownload) @@ -103,6 +103,8 @@ QByteArray SessionSettings::serialize() const { << qint32(_lastNonPremiumLimitDownload) << qint32(_lastNonPremiumLimitUpload); } + + Ensures(result.size() == size); return result; } diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 83d8e52f4..bfac71560 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -3198,4 +3198,18 @@ bool Account::decrypt( return true; } +Webview::StorageId TonSiteStorageId() { + auto result = Webview::StorageId{ + .path = BaseGlobalPath() + u"webview-tonsite"_q, + .token = Core::App().settings().tonsiteStorageToken(), + }; + if (result.token.isEmpty()) { + result.token = QByteArray::fromStdString( + Webview::GenerateStorageToken()); + Core::App().settings().setTonsiteStorageToken(result.token); + Core::App().saveSettingsDelayed(); + } + return result; +} + } // namespace Storage diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index d1314cde2..84533f93d 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -327,4 +327,6 @@ private: }; +[[nodiscard]] Webview::StorageId TonSiteStorageId(); + } // namespace Storage From 8b2bbfba6aaaa764f83a35ca0c1b26d7bde59187 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 30 Jul 2024 10:19:59 +0200 Subject: [PATCH 131/163] Navigate back/forward in tonsite pages. --- Telegram/Resources/iv_html/page.js | 3 - Telegram/SourceFiles/iv/iv.style | 13 +- Telegram/SourceFiles/iv/iv_controller.cpp | 146 ++++++++++++++---- Telegram/SourceFiles/iv/iv_controller.h | 5 +- .../settings/settings_experimental.cpp | 2 + Telegram/lib_webview | 2 +- 6 files changed, 130 insertions(+), 41 deletions(-) diff --git a/Telegram/Resources/iv_html/page.js b/Telegram/Resources/iv_html/page.js index bae02fe48..fda34772f 100644 --- a/Telegram/Resources/iv_html/page.js +++ b/Telegram/Resources/iv_html/page.js @@ -618,9 +618,6 @@ var IV = { element.getAnimations().forEach( (animation) => animation.finish()); }, - back: function () { - window.history.back(); - }, menuShown: function (shown) { var already = document.getElementById('menu_page_blocker'); if (already && shown) { diff --git a/Telegram/SourceFiles/iv/iv.style b/Telegram/SourceFiles/iv/iv.style index c8c9c6b69..6a5757276 100644 --- a/Telegram/SourceFiles/iv/iv.style +++ b/Telegram/SourceFiles/iv/iv.style @@ -22,12 +22,21 @@ ivMenuToggle: IconButton(defaultIconButton) { } } ivMenuPosition: point(-2px, 40px); +ivBackIcon: icon {{ "box_button_back", menuIconColor }}; ivBack: IconButton(ivMenuToggle) { width: 60px; - icon: icon {{ "box_button_back", menuIconColor }}; - iconOver: icon {{ "box_button_back", menuIconColor }}; + icon: ivBackIcon; + iconOver: ivBackIcon; rippleAreaPosition: point(12px, 6px); } +ivBackIconDisabled: icon {{ "box_button_back", menuIconFg }}; +ivForwardIcon: icon {{ "box_button_back-flip_horizontal", menuIconColor }}; +ivForward: IconButton(ivBack) { + width: 48px; + icon: ivForwardIcon; + iconOver: ivForwardIcon; + rippleAreaPosition: point(0px, 6px); +} ivSubtitleFont: font(16px semibold); ivSubtitle: FlatLabel(defaultFlatLabel) { textFg: boxTitleFg; diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 07ddd4e9d..1870011dc 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -127,6 +127,51 @@ namespace { return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray(); } +[[nodiscard]] QString TonsiteToHttps(QString value) { + const auto ChangeHost = [](QString tonsite) { + tonsite = tonsite.replace('-', "-h"); + tonsite = tonsite.replace('.', "-d"); + return tonsite + ".magic.org"; + }; + auto parsed = QUrl(value); + if (parsed.isValid()) { + parsed.setScheme("https"); + parsed.setHost(ChangeHost(parsed.host())); + if (parsed.path().isEmpty()) { + parsed.setPath(u"/"_q); + } + return parsed.toString(); + } + const auto part = value.mid(u"tonsite://"_q.size()); + const auto split = part.indexOf('/'); + return "https://" + + ChangeHost((split < 0) ? part : part.left(split)) + + ((split < 0) ? u"/"_q : part.mid(split)); +} + +[[nodiscard]] QString HttpsToTonsite(QString value) { + const auto ChangeHost = [](QString https) { + https.replace(".magic.org", QString()); + https = https.replace("-d", "."); + https = https.replace("-h", "-"); + return https; + }; + auto parsed = QUrl(value); + if (parsed.isValid()) { + parsed.setScheme("tonsite"); + parsed.setHost(ChangeHost(parsed.host())); + if (parsed.path().isEmpty()) { + parsed.setPath(u"/"_q); + } + return parsed.toString(); + } + const auto part = value.mid(u"https://"_q.size()); + const auto split = part.indexOf('/'); + return "tonsite://" + + ChangeHost((split < 0) ? part : part.left(split)) + + ((split < 0) ? u"/"_q : part.mid(split)); +} + } // namespace Controller::Controller( @@ -151,6 +196,7 @@ Controller::~Controller() { _ready = false; _webview = nullptr; _back.destroy(); + _forward.destroy(); _menu = nullptr; _menuToggle.destroy(); _subtitle = nullptr; @@ -168,15 +214,22 @@ void Controller::updateTitleGeometry(int newWidth) const { QPainter(_subtitleWrap.get()).fillRect(clip, st::windowBg); }, _subtitleWrap->lifetime()); - const auto progress = _subtitleLeft.value(_back->toggled() ? 1. : 0.); - const auto left = anim::interpolate( - st::ivSubtitleLeft, - _back->width() + st::ivSubtitleSkip, - progress); + const auto progressBack = _subtitleBackShift.value( + _back->toggled() ? 1. : 0.); + const auto progressForward = _subtitleForwardShift.value( + _forward->toggled() ? 1. : 0.); + const auto backAdded = _back->width() + + st::ivSubtitleSkip + - st::ivSubtitleLeft; + const auto forwardAdded = _forward->width(); + const auto left = st::ivSubtitleLeft + + anim::interpolate(0, backAdded, progressBack) + + anim::interpolate(0, forwardAdded, progressForward); _subtitle->resizeToWidth(newWidth - left - _menuToggle->width()); _subtitle->moveToLeft(left, st::ivSubtitleTop); _back->moveToLeft(0, 0); + _forward->moveToLeft(_back->width() * progressBack, 0); _menuToggle->moveToRight(0, 0); } @@ -191,12 +244,12 @@ void Controller::initControls() { _subtitleWrap.get(), _subtitleText.value(), st::ivSubtitle); + _subtitle->setSelectable(true); _subtitleText.value( ) | rpl::start_with_next([=](const QString &subtitle) { const auto prefix = tr::lng_iv_window_title(tr::now); _window->setWindowTitle(prefix + ' ' + QChar(0x2014) + ' ' + subtitle); }, _subtitle->lifetime()); - _subtitle->setAttribute(Qt::WA_TransparentForMouseEvents); _menuToggle.create(_subtitleWrap.get(), st::ivMenuToggle); _menuToggle->setClickedCallback([=] { showMenu(); }); @@ -206,15 +259,25 @@ void Controller::initControls() { object_ptr<Ui::IconButton>(_subtitleWrap.get(), st::ivBack)); _back->entity()->setClickedCallback([=] { if (_webview) { - _webview->eval("IV.back();"); + _webview->eval("window.history.back();"); } else { _back->hide(anim::type::normal); } }); + _forward.create( + _subtitleWrap.get(), + object_ptr<Ui::IconButton>(_subtitleWrap.get(), st::ivForward)); + _forward->entity()->setClickedCallback([=] { + if (_webview) { + _webview->eval("window.history.forward();"); + } else { + _forward->hide(anim::type::normal); + } + }); _back->toggledValue( ) | rpl::start_with_next([=](bool toggled) { - _subtitleLeft.start( + _subtitleBackShift.start( [=] { updateTitleGeometry(_window->body()->width()); }, toggled ? 0. : 1., toggled ? 1. : 0., @@ -222,7 +285,18 @@ void Controller::initControls() { }, _back->lifetime()); _back->hide(anim::type::instant); - _subtitleLeft.stop(); + _forward->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + _subtitleForwardShift.start( + [=] { updateTitleGeometry(_window->body()->width()); }, + toggled ? 0. : 1., + toggled ? 1. : 0., + st::fadeWrapDuration); + }, _forward->lifetime()); + _forward->hide(anim::type::instant); + + _subtitleBackShift.stop(); + _subtitleForwardShift.stop(); } void Controller::show( @@ -254,24 +328,7 @@ void Controller::update(Prepared page) { void Controller::showTonSite( const Webview::StorageId &storageId, QString uri) { - const auto url = [&] { - auto parsed = QUrl(uri); - if (parsed.isValid()) { - auto host = parsed.host(); - host = host.replace('-', "-h"); - host = host.replace('.', "-d"); - parsed.setHost(host + ".magic.org"); - parsed.setScheme("https"); - return parsed.toString(); - } - auto part = uri.mid(u"tonsite://"_q.size()); - const auto split = part.indexOf('/'); - auto host = (split < 0) ? part : part.left(split); - host = host.replace('-', "-h"); - host = host.replace('.', "-d"); - part = (split < 0) ? QString() : part.mid(split); - return "https://" + host + ".magic.org" + part; - }(); + const auto url = TonsiteToHttps(uri); if (!_webview) { createWebview(storageId); } @@ -281,7 +338,13 @@ void Controller::showTonSite( } else { _events.fire({ Event::Type::Close }); } - _subtitleText = uri; + _url = url; + _subtitleText = _url.value( + ) | rpl::filter([=](const QString &url) { + return !url.isEmpty() && url != u"about:blank"_q; + }) | rpl::map([=](QString value) { + return HttpsToTonsite(value); + }); _menuToggle->hide(); } @@ -438,12 +501,12 @@ void Controller::createWebview(const Webview::StorageId &storageId) { if (!script.isEmpty()) { _webview->eval(script); } - } else if (event == u"location_change"_q) { - _index = object.value("index").toInt(); - _hash = object.value("hash").toString(); - _back->toggle( - (object.value("position").toInt() > 0), - anim::type::normal); + //} else if (event == u"location_change"_q) { + // _index = object.value("index").toInt(); + // _hash = object.value("hash").toString(); + // _back->toggle( + // (object.value("position").toInt() > 0), + // anim::type::normal); } }); }); @@ -524,6 +587,21 @@ void Controller::createWebview(const Webview::StorageId &storageId) { return Webview::DataResult::Failed; }); + raw->navigationHistoryState( + ) | rpl::start_with_next([=](Webview::NavigationHistoryState state) { + _back->toggle( + state.canGoBack || state.canGoForward, + anim::type::normal); + _forward->toggle(state.canGoForward, anim::type::normal); + _back->entity()->setDisabled(!state.canGoBack); + _back->entity()->setIconOverride( + state.canGoBack ? nullptr : &st::ivBackIconDisabled, + state.canGoBack ? nullptr : &st::ivBackIconDisabled); + _back->setAttribute( + Qt::WA_TransparentForMouseEvents, + !state.canGoBack); + }, _webview->lifetime()); + raw->init(R"()"); } diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h index 1f0ecaf9a..b732ec57a 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -127,11 +127,14 @@ private: std::unique_ptr<Ui::RpWindow> _window; std::unique_ptr<Ui::RpWidget> _subtitleWrap; + rpl::variable<QString> _url; rpl::variable<QString> _subtitleText; std::unique_ptr<Ui::FlatLabel> _subtitle; - Ui::Animations::Simple _subtitleLeft; + Ui::Animations::Simple _subtitleBackShift; + Ui::Animations::Simple _subtitleForwardShift; object_ptr<Ui::IconButton> _menuToggle = { nullptr }; object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _back = { nullptr }; + object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _forward = { nullptr }; base::unique_qptr<Ui::PopupMenu> _menu; Ui::RpWidget *_container = nullptr; std::unique_ptr<Webview::Window> _webview; diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index f578bce82..69e0ef89e 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwindow.h" #include "media/player/media_player_instance.h" +#include "webview/platform/win/webview_windows_edge_chromium.h" #include "webview/webview_embed.h" #include "window/main_window.h" #include "window/window_peer_menu.h" @@ -149,6 +150,7 @@ void SetupExperimental( addToggle(Media::Player::kOptionDisableAutoplayNext); addToggle(kOptionSendLargePhotos); addToggle(Webview::kOptionWebviewDebugEnabled); + addToggle(Webview::EdgeChromium::kOptionWebviewLegacyEdge); addToggle(kOptionAutoScrollInactiveChat); addToggle(Window::Notifications::kOptionGNotification); addToggle(Core::kOptionFreeType); diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 363db4e49..cc8f41d10 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 363db4e49a0b78e5dd08bd922e09cf8810318c09 +Subproject commit cc8f41d10b66c1e66f16728f95f217a62d2dc7ac From 4108debca0f671dc15d2689270bf2f92d5b9fa5a Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 30 Jul 2024 12:40:33 +0200 Subject: [PATCH 132/163] Fix possible crash in web apps. --- Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index a8dee5f76..b2159ebb1 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -746,8 +746,9 @@ postEvent: function(eventType, eventData) { &QGuiApplication::focusWindowChanged ) | rpl::filter([=](QWindow *focused) { const auto handle = _widget->window()->windowHandle(); - return _webview - && !_webview->window.widget()->isHidden() + const auto widget = _webview ? _webview->window.widget() : nullptr; + return widget + && !widget->isHidden() && handle && (focused == handle); }) | rpl::start_with_next([=] { From 699a7bdc58fc12711f331313c94d417075c550e1 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 30 Jul 2024 15:25:11 +0200 Subject: [PATCH 133/163] Fix possible crash in message field. Fixes #28203. --- Telegram/SourceFiles/chat_helpers/message_field.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 8ee5170fb..b77fe3e3c 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -392,6 +392,9 @@ void InitMessageFieldHandlers( Fn<bool()> customEmojiPaused, Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji, const style::InputField *fieldStyle) { + const auto paused = [customEmojiPaused] { + return customEmojiPaused && customEmojiPaused(); + }; field->setTagMimeProcessor( FieldTagMimeProcessor(session, allowPremiumEmoji)); field->setCustomTextContext([=](Fn<void()> repaint) { @@ -399,10 +402,10 @@ void InitMessageFieldHandlers( .session = session, .customEmojiRepaint = std::move(repaint), }); - }, [customEmojiPaused] { - return On(PowerSaving::kEmojiChat) || customEmojiPaused(); - }, [customEmojiPaused] { - return On(PowerSaving::kChatSpoiler) || customEmojiPaused(); + }, [paused] { + return On(PowerSaving::kEmojiChat) || paused(); + }, [paused] { + return On(PowerSaving::kChatSpoiler) || paused(); }); field->setInstantReplaces(Ui::InstantReplaces::Default()); field->setInstantReplacesEnabled( From a25b2e9700a2d06d91e32d37bf51955eb80b819f Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 30 Jul 2024 15:53:06 +0200 Subject: [PATCH 134/163] Show webview error in Iv::Controller. --- Telegram/SourceFiles/iv/iv_controller.cpp | 68 +++++++++++++++++-- Telegram/SourceFiles/iv/iv_controller.h | 3 + .../payments/ui/payments_panel.cpp | 30 ++------ .../settings/settings_experimental.cpp | 3 +- .../ui/chat/attach/attach_bot_webview.cpp | 36 +++++----- .../ui/chat/attach/attach_bot_webview.h | 2 + 6 files changed, 91 insertions(+), 51 deletions(-) diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 1870011dc..487f9295c 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/invoke_queued.h" #include "base/qt_signal_producer.h" #include "base/qthelp_url.h" +#include "core/file_utilities.h" #include "iv/iv_data.h" #include "lang/lang_keys.h" +#include "ui/chat/attach/attach_bot_webview.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" @@ -28,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/palette.h" #include "styles/style_iv.h" #include "styles/style_menu_icons.h" +#include "styles/style_payments.h" // paymentsCriticalError #include "styles/style_widgets.h" #include "styles/style_window.h" @@ -194,7 +197,7 @@ Controller::~Controller() { _window->hide(); } _ready = false; - _webview = nullptr; + base::take(_webview); _back.destroy(); _forward.destroy(); _menu = nullptr; @@ -335,8 +338,6 @@ void Controller::showTonSite( if (_webview && _webview->widget()) { _webview->navigate(url); activate(); - } else { - _events.fire({ Event::Type::Close }); } _url = url; _subtitleText = _url.value( @@ -435,7 +436,7 @@ void Controller::createWebview(const Webview::StorageId &storageId) { window->lifetime().add([=] { _ready = false; - _webview = nullptr; + base::take(_webview); }); window->events( @@ -449,11 +450,32 @@ void Controller::createWebview(const Webview::StorageId &storageId) { } } }, window->lifetime()); - raw->widget()->show(); + + const auto widget = raw->widget(); + if (!widget) { + base::take(_webview); + showWebviewError(); + return; + } + widget->show(); + + QObject::connect(widget, &QObject::destroyed, [=] { + if (!_webview) { + // If we destroyed _webview ourselves, + // we don't show any message, nothing crashed. + return; + } + crl::on_main(window, [=] { + showWebviewError({ "Error: WebView has crashed." }); + }); + base::take(_webview); + }); _container->sizeValue( ) | rpl::start_with_next([=](QSize size) { - raw->widget()->setGeometry(QRect(QPoint(), size)); + if (const auto widget = raw->widget()) { + widget->setGeometry(QRect(QPoint(), size)); + } }, _container->lifetime()); raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) { @@ -600,11 +622,45 @@ void Controller::createWebview(const Webview::StorageId &storageId) { _back->setAttribute( Qt::WA_TransparentForMouseEvents, !state.canGoBack); + _url = QString::fromStdString(state.url); }, _webview->lifetime()); raw->init(R"()"); } +void Controller::showWebviewError() { + const auto available = Webview::Availability(); + if (available.error != Webview::Available::Error::None) { + showWebviewError(Ui::BotWebView::ErrorText(available)); + } else { + showWebviewError({ "Error: Could not initialize WebView." }); + } +} + +void Controller::showWebviewError(TextWithEntities text) { + auto error = Ui::CreateChild<Ui::PaddingWrap<Ui::FlatLabel>>( + _container, + object_ptr<Ui::FlatLabel>( + _container, + rpl::single(text), + st::paymentsCriticalError), + st::paymentsCriticalErrorPadding); + error->entity()->setClickHandlerFilter([=]( + const ClickHandlerPtr &handler, + Qt::MouseButton) { + const auto entity = handler->getTextEntity(); + if (entity.type != EntityType::CustomUrl) { + return true; + } + File::OpenUrl(entity.data); + return false; + }); + error->show(); + _container->sizeValue() | rpl::start_with_next([=](QSize size) { + error->setGeometry(0, 0, size.width(), size.height() * 2 / 3); + }, error->lifetime()); +} + void Controller::showInWindow( const Webview::StorageId &storageId, Prepared page) { diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h index b732ec57a..c92f4c4b2 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -123,6 +123,9 @@ private: void showShareMenu(); void destroyShareMenu(); + void showWebviewError(); + void showWebviewError(TextWithEntities text); + const not_null<Delegate*> _delegate; std::unique_ptr<Ui::RpWindow> _window; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index d97628285..2bc023e8c 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/wrap/fade_wrap.h" #include "ui/boxes/single_choice_box.h" +#include "ui/chat/attach/attach_bot_webview.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/effects/radial_animation.h" @@ -908,32 +909,9 @@ std::shared_ptr<Show> Panel::uiShow() { void Panel::showWebviewError( const QString &text, const Webview::Available &information) { - using Error = Webview::Available::Error; - Expects(information.error != Error::None); - - auto rich = TextWithEntities{ text }; - rich.append("\n\n"); - switch (information.error) { - case Error::NoWebview2: { - rich.append(tr::lng_payments_webview_install_edge( - tr::now, - lt_link, - Text::Link( - "Microsoft Edge WebView2 Runtime", - "https://go.microsoft.com/fwlink/p/?LinkId=2124703"), - Ui::Text::WithEntities)); - } break; - case Error::NoWebKitGTK: - rich.append(tr::lng_payments_webview_install_webkit(tr::now)); - break; - case Error::OldWindows: - rich.append(tr::lng_payments_webview_update_windows(tr::now)); - break; - default: - rich.append(QString::fromStdString(information.details)); - break; - } - showCriticalError(rich); + showCriticalError(TextWithEntities{ text }.append( + "\n\n" + ).append(BotWebView::ErrorText(information))); } void Panel::updateThemeParams(const Webview::ThemeParams ¶ms) { diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 69e0ef89e..c17eda5b8 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -24,7 +24,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwindow.h" #include "media/player/media_player_instance.h" -#include "webview/platform/win/webview_windows_edge_chromium.h" #include "webview/webview_embed.h" #include "window/main_window.h" #include "window/window_peer_menu.h" @@ -150,7 +149,7 @@ void SetupExperimental( addToggle(Media::Player::kOptionDisableAutoplayNext); addToggle(kOptionSendLargePhotos); addToggle(Webview::kOptionWebviewDebugEnabled); - addToggle(Webview::EdgeChromium::kOptionWebviewLegacyEdge); + addToggle(Webview::kOptionWebviewLegacyEdge); addToggle(kOptionAutoScrollInactiveChat); addToggle(Window::Notifications::kOptionGNotification); addToggle(Core::kOptionFreeType); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index b2159ebb1..7b57464f7 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -1401,32 +1401,34 @@ if (window.TelegramGameProxy) { )"); } -void Panel::showWebviewError( - const QString &text, - const Webview::Available &information) { - using Error = Webview::Available::Error; - Expects(information.error != Error::None); +TextWithEntities ErrorText(const Webview::Available &info) { + Expects(info.error != Webview::Available::Error::None); - auto rich = TextWithEntities{ text }; - rich.append("\n\n"); - switch (information.error) { - case Error::NoWebview2: { - rich.append(tr::lng_payments_webview_install_edge( + using Error = Webview::Available::Error; + switch (info.error) { + case Error::NoWebview2: + return tr::lng_payments_webview_install_edge( tr::now, lt_link, Text::Link( "Microsoft Edge WebView2 Runtime", "https://go.microsoft.com/fwlink/p/?LinkId=2124703"), - Ui::Text::WithEntities)); - } break; + Ui::Text::WithEntities); case Error::NoWebKitGTK: - rich.append(tr::lng_payments_webview_install_webkit(tr::now)); - break; + return { tr::lng_payments_webview_install_webkit(tr::now) }; + case Error::OldWindows: + return { tr::lng_payments_webview_update_windows(tr::now) }; default: - rich.append(QString::fromStdString(information.details)); - break; + return { QString::fromStdString(info.details) }; } - showCriticalError(rich); +} + +void Panel::showWebviewError( + const QString &text, + const Webview::Available &information) { + showCriticalError(TextWithEntities{ text }.append( + "\n\n" + ).append(ErrorText(information))); } rpl::lifetime &Panel::lifetime() { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index e25483b89..f687c24e6 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -30,6 +30,8 @@ struct Available; namespace Ui::BotWebView { +[[nodiscard]] TextWithEntities ErrorText(const Webview::Available &info); + struct MainButtonArgs { bool isActive = false; bool isVisible = false; From dac4389e371e001c89a790de6a10c96d7d774dbb Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 30 Jul 2024 18:37:57 +0300 Subject: [PATCH 135/163] Fix build on Linux. --- Telegram/SourceFiles/boxes/gift_credits_box.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp index d03a73641..a2e36038a 100644 --- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp @@ -124,7 +124,7 @@ void GiftCreditsBox( 0, [=] { gifted(); box->uiShow()->hideLayer(); }); - const auto bottom = box->setPinnedToBottomContent( + box->setPinnedToBottomContent( object_ptr<Ui::VerticalLayout>(box)); } From fb9ce6d3a877f4773b40bb8d5c67eeec920fb887 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 30 Jul 2024 18:38:17 +0300 Subject: [PATCH 136/163] Provide canGo[Back|Forward] for tonsite-s. --- Telegram/lib_webview | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_webview b/Telegram/lib_webview index cc8f41d10..2c95b169b 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit cc8f41d10b66c1e66f16728f95f217a62d2dc7ac +Subproject commit 2c95b169b30035a6e85d0f5534c4477adff393e2 From bb6c94ef4f1623b4c19b5f48bc67e2e046f51f2b Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 30 Jul 2024 19:19:30 +0200 Subject: [PATCH 137/163] Handle tonsite:// links from the system. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 3 ++ Telegram/SourceFiles/core/application.cpp | 38 ++++++++++++++------ Telegram/SourceFiles/iv/iv_controller.cpp | 10 +++--- Telegram/SourceFiles/iv/iv_instance.cpp | 3 +- Telegram/Telegram.plist | 1 + 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 45ec14889..3d4040dd7 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -37,6 +37,9 @@ <Extensions> <uap3:Extension Category="windows.protocol"> <uap3:Protocol Name="tg" Parameters="-- "%1"" /> + <uap3:Extension Category="windows.protocol"> + </uap3:Extension> + <uap3:Protocol Name="tonsite" Parameters="-- "%1"" /> </uap3:Extension> <desktop:Extension Category="windows.startupTask" diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index b2c2ec4db..94fa2ae4d 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -683,7 +683,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) { if (const auto file = event->file(); !file.isEmpty()) { _filesToOpen.append(file); _fileOpenTimer.callOnce(kFileOpenTimeoutMs); - } else if (event->url().scheme() == u"tg"_q) { + } else if (event->url().scheme() == u"tg"_q + || event->url().scheme() == u"tonsite"_q) { const auto url = QString::fromUtf8( event->url().toEncoded().trimmed()); cSetStartUrl(url.mid(0, 8192)); @@ -1084,13 +1085,17 @@ void Application::checkSendPaths() { } void Application::checkStartUrl() { - if (!cStartUrl().isEmpty() - && _lastActivePrimaryWindow - && !_lastActivePrimaryWindow->locked()) { + if (!cStartUrl().isEmpty()) { const auto url = cStartUrl(); - cSetStartUrl(QString()); - if (!openLocalUrl(url, {})) { - cSetStartUrl(url); + if (url.startsWith("tonsite://", Qt::CaseInsensitive)) { + cSetStartUrl(QString()); + iv().showTonSite(url, {}); + } else if (_lastActivePrimaryWindow + && !_lastActivePrimaryWindow->locked()) { + cSetStartUrl(QString()); + if (!openLocalUrl(url, {})) { + cSetStartUrl(url); + } } } } @@ -1798,11 +1803,13 @@ void Application::startShortcuts() { } void Application::RegisterUrlScheme() { + const auto arguments = Launcher::Instance().customWorkingDir() + ? u"-workdir \"%1\""_q.arg(cWorkingDir()) + : QString(); + base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{ .executable = Platform::ExecutablePathForShortcuts(), - .arguments = Launcher::Instance().customWorkingDir() - ? u"-workdir \"%1\""_q.arg(cWorkingDir()) - : QString(), + .arguments = arguments, .protocol = u"tg"_q, .protocolName = u"Telegram Link"_q, .shortAppName = u"tdesktop"_q, @@ -1810,6 +1817,17 @@ void Application::RegisterUrlScheme() { .displayAppName = AppName.utf16(), .displayAppDescription = AppName.utf16(), }); + + base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{ + .executable = Platform::ExecutablePathForShortcuts(), + .arguments = arguments, + .protocol = u"tonsite"_q, + .protocolName = u"TonSite Link"_q, + .shortAppName = u"tdesktop"_q, + .longAppName = QCoreApplication::applicationName(), + .displayAppName = AppName.utf16(), + .displayAppDescription = AppName.utf16(), + }); } bool IsAppLaunched() { diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 487f9295c..00b645505 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -523,12 +523,10 @@ void Controller::createWebview(const Webview::StorageId &storageId) { if (!script.isEmpty()) { _webview->eval(script); } - //} else if (event == u"location_change"_q) { - // _index = object.value("index").toInt(); - // _hash = object.value("hash").toString(); - // _back->toggle( - // (object.value("position").toInt() > 0), - // anim::type::normal); + } else if (event == u"location_change"_q) { + _index = object.value("index").toInt(); + _hash = object.value("hash").toString(); + _webview->refreshNavigationHistoryState(); } }); }); diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp index 480d73573..3b327e5a6 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -1107,8 +1107,7 @@ void Instance::showTonSite( case Type::OpenPage: case Type::OpenLink: if (urlChecked) { - File::OpenUrl(event.url); - closeAll(); + UrlClickHandler::Open(event.url); } else if (tonsite) { showTonSite(event.url); } diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist index b68e2109c..72913e85e 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -38,6 +38,7 @@ <key>CFBundleURLSchemes</key> <array> <string>tg</string> + <string>tonsite</string> </array> </dict> </array> From 8959679b3cfe29f756cb7437ff6fd4eeeab05987 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 13:05:54 +0200 Subject: [PATCH 138/163] Make IV internal links bg semi-transparent. --- Telegram/SourceFiles/iv/iv_controller.cpp | 9 ++- Telegram/SourceFiles/ui/webview_helpers.cpp | 66 +++++++++++++++++---- Telegram/SourceFiles/ui/webview_helpers.h | 4 ++ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 00b645505..e369ab5c0 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -65,7 +65,7 @@ namespace { { "box-divider-bg", &st::boxDividerBg }, { "box-divider-fg", &st::boxDividerFg }, { "light-button-fg", &st::lightButtonFg }, - { "light-button-bg-over", &st::lightButtonBgOver }, + //{ "light-button-bg-over", &st::lightButtonBgOver }, { "menu-icon-fg", &st::menuIconFg }, { "menu-icon-fg-over", &st::menuIconFgOver }, { "menu-bg", &st::menuBg }, @@ -82,7 +82,12 @@ namespace { static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{ { "iv-join-channel", tr::lng_iv_join_channel }, }; - return Ui::ComputeStyles(map, phrases); + return Ui::ComputeStyles(map, phrases) + + ';' + + Ui::ComputeSemiTransparentOverStyle( + "light-button-bg-over", + st::lightButtonBgOver, + st::windowBg); } [[nodiscard]] QByteArray WrapPage(const Prepared &page) { diff --git a/Telegram/SourceFiles/ui/webview_helpers.cpp b/Telegram/SourceFiles/ui/webview_helpers.cpp index 24b7155ce..3be7a6359 100644 --- a/Telegram/SourceFiles/ui/webview_helpers.cpp +++ b/Telegram/SourceFiles/ui/webview_helpers.cpp @@ -10,24 +10,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" namespace Ui { +namespace { -[[nodiscard]] QByteArray ComputeStyles( +[[nodiscard]] QByteArray Serialize(const QColor &qt) { + if (qt.alpha() == 255) { + return '#' + + QByteArray::number(qt.red(), 16).right(2) + + QByteArray::number(qt.green(), 16).right(2) + + QByteArray::number(qt.blue(), 16).right(2); + } + return "rgba(" + + QByteArray::number(qt.red()) + "," + + QByteArray::number(qt.green()) + "," + + QByteArray::number(qt.blue()) + "," + + QByteArray::number(qt.alpha() / 255.) + ")"; +} + +} // namespace + +QByteArray ComputeStyles( const base::flat_map<QByteArray, const style::color*> &colors, const base::flat_map<QByteArray, tr::phrase<>> &phrases, bool nightTheme) { static const auto serialize = [](const style::color *color) { - const auto qt = (*color)->c; - if (qt.alpha() == 255) { - return '#' - + QByteArray::number(qt.red(), 16).right(2) - + QByteArray::number(qt.green(), 16).right(2) - + QByteArray::number(qt.blue(), 16).right(2); - } - return "rgba(" - + QByteArray::number(qt.red()) + "," - + QByteArray::number(qt.green()) + "," - + QByteArray::number(qt.blue()) + "," - + QByteArray::number(qt.alpha() / 255.) + ")"; + return Serialize((*color)->c); }; static const auto escape = [](tr::phrase<> phrase) { const auto text = phrase(tr::now); @@ -63,6 +69,40 @@ namespace Ui { return result; } +QByteArray ComputeSemiTransparentOverStyle( + const QByteArray &name, + const style::color &over, + const style::color &bg) { + const auto result = [&](const QColor &c) { + return "--td-"_q + name + ':' + Serialize(c) + ';'; + }; + if (over->c.alpha() < 255) { + return result(over->c); + } + // The most transparent color that will still give the same result. + const auto r0 = bg->c.red(); + const auto g0 = bg->c.green(); + const auto b0 = bg->c.blue(); + const auto r1 = over->c.red(); + const auto g1 = over->c.green(); + const auto b1 = over->c.blue(); + const auto mina = [](int c0, int c1) { + return (c0 == c1) + ? 0 + : (c0 > c1) + ? (((c0 - c1) * 255) / c0) + : (((c1 - c0) * 255) / (255 - c0)); + }; + const auto rmina = mina(r0, r1); + const auto gmina = mina(g0, g1); + const auto bmina = mina(b0, b1); + const auto a = std::max({ rmina, gmina, bmina }); + const auto r = (r1 * 255 - r0 * (255 - a)) / a; + const auto g = (g1 * 255 - g0 * (255 - a)) / a; + const auto b = (b1 * 255 - b0 * (255 - a)) / a; + return result(QColor(r, g, b, a)); +} + QByteArray EscapeForAttribute(QByteArray value) { return value .replace('&', "&") diff --git a/Telegram/SourceFiles/ui/webview_helpers.h b/Telegram/SourceFiles/ui/webview_helpers.h index 3d8a51066..518096479 100644 --- a/Telegram/SourceFiles/ui/webview_helpers.h +++ b/Telegram/SourceFiles/ui/webview_helpers.h @@ -20,6 +20,10 @@ namespace Ui { const base::flat_map<QByteArray, const style::color*> &colors, const base::flat_map<QByteArray, tr::phrase<>> &phrases, bool nightTheme = false); +[[nodiscard]] QByteArray ComputeSemiTransparentOverStyle( + const QByteArray &name, + const style::color &over, + const style::color &bg); [[nodiscard]] QByteArray EscapeForAttribute(QByteArray value); [[nodiscard]] QByteArray EscapeForScriptString(QByteArray value); From 76314e3c03267dc06b9c6d4cd63894f0ffdd0bbb Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 13:43:55 +0200 Subject: [PATCH 139/163] Close additional windows on passcode lock. --- Telegram/SourceFiles/core/application.cpp | 40 ++++++++++++++--------- Telegram/SourceFiles/core/application.h | 1 + 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 94fa2ae4d..39b7ac32f 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -188,8 +188,11 @@ Application::Application() _platformIntegration->init(); passcodeLockChanges( - ) | rpl::start_with_next([=] { + ) | rpl::start_with_next([=](bool locked) { _shouldLockAt = 0; + if (locked) { + closeAdditionalWindows(); + } }, _lifetime); passcodeLockChanges( @@ -211,6 +214,16 @@ Application::Application() }, _lifetime); } +void Application::closeAdditionalWindows() { + Payments::CheckoutProcess::ClearAll(); + for (const auto &[index, account] : _domain->accounts()) { + if (account->sessionExists()) { + account->session().attachWebView().closeAll(); + } + } + _iv->closeAll(); +} + Application::~Application() { if (_saveSettingsTimer && _saveSettingsTimer->isActive()) { Local::writeSettings(); @@ -230,13 +243,7 @@ Application::~Application() { // // For example Domain::removeRedundantAccounts() is called from // Domain::finish() and there is a violation on Ensures(started()). - Payments::CheckoutProcess::ClearAll(); - for (const auto &[index, account] : _domain->accounts()) { - if (account->sessionExists()) { - account->session().attachWebView().closeAll(); - } - } - _iv->closeAll(); + closeAdditionalWindows(); _domain->finish(); @@ -1087,14 +1094,15 @@ void Application::checkSendPaths() { void Application::checkStartUrl() { if (!cStartUrl().isEmpty()) { const auto url = cStartUrl(); - if (url.startsWith("tonsite://", Qt::CaseInsensitive)) { - cSetStartUrl(QString()); - iv().showTonSite(url, {}); - } else if (_lastActivePrimaryWindow - && !_lastActivePrimaryWindow->locked()) { - cSetStartUrl(QString()); - if (!openLocalUrl(url, {})) { - cSetStartUrl(url); + if (!Core::App().passcodeLocked()) { + if (url.startsWith("tonsite://", Qt::CaseInsensitive)) { + cSetStartUrl(QString()); + iv().showTonSite(url, {}); + } else if (_lastActivePrimaryWindow) { + cSetStartUrl(QString()); + if (!openLocalUrl(url, {})) { + cSetStartUrl(url); + } } } } diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index eca07e751..373ba3546 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -378,6 +378,7 @@ private: void showOpenGLCrashNotification(); void clearPasscodeLock(); + void closeAdditionalWindows(); bool openCustomUrl( const QString &protocol, From cf896aeb13a28141b3a2866495b4f1e964749d08 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 13:44:07 +0200 Subject: [PATCH 140/163] Improve focusing of shown layers. --- Telegram/SourceFiles/window/section_widget.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 4ec94b0ab..5a72a6984 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -457,7 +457,11 @@ void SectionWidget::showFinished() { showChildren(); showFinishedHook(); - controller()->widget()->setInnerFocus(); + if (isAncestorOf(window()->focusWidget())) { + setInnerFocus(); + } else { + controller()->widget()->setInnerFocus(); + } } rpl::producer<int> SectionWidget::desiredHeight() const { From db80096e6b5d9b839d29abcabc6ad56e68884003 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 14:08:18 +0200 Subject: [PATCH 141/163] Use corporate sign certificate identity. --- Telegram/build/build.sh | 2 +- Telegram/build/updates.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/build/build.sh b/Telegram/build/build.sh index 08bd287ad..aa2dcc1ec 100755 --- a/Telegram/build/build.sh +++ b/Telegram/build/build.sh @@ -327,7 +327,7 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "macstore" ]; then echo "Signing the application.." if [ "$BuildTarget" == "mac" ]; then - codesign --force --deep --timestamp --options runtime --sign "Developer ID Application: John Preston" "$ReleasePath/$BundleName" --entitlements "$HomePath/Telegram/Telegram.entitlements" + codesign --force --deep --timestamp --options runtime --sign "Developer ID Application: Telegram FZ-LLC (C67CF9S4VU)" "$ReleasePath/$BundleName" --entitlements "$HomePath/Telegram/Telegram.entitlements" elif [ "$BuildTarget" == "macstore" ]; then codesign --force --timestamp --options runtime --sign "3rd Party Mac Developer Application: Telegram FZ-LLC (C67CF9S4VU)" "$ReleasePath/$BundleName/Contents/Frameworks/Breakpad.framework/Versions/A/Resources/breakpadUtilities.dylib" --entitlements "$HomePath/Telegram/Breakpad.entitlements" codesign --force --deep --timestamp --options runtime --sign "3rd Party Mac Developer Application: Telegram FZ-LLC (C67CF9S4VU)" "$ReleasePath/$BundleName" --entitlements "$HomePath/Telegram/Telegram Lite.entitlements" diff --git a/Telegram/build/updates.py b/Telegram/build/updates.py index 7a05070e7..f6f701eda 100644 --- a/Telegram/build/updates.py +++ b/Telegram/build/updates.py @@ -81,7 +81,7 @@ if building: if result != 0: finish(1, 'While stripping Telegram.') - result = subprocess.call('codesign --force --deep --timestamp --options runtime --sign "Developer ID Application: John Preston" Telegram.app --entitlements "../../Telegram/Telegram/Telegram.entitlements"', shell=True) + result = subprocess.call('codesign --force --deep --timestamp --options runtime --sign "Developer ID Application: Telegram FZ-LLC (C67CF9S4VU)" Telegram.app --entitlements "../../Telegram/Telegram/Telegram.entitlements"', shell=True) if result != 0: finish(1, 'While signing Telegram.') From 813d0501dafde8da484c6f324ddbbfff51ea0f4c Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 17:03:50 +0200 Subject: [PATCH 142/163] Fix build on Windows. --- Telegram/SourceFiles/boxes/sticker_set_box.cpp | 8 ++++---- .../SourceFiles/chat_helpers/stickers_list_widget.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index aef0dd941..d9d3b9a6f 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -1476,7 +1476,7 @@ void StickerSetBox::Inner::fillDeleteStickerBox( sticker->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(sticker); - if (const auto strong = weak.get()) { + if (const auto strong = weak.data()) { const auto paused = On(PowerSaving::kStickersPanel) || show->paused(ChatHelpers::PauseReason::Layer); paintSticker(p, index, QPoint(), paused, crl::now()); @@ -1530,14 +1530,14 @@ void StickerSetBox::Inner::fillDeleteStickerBox( Data::StickersType::Stickers); }, [](const auto &) { }); - if (const auto strong = weak.get()) { + if (const auto strong = weak.data()) { applySet(result); } - if (const auto strongBox = weakBox.get()) { + if (const auto strongBox = weakBox.data()) { strongBox->closeBox(); } }).fail([=](const MTP::Error &error) { - if (const auto strongBox = weakBox.get()) { + if (const auto strongBox = weakBox.data()) { strongBox->uiShow()->showToast(error.type()); } }).send(); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 4258edbac..e1e40cc62 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -1735,7 +1735,7 @@ void StickersListWidget::showStickerSetBox( document->owner().stickers().updated( Data::StickersType::Stickers) ) | rpl::start_with_next([=, weak = Ui::MakeWeak(this)] { - if (weak.get()) { + if (weak.data()) { showStickerSetBox(document, setId); } lifetime->destroy(); From a422aec99a08044b29606d904551fbbe76af4a37 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 18:59:13 +0200 Subject: [PATCH 143/163] Fix cancel of bot app confirm. --- Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 5810310fa..e5c9ac6e3 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -787,6 +787,10 @@ void WebViewInstance::confirmAppOpen( done((*allowed) && (*allowed)->checked()); close(); }; + const auto cancelled = [=](Fn<void()> close) { + botClose(); + close(); + }; Ui::ConfirmBox(box, { tr::lng_allow_bot_webview( tr::now, @@ -794,7 +798,7 @@ void WebViewInstance::confirmAppOpen( Ui::Text::Bold(_bot->name()), Ui::Text::RichLangValue), crl::guard(this, callback), - crl::guard(this, [=] { botClose(); }), + crl::guard(this, cancelled), }); if (writeAccess) { (*allowed) = box->addRow( From 148690d8b1a2ea1c65ee2e7311388370ef0920c8 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 18:59:31 +0200 Subject: [PATCH 144/163] Open .ton as a tonsite. --- Telegram/SourceFiles/core/click_handler_types.cpp | 4 +++- Telegram/SourceFiles/core/local_url_handlers.cpp | 7 +++++++ Telegram/SourceFiles/iv/iv_controller.cpp | 10 +++++++--- Telegram/lib_ui | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 915b12f4f..b1ba3d166 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -122,7 +122,9 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) { return result; }())); } else { - const auto parsedUrl = QUrl::fromUserInput(url); + const auto parsedUrl = url.startsWith(u"tonsite://"_q) + ? QUrl(url) + : QUrl::fromUserInput(url); if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) { const auto my = context.value<ClickHandlerContext>(); if (!my.show) { diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 6777464c1..28aa99822 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -1337,6 +1337,13 @@ QString TryConvertUrlToLocal(QString url) { using namespace qthelp; auto matchOptions = RegExOption::CaseInsensitive; + auto tonsiteMatch = (url.indexOf(u".ton") >= 0) + ? regex_match(u"^(https?://)?[^/@:]+\\.ton($|/)"_q, url, matchOptions) + : RegularExpressionMatch(QRegularExpressionMatch()); + if (tonsiteMatch) { + const auto protocol = tonsiteMatch->captured(1); + return u"tonsite://"_q + url.mid(protocol.size()); + } auto subdomainMatch = regex_match(u"^(https?://)?([a-zA-Z0-9\\_]+)\\.t\\.me(/\\d+)?/?(\\?.+)?"_q, url, matchOptions); if (subdomainMatch) { const auto name = subdomainMatch->captured(2); diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index e369ab5c0..067e90b48 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -166,12 +166,16 @@ namespace { }; auto parsed = QUrl(value); if (parsed.isValid()) { + const auto host = ChangeHost(parsed.host()); + const auto emptyPath = parsed.path().isEmpty(); parsed.setScheme("tonsite"); - parsed.setHost(ChangeHost(parsed.host())); - if (parsed.path().isEmpty()) { + parsed.setHost(host); + if (emptyPath) { parsed.setPath(u"/"_q); } - return parsed.toString(); + if (parsed.isValid()) { + return parsed.toString(); + } } const auto part = value.mid(u"https://"_q.size()); const auto split = part.indexOf('/'); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 97bfa6cef..8db5d1aa5 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 97bfa6cef474b3b311a178ff1a1042d09972a7c7 +Subproject commit 8db5d1aa533334c75ed2598ecf3607768ae9b418 From 0b6bd7075ae82aa05cb05e769855e9a507e327d9 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 19:06:47 +0200 Subject: [PATCH 145/163] Version 5.3. - View recent and popular web apps in chats search. - Open several web apps in different windows. - Gift Telegram Stars to your friends. - Send location marks and venues. - Open tonsite:// links in webview. - Edit order of stickers in your packs. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/build/version | 12 ++++++------ changelog.txt | 9 +++++++++ 6 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 3d4040dd7..cffa28fb5 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ <Identity Name="TelegramMessengerLLP.TelegramDesktop" ProcessorArchitecture="ARCHITECTURE" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" - Version="5.2.6.0" /> + Version="5.3.0.0" /> <Properties> <DisplayName>Telegram Desktop</DisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 83eecf0a7..b9e1fb916 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,6,0 - PRODUCTVERSION 5,2,6,0 + FILEVERSION 5,3,0,0 + PRODUCTVERSION 5,3,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", "5.2.6.0" + VALUE "FileVersion", "5.3.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.6.0" + VALUE "ProductVersion", "5.3.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 40bb30f11..0f70bef0e 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,2,6,0 - PRODUCTVERSION 5,2,6,0 + FILEVERSION 5,3,0,0 + PRODUCTVERSION 5,3,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", "5.2.6.0" + VALUE "FileVersion", "5.3.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.2.6.0" + VALUE "ProductVersion", "5.3.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 111881fd4..bb5f9b405 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 = 5002006; -constexpr auto AppVersionStr = "5.2.6"; -constexpr auto AppBetaVersion = true; +constexpr auto AppVersion = 5003000; +constexpr auto AppVersionStr = "5.3"; +constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index f10750e38..4829ec4b2 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5002006 -AppVersionStrMajor 5.2 -AppVersionStrSmall 5.2.6 -AppVersionStr 5.2.6 -BetaChannel 1 +AppVersion 5003000 +AppVersionStrMajor 5.3 +AppVersionStrSmall 5.3 +AppVersionStr 5.3.0 +BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.2.6.beta +AppVersionOriginal 5.3 diff --git a/changelog.txt b/changelog.txt index 5ec213f7e..f8929141f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,12 @@ +5.3 (31.07.24) + +- View recent and popular web apps in chats search. +- Open several web apps in different windows. +- Gift Telegram Stars to your friends. +- Send location marks and venues. +- Open tonsite:// links in webview. +- Edit order of stickers in your packs. + 5.2.6 beta (29.07.24) - Fix launching on X11. (Linux) From b7c14f17a7b42272d164834af5e82d33cfab46c6 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Wed, 31 Jul 2024 23:06:39 +0200 Subject: [PATCH 146/163] Version 5.3: Fix build with Xcode. --- Telegram/lib_webview | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 2c95b169b..4b2399232 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 2c95b169b30035a6e85d0f5534c4477adff393e2 +Subproject commit 4b239923219d6a186b96de913506a0777e86c68e From 503c3c7b00409acb4b76893a40b07f11fdf4474b Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 00:20:34 +0300 Subject: [PATCH 147/163] Version 5.3: Update cmake_helpers submodule. --- cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake b/cmake index 17c758e2b..721383d0b 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 17c758e2b9f09a58f14582bde3561873ce026039 +Subproject commit 721383d0b901cc5d5cbeec29386671d145f5a1e5 From 993c0ee64817597b0cdaa09e394ab086c9bd61a0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin <fedin-ilja2010@ya.ru> Date: Thu, 1 Aug 2024 04:49:52 +0400 Subject: [PATCH 148/163] Ensure fake modal widget is a window --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 32437fb77..b49cef3bb 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -109,6 +109,7 @@ void PortalAutostart(bool enabled, Fn<void(bool)> done) { auto &raw = **window; raw.setAttribute(Qt::WA_DontShowOnScreen); + raw.setWindowFlag(Qt::Window); raw.setWindowModality(Qt::WindowModal); raw.show(); From 7c1510b6112d07cf0ff2e87720e2269fb82e5294 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 12:55:23 +0200 Subject: [PATCH 149/163] Fix crash on empty gift receivers list. --- Telegram/SourceFiles/boxes/gift_premium_box.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 3b8f6f1b5..d478a7c6e 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1014,6 +1014,7 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) { if (users.empty()) { show->showToast( tr::lng_settings_gift_premium_choose(tr::now)); + return; } const auto giftBox = show->show( Box(GiftsBox, _controller, users, api, ref)); From 6b96466c5e7b79bdd2fabd4fbd822739d49e1987 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 12:55:34 +0200 Subject: [PATCH 150/163] Fix possible crash in forward preview. --- .../history/view/controls/history_view_draft_options.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a0d3d6066..ee24c7eb4 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -370,7 +370,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { userpicTop, width(), st::msgPhotoSize); - } else if (const auto info = item->originalHiddenSenderInfo()) { + } else if (const auto info = item->displayHiddenSenderInfo()) { if (info->customUserpic.empty()) { info->emptyUserpic.paintCircle( p, From 51fc104c60efe0e697af8f0c75098a218b067753 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 13:28:03 +0200 Subject: [PATCH 151/163] Show bot active users count in status. --- Telegram/SourceFiles/data/data_peer_values.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index e4694f88d..a0578000f 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -53,6 +53,12 @@ std::optional<QString> OnlineTextSpecial(not_null<UserData*> user) { } else if (user->isSupport()) { return tr::lng_status_support(tr::now); } else if (user->isBot()) { + if (const auto count = user->botInfo->activeUsers) { + return tr::lng_bot_status_users( + tr::now, + lt_count_decimal, + count); + } return tr::lng_status_bot(tr::now); } else if (user->isServiceUser()) { return tr::lng_status_support(tr::now); From 7f3dc27aa9207aa869c76176763b93988cab85af Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 13:28:25 +0200 Subject: [PATCH 152/163] Allow removing mini apps from recents. --- .../dialogs/ui/dialogs_suggestions.cpp | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 18ae94b37..a1391e3ba 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -166,16 +166,17 @@ void FillEntryMenu( .icon = &st::menuIconDeleteAttention, .isAttention = true, }); - - add({ - .text = descriptor.removeAllText, - .handler = RemoveAllConfirm( - descriptor.controller, - descriptor.removeAllConfirm, - descriptor.removeAll), - .icon = &st::menuIconCancelAttention, - .isAttention = true, - }); + if (!descriptor.removeAllText.isEmpty()) { + add({ + .text = descriptor.removeAllText, + .handler = RemoveAllConfirm( + descriptor.controller, + descriptor.removeAllConfirm, + descriptor.removeAll), + .icon = &st::menuIconCancelAttention, + .isAttention = true, + }); + } } RecentRow::RecentRow(not_null<PeerData*> peer) @@ -422,6 +423,9 @@ public: not_null<Window::SessionController*> window); void prepare() override; + base::unique_qptr<Ui::PopupMenu> rowContextMenu( + QWidget *parent, + not_null<PeerListRow*> row) override; void load(); @@ -1031,6 +1035,35 @@ void RecentAppsController::prepare() { }, _lifetime); } +base::unique_qptr<Ui::PopupMenu> RecentAppsController::rowContextMenu( + QWidget *parent, + not_null<PeerListRow*> row) { + auto result = base::make_unique_q<Ui::PopupMenu>( + parent, + st::popupMenuWithIcons); + const auto peer = row->peer(); + const auto weak = base::make_weak(this); + const auto session = &this->session(); + const auto removeOne = crl::guard(session, [=] { + if (weak) { + const auto rowId = peer->id.value; + if (const auto row = delegate()->peerListFindRow(rowId)) { + setCount(std::max(0, countCurrent() - 1)); + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); + } + } + session->topBotApps().remove(peer); + }); + FillEntryMenu(Ui::Menu::CreateAddActionCallback(result), { + .controller = window(), + .peer = peer, + .removeOneText = tr::lng_recent_remove(tr::now), + .removeOne = removeOne, + }); + return result; +} + void RecentAppsController::load() { session().topBotApps().reload(); } From 11c91c1a42ada13d2b3af04960cf823cb6a017c7 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 14:13:27 +0200 Subject: [PATCH 153/163] Don't show recent apps in popular apps. --- .../dialogs/ui/dialogs_suggestions.cpp | 62 ++++++++++++++----- .../dialogs/ui/dialogs_suggestions.h | 2 + 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index a1391e3ba..96529c241 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -429,11 +429,15 @@ public: void load(); + [[nodiscard]] rpl::producer<> refreshed() const; + [[nodiscard]] bool shown(not_null<PeerData*> peer) const; + private: void appendRow(not_null<UserData*> bot); void fill(); std::vector<not_null<UserData*>> _bots; + rpl::event_stream<> _refreshed; rpl::lifetime _lifetime; }; @@ -442,7 +446,9 @@ class PopularAppsController final : public Suggestions::ObjectListController { public: explicit PopularAppsController( - not_null<Window::SessionController*> window); + not_null<Window::SessionController*> window, + Fn<bool(not_null<PeerData*>)> filterOut, + rpl::producer<> filterOutRefreshes); void prepare() override; @@ -452,6 +458,8 @@ private: void fill(); void appendRow(not_null<UserData*> bot); + Fn<bool(not_null<PeerData*>)> _filterOut; + rpl::producer<> _filterOutRefreshes; History *_activeHistory = nullptr; bool _requested = false; rpl::lifetime _lifetime; @@ -1068,6 +1076,14 @@ void RecentAppsController::load() { session().topBotApps().reload(); } +rpl::producer<> RecentAppsController::refreshed() const { + return _refreshed.events(); +} + +bool RecentAppsController::shown(not_null<PeerData*> peer) const { + return delegate()->peerListFindRow(peer->id.value) != nullptr; +} + void RecentAppsController::fill() { const auto count = countCurrent(); const auto limit = expandedCurrent() @@ -1087,6 +1103,8 @@ void RecentAppsController::fill() { } } delegate()->peerListRefreshRows(); + + _refreshed.fire({}); } void RecentAppsController::appendRow(not_null<UserData*> bot) { @@ -1099,13 +1117,21 @@ void RecentAppsController::appendRow(not_null<UserData*> bot) { } PopularAppsController::PopularAppsController( - not_null<Window::SessionController*> window) -: ObjectListController(window) { + not_null<Window::SessionController*> window, + Fn<bool(not_null<PeerData*>)> filterOut, + rpl::producer<> filterOutRefreshes) +: ObjectListController(window) +, _filterOut(std::move(filterOut)) +, _filterOutRefreshes(std::move(filterOutRefreshes)) { } void PopularAppsController::prepare() { setupPlainDivider(tr::lng_bot_apps_popular()); - fill(); + rpl::single() | rpl::then( + std::move(_filterOutRefreshes) + ) | rpl::start_with_next([=] { + fill(); + }, _lifetime); } void PopularAppsController::load() { @@ -1122,13 +1148,13 @@ void PopularAppsController::load() { } void PopularAppsController::fill() { - const auto attachWebView = &session().attachWebView(); - const auto &list = attachWebView->popularAppBots(); - if (list.empty()) { - return; + while (delegate()->peerListFullRowsCount()) { + delegate()->peerListRemoveRow(delegate()->peerListRowAt(0)); } - for (const auto &bot : list) { - appendRow(bot); + for (const auto &bot : session().attachWebView().popularAppBots()) { + if (!_filterOut || !_filterOut(bot)) { + appendRow(bot); + } } delegate()->peerListRefreshRows(); setCount(delegate()->peerListFullRowsCount()); @@ -1136,10 +1162,10 @@ void PopularAppsController::fill() { void PopularAppsController::appendRow(not_null<UserData*> bot) { auto row = std::make_unique<PeerListRow>(bot); - if (const auto count = bot->botInfo->activeUsers) { - row->setCustomStatus( - tr::lng_bot_status_users(tr::now, lt_count_decimal, count)); - } + //if (const auto count = bot->botInfo->activeUsers) { + // row->setCustomStatus( + // tr::lng_bot_status_users(tr::now, lt_count_decimal, count)); + //} delegate()->peerListAppendRow(std::move(row)); } @@ -1896,6 +1922,10 @@ auto Suggestions::setupRecommendations() -> std::unique_ptr<ObjectList> { auto Suggestions::setupRecentApps() -> std::unique_ptr<ObjectList> { const auto controller = lifetime().make_state<RecentAppsController>( _controller); + _recentAppsShows = [=](not_null<PeerData*> peer) { + return controller->shown(peer); + }; + _recentAppsRefreshed = controller->refreshed(); auto result = setupObjectList( _appsScroll.get(), @@ -1952,7 +1982,9 @@ auto Suggestions::setupRecentApps() -> std::unique_ptr<ObjectList> { auto Suggestions::setupPopularApps() -> std::unique_ptr<ObjectList> { const auto controller = lifetime().make_state<PopularAppsController>( - _controller); + _controller, + _recentAppsShows, + rpl::duplicate(_recentAppsRefreshed)); const auto addToScroll = [=] { const auto wrap = _recentApps->wrap; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 549851d72..cf8b21780 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -189,6 +189,8 @@ private: const std::unique_ptr<Ui::ElasticScroll> _appsScroll; const not_null<Ui::VerticalLayout*> _appsContent; + rpl::producer<> _recentAppsRefreshed; + Fn<bool(not_null<PeerData*>)> _recentAppsShows; const std::unique_ptr<ObjectList> _recentApps; const std::unique_ptr<ObjectList> _popularApps; From 0af3028cd6868ad56d1d159e14f00f0ccce1edbd Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 14:47:16 +0200 Subject: [PATCH 154/163] Allow opening tonsite:// from web apps. --- Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index e5c9ac6e3..3f549edb1 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1117,9 +1117,10 @@ Webview::ThemeParams WebViewInstance::botThemeParams() { bool WebViewInstance::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)) { + if (Core::InternalPassportLink(local)) { + return true; + } else if (!local.startsWith(u"tg://"_q, Qt::CaseInsensitive) + && !local.startsWith(u"tonsite://"_q, Qt::CaseInsensitive)) { return false; } const auto bot = _bot; From 4864a6996f8f0b06c56dce3b89825eaf7affc009 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 14:47:33 +0200 Subject: [PATCH 155/163] Open links from tonsite:// externally. --- Telegram/SourceFiles/iv/iv_controller.cpp | 22 +++++++++++++++++----- Telegram/SourceFiles/iv/iv_controller.h | 1 + 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 067e90b48..11df08527 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -257,10 +257,15 @@ void Controller::initControls() { _subtitleText.value(), st::ivSubtitle); _subtitle->setSelectable(true); - _subtitleText.value( - ) | rpl::start_with_next([=](const QString &subtitle) { + + _windowTitleText = _subtitleText.value( + ) | rpl::map([=](const QString &subtitle) { const auto prefix = tr::lng_iv_window_title(tr::now); - _window->setWindowTitle(prefix + ' ' + QChar(0x2014) + ' ' + subtitle); + return prefix + ' ' + QChar(0x2014) + ' ' + subtitle; + }); + _windowTitleText.value( + ) | rpl::start_with_next([=](const QString &title) { + _window->setWindowTitle(title); }, _subtitle->lifetime()); _menuToggle.create(_subtitleWrap.get(), st::ivMenuToggle); @@ -355,6 +360,7 @@ void Controller::showTonSite( }) | rpl::map([=](QString value) { return HttpsToTonsite(value); }); + _windowTitleText = _subtitleText; _menuToggle->hide(); } @@ -488,7 +494,12 @@ void Controller::createWebview(const Webview::StorageId &storageId) { }, _container->lifetime()); raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) { - return true; + if (uri.startsWith(u"http://desktop-app-resource/"_q) + || QUrl(uri).host().toLower().endsWith(u".magic.org"_q)) { + return true; + } + _events.fire({ .type = Event::Type::OpenLink, .url = uri }); + return false; }); raw->setNavigationDoneHandler([=](bool success) { }); @@ -578,7 +589,8 @@ void Controller::createWebview(const Webview::StorageId &storageId) { || index >= _pages.size()) { return Webview::DataResult::Failed; } - return finishWith(WrapPage(_pages[index]), "text/html; charset=utf-8"); + return finishWith( + WrapPage(_pages[index]), "text/html; charset=utf-8"); } else if (id.starts_with("page") && id.ends_with(".json")) { auto index = 0; const auto result = std::from_chars( diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h index c92f4c4b2..30e975d02 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -132,6 +132,7 @@ private: std::unique_ptr<Ui::RpWidget> _subtitleWrap; rpl::variable<QString> _url; rpl::variable<QString> _subtitleText; + rpl::variable<QString> _windowTitleText; std::unique_ptr<Ui::FlatLabel> _subtitle; Ui::Animations::Simple _subtitleBackShift; Ui::Animations::Simple _subtitleForwardShift; From 281ad01b850c07a0dc8c32fdc5cdbb8306c3b500 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 16:35:08 +0200 Subject: [PATCH 156/163] Add ada library. --- Telegram/build/docker/centos_env/Dockerfile | 13 +++++++++++ Telegram/build/prepare/prepare.py | 24 +++++++++++++++++++++ cmake | 2 +- snap/snapcraft.yaml | 18 ++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 568d6663a..82f184cb8 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -801,6 +801,18 @@ RUN cmake --build out --config Debug --parallel \ && find out -mindepth 1 -maxdepth 1 ! -name Debug -exec rm -rf {} \; {%- endif %} +FROM builder AS ada +RUN git clone -b v2.9.0 --depth=1 {{ GIT }}/ada-url/ada.git \ + && cd ada \ + && cmake -GNinja -B build . \ + -D CMAKE_BUILD_TYPE=None \ + -D ADA_TESTING=OFF \ + -D ADA_TOOLS=OFF \ + && cmake --build build --parallel \ + && DESTDIR="{{ LibrariesPath }}/ada-cache" cmake --install build \ + && cd .. \ + && rm -rf ada + FROM builder COPY --link --from=zlib {{ LibrariesPath }}/zlib-cache / COPY --link --from=xz {{ LibrariesPath }}/xz-cache / @@ -844,6 +856,7 @@ COPY --link --from=breakpad {{ LibrariesPath }}/breakpad-cache / COPY --link --from=webrtc {{ LibrariesPath }}/tg_owt tg_owt COPY --link --from=webrtc_release {{ LibrariesPath }}/tg_owt/out/Release tg_owt/out/Release COPY --link --from=libwebp {{ LibrariesPath }}/libwebp-cache / +COPY --link --from=ada {{ LibrariesPath }}/ada-cache / {%- if DEBUG %} COPY --link --from=webrtc_debug {{ LibrariesPath }}/tg_owt/out/Debug tg_owt/out/Debug diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 83e16284f..67e767003 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1831,6 +1831,30 @@ release: lipo -create Release.arm64/libtg_owt.a Release.x86_64/libtg_owt.a -output Release/libtg_owt.a """) +stage('ada', """ + git clone -b v2.9.0 https://github.com/ada-url/ada.git + cd ada +win: + cmake -B out . ^ + -A %WIN32X64% ^ + -D ADA_TESTING=OFF ^ + -D ADA_TOOLS=OFF ^ + -D CMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>" ^ + -D CMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ + -D CMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" + cmake --build out --config Debug --parallel + cmake --build out --config Release --parallel +mac: + CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake -B build . \\ + -D ADA_TESTING=OFF \\ + -D ADA_TOOLS=OFF \\ + -D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=$MACOSX_DEPLOYMENT_TARGET \\ + -D CMAKE_OSX_ARCHITECTURES="x86_64;arm64" \\ + -D CMAKE_INSTALL_PREFIX:STRING=$USED_PREFIX + cmake --build build $MAKE_THREADS_CNT + cmake --install build +""") + stage('protobuf', """ win: git clone --recursive -b v21.9 https://github.com/protocolbuffers/protobuf diff --git a/cmake b/cmake index 721383d0b..6a1ac8a4e 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 721383d0b901cc5d5cbeec29386671d145f5a1e5 +Subproject commit 6a1ac8a4eebb968ff6ca538088006a3d06d47421 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b5f71d99f..8378b599f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -488,3 +488,21 @@ parts: after: - ffmpeg - libjxl + + ada: + source: https://github.com/ada-url/ada.git + source-depth: 1 + source-tag: v2.9.0 + plugin: cmake + build-environment: + - LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s + cmake-generator: Ninja + cmake-parameters: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_INSTALL_PREFIX=/usr + - -DADA_TESTING=OFF + - -DADA_TOOLS=OFF + prime: + - -./usr/include + - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/cmake + - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.a From 2ff0ed50be10da0f96d530205d032aedffb57642 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 16:35:55 +0200 Subject: [PATCH 157/163] Encode/Decode tonsite:// punycode correctly. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/iv/iv_controller.cpp | 78 ++++++++++++++--------- Telegram/SourceFiles/iv/iv_controller.h | 1 + Telegram/SourceFiles/iv/iv_instance.cpp | 6 +- Telegram/cmake/td_iv.cmake | 1 + 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 64bbb68d8..3924a85f6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5318,6 +5318,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_iv_join_channel" = "Join"; "lng_iv_window_title" = "Instant View"; "lng_iv_wrong_layout" = "Wrong layout?"; +"lng_iv_not_supported" = "This link appears to be invalid."; "lng_limit_download_title" = "Download speed limited"; "lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}."; diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 11df08527..759ef0db3 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -44,6 +44,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <QtGui/QWindow> #include <charconv> +#include <ada.h> + namespace Iv { namespace { @@ -137,50 +139,62 @@ namespace { [[nodiscard]] QString TonsiteToHttps(QString value) { const auto ChangeHost = [](QString tonsite) { + const auto fake = "http://" + tonsite.toStdString(); + const auto parsed = ada::parse<ada::url>(fake); + if (!parsed) { + return QString(); + } + tonsite = QString::fromStdString(parsed->get_hostname()); tonsite = tonsite.replace('-', "-h"); tonsite = tonsite.replace('.', "-d"); return tonsite + ".magic.org"; }; - auto parsed = QUrl(value); - if (parsed.isValid()) { - parsed.setScheme("https"); - parsed.setHost(ChangeHost(parsed.host())); - if (parsed.path().isEmpty()) { - parsed.setPath(u"/"_q); - } - return parsed.toString(); + const auto prefix = u"tonsite://"_q; + if (!value.toLower().startsWith(prefix)) { + return QString(); } - const auto part = value.mid(u"tonsite://"_q.size()); + const auto part = value.mid(prefix.size()); const auto split = part.indexOf('/'); - return "https://" - + ChangeHost((split < 0) ? part : part.left(split)) - + ((split < 0) ? u"/"_q : part.mid(split)); + const auto host = ChangeHost((split < 0) ? part : part.left(split)); + if (host.isEmpty()) { + return QString(); + } + return "https://" + host + ((split < 0) ? u"/"_q : part.mid(split)); } [[nodiscard]] QString HttpsToTonsite(QString value) { const auto ChangeHost = [](QString https) { - https.replace(".magic.org", QString()); + const auto dot = https.indexOf('.'); + if (dot < 0 || https.mid(dot).toLower() != u".magic.org"_q) { + return QString(); + } + https = https.mid(0, dot); https = https.replace("-d", "."); https = https.replace("-h", "-"); - return https; + auto parts = https.split('.'); + for (auto &part : parts) { + if (part.startsWith(u"xn--"_q)) { + const auto utf8 = part.mid(4).toStdString(); + auto out = std::u32string(); + if (ada::idna::punycode_to_utf32(utf8, out)) { + part = QString::fromUcs4(out.data(), out.size()); + } + } + } + return parts.join('.'); }; - auto parsed = QUrl(value); - if (parsed.isValid()) { - const auto host = ChangeHost(parsed.host()); - const auto emptyPath = parsed.path().isEmpty(); - parsed.setScheme("tonsite"); - parsed.setHost(host); - if (emptyPath) { - parsed.setPath(u"/"_q); - } - if (parsed.isValid()) { - return parsed.toString(); - } + const auto prefix = u"https://"_q; + if (!value.toLower().startsWith(prefix)) { + return value; } - const auto part = value.mid(u"https://"_q.size()); + const auto part = value.mid(prefix.size()); const auto split = part.indexOf('/'); + const auto host = ChangeHost((split < 0) ? part : part.left(split)); + if (host.isEmpty()) { + return value; + } return "tonsite://" - + ChangeHost((split < 0) ? part : part.left(split)) + + host + ((split < 0) ? u"/"_q : part.mid(split)); } @@ -342,10 +356,16 @@ void Controller::update(Prepared page) { } } +bool Controller::IsGoodTonSiteUrl(const QString &uri) { + return !TonsiteToHttps(uri).isEmpty(); +} + void Controller::showTonSite( const Webview::StorageId &storageId, QString uri) { const auto url = TonsiteToHttps(uri); + Assert(!url.isEmpty()); + if (!_webview) { createWebview(storageId); } @@ -360,7 +380,7 @@ void Controller::showTonSite( }) | rpl::map([=](QString value) { return HttpsToTonsite(value); }); - _windowTitleText = _subtitleText; + _windowTitleText = _subtitleText.value(); _menuToggle->hide(); } diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h index 30e975d02..9b5af4e6b 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -76,6 +76,7 @@ public: base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues); void update(Prepared page); + [[nodiscard]] static bool IsGoodTonSiteUrl(const QString &uri); void showTonSite(const Webview::StorageId &storageId, QString uri); [[nodiscard]] bool active() const; diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp index 3b327e5a6..8078828d6 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "ui/layers/layer_widget.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/basic_click_handlers.h" #include "webview/webview_data_stream_memory.h" #include "webview/webview_interface.h" @@ -1074,7 +1075,10 @@ void Instance::openWithIvPreferred( void Instance::showTonSite( const QString &uri, QVariant context) { - if (Platform::IsMac()) { + if (!Controller::IsGoodTonSiteUrl(uri)) { + Ui::Toast::Show(tr::lng_iv_not_supported(tr::now)); + return; + } else if (Platform::IsMac()) { // Otherwise IV is not visible under the media viewer. Core::App().hideMediaView(); } diff --git a/Telegram/cmake/td_iv.cmake b/Telegram/cmake/td_iv.cmake index 602abf41c..1d4edf9f5 100644 --- a/Telegram/cmake/td_iv.cmake +++ b/Telegram/cmake/td_iv.cmake @@ -38,6 +38,7 @@ PUBLIC tdesktop::td_scheme PRIVATE desktop-app::lib_webview + desktop-app::external_ada tdesktop::td_lang tdesktop::td_ui ) From 74f7fa80b7882fb67baa42e0138379aa36e9f455 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 16:36:13 +0200 Subject: [PATCH 158/163] Fix opening .ton links. --- Telegram/SourceFiles/core/ui_integration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index bf1b49432..60c9b0045 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -239,7 +239,7 @@ bool UiIntegration::handleUrlClick( Core::App().openLocalUrl(local, context); return true; } else if (local.startsWith(u"tonsite://"_q, Qt::CaseInsensitive)) { - Core::App().iv().showTonSite(url, context); + Core::App().iv().showTonSite(local, context); return true; } else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) { Core::App().openInternalUrl(local, context); From e9bb6f65e3610fd55b3f19b8d2eb6e88ad627bf4 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 16:36:38 +0200 Subject: [PATCH 159/163] Update submodules. --- Telegram/lib_webrtc | 2 +- Telegram/lib_webview | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index d9a08df0c..022203200 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit d9a08df0c0b64e4323e23c9ccefe754fd86d0f44 +Subproject commit 0222032008a93fb6f198fdeeafd4ee063d0b624b diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 4b2399232..c27c69953 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 4b239923219d6a186b96de913506a0777e86c68e +Subproject commit c27c69953db52cfcb56abc3d422764f0fb4c2152 From 65a14bcab4a8fdb6875fc6f3b5ea96f642b0f547 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 1 Aug 2024 15:01:46 +0300 Subject: [PATCH 160/163] Removed placeholder from input field in box for renaming sticker set. --- Telegram/SourceFiles/chat_helpers/chat_helpers.style | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index d48f32c00..63cf5048a 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1414,8 +1414,15 @@ editTagLimit: FlatLabel(defaultFlatLabel) { } editStickerSetNameField: InputField(defaultInputField) { - textMargins: margins(0px, 28px, 26px, 4px); - heightMax: 55px; + textMargins: margins(0px, 8px, 26px, 4px); + heightMin: 36px; + heightMax: 36px; + placeholderFg: placeholderFg; + placeholderFgActive: placeholderFgActive; + placeholderFgError: placeholderFgActive; + placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; } editStickerSetNameLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { color: lightButtonFg; From 4a5d8046d5de32b1990fcd39dc12c7b04328e8ab Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 1 Aug 2024 17:18:31 +0200 Subject: [PATCH 161/163] Version 5.3.1. - Open normal links from tonsite-s in system browser. - Fix unicode tonsite:// links. - Fix crash on Linux X11. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- changelog.txt | 6 ++++++ 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index cffa28fb5..261fb0911 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ <Identity Name="TelegramMessengerLLP.TelegramDesktop" ProcessorArchitecture="ARCHITECTURE" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" - Version="5.3.0.0" /> + Version="5.3.1.0" /> <Properties> <DisplayName>Telegram Desktop</DisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index b9e1fb916..eb9e2d265 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,3,0,0 - PRODUCTVERSION 5,3,0,0 + FILEVERSION 5,3,1,0 + PRODUCTVERSION 5,3,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", "5.3.0.0" + VALUE "FileVersion", "5.3.1.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.3.0.0" + VALUE "ProductVersion", "5.3.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 0f70bef0e..9e4b2aced 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,3,0,0 - PRODUCTVERSION 5,3,0,0 + FILEVERSION 5,3,1,0 + PRODUCTVERSION 5,3,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", "5.3.0.0" + VALUE "FileVersion", "5.3.1.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.3.0.0" + VALUE "ProductVersion", "5.3.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index bb5f9b405..146e4b1f0 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 = 5003000; -constexpr auto AppVersionStr = "5.3"; +constexpr auto AppVersion = 5003001; +constexpr auto AppVersionStr = "5.3.1"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 4829ec4b2..928f3047d 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5003000 +AppVersion 5003001 AppVersionStrMajor 5.3 -AppVersionStrSmall 5.3 -AppVersionStr 5.3.0 +AppVersionStrSmall 5.3.1 +AppVersionStr 5.3.1 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.3 +AppVersionOriginal 5.3.1 diff --git a/changelog.txt b/changelog.txt index f8929141f..cf6616399 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +5.3.1 (01.08.24) + +- Open normal links from tonsite-s in system browser. +- Fix unicode tonsite:// links. +- Fix crash on Linux X11. + 5.3 (31.07.24) - View recent and popular web apps in chats search. From a982560a62a5cd2932491816cd20239707e1d91b Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 2 Aug 2024 05:31:59 +0300 Subject: [PATCH 162/163] Fix build on Linux. --- Telegram/lib_webrtc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 022203200..8751e27d5 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 0222032008a93fb6f198fdeeafd4ee063d0b624b +Subproject commit 8751e27d50d2f26b5d20673e5ddba38e90953570 From 0de50808743cfeaff0925cf7a63fdda207e29e85 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 2 Aug 2024 18:12:03 +0300 Subject: [PATCH 163/163] Version 5.3.2. - Fix crash on launch by focing non-LTO jemalloc build. Fixes #28213, fixes #28221. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- changelog.txt | 4 ++++ cmake | 2 +- 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 261fb0911..89f5f8761 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ <Identity Name="TelegramMessengerLLP.TelegramDesktop" ProcessorArchitecture="ARCHITECTURE" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" - Version="5.3.1.0" /> + Version="5.3.2.0" /> <Properties> <DisplayName>Telegram Desktop</DisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index eb9e2d265..eb4271d8a 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,3,1,0 - PRODUCTVERSION 5,3,1,0 + FILEVERSION 5,3,2,0 + PRODUCTVERSION 5,3,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.3.1.0" + VALUE "FileVersion", "5.3.2.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.3.1.0" + VALUE "ProductVersion", "5.3.2.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 9e4b2aced..d7e88ba04 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,3,1,0 - PRODUCTVERSION 5,3,1,0 + FILEVERSION 5,3,2,0 + PRODUCTVERSION 5,3,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.3.1.0" + VALUE "FileVersion", "5.3.2.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.3.1.0" + VALUE "ProductVersion", "5.3.2.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 146e4b1f0..9fdc8cf18 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 = 5003001; -constexpr auto AppVersionStr = "5.3.1"; +constexpr auto AppVersion = 5003002; +constexpr auto AppVersionStr = "5.3.2"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 928f3047d..37a420e7a 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5003001 +AppVersion 5003002 AppVersionStrMajor 5.3 -AppVersionStrSmall 5.3.1 -AppVersionStr 5.3.1 +AppVersionStrSmall 5.3.2 +AppVersionStr 5.3.2 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.3.1 +AppVersionOriginal 5.3.2 diff --git a/changelog.txt b/changelog.txt index cf6616399..e43e0dbe2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +5.3.2 (02.08.24) + +- Fix crash on launch in some Linux systems. + 5.3.1 (01.08.24) - Open normal links from tonsite-s in system browser. diff --git a/cmake b/cmake index 6a1ac8a4e..08de4f18f 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 6a1ac8a4eebb968ff6ca538088006a3d06d47421 +Subproject commit 08de4f18f5e4459689957b3aa115e10d8cbef9d6