From e6e1b9446d692d1e8b8869edb2c9bc6427bb6773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Novomesk=C3=BD?= 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 2b0c82ed0d..71c9533ebe 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 454da33b19..033424e1d6 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?= 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 60d60ea473..8b1f71c68f 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$<$: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 5742caae65..4a4bc4cd34 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 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 0975b5c6f1..da359769fa 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 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 d530e4779b..eebbb18480 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 05a03df4dc..aa14b12c1b 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 7a3b133c49..a35cfd3c44 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 cb79c3beb5..aed4cc6e34 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 9baca321c9..695f8f58aa 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 886cd9935b..fbae709660 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 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 ca0c7bf097..6de53f12d6 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 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 f30400147d..1a50fd2300 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 ebd8609ee7..067733899d 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 e78642348d..baab38fb77 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 9f48191f02..3f4ce02f2c 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)> 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(a02g%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~@X33)zw!sN(u`bU8H)|=AYM|>eYYz^Or9!M}t?YcWT^w`}XavTT;KY zM7Xr3zIySZLn+{yieQ+wQ0KCTK`XcH+SN7jX_2MIn_?kM~Sob(CO2s zw{JI}eb$Wk;Nd<0cJADnW7fTN(FA+>Cf}JpY9S#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)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- zii%2QWhE(IU0oFw6+L?NNJ4jie;)!RB_$Gc+A0fDk!4X)QQO}&u*UQbUiC@4rnLxZoIl$7M<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!9lqk?HI(my1Hoa#mhuTM}PS6fv*LJ(EHFX?;45tD*sSiTwG>nXX%Z_e{nEW+@baO z@#BvlKa%|A-Qd=Wy*wi*x+KtTgBOa$iU z=91`g?D1xZTp-re)JUj1I5?0)7;`09Y>U^}+}tFwFJHc(crup!ijsiK#fB(G&G!;m>^-CH<~er zDc2JJNdX)Zr?j+`zK-$nMJETkH2=`Rkcm=GK4GRn{K@6WtrGeP3LwGZmwl{ZjbZ3k z>Yl$CG8-Ek2}Jys?*Sq3H%L+7`mZr(d-}q0000|TbjT(v>&wrcot+Wq98oSKdqi0$d)#rHLeeFq zIwPm^wb#k~efs_J`~LBM-p?Md=kp4D>6oQxumJ)w9vkWD-1k1WmLE3E;5qtsYjSwg(w6qwo8Ql#}BRR*bs_I5s-}f<*Q7{Z)^Wd z7=~msUSZc0esU+vUf3|tK9MUsCpY(g4ZKv=t~vVj_;&Vh{}3gw4~`|;;I;|))?}Rx zxy6Tj|9Eeiw!Jj6G?JGGwSt05^VtSMB`D5-%Rr}8JlM7#J<>*N1dV4OsrOQ)-flCZyOzeiZxGY8{{i&k^8GN0?M8((@SFov~L|DN?*%iXg$JkVLFeDX5^;!G`KtF zsMU2KMVEsK-X1tF4$JhGetoYie2s;oYVS=MEAYxVjMZBKR!tJe0jKK%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&KHM#e0wOcd$!WNb30oaFnvA~y0>eB83A#DziRg_GBI*j@OYz^oLiUX{*c zhLp&%{Xrz3zD;}pxi{*k`WeqaI3U 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`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$ZZJFB_Hx}D{}2fRXiy4hpNN2TCba^$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 70648d627a..a3bc8b69bd 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 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 691d84f16c..bb99e43a86 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 ecac121b9c..6c129512de 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 afdac71466..33fae62ebd 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 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 fe0610dafe..0a3c5a0647 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 779f0386b1..09aa2178ee 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 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 e3f90912f4..505fa455bd 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 order) { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index 49fd71fa11..c1fb3a3793 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 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 85ec9439b1..2307b0d2a1 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 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 bce7a9ab1a..f8259eec76 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 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 15dfedecd1..a7b2690eab 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 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 067733899d..366fc98435 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 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 5e9dde92e8..569c61bb93 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 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 0b9b247853..e779c8f20f 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 25facad342..2d25928604 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 f701713cd7..eb94965403 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 659b918124..363db4e49a 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 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 db9afbe0e0..ea754fcf94 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 d08ce9a11c..363ad342f3 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 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 366fc98435..96be2a6b72 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 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 53203464b9..46918013d2 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.2.3.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 2d9446598c..c47677eb21 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 abd5f4d0e0..5594959fe5 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 57252c888c..e1ac92bf4e 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 5531916133..4d2b860593 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 0971b69ca9..ccdc72548a 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 6e398bcc66..c0298f855a 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 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 cf4611563a..9290512611 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 043a8ad186..e7e3054aa4 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -1409,55 +1409,6 @@ std::vector> ShareBox::Inner::selected() const { return result; } -QString AppendShareGameScoreUrl( - not_null session, - const QString &url, - const FullMsgId &fullId) { - auto shareHashData = QByteArray(0x20, Qt::Uninitialized); - auto shareHashDataInts = reinterpret_cast(shareHashData.data()); - const auto peer = fullId.peer - ? session->data().peerLoaded(fullId.peer) - : static_cast(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(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> &result, const MessageIdsList &msgIds) { @@ -1612,9 +1563,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( } void FastShareMessage( - not_null controller, + std::shared_ptr show, not_null 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(std::move(copyCallback)) : Fn(); - controller->show( - Box(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::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 controller, + not_null item) { + FastShareMessage(controller->uiShow(), item); } void FastShareLink( @@ -1793,111 +1747,3 @@ auto SharePremiumRequiredError() -> Fn)> { return WritePremiumRequiredError; } - -void ShareGameScoreByHash( - not_null 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(hashEncrypted.data()) ^ *reinterpret_cast(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(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(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( - 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 32e824b15c..d0bf28ce91 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 session, - const QString &url, - const FullMsgId &fullId); -void ShareGameScoreByHash( - not_null controller, - const QString &hash); +class ShareBox; + +void FastShareMessage( + std::shared_ptr show, + not_null item); void FastShareMessage( not_null controller, not_null item); diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 9b03e0b743..eadb16181e 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(); + 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(); if (const auto controller = my.sessionWindow.get()) { const auto callback = [=, bot = _bot](Fn 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 03c3254ab0..9f4860da36 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 &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 a53ab20284..49013f1c85 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 controller, FullMsgId itemId, Context context) { - const auto item = controller->session().data().message(itemId); + CopyPostLink(controller->uiShow(), itemId, context); +} + +void CopyPostLink( + std::shared_ptr 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 8f00f4da80..efb2a5fdd3 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 controller, FullMsgId itemId, Context context); +void CopyPostLink( + std::shared_ptr show, + FullMsgId itemId, + Context context); void CopyStoryLink( std::shared_ptr 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 eb938ed263..ff26370112 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(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 AttachWebView::uiShow() { + class Show final : public Main::SessionShow { + public: + explicit Show(not_null that) : _that(that) { + } + + void showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + Ui::LayerOptions options, + anim::type animated) const override { + using UniqueLayer = std::unique_ptr; + using ObjectBox = object_ptr; + const auto panel = _that ? _that->_panel.get() : nullptr; + if (auto layerWidget = std::get_if(&layer)) { + Unexpected("Layers in AttachWebView are not implemented."); + } else if (auto box = std::get_if(&layer)) { + if (panel) { + panel->showBox(std::move(*box), options, animated); + } + } else if (panel) { + panel->hideLayer(animated); + } + } + [[nodiscard]] not_null 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 _that; + + }; + return std::make_shared(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 0f8cb6e13c..87648b07e8 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 lookupLastAction( const QString &url) const; + struct ShowGameParams { + not_null bot; + FullMsgId context; + QString url; + QString title; + }; + void showGame(ShowGameParams &¶ms); + + [[nodiscard]] std::shared_ptr 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 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> _disclaimerAccepted; + FullMsgId _gameContext; + std::unique_ptr _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 569c61bb93..d367e3382e 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 #include #include +#include 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 box) { + showBox(std::move(box), LayerOption::KeepOther, anim::type::normal); +} + +void Panel::showBox( + object_ptr 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 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 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 91e4232793..d9071c846e 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; } // 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; @@ -67,6 +70,7 @@ public: virtual void botAllowWriteAccess(Fn callback) = 0; virtual void botSharePhone(Fn callback) = 0; virtual void botInvokeCustomMethod(CustomMethodRequest request) = 0; + virtual void botShareGameScore() = 0; virtual void botClose() = 0; }; @@ -89,7 +93,13 @@ public: rpl::producer bottomText); void showBox(object_ptr box); + void showBox( + object_ptr box, + LayerOptions options, + anim::type animated); + void hideLayer(anim::type animated); void showToast(TextWithEntities &&text); + not_null toastParent() const; void showCriticalError(const TextWithEntities &text); void showWebviewError( const QString &text, From 94ad8f9bc38d2225601c9eb9529c75f789acc7e9 Mon Sep 17 00:00:00 2001 From: John Preston 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 cb33e8a9a2..6e60f072df 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 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 ae24ad4d3e..4ec94b0ab7 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 SectionWidget::desiredHeight() const { From 46b69a938b6d0ed3ef2660d632c8c4c6a9934271 Mon Sep 17 00:00:00 2001 From: John Preston 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 e3e0de6c6e..79dfd0977b 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 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 60a1c5f43d..e4694f88d6 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -65,12 +65,14 @@ std::optional 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 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 96be2a6b72..9b69f3855a 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 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 71c9533ebe..e4f7d1fb11 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 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 ea754fcf94..0c60906ec3 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 16dbfb994a..9e65eac39c 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 cc16c47bad..72824a2eef 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 f49776ede4..d988fc00c3 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 adbad2b7fb..5d4c7e42db 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 d9c306013d..d2386e7e62 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(); + 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([=]( + ClickContext context) { + const auto my = context.other.value(); + 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(); + 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, PrepareErrorText)); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 8d262a52e3..7e9fb6396b 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 { + 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 107bb94160..09fa2b43f3 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()) { result.link = custom->link; + } else if (const auto payment = item->Get()) { + 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 7b8700811d..394a50efaf 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 pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions 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 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 groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector 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 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 groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector 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 = 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 648a649003..3b88e8d484 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 controller, + FullMsgId refundItemId) { + const auto owner = &controller->session().data(); + const auto item = owner->message(refundItemId); + const auto refund = item + ? item->Get() + : 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 GenericEntryPhoto( not_null parent, Fn(Fn)> callback, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index efea501b9a..406bfabcaf 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 controller, PeerData *premiumBot, const Data::CreditsHistoryEntry &e); +void ShowRefundInfoBox( + not_null controller, + FullMsgId refundItemId); [[nodiscard]] object_ptr GenericEntryPhoto( not_null 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 8b1f71c68f..6704bdbd28 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 1703e15d0f..2bc778741d 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 5af8577bf2..4e99a01ba8 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 ce23d235ff..df85e62544 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 SetupChatsPreview( rules.id(), rules.title(), rules.iconEmoji(), + rules.colorIndex(), (rules.flags() & ~flag), rules.always(), rules.pinned(), @@ -104,6 +105,7 @@ not_null 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 efac543af9..7a6f63a743 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 colorIndex, Flags flags, base::flat_set> always, std::vector> 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(pinned), MTP_vector(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(pinned), MTP_vector(include), MTP_vector(never)); @@ -239,6 +249,10 @@ QString ChatFilter::iconEmoji() const { return _iconEmoji; } +std::optional 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 7b5a964768..e123ab2c15 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 colorIndex, Flags flags, base::flat_set> always, std::vector> pinned, @@ -71,6 +72,7 @@ public: [[nodiscard]] FilterId id() const; [[nodiscard]] QString title() const; [[nodiscard]] QString iconEmoji() const; + [[nodiscard]] std::optional 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 _colorIndex; base::flat_set> _always; std::vector> _pinned; base::flat_set> _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 0c60906ec3..e7b50f139b 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 972a7a5f44..58dc2941fa 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 2f76943950..6162024fd9 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 d6f880bdae..81ad7778dd 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; 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 84ad81390f..c16acc204c 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(_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 f8259eec76..74f6f7ed86 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 e7b50f139b..451f34da56 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 a7b2690eab..da16410b03 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 310af8fa6a..f2f98d05e3 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( @@ -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 451f34da56..5c461c1efd 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 58dc2941fa..fdf1eda03f 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 956c7d0c37..a176be565d 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(peer, descriptor); - } else if (item.peerType == Type::PremiumBot) { - return std::make_unique(_premiumBot, descriptor); } else { return std::make_unique(descriptor); } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 3b88e8d484..282945d9ef 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(content); + using ColoredMiniStars = Ui::Premium::ColoredMiniStars; + const auto stars = widget->lifetime().make_state( + 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 3f4ce02f2c..e99a4d4eb3 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -193,6 +193,21 @@ not_null AddInputFieldForCredits( PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry) { + using PeerType = Data::CreditsHistoryEntry::PeerType; + if (entry.peerType == PeerType::PremiumBot) { + const auto svg = std::make_shared(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(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)> 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 169fe8883b..cbe32faf5b 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(0); - const auto fill = [=]( + const auto fill = [=, peer = _peer]( not_null 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::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(_peer, true, true); - const auto apiIn = apiLifetime->make_state(_peer, true, false); - const auto apiOut = apiLifetime->make_state(_peer, false, true); + const auto apiFull = apiLifetime->make_state(peer, true, true); + const auto apiIn = apiLifetime->make_state(peer, true, false); + const auto apiOut = apiLifetime->make_state(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 c16acc204c..c96dac4eab 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 a176be565d..a9649fb4de 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 entryClickedCallback; - not_null premiumBot; + not_null peer; not_null creditIcon; bool in = false; bool out = false; @@ -889,7 +889,6 @@ private: void applySlice(const Data::CreditsStatusSlice &slice); const not_null _session; - const not_null _premiumBot; Fn _entryClickedCallback; not_null 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 2d381cb7cd..8423de55cc 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 container, Fn entryClickedCallback, - not_null premiumBot, + not_null peer, not_null creditIcon, bool in, bool out); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 0890f2703d..5088ce62aa 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -128,6 +128,7 @@ void Credits::setupHistory(not_null container) { container, object_ptr(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 container) { fullSlice, fullWrap->entity(), entryClicked, - premiumBot, + self, &_star, true, true); @@ -240,7 +241,7 @@ void Credits::setupHistory(not_null container) { inSlice, inWrap->entity(), entryClicked, - premiumBot, + self, &_star, true, false); @@ -249,7 +250,7 @@ void Credits::setupHistory(not_null container) { outSlice, outWrap->entity(), std::move(entryClicked), - premiumBot, + self, &_star, false, true); @@ -263,7 +264,6 @@ void Credits::setupHistory(not_null container) { const auto apiLifetime = content->lifetime().make_state(); { using Api = Api::CreditsHistory; - const auto self = _controller->session().user(); const auto apiFull = apiLifetime->make_state(self, true, true); const auto apiIn = apiLifetime->make_state(self, true, false); const auto apiOut = apiLifetime->make_state(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 1a302bd10c..7838dda794 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 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 6528a858e0..6f3bc7a002 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 5c461c1efd..122260d220 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 0000000000..0c791008df --- /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 0000000000..6a35060f00 --- /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 0000000000..10a810aa91 --- /dev/null +++ b/Telegram/Resources/qrc/telegram/picker.qrc @@ -0,0 +1,6 @@ + + + ../../picker_html/picker.css + ../../picker_html/picker.js + + diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index ade419248f..bf4d472fee 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(); + + 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( + MTP_flags(sendFlags), + peer->input, + Data::Histories::ReplyToPlaceholder(), + std::move(inputMedia), + MTPstring(), + MTP_long(randomId), + MTPReplyMarkup(), + MTPvector(), + 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 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 peer, diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h index 2fdbad8437..c4bafc5378 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 peer, diff --git a/Telegram/SourceFiles/core/current_geo_location.cpp b/Telegram/SourceFiles/core/current_geo_location.cpp new file mode 100644 index 0000000000..295bde8244 --- /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 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 0000000000..2715699eeb --- /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 callback); + +} // namespace Core diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index a5e0090db8..6fb00d550d 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 0000000000..d4e3831219 --- /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 &CountryBounds() { + static const auto result = base::flat_map{ + { 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 0000000000..4f0944c81a --- /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 + +namespace Raw { + +struct GeoBounds { + double minLat = 0.; + double minLon = 0.; + double maxLat = 0.; + double maxLon = 0.; +}; + +[[nodiscard]] const base::flat_map &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 ff26370112..c6b0139560 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 session) { + return u""_q; +} + void ShowChooseBox( not_null controller, PeerTypes types, @@ -1793,6 +1800,21 @@ void AttachWebView::toggleInMenu( }).send(); } +void ChooseAndSendLocation( + not_null 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 MakeAttachBotsMenu( not_null parent, not_null controller, @@ -1847,6 +1869,14 @@ std::unique_ptr 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 0000000000..5ac0c49a78 --- /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 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 0000000000..6bb8195430 --- /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 0000000000..6bb8195430 --- /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 0000000000..03bf727ede --- /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 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 0000000000..245342fc36 --- /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 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 0000000000..37682c108d --- /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 +#include + +namespace Platform { + +void ResolveCurrentExactLocation(Fn 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 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 0000000000..6bb8195430 --- /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 0000000000..21c5b9861d --- /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 +#include +#include +#include +#include + +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"( + + + + + + + + + + + +
+
+ + + +)"_q; +} + +} // namespace + +LocationPicker::LocationPicker(Descriptor &&descriptor) +: _callback(std::move(descriptor.callback)) +, _quit(std::move(descriptor.quit)) +, _window(std::make_unique()) +, _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(window->body().get()); + const auto button = Ui::CreateChild( + 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( + _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 e) { + if (e->type() == QEvent::Close) { + close(); + } else if (e->type() == QEvent::KeyPress) { + const auto event = static_cast(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( + 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::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 0000000000..220c0c4264 --- /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 callback; + Fn quit; + Webview::StorageId storageId; + rpl::producer<> closeRequests; + }; + + [[nodiscard]] static bool Available(const QString &token); + static not_null 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 _callback; + Fn _quit; + std::unique_ptr _window; + RpWidget *_container = nullptr; + std::unique_ptr _webview; + SingleQueuedInvokation _updateStyles; + bool _subscribedToColors = false; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 3555c92189..b11016acbd 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 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 6a35060f00..54eb60abae 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 295bde8244..eb2125bf54 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 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 03bf727ede..2812a0c333 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 + +@interface LocationDelegate : NSObject + +- (id) initWithCallback:(Fn)callback; +- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations; +- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error; +- (void) locationManager:(CLLocationManager *) manager didChangeAuthorizationStatus:(CLAuthorizationStatus) status; +- (void) dealloc; + +@end + +@implementation LocationDelegate { +CLLocationManager *_manager; +Fn _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)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*)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 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 21c5b9861d..804593b077 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 #include #include #include @@ -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 53b8daf8b9..b68e2109c5 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -12,12 +12,20 @@ Icon.icns CFBundleIdentifier @bundle_identifier_plist@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @output_name@ CFBundlePackageType APPL CFBundleShortVersionString @desktop_app_version_string@ CFBundleSignature ???? + CFBundleSupportedPlatforms + + MacOSX + CFBundleURLTypes @@ -39,16 +47,18 @@ LSApplicationCategoryType public.app-category.social-networking - LSMinimumSystemVersion - @CMAKE_OSX_DEPLOYMENT_TARGET@ LSFileQuarantineEnabled + LSMinimumSystemVersion + @CMAKE_OSX_DEPLOYMENT_TARGET@ NOTE NSMicrophoneUsageDescription We need access to your microphone so that you can record voice messages and make calls. NSCameraUsageDescription We need access to your camera so that you can record video messages and make video calls. + NSLocationUsageDescription + We need access to your location so that you can send your current locations. NSPrincipalClass NSApplication NSSupportsAutomaticGraphicsSwitching diff --git a/Telegram/Telegram/Telegram Lite.entitlements b/Telegram/Telegram/Telegram Lite.entitlements index 46355b637b..050eea08f0 100644 --- a/Telegram/Telegram/Telegram Lite.entitlements +++ b/Telegram/Telegram/Telegram Lite.entitlements @@ -18,5 +18,7 @@ com.apple.security.device.camera + com.apple.security.personal-information.location + diff --git a/Telegram/Telegram/Telegram.entitlements b/Telegram/Telegram/Telegram.entitlements index 97c1f6d587..af28832209 100644 --- a/Telegram/Telegram/Telegram.entitlements +++ b/Telegram/Telegram/Telegram.entitlements @@ -6,5 +6,7 @@ com.apple.security.device.camera + com.apple.security.personal-information.location + diff --git a/cmake b/cmake index 4a4bc4cd34..78b441c9c6 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 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 0b1290dc8d..8c7bcbb937 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 804593b077..5b77f017ad 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()) +, _window(std::make_unique()) +, _body((_window->setInnerSize(st::pickLocationWindow) + , _window->showInner(base::make_unique_q(_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(window->body().get()); - const auto button = Ui::CreateChild( - window->body(), + _container = CreateChild(_body.get()); + const auto scroll = CreateChild(_body.get()); + const auto controls = scroll->setOwnedWidget( + object_ptr(scroll)); + const auto toppad = controls->add(object_ptr(controls)); + + const auto button = controls->add(object_ptr( + 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(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 220c0c4264..f18957b9dc 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 _callback; Fn _quit; - std::unique_ptr _window; + std::unique_ptr _window; + not_null _body; RpWidget *_container = nullptr; std::unique_ptr _webview; SingleQueuedInvokation _updateStyles; From 310837c9e18261ceca238c2fbd682fa15f8e8e9e Mon Sep 17 00:00:00 2001 From: John Preston 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 6f3bc7a002..592f1329ff 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*HKsV?vQcr7M$_75r$5`Mg)k;O7Ja#E-m*;BX$upZXO`6lBt%Zr=$gXW^x$)yL4cgu z49&x5jz6AgSHAfsN7Dpr1BM{cG^w;a6RF-6&wkrYvTSCYu=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)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFMM*?KR9Fe^SUpH`Q5e1jhBygD z`4bH-Leykawg}%4)D+9oO`sbJDvMJfA2lsdp+lB+VAl@1HH|_fSTNFHhaC^rKP2rnHjs?ZnN3ob8~ZZ zeSLj)c6NApn9t`MjfR@JG1d6^csw3|d3pKE9w2}QvN3l>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 zv!8z&Z81(!6bfP9mdFnF_V#$+)6>(Tp`mU&6an#Cn0E^Wm0T{zo6gP6Q5JxBEzBt` zSud4JjOp|9Gj1|U3(2%2cPOrev(u zI520TN$C9iTrH1R!Iq3%- zOeD_O798i_=N<|+g%OplI^ zs8p(ce0Px#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&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`02#G>t;2s;Vk^>e#gPd#pdVdcXxLuZ$T)`5C9p(+B}H3;^N}Kz(68c1v@%AnwXe4JUoOwAt3=&@$vC0 z4CN965IZBo-&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_VwP 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+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#5O91SJb#z=)o^$00000NkvXXu0mjfNxH1Z literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 122260d220..b15436b2b0 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 54eb60abae..cb936cf0b0 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 8c7bcbb937..452a9f89b7 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 2021e29434..f2ad99f46f 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 eb2125bf54..b649ea2ea7 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 +#include +#include +#include +#include +#include +#include namespace Core { +namespace { + +constexpr auto kDestroyManagerTimeout = 20 * crl::time(1000); + +void ResolveLocationAddressGeneric( + const GeoLocation &location, + const QString &token, + Fn 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(); + 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> sent; + base::Timer destroyer; + }; + + static auto state = QPointer(); + if (!state) { + state = Ui::CreateChild(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 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 callback) { }); } +void ResolveLocationAddress( + const GeoLocation &location, + const QString &token, + Fn 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 2715699eeb..e3b92222e9 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 callback); +void ResolveLocationAddress( + const GeoLocation &location, + const QString &token, + Fn callback); + } // namespace Core diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index 6fb00d550d..10f05adfbe 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 869476243c..e43e6423ad 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3328,6 +3328,22 @@ void Session::documentApplyFields( } } +not_null 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()), {}, {}); + _venueIcons.emplace(icon, result); + return result; +} + not_null 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 12c4cba3a6..06b4b267d3 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 venueIconDocument( + const QString &icon); [[nodiscard]] not_null webpage(WebPageId id); not_null processWebpage(const MTPWebPage &data); @@ -1002,6 +1004,7 @@ private: FullStoryId, base::flat_set>> _storyItems; base::flat_map> _highlightings; + base::flat_map> _venueIcons; base::flat_set> _webpagesUpdated; base::flat_set> _gamesUpdated; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index c6b0139560..b9233313ce 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 session) { + return u""_q; +} + void ShowChooseBox( not_null 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 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 3b30dc6dbb..4161fe8762 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 #include -#include "base/call_delayed.h" - namespace Iv { namespace { @@ -79,67 +78,7 @@ namespace { static const auto phrases = base::flat_map>{ { "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 { @@ -194,7 +133,7 @@ Controller::Controller( Fn 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 ea61115740..c14d490511 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 454339f494..1da5325ef8 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::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 8a4db80df9..289230b644 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 5ac0c49a78..6f42c0afdb 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 callback) { callback({}); } +void ResolveLocationAddress( + const Core::GeoLocation &location, + Fn 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 2812a0c333..452bc6e3ae 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 callback) { [[LocationDelegate alloc] initWithCallback:std::move(callback)]; } +void ResolveLocationAddress( + const Core::GeoLocation &location, + Fn 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 245342fc36..9feb4b376e 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 callback); +void ResolveLocationAddress( + const Core::GeoLocation &location, + Fn 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 37682c108d..a83dedb882 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 #include +#include +#include + namespace Platform { void ResolveCurrentExactLocation(Fn callback) { @@ -56,4 +59,10 @@ void ResolveCurrentExactLocation(Fn callback) { } } +void ResolveLocationAddress( + const Core::GeoLocation &location, + Fn callback) { + callback({}); +} + } // namespace Platform diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 5b77f017ad..a4658e7bee 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 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 _delegate; + VenueData _data; + +}; + +VenueRow::VenueRow( + not_null 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 session, + rpl::producer> content); + + void prepare() override; + void rowClicked(not_null row) override; + void rowRightActionClicked(not_null 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 document; + std::shared_ptr media; + uint32 paletteVersion : 31 = 0; + uint32 iconLoaded : 1 = 0; + QImage image; + QImage icon; + }; + + void appendRow(const VenueData &data); + + void rebuild(const std::vector &rows); + + const not_null _session; + rpl::variable> _rows; + + base::flat_map _icons; + + rpl::lifetime _lifetime; + +}; + +[[nodiscard]] QString NormalizeVenuesQuery(QString query) { + return query.trimmed().toLower(); +} + +LinksController::LinksController( + not_null session, + rpl::producer> content) +: _session(session) +, _rows(std::move(content)) { +} + +void LinksController::prepare() { + _rows.value( + ) | rpl::start_with_next([=](const std::vector &rows) { + rebuild(rows); + }, _lifetime); +} + +void LinksController::rebuild(const std::vector &rows) { + auto i = 0; + auto count = delegate()->peerListFullRowsCount(); + while (i < rows.size()) { + if (i < count) { + const auto row = delegate()->peerListRowAt(i); + static_cast(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 row) { + const auto venue = static_cast(row.get())->data(); + venue; +} + +void LinksController::rowRightActionClicked(not_null row) { + delegate()->peerListShowRowMenu(row, true); +} + +Main::Session &LinksController::session() const { + return *_session; +} + +void LinksController::appendRow(const VenueData &data) { + delegate()->peerListAppendRow(std::make_unique(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{ + { "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>{ + { "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 MakeSendLocationButton( + QWidget *parent, + rpl::producer address) { + auto result = object_ptr( + parent, + QString(), + st::pickLocationButton); + const auto raw = result.data(); + + const auto st = &st::pickLocationVenue; + const auto icon = CreateChild(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(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( + raw, + tr::lng_maps_point_send(tr::now), + st::pickLocationButtonText); + name->show(); + const auto status = CreateChild( + 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 container, + std::shared_ptr show, + rpl::producer> value) { + auto &lifetime = container->lifetime(); + const auto delegate = lifetime.make_state( + show); + const auto controller = lifetime.make_state( + &show->session(), + std::move(value)); + controller->setStyleOverrides(&st::pickLocationVenueList); + const auto content = container->add(object_ptr( + container, + controller)); + delegate->setContent(content); + controller->setDelegate(delegate); + + show->session().downloaderTaskFinished() | rpl::start_with_next([=] { + content->update(); + }, content->lifetime()); +} + +[[nodiscard]] PickerVenueList ParseVenues( + not_null 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 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(_body.get()); - const auto scroll = CreateChild(_body.get()); - const auto controls = scroll->setOwnedWidget( - object_ptr(scroll)); + _scroll = CreateChild(_body.get()); + const auto controls = _scroll->setOwnedWidget( + object_ptr(_scroll)); const auto toppad = controls->add(object_ptr(controls)); - const auto button = controls->add(object_ptr( - 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(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(state); + }) | rpl::map([=](PickerVenueState &&state) { + return std::move(v::get(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 f18957b9dc..9a4ca56d59 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 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 session; Fn callback; Fn 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 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 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 _window; not_null _body; RpWidget *_container = nullptr; + ScrollArea *_scroll = nullptr; std::unique_ptr _webview; SingleQueuedInvokation _updateStyles; bool _subscribedToColors = false; + Core::GeoLocation _geocoderResolvingFor; + rpl::variable _geocoderAddress; + + rpl::variable _venueState; + + const not_null _session; + MTP::Sender _api; + UserData *_venuesBot = nullptr; + mtpRequestId _venuesBotRequestId = 0; + mtpRequestId _venuesRequestId = 0; + Core::GeoLocation _venuesRequestLocation; + QString _venuesRequestQuery; + base::flat_map> _venuesCache; + }; } // namespace Ui diff --git a/Telegram/SourceFiles/ui/webview_helpers.cpp b/Telegram/SourceFiles/ui/webview_helpers.cpp new file mode 100644 index 0000000000..24b7155ce9 --- /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 &colors, + const base::flat_map> &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 0000000000..3d8a510669 --- /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 +struct phrase; +} // namespace tr + +namespace Ui { + +[[nodiscard]] QByteArray ComputeStyles( + const base::flat_map &colors, + const base::flat_map> &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 b11016acbd..9bc3f6c766 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 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 0c791008df..4d8f378aed 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 cb936cf0b0..27ffd3b6c6 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 b649ea2ea7..818ea361aa 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 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(); const auto i = Cache.find(partialUrl); if (i != end(Cache)) { @@ -161,16 +198,21 @@ void ResolveCurrentGeoLocation(Fn callback) { void ResolveLocationAddress( const GeoLocation &location, + const QString &language, const QString &token, Fn 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 e3b92222e9..dab4ffd00b 100644 --- a/Telegram/SourceFiles/core/current_geo_location.h +++ b/Telegram/SourceFiles/core/current_geo_location.h @@ -55,6 +55,7 @@ void ResolveCurrentGeoLocation(Fn callback); void ResolveLocationAddress( const GeoLocation &location, + const QString &language, const QString &token, Fn callback); diff --git a/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp index 6f42c0afdb..f87c343d36 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 callback) { } void ResolveLocationAddress( const Core::GeoLocation &location, + const QString &language, Fn 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 452bc6e3ae..1a5382f199 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 callback) { void ResolveLocationAddress( const Core::GeoLocation &location, + const QString &language, Fn callback) { callback({}); } diff --git a/Telegram/SourceFiles/platform/platform_current_geo_location.h b/Telegram/SourceFiles/platform/platform_current_geo_location.h index 9feb4b376e..269ee81f32 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 callback); void ResolveLocationAddress( const Core::GeoLocation &location, + const QString &language, Fn 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 a83dedb882..9c7967588e 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 callback) { void ResolveLocationAddress( const Core::GeoLocation &location, + const QString &language, Fn callback) { callback({}); } diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index a4658e7bee..2eef9d698a 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( -
+
+
+ + + + + + + + + +
+
+ + + + +
+
@@ -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 9a4ca56d59..e9c0d9801d 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 _geocoderAddress; From a130bb1be6d3c3fbae888fd64f062b794afcd56e Mon Sep 17 00:00:00 2001 From: John Preston 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 4d8f378aed..ac3d5912b1 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 27ffd3b6c6..e44fd51a95 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 818ea361aa..2ec4a9cfe4 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 dab4ffd00b..3b495f1159 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 2eef9d698a..b5920e1310 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 session, rpl::producer> content); @@ -176,21 +177,21 @@ private: return query.trimmed().toLower(); } -LinksController::LinksController( +VenuesController::VenuesController( not_null session, rpl::producer> content) : _session(session) , _rows(std::move(content)) { } -void LinksController::prepare() { +void VenuesController::prepare() { _rows.value( ) | rpl::start_with_next([=](const std::vector &rows) { rebuild(rows); }, _lifetime); } -void LinksController::rebuild(const std::vector &rows) { +void VenuesController::rebuild(const std::vector &rows) { auto i = 0; auto count = delegate()->peerListFullRowsCount(); while (i < rows.size()) { @@ -209,24 +210,24 @@ void LinksController::rebuild(const std::vector &rows) { delegate()->peerListRefreshRows(); } -void LinksController::rowClicked(not_null row) { +void VenuesController::rowClicked(not_null row) { const auto venue = static_cast(row.get())->data(); venue; } -void LinksController::rowRightActionClicked(not_null row) { +void VenuesController::rowRightActionClicked(not_null 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(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>{ { "maps-places-in-area", tr::lng_maps_places_in_area }, @@ -358,6 +360,9 @@ void LinksController::rowPaintIcon( +
+
+
@@ -467,7 +472,7 @@ void SetupVenues( auto &lifetime = container->lifetime(); const auto delegate = lifetime.make_state( show); - const auto controller = lifetime.make_state( + const auto controller = lifetime.make_state( &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 e9c0d9801d..0adfc166fc 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 _geocoderAddress; rpl::variable _venueState; From 8ce10d55038e2d025e5008c4d84e2cb3210b1acc Mon Sep 17 00:00:00 2001 From: John Preston 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 10f05adfbe..5157f79a1c 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 b9233313ce..d52e13b82d 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 session) { - return u""_q; -} - -[[nodiscard]] QString ResolveGeocodingToken(not_null session) { - return u""_q; +[[nodiscard]] Ui::LocationPickerConfig ResolveMapsConfig( + not_null session) { + const auto &appConfig = session->appConfig(); + auto map = appConfig.get>( + u"tdesktop_config_map"_q, + base::flat_map()); + return { + .mapsToken = map[u"maps"_q], + .geoToken = map[u"geo"_q], + }; } void ShowChooseBox( @@ -1806,12 +1810,18 @@ void AttachWebView::toggleInMenu( void ChooseAndSendLocation( not_null 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 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 2507dc8f1b..6a4c63c35e 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -144,28 +144,22 @@ std::vector AppConfig::getStringArray( }); } -std::vector> AppConfig::getStringMapArray( +base::flat_map AppConfig::getStringMap( const QString &key, - std::vector> &&fallback) const { + base::flat_map &&fallback) const { return getValue(key, [&](const MTPJSONValue &value) { - return value.match([&](const MTPDjsonArray &data) { - auto result = std::vector>(); + return value.match([&](const MTPDjsonObject &data) { + auto result = base::flat_map(); 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(); - 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 67b9853482..f09d5f120d 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>) { return getStringArray(key, std::move(fallback)); + } else if constexpr ( + std::is_same_v>) { + return getStringMap(key, std::move(fallback)); } else if constexpr (std::is_same_v>) { return getIntArray(key, std::move(fallback)); - } else if constexpr (std::is_same_v< - Type, - std::vector>>) { - return getStringMapArray(key, std::move(fallback)); } else if constexpr (std::is_same_v) { return getBool(key, fallback); } @@ -78,9 +77,9 @@ private: [[nodiscard]] std::vector getStringArray( const QString &key, std::vector &&fallback) const; - [[nodiscard]] std::vector> getStringMapArray( + [[nodiscard]] base::flat_map getStringMap( const QString &key, - std::vector> &&fallback) const; + base::flat_map &&fallback) const; [[nodiscard]] std::vector getIntArray( const QString &key, std::vector &&fallback) const; diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index b5920e1310..9d926f9808 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 session, - rpl::producer> content); + rpl::producer> content, + Fn callback); void prepare() override; void rowClicked(not_null row) override; @@ -165,6 +164,7 @@ private: void rebuild(const std::vector &rows); const not_null _session; + const Fn _callback; rpl::variable> _rows; base::flat_map _icons; @@ -179,8 +179,10 @@ private: VenuesController::VenuesController( not_null session, - rpl::producer> content) + rpl::producer> content, + Fn callback) : _session(session) +, _callback(std::move(callback)) , _rows(std::move(content)) { } @@ -211,8 +213,7 @@ void VenuesController::rebuild(const std::vector &rows) { } void VenuesController::rowClicked(not_null row) { - const auto venue = static_cast(row.get())->data(); - venue; + _callback(static_cast(row.get())->data()); } void VenuesController::rowRightActionClicked(not_null row) { @@ -468,13 +469,15 @@ void VenuesController::rowPaintIcon( void SetupVenues( not_null container, std::shared_ptr show, - rpl::producer> value) { + rpl::producer> value, + Fn callback) { auto &lifetime = container->lifetime(); const auto delegate = lifetime.make_state( show); const auto controller = lifetime.make_state( &show->session(), - std::move(value)); + std::move(value), + std::move(callback)); controller->setStyleOverrides(&st::pickLocationVenueList); const auto content = container->add(object_ptr( 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()) , _body((_window->setInnerSize(st::pickLocationWindow) @@ -550,13 +554,9 @@ std::shared_ptr 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(state); }) | rpl::map([=](PickerVenueState &&state) { return std::move(v::get(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 0adfc166fc..b7996ee50d 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 session; - Fn callback; + Fn callback; Fn 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 Show(Descriptor &&descriptor); void close(); @@ -114,9 +113,8 @@ private: void venuesRequest(Core::GeoLocation location, QString query = {}); void venuesSendRequest(); - rpl::lifetime _lifetime; - - Fn _callback; + LocationPickerConfig _config; + Fn _callback; Fn _quit; std::unique_ptr _window; not_null _body; @@ -143,6 +141,8 @@ private: QString _venuesRequestQuery; base::flat_map> _venuesCache; + rpl::lifetime _lifetime; + }; } // namespace Ui From 917d1841c1ce1a6ca8afb78c14e891f4034d16a7 Mon Sep 17 00:00:00 2001 From: John Preston 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 4161fe8762..e72298f6c4 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(); 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 d367e3382e..cbc27c0b85 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 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 9d926f9808..d363bbe6d9 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(_body.get()); const auto controls = _scroll->setOwnedWidget( object_ptr(_scroll)); - const auto toppad = controls->add(object_ptr(controls)); - const auto button = controls->add( - MakeSendLocationButton(controls, _geocoderAddress.value()), + _mapControlsWrap = controls->add( + object_ptr>( + controls, + object_ptr(controls)) + )->setDuration(0); + _mapControlsWrap->show(anim::type::instant); + const auto mapControls = _mapControlsWrap->entity(); + + const auto toppad = mapControls->add(object_ptr(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(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 query) { + venuesSearchChanged(query); + }); + } + _venuesSearchLocation = location; +} + +void LocationPicker::venuesSearchChanged( + const std::optional &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 b7996ee50d..6014bd06bb 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 +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 &query); LocationPickerConfig _config; Fn _callback; @@ -119,6 +126,7 @@ private: std::unique_ptr _window; not_null _body; RpWidget *_container = nullptr; + SlideWrap *_mapControlsWrap = nullptr; ScrollArea *_scroll = nullptr; std::unique_ptr _webview; SingleQueuedInvokation _updateStyles; @@ -133,13 +141,19 @@ private: rpl::variable _venueState; const not_null _session; + std::optional _venuesSearchLocation; + std::optional _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> _venuesCache; + Core::GeoLocation _venuesNoSearchLocation; + rpl::variable _venuesSearchShown = false; rpl::lifetime _lifetime; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 9b69f3855a..a95caea1ad 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 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 b15436b2b0..08368d7484 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 452a9f89b7..d31d2919a3 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 d363bbe6d9..ed9d84f7a8 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 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(container); + view->resize(container->width(), st::recentPeersEmptyHeightMin); + view->show(); + + ResizeFitChild(container, view); +} + +void SetupEmptyView( + not_null container, + std::optional query) { + using Icon = Dialogs::SearchEmptyIcon; + const auto view = CreateChild( + 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 container, std::shared_ptr show, - rpl::producer> value, + rpl::producer value, Fn callback) { + const auto otherWrap = container->add(object_ptr>( + container, + object_ptr(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(state)) { + otherWrap->hide(anim::type::instant); + return; + } else if (v::is(state)) { + SetupLoadingView(other); + } else { + const auto n = std::get_if(&state); + SetupEmptyView(other, n ? n->query : std::optional()); + } + otherWrap->show(anim::type::instant); + }, otherWrap->lifetime()); + auto &lifetime = container->lifetime(); + auto venuesList = rpl::duplicate( + value + ) | rpl::map([=](PickerVenueState &&state) { + return v::is(state) + ? std::move(v::get(state).list) + : std::vector(); + }); const auto delegate = lifetime.make_state( show); const auto controller = lifetime.make_state( &show->session(), - std::move(value), + std::move(venuesList), std::move(callback)); controller->setStyleOverrides(&st::pickLocationVenueList); const auto content = container->add(object_ptr( @@ -601,8 +682,7 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { _mapControlsWrap = controls->add( object_ptr>( controls, - object_ptr(controls)) - )->setDuration(0); + object_ptr(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(state); - }) | rpl::map([=](PickerVenueState &&state) { - return std::move(v::get(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 a95caea1ad..f046725ecd 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 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 d31d2919a3..a40d1e0ad3 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 d52e13b82d..4dbd94e1d4 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 ed9d84f7a8..5438d00d44 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(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 6014bd06bb..756e6b1623 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 session; Fn callback; Fn quit; @@ -145,6 +146,7 @@ private: std::optional _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 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 08368d7484..fc725ff1d4 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 a40d1e0ad3..9b92f4815b 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 5438d00d44..b461a902b2 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 MakeFoursquarePromo() { + auto result = object_ptr((QWidget*)nullptr); + const auto raw = result.data(); + raw->resize(0, st::pickLocationPromoHeight); + const auto shadow = CreateChild(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 session, rpl::producer> content, @@ -214,6 +236,11 @@ void VenuesController::rebuild(const std::vector &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 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 237c30b87a..8f8b15ecf7 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 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 a35cfd3c44..9b8b959e0d 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 #include +#include + #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 0e03904f70..ded5dd0d13 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 26f4783761..9ebed92fcd 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 #include +#include #include #include #include @@ -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 b9fa8b84d8..0c23e3803b 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 e4f7d1fb11..00a3dc3b78 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 6704bdbd28..2f5986864f 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 881ad99526..3f6938e721 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 eb94965403..81d01697c2 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit eb9496540356945e2c9fb700bcfa51444fd36f41 +Subproject commit 81d01697c2ee2b46c5e5c41ce1176c230264e4cb diff --git a/cmake b/cmake index 78b441c9c6..d50ff1a71e 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 78b441c9c6ad8a14a8f97a28825babcadc6bf781 +Subproject commit d50ff1a71e080e63f71c451f500a2f03f9d48539 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 033424e1d6..b3c8d19054 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 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 fc725ff1d4..c692195bca 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 9f12728596..0ca37c6292 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(); const auto RegisterV2 = tgcalls::Register(); const auto RegV2Ref = tgcalls::Register(); -const auto RegisterV240 = tgcalls::Register(); const auto RegisterLegacy = tgcalls::Register(); [[nodiscard]] base::flat_set CollectEndpointIds( diff --git a/Telegram/SourceFiles/data/business/data_business_info.cpp b/Telegram/SourceFiles/data/business/data_business_info.cpp index e158c7a3a8..d9bb6ec7a9 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 fail) { session->user()->setBusinessDetails(std::move(details)); } +void BusinessInfo::saveLocation( + BusinessLocation data, + Fn 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 7b99e4c8fc..01993f2234 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 fail); void saveChatIntro(ChatIntro data, Fn fail); + void saveLocation(BusinessLocation data, Fn fail); void saveAwaySettings(AwaySettings data, Fn fail); void applyAwaySettings(AwaySettings data); diff --git a/Telegram/SourceFiles/data/data_location.cpp b/Telegram/SourceFiles/data/data_location.cpp index 80727b3ba9..457bb64113 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 5157f79a1c..114d21c08e 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 4dbd94e1d4..c42ecb3b99 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 e9bc2f73ec..688a6940d2 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 4a2f14e73a..e5bb0f8e38 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 controller); void save(); - [[nodiscard]] bool mapSupported() const; + void setupPicker(not_null content); + void setupUnsupported(not_null content); + [[nodiscard]] bool mapSupported() const; + void chooseOnMap(); + + const Ui::LocationPickerConfig _config; + rpl::variable _data; + rpl::variable _map = nullptr; + std::shared_ptr _view; Ui::RoundRect _bottomSkipRounding; }; +[[nodiscard]] Ui::LocationPickerConfig ResolveBusinessMapsConfig( + not_null session) { + const auto &appConfig = session->appConfig(); + auto map = appConfig.get>( + u"tdesktop_config_map"_q, + base::flat_map()); + return { + .mapsToken = map[u"bmaps"_q], + .geoToken = map[u"bgeo"_q], + }; +} + Location::Location( QWidget *parent, not_null controller) : BusinessSection(parent, controller) +, _config(ResolveBusinessMapsConfig(&controller->session())) , _bottomSkipRounding(st::boxRadius, st::boxDividerBg) { setupContent(controller); } @@ -65,12 +100,23 @@ rpl::producer Location::title() { } void Location::setupContent( - not_null controller) { + not_null controller) { using namespace rpl::mappers; const auto content = Ui::CreateChild(this); -#if 0 // #TODO location choosing + if (mapSupported()) { + setupPicker(content); + } else { + setupUnsupported(content); + } + + Ui::ResizeFitChild(this, content); +} + +void Location::setupPicker(not_null 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>( + content, + object_ptr(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 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 b461a902b2..3f1c74fe00 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 MakeSendLocationButton( +[[nodiscard]] object_ptr MakeChooseLocationButton( QWidget *parent, + rpl::producer label, rpl::producer address) { auto result = object_ptr( parent, @@ -465,7 +466,7 @@ void VenuesController::rowPaintIcon( }); const auto name = CreateChild( raw, - tr::lng_maps_point_send(tr::now), + std::move(label), st::pickLocationButtonText); name->show(); const auto status = CreateChild( @@ -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(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 756e6b1623..0a14851575 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 chooseLabel; PeerData *recipient = nullptr; not_null session; + Core::GeoLocation initial; Fn callback; Fn quit; Webview::StorageId storageId; @@ -132,6 +134,7 @@ private: std::unique_ptr _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 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 f046725ecd..58329bfe19 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 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 1a5382f199..0ea5544638 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 @@ -120,7 +121,34 @@ void ResolveLocationAddress( const Core::GeoLocation &location, const QString &language, Fn callback) { - callback({}); + CLGeocoder *geocoder = [[CLGeocoder alloc] init]; + CLLocation *request = [[CLLocation alloc] + initWithLatitude:location.point.x() + longitude:location.point.y()]; + [geocoder reverseGeocodeLocation:request completionHandler:^( + NSArray * __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 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 3f1c74fe00..8cce445711 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 0a14851575..97ce724f34 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; 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 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 592f1329ff..48cf60eff8 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 2f5986864f..6300390d3d 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 078006d29a..c1d6b02736 160000 --- a/Telegram/lib_crl +++ b/Telegram/lib_crl @@ -1 +1 @@ -Subproject commit 078006d29af0002e6cd8c61a405cdeaf65b37142 +Subproject commit c1d6b0273653095b10b4d0f4f7c30b614b690fd5 diff --git a/cmake b/cmake index d50ff1a71e..37d42a7225 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 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 6d001d4075..75674299f8 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 6f4e2d4663..1e38d721ad 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 1a50fd2300..54639131bf 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 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 6300390d3d..e1e31ddfb2 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 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 00a3dc3b78..b5f7c86cd9 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 e1e31ddfb2..0658703b78 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 b3c8d19054..0a3d3f8552 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 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 0658703b78..ea305575e5 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 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 a9649fb4de..9c2a692e78 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(peer, descriptor); From 2cd6bfef067dcf351db1dbc472ed780ea99aaa1c Mon Sep 17 00:00:00 2001 From: John Preston 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 3f6938e721..018a7cfb00 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 37d42a7225..3fe3123354 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 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 0c23e3803b..2dc886d05e 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 81d01697c2..839d9f0aad 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 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 b5f7c86cd9..d60e6d403c 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 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 156ba726a5..e563e16753 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 fe95f60dbc..777dde80c4 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 3f3b65caaf..6dc539c61e 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 23e8143e31..21183fa389 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 4bcf93fe9b..4e6f383527 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 5c5ee76355..b0dd43f657 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 54639131bf..4ac8cf9d65 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 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 c692195bca..d3427dce9e 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 cbc27c0b85..cb922a4ff5 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 d9071c846e..e25483b89a 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 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 451957c21a..8febb5f42f 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 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 0d93ee0b6c..4c6d4970dc 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 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 d3427dce9e..6c98281895 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 39d7983d73..a8771cc17b 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 dd32fa00f9..71b01fbf86 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 item) { } } }); + const auto reactivate = controller + ? crl::guard( + controller, + [=](auto) { controller->widget()->activate(); }) + : Fn(); + 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()), - ((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 c42ecb3b99..c8e88aed7d 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 reactivate) +-> Fn { + 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; + using CreditsReceiptPtr = std::shared_ptr; + 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 87648b07e8..f9f59f1ae3 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 nonPanelPaymentFormFactory( + Fn reactivate); const not_null _session; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index cbb435389b..9a5daf9a4f 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 3c7f112ca5..e783bba58c 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, - std::shared_ptr> { +struct RealFormPresentedNotification { +}; +struct NonPanelPaymentForm + : std::variant< + std::shared_ptr, + std::shared_ptr, + RealFormPresentedNotification> { using variant::variant; }; @@ -183,6 +187,7 @@ private: Fn _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 69cf7abdea..b62987a828 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 item) { return invoice && (invoice->currency == Ui::kCreditsCurrency); } +void ProcessCreditsPayment( + std::shared_ptr show, + QPointer fireworks, + std::shared_ptr form, + Fn maybeReturnToBot) { + const auto lifetime = std::make_shared(); + const auto api = lifetime->make_state( + show->session().user()); + const auto sendBox = [=] { + const auto unsuccessful = std::make_shared(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 controller, + std::shared_ptr receipt, + Fn 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 ProcessNonPanelPaymentFormFactory( not_null controller, Fn maybeReturnToBot) { return [=](NonPanelPaymentForm form) { using CreditsFormDataPtr = std::shared_ptr; using CreditsReceiptPtr = std::shared_ptr; - if (const auto creditsData = std::get_if(&form)) { - const auto form = *creditsData; - const auto lifetime = std::make_shared(); - const auto api = lifetime->make_state( - controller->session().user()); - const auto sendBox = [=, weak = base::make_weak(controller)] { - if (const auto strong = weak.get()) { - const auto unsuccessful = std::make_shared(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(&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 e8ab9375c8..53a31f81c6 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 item); +void ProcessCreditsPayment( + std::shared_ptr show, + QPointer fireworks, + std::shared_ptr form, + Fn maybeReturnToBot = nullptr); + +void ProcessCreditsReceipt( + not_null controller, + std::shared_ptr receipt, + Fn maybeReturnToBot = nullptr); + Fn ProcessNonPanelPaymentFormFactory( not_null controller, Fn maybeReturnToBot = nullptr); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 5088ce62aa..e80f82a62d 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 282945d9ef..1eaef45682 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 controller, + std::shared_ptr show, not_null container, int minimumCredits, Fn 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( - 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 PaidMediaThumbnail( void SmallBalanceBox( not_null box, - not_null controller, + std::shared_ptr show, int creditsNeeded, UserId botId, Fn 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( 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( - 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 406bfabcaf..47d5323c7a 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 controller, + std::shared_ptr show, not_null container, int minCredits, Fn paid); @@ -77,7 +81,7 @@ void ShowRefundInfoBox( void SmallBalanceBox( not_null box, - not_null controller, + std::shared_ptr show, int creditsNeeded, UserId botId, Fn paid); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index cb922a4ff5..ae27e49389 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 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 9f4860da36..675bb5edcf 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -1268,7 +1268,7 @@ const std::vector &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 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 5d8be61445..8ee5170fb3 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 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 58329bfe19..007e3a519f 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 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 2dc886d05e..ce592ecab2 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 839d9f0aad..5d44a8acc3 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 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 bf4d472fee..c05a42e953 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 c8e88aed7d..b701fe21a7 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 AttachWebView::uiShow() { using UniqueLayer = std::unique_ptr; using ObjectBox = object_ptr; const auto panel = _that ? _that->_panel.get() : nullptr; - if (auto layerWidget = std::get_if(&layer)) { + if (v::is(layer)) { Unexpected("Layers in AttachWebView are not implemented."); } else if (auto box = std::get_if(&layer)) { if (panel) { diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index d60e6d403c..0091881ed0 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 018a7cfb00..44539b54b3 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 3fe3123354..b3871c4efd 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 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 9290512611..7514121eb3 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 79dfd0977b..19f488b0b4 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 eadb16181e..915b12f4fd 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 a635941955..b3aa0bae02 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 elementDelegate; base::weak_ptr sessionWindow; + std::shared_ptr botWebviewContext; std::shared_ptr 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 675bb5edcf..223cec0439 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()), - .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 0a3c5a0647..789390bbfb 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 b701fe21a7..8e993f4f74 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 weak, + not_null 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 controller, + std::shared_ptr show, + not_null session, PeerTypes types, Fn)> callback, rpl::producer titleOverride = nullptr) { @@ -206,26 +222,36 @@ void ShowChooseBox( box->closeBox(); }); }; - *weak = controller->show(Box( + *weak = show->show(Box( std::make_unique( - &controller->session(), + session, std::move(done), std::move(filter)), std::move(initBox))); } -[[nodiscard]] base::flat_set> &ActiveWebViews() { - static auto result = base::flat_set>(); - return result; +void ShowChooseBox( + not_null controller, + PeerTypes types, + Fn)> callback, + rpl::producer titleOverride = nullptr) { + ShowChooseBox( + controller->uiShow(), + &controller->session(), + types, + std::move(callback), + std::move(titleOverride)); } -void FillDisclaimerBox(not_null box, Fn done) { +void FillDisclaimerBox( + not_null box, + Fn done) { const auto updateCheck = std::make_shared>(); const auto validateCheck = std::make_shared>(); const auto callback = [=](Fn close) { if (validateCheck && (*validateCheck)()) { - done(); + done(true); close(); } }; @@ -236,6 +262,7 @@ void FillDisclaimerBox(not_null box, Fn done) { tr::now, Ui::Text::RichLangValue), .confirmed = callback, + .cancelled = [=](Fn 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 box, Fn done) { }; } +WebViewContext ResolveContext( + not_null 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 parent, + std::shared_ptr show, const style::Menu &st, const AttachWebViewBot &bot, Fn callback); @@ -317,6 +377,7 @@ private: void prepare(); void paint(Painter &p); + const std::shared_ptr _show; const not_null _dummyAction; const style::Menu &_st; const AttachWebViewBot _bot; @@ -334,10 +395,12 @@ private: BotAction::BotAction( not_null parent, + std::shared_ptr show, const style::Menu &st, const AttachWebViewBot &bot, Fn 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 e) { } // namespace +base::weak_ptr WebViewInstance::PendingActivation; + MenuBotIcon::MenuBotIcon( QWidget *parent, std::shared_ptr media) @@ -528,82 +594,514 @@ PeerTypes ParseChooseTypes(QStringView choose) { return result; } -struct AttachWebView::Context { - base::weak_ptr controller; - Dialogs::EntryState dialogsEntryState; - Api::SendAction action; - bool fromSwitch = false; - bool fromMainMenu = false; - bool fromBotApp = false; -}; - -AttachWebView::AttachWebView(not_null session) -: _session(session) -, _refreshTimer([=] { requestBots(); }) { - _refreshTimer.callEach(kRefreshBotsTimeout); -} - -AttachWebView::~AttachWebView() { - ActiveWebViews().remove(this); -} - -void AttachWebView::request( - not_null 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); - _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 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 done) { + if (_bot->isVerified() + || _session->local().isBotTrustedOpenWebView(_bot->id)) { + done(); + return; + } + const auto callback = [=](Fn 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 done) { + _parentShow->show(Box([=](not_null box) { + const auto allowed = std::make_shared(); + const auto callback = [=](Fn 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( + 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(_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(_source) + ? (Flag::f_url | Flag::f_from_switch_webview) + : v::is(_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(_source)) { + maybeChooseAndRequestButton(types); + } else if (v::is(_source)) { + requestButton(); + } else { + requestSimple(); + } + }); + _session->attachWebView().requestAddToMenu(_bot, done); +} + +void WebViewInstance::maybeChooseAndRequestButton(PeerTypes supported) { + Expects(v::is(_source)); + + const auto link = v::get(_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 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(_source) + || (_context.action->history->peer != _bot); + const auto hasRemoveFromMenu = (attached != end(bots)) + && (!attached->inactive || attached->inMainMenu) + && (v::is(_source) + || v::is(_source) + || v::is(_source)); + const auto buttons = (hasOpenBot ? Button::OpenBot : Button::None) + | (!hasRemoveFromMenu + ? Button::None + : attached->inMainMenu + ? Button::RemoveFromMainMenu + : Button::RemoveFromMenu); + const auto allowClipboardRead = v::is(_source) + || v::is(_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(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(_source)); + + const auto game = v::get(_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(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(_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 reactivate) -> Fn { 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; using CreditsReceiptPtr = std::shared_ptr; @@ -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>( "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(&_source); + if (!button + || !button->simple + || _context.action->history->peer != _bot + || _dataSent) { return; } - const auto randomId = base::RandomValue(); + _dataSent = true; _session->api().request(MTPmessages_SendWebViewData( _bot->inputUser, - MTP_long(randomId), - MTP_string(_lastShownButtonText), + MTP_long(base::RandomValue()), + 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 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 callback) { +void WebViewInstance::botCheckWriteAccess(Fn callback) { _session->api().request(MTPbots_CanSendMessage( _bot->inputUser )).done([=](const MTPBool &result) { @@ -790,46 +1293,43 @@ void AttachWebView::botCheckWriteAccess(Fn callback) { }).send(); } -void AttachWebView::botAllowWriteAccess(Fn callback) { +void WebViewInstance::botAllowWriteAccess(Fn 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 callback) { +void WebViewInstance::botSharePhone(Fn 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(_source) + ? v::get(_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 controller, - const Api::SendAction &action) { - return { - .controller = controller, - .dialogsEntryState = controller->currentDialogsEntryState(), - .action = action, +std::shared_ptr WebViewInstance::uiShow() { + class Show final : public Main::SessionShow { + public: + explicit Show(not_null that) : _that(that) { + } + + void showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + Ui::LayerOptions options, + anim::type animated) const override { + using UniqueLayer = std::unique_ptr; + using ObjectBox = object_ptr; + const auto panel = _that ? _that->_panel.get() : nullptr; + if (v::is(layer)) { + Unexpected("Layers in WebView are not implemented."); + } else if (auto box = std::get_if(&layer)) { + if (panel) { + panel->showBox(std::move(*box), options, animated); + } + } else if (panel) { + panel->hideLayer(animated); + } + } + [[nodiscard]] not_null 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 _that; + }; + return std::make_shared(this); } -bool AttachWebView::IsSame( - const std::unique_ptr &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 session) +: _session(session) +, _refreshTimer([=] { requestBots(); }) { + _refreshTimer.callEach(kRefreshBotsTimeout); } -void AttachWebView::request( +AttachWebView::~AttachWebView() = default; + +void AttachWebView::openByUsername( not_null controller, const Api::SendAction &action, - not_null bot, - const WebViewButton &button) { - requestWithOptionalConfirm( - bot, - button, - LookupContext(controller, action), - button.fromAttachMenu ? nullptr : controller.get()); -} - -void AttachWebView::requestWithOptionalConfirm( - not_null 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); - 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 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 instance) { + const auto i = ranges::find( + _instances, + instance.get(), + &std::unique_ptr::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 bot, - AddToMenuOpen open) { - requestAddToMenu(bot, open, nullptr, std::nullopt); -} - -void AttachWebView::requestAddToMenu( - not_null bot, - AddToMenuOpen open, - Window::SessionController *controller, - std::optional 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( - LookupContext(controller, *action)); + Fn 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(&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(open) - ? v::get(open) - : AddToMenuOpenAttach(); - const auto chooseTypes = openAttach.chooseTypes; - const auto strong = chooseController.get(); - if (v::is(open)) { - if (!context) { - return false; - } - const auto &openApp = v::get(open); - _app = openApp.app; - _startCommand = openApp.startCommand; - _context = std::make_unique(*context); - requestAppView(true); - return true; - } else if (!strong) { - if (wasController || !v::is(open)) { - // Just ignore the click if controller was destroyed. - return true; - } - } else if (v::is(open)) { - const auto &openMenu = v::get(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 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 bot) { - toggleInMenu(bot, ToggledState::Removed, [=] { - showToast(tr::lng_bot_remove_from_menu_done(tr::now)); - }); -} - -std::optional 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 bot) { - if (!_context) { - return; +void AttachWebView::removeFromMenu( + std::shared_ptr show, + not_null 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 show, Fn)> 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 controller, - not_null bot, - const WebViewButton &button) { - cancel(); - _bot = bot; - _context = std::make_unique(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 controller, - not_null 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 controller, - not_null bot) { - if (openAppFromMenuLink(controller, bot)) { - return; - } - - cancel(); - _bot = bot; - _context = std::make_unique(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 controller, - const Api::SendAction &action, - not_null 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->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 box) { - const auto allowed = std::make_shared(); - const auto done = [=](Fn 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( - 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 controller, - Fn 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(std::move(descriptor))); + _instances.back()->activate(); } void AttachWebView::acceptMainMenuDisclaimer( - not_null 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 show, + not_null bot, + Fn 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(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(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 AttachWebView::uiShow() { - class Show final : public Main::SessionShow { - public: - explicit Show(not_null that) : _that(that) { - } - - void showOrHideBoxOrLayer( - std::variant< - v::null_t, - object_ptr, - std::unique_ptr> &&layer, - Ui::LayerOptions options, - anim::type animated) const override { - using UniqueLayer = std::unique_ptr; - using ObjectBox = object_ptr; - const auto panel = _that ? _that->_panel.get() : nullptr; - if (v::is(layer)) { - Unexpected("Layers in AttachWebView are not implemented."); - } else if (auto box = std::get_if(&layer)) { - if (panel) { - panel->showBox(std::move(*box), options, animated); - } - } else if (panel) { - panel->hideLayer(animated); - } - } - [[nodiscard]] not_null 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 _that; - - }; - return std::make_shared(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 callback) { + Fn callback) { const auto active = Core::App().activeWindow(); if (!active) { + if (callback) { + callback(false); + } return; } - _confirmAddBox = active->show(Box([=](not_null box) { + const auto weak = base::make_weak(active); + active->show(Box([=](not_null box) { const auto allowed = std::make_shared(); const auto disclaimer = !disclaimerAccepted(bot); const auto done = [=](Fn 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( box, @@ -1785,6 +1722,9 @@ void AttachWebView::confirmAddToMenu( Ui::Text::Bold(bot.name), Ui::Text::WithEntities), done, + (callback + ? [=](Fn close) { callback(false); close(); } + : Fn)>()), }); if (bot.requestWriteAccess) { (*allowed) = box->addRow( @@ -1813,7 +1753,7 @@ void AttachWebView::confirmAddToMenu( void AttachWebView::toggleInMenu( not_null bot, ToggledState state, - Fn callback) { + Fn 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()); }).fail([=] { cancel(); + if (callback) { + callback(false); + } }).send(); } @@ -1923,14 +1866,18 @@ std::unique_ptr 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( 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 f9f59f1ae3..6e11c87cd4 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 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 from; + QString appname; + QString token; + + friend inline bool operator==( + const WebViewSourceLinkApp &, + const WebViewSourceLinkApp &) = default; +}; + +struct WebViewSourceLinkAttachMenu { // ?startattach + base::weak_ptr from; + base::weak_ptr thread; + PeerTypes choose; + QString token; + + friend inline bool operator==( + const WebViewSourceLinkAttachMenu &, + const WebViewSourceLinkAttachMenu &) = default; +}; + +struct WebViewSourceLinkBotProfile { // t.me/botusername?startapp + base::weak_ptr 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 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 controller; + Dialogs::EntryState dialogsEntryState; + std::optional action; + bool maySkipConfirmation = false; +}; + +struct WebViewDescriptor { + not_null bot; + std::shared_ptr parentShow; + WebViewContext context; + WebViewButton button; + WebViewSource source; +}; + +class WebViewInstance final : public base::has_weak_ptr , public Ui::BotWebView::Delegate { public: - explicit AttachWebView(not_null 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 controller, - const Api::SendAction &action, - const QString &botUsername, - const QString &startCommand); - void request( - not_null controller, - const Api::SendAction &action, - not_null bot, - const WebViewButton &button); - void requestSimple( - not_null controller, - not_null bot, - const WebViewButton &button); - void requestMenu( - not_null controller, - not_null bot); - void requestApp( - not_null controller, - const Api::SendAction &action, - not_null bot, - const QString &appName, - const QString &startParam, - bool forceConfirmation); + [[nodiscard]] Main::Session &session() const; + [[nodiscard]] not_null bot() const; + [[nodiscard]] WebViewSource source() const; - void cancel(); - - void requestBots(Fn callback = nullptr); - [[nodiscard]] const std::vector &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 bot, - AddToMenuOpen open); - void requestAddToMenu( - not_null bot, - AddToMenuOpen open, - Window::SessionController *controller, - std::optional action); - void removeFromMenu(not_null bot); - - [[nodiscard]] std::optional lookupLastAction( - const QString &url) const; - - struct ShowGameParams { - not_null bot; - FullMsgId context; - QString url; - QString title; - }; - void showGame(ShowGameParams &¶ms); + void activate(); + void close(); [[nodiscard]] std::shared_ptr 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 done); + void confirmAppOpen(bool writeAccess, Fn done); + + void show(const QString &url, uint64 queryId = 0); + void showGame(); + void started(uint64 queryId); + + [[nodiscard]] Window::SessionController *windowForThread( + not_null thread); + + auto nonPanelPaymentFormFactory( + Fn reactivate) + -> Fn; 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 controller, - const Api::SendAction &action); - [[nodiscard]] static bool IsSame( - const std::unique_ptr &a, - const Context &b); + const std::shared_ptr _parentShow; + const not_null _session; + const not_null _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 _panel; + + static base::weak_ptr PendingActivation; + +}; + +class AttachWebView final : public base::has_weak_ptr { +public: + explicit AttachWebView(not_null session); + ~AttachWebView(); + + void open(WebViewDescriptor &&descriptor); + void openByUsername( not_null controller, + const Api::SendAction &action, + const QString &botUsername, + const QString &startCommand); + + void cancel(); + + void requestBots(Fn callback = nullptr); + [[nodiscard]] const std::vector &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 show, not_null bot); - void requestWithOptionalConfirm( + + enum class AddToMenuResult { + AlreadyInMenu, + Added, + Unsupported, + Cancelled, + }; + void requestAddToMenu( not_null 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)> done); - - void confirmOpen( - not_null controller, - Fn done); + Fn done); void acceptMainMenuDisclaimer( - not_null controller, - const WebViewButton &button); + std::shared_ptr show, + not_null bot, + Fn done); + + void close(not_null instance); + void closeAll(); + +private: + void resolveUsername( + std::shared_ptr show, + Fn)> done); enum class ToggledState { Removed, @@ -232,67 +364,35 @@ private: void toggleInMenu( not_null bot, ToggledState state, - Fn callback = nullptr); - - void show( - uint64 queryId, - const QString &url, - const QString &buttonText = QString(), - bool allowClipboardRead = false, - const BotAppData *app = nullptr, - bool fromMainMenu = false); + Fn callback = nullptr); void confirmAddToMenu( AttachWebViewBot bot, - Fn callback = nullptr); - void confirmAppOpen(bool requestWriteAccess); - void requestAppView(bool allowWrite); - void started(uint64 queryId); - - void showToast( - const QString &text, - Window::SessionController *controller = nullptr); - Fn nonPanelPaymentFormFactory( - Fn reactivate); + Fn callback = nullptr); const not_null _session; base::Timer _refreshTimer; - std::unique_ptr _context; - std::unique_ptr _lastShownContext; - QString _lastShownUrl; - uint64 _lastShownQueryId = 0; - QString _lastShownButtonText; - UserData *_bot = nullptr; QString _botUsername; - QString _botAppName; QString _startCommand; - BotAppData *_app = nullptr; - QPointer _confirmAddBox; - bool _appConfirmationRequired = false; - bool _appRequestWriteAccess = false; mtpRequestId _requestId = 0; - mtpRequestId _prolongId = 0; uint64 _botsHash = 0; mtpRequestId _botsRequestId = 0; std::vector> _botsRequestCallbacks; - std::unique_ptr _addToMenuContext; - UserData *_addToMenuBot = nullptr; - mtpRequestId _addToMenuId = 0; - AddToMenuOpen _addToMenuOpen; - base::weak_ptr _addToMenuChooseController; + struct AddToMenuProcess { + mtpRequestId requestId = 0; + std::vector> done; + }; + base::flat_map, AddToMenuProcess> _addToMenu; std::vector _attachBots; rpl::event_stream<> _attachBotsUpdates; base::flat_set> _disclaimerAccepted; - FullMsgId _gameContext; - - std::unique_ptr _panel; - bool _catchingCancelInShowCall = false; + std::vector> _instances; }; diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 10c6236bf9..2418c0f8a2 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 df1f2ef258..0cfdeece27 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 ae27e49389..6a50d25905 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 b70fb833ad..629b6cf16c 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 8380e458a1..ebd403da48 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()); + 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()), }, - parentController(), - (contextUser - ? Api::SendAction( - contextUser->owner().history(contextUser)) - : std::optional())); + .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 2c74571228..74a783696f 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 attachBotToggleCommand; - bool attachBotMenuOpen = false; + bool attachBotMainOpen = false; + bool attachBotMainCompact = false; InlineBots::PeerTypes attachBotChooseTypes; std::optional voicechatHash; FullMsgId clickFromMessageId; - QString clickFromAttachBotWebviewUrl; + std::shared_ptr clickFromBotWebviewContext; }; } // namespace Window From c70866a9956ae5ab2612e10637e7427c0ae29245 Mon Sep 17 00:00:00 2001 From: John Preston 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 e72298f6c4..b3fb82a0d7 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 #include #include +#include #include #include #include @@ -280,14 +282,13 @@ void Controller::createWindow() { _window = std::make_unique(); 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 6a50d25905..a8dee5f762 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 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 d070c93d2a..a49ba88877 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 +#include namespace Iv { namespace { @@ -298,12 +300,33 @@ ShareBoxResult Shown::shareBox(ShareBoxDescriptor &&descriptor) { state->destroyRequests.fire({}); }, wrap->lifetime()); + const auto waiting = layer->lifetime().make_state(); 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 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 ce592ecab2..358a7b0ec0 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 5d44a8acc3..1f0b2531d5 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 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 8e993f4f74..27e37053a3 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 callback) { } void WebViewInstance::botSharePhone(Fn 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 007e3a519f..7dd187eeeb 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 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 0091881ed0..463472041c 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 ea305575e5..f5a64b413a 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 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 f326275fdf..d5f76bf86d 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 acf09d8456..214de059c4 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 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 6d8a8ec54d..32437fb772 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 done) { uniqueName.erase(0, 1); uniqueName.replace(uniqueName.find('.'), 1, 1, '_'); - const auto window = std::make_shared(); - window->setAttribute(Qt::WA_DontShowOnScreen); - window->setWindowModality(Qt::ApplicationModal); - window->show(); + const auto parent = []() -> QPointer { + const auto active = Core::App().activeWindow(); + if (!active) { + return nullptr; + } + + return active->widget().get(); + }(); + + const auto window = std::make_shared>( + 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 done) { }); }); - std::vector commandline; commandline.push_back(executable.toStdString()); if (Core::Launcher::Instance().customWorkingDir()) { @@ -156,7 +170,9 @@ void PortalAutostart(bool enabled, Fn 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 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 463472041c..f19f8f1e46 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 0bf1aae043..e7a34e6aef 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 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 f19f8f1e46..143bf072ce 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 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 143bf072ce..ca962d9353 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 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 ca962d9353..0f101584b2 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 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 4ac8cf9d65..21a8611ab7 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 7dd187eeeb..45f3a33067 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 7dd187eeeb46b68dadc5752c3f3fc34e5e1d7e94 +Subproject commit 45f3a330679b775f0f56bbea7b40f83ebd8f5639 diff --git a/cmake b/cmake index b3871c4efd..462b85cc62 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 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 358a7b0ec0..ce5bc41b27 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 1f0b2531d5..70b83ba715 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 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 ce5bc41b27..0d88a47c7f 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 21183fa389..e3217ca409 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 0f101584b2..303aab4789 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 462b85cc62..17c758e2b9 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 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 46918013d2..27e1bbfe4b 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.2.4.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index c47677eb21..a256817c41 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 5594959fe5..8f3f05d484 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 e1ac92bf4e..45302d07a8 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 4d2b860593..446ec84d7b 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 c0298f855a..8d5ac75b8f 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 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 4b92ee0757..ea204a7704 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 84c3361310..927dba8f86 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 c6a7e9c6e6..985bc03f9f 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 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( _window, diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 45f3a33067..03f250aab2 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 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 303aab4789..99816a198b 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 f5a64b413a..b7c447019f 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 70b83ba715..d9a08df0c0 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 0a3d3f8552..12fa23acb0 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 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 03f250aab2..99d9142130 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 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 223cec0439..6777464c17 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 ebd403da48..ef1bddf245 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 74a783696f..d64d82c535 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 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 48cf60eff8..b9ee4785c2 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 6c98281895..b902aa4f69 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 cd8aa54e2d..81f098d670 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 9b92f4815b..47c1e8b47a 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 0000000000..4402e4ccf8 --- /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 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 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 0000000000..ad10460952 --- /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 picker); + +private: + struct Entry; + + std::vector _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 27e37053a3..e7d842c64b 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 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 MakeAttachBotsMenu( diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index c14d490511..cc09dacf52 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(this)) , _topPeers(std::make_unique(this)) , _factchecks(std::make_unique(this)) +, _locationPickers(std::make_unique()) , _cachedReactionIconFactory(std::make_unique()) , _supportHelper(Support::Helper::Create(this)) , _saveSettingsTimer([=] { saveSettings(); }) { diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 9581e7cd42..a69373a36f 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 _sponsoredMessages; const std::unique_ptr _topPeers; const std::unique_ptr _factchecks; + const std::unique_ptr _locationPickers; using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory; const std::unique_ptr _cachedReactionIconFactory; diff --git a/Telegram/SourceFiles/settings/business/settings_location.cpp b/Telegram/SourceFiles/settings/business/settings_location.cpp index e5bb0f8e38..6b659d0441 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; rpl::variable _map = nullptr; + base::weak_ptr _picker; std::shared_ptr _view; Ui::RoundRect _bottomSkipRounding; @@ -232,6 +233,10 @@ void Location::setupPicker(not_null 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 8cce445711..b6e3dc4411 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 #include @@ -185,11 +188,12 @@ private: [[nodiscard]] object_ptr MakeFoursquarePromo() { auto result = object_ptr((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(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 SetupMapPlaceholder( + not_null parent, + int minHeight, + int maxHeight, + Fn choose) { + const auto result = CreateChild(parent); + + const auto top = CreateChild(result); + const auto bottom = CreateChild(result); + + const auto icon = CreateChild(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(); + 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( + 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(_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(_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(_body.get()); const auto controls = _scroll->setOwnedWidget( object_ptr(_scroll)); @@ -727,17 +834,6 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { const auto toppad = mapControls->add(object_ptr(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(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(_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( _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 97ce724f34..943e7ac75f 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 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 _window; not_null _body; RpWidget *_container = nullptr; + RpWidget *_mapPlaceholder = nullptr; + RpWidget *_mapLoading = nullptr; + AbstractButton *_mapButton = nullptr; SlideWrap *_mapControlsWrap = nullptr; + rpl::variable _chooseButtonLabel; ScrollArea *_scroll = nullptr; + Webview::StorageId _webviewStorageId; std::unique_ptr _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 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 0d88a47c7f..9bf4065ea0 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 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 b6e3dc4411..945e5cf8fe 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 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 27e1bbfe4b..4716432a8a 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.2.5.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a256817c41..91720d2351 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 8f3f05d484..829055b26b 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 45302d07a8..fcfe70dd86 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 446ec84d7b..3e6807e8b5 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 8d5ac75b8f..09adb58350 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 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 bd51bb74a5..3a37311e67 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 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 4716432a8a..45ec14889b 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.2.6.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 91720d2351..83eecf0a79 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 829055b26b..40bb30f11b 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 fcfe70dd86..111881fd48 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 99816a198b..568d6663af 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 b7c447019f..83e16284fd 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 3e6807e8b5..f10750e38d 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 09adb58350..5ec213f7e9 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 12fa23acb0..b5f71d99f2 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 4c6d4970dc..e1d67952eb 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 feb6d395ed..aea01c5924 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 InfiniteRadialAnimationWidget( not_null parent, - int size) { + int size, + const style::InfiniteRadialAnimation *st) { class Widget final : public Ui::RpWidget { public: - Widget(not_null p, int size) + Widget( + not_null 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 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(parent.get(), size); + return Ui::CreateChild(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 221f6017f1..bc42a9d1ca 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 InfiniteRadialAnimationWidget( not_null parent, - int size); + int size, + const style::InfiniteRadialAnimation *st = nullptr); void AddChildToWidgetCenter( not_null 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 19f488b0b4..b2c2ec4db1 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 8ef614d218..ec9fa548e9 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 ce806928c3..2853340a53 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 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( parent, @@ -1223,7 +1206,7 @@ base::unique_qptr 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 Members::Controller::createRowContextMenu( // PopupMenu::hide activates back the group call panel :( delete weakMenu; } - callback(window); - window->widget()->activate(); + window->invokeForSessionController( + account, + participantPeer, + [&](not_null 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 b902aa4f69..47b46c6e91 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 b3c589be79..afd4d233f9 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 _pathGradient; mutable StickerPremiumMark _premiumMark; @@ -538,6 +544,70 @@ void StickerSetBox::updateTitleAndButtons() { updateButtons(); } +void ChangeSetNameBox( + not_null box, + not_null data, + const StickerSetIdentifier &input) { + box->setTitle(tr::lng_stickers_box_edit_name_title()); + box->addRow( + object_ptr( + 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( + box, + st::editStickerSetNameField.heightMin)); + auto owned = object_ptr( + 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)>; + if (!_inner->amSetCreator()) { + return Filler(nullptr); + } + const auto data = &_session->data(); + return Filler([=, show = _show, set = _set]( + not_null 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( 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 47c1e8b47a..be9d5f8e47 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 99d9142130..97bfa6cef4 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 afd4d233f9..e1906b0c8f 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 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 box, not_null data, - const StickerSetIdentifier &input) { + const StickerSetIdentifier &input, + Fn done) { + struct State final { + rpl::variable requestId = 0; + Ui::RpWidget* saveButton = nullptr; + }; box->setTitle(tr::lng_stickers_box_edit_name_title()); box->addRow( object_ptr( box, tr::lng_stickers_box_edit_name_about(), st::boxLabel)); + const auto state = box->lifetime().make_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 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 be9d5f8e47..40ab1d705e 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 47b46c6e91..3aa6ac586b 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 e1906b0c8f..3cc0954219 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 setArchived() const; [[nodiscard]] rpl::producer<> updateControls() const; + void setReorderState(bool enabled) { + _dragging.enabled = enabled; + } + [[nodiscard]] bool reorderState() const { + return _dragging.enabled; + } + [[nodiscard]] rpl::producer errors() const; void archiveStickers(); @@ -338,6 +347,9 @@ private: not_null sticker, Api::SendOptions options); + [[nodiscard]] QPoint posFromIndex(int index) const; + [[nodiscard]] bool isDraggedAnimating() const; + not_null 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 _shiftAnimations; + const std::unique_ptr _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 StickerSetBox::Inner::getLottiePlayer() { if (!_lottiePlayer) { _lottiePlayer = std::make_unique( @@ -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 21a8611ab7..ca4503b307 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 3cc0954219..48fe9828b3 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 document, int index); + void chosen( int index, not_null sticker, @@ -394,6 +396,8 @@ private: int lastSelected = -1; QPoint point; } _dragging; + std::deque> _reorderRequests; + std::optional _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 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 48fe9828b3..dd5a011836 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> _reorderRequests; std::optional _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 3aa6ac586b..57dbaad872 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 dd5a011836..aef0dd941d 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 document, int index); + void fillDeleteStickerBox(not_null 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 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 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(container)); + line->resize(line->width(), _singleSize.height()); + + const auto sticker = Ui::CreateChild(line); + auto &lifetime = sticker->lifetime(); + struct State final { + rpl::variable requestId = 0; + Ui::RpWidget* saveButton = nullptr; + }; + const auto state = lifetime.make_state(); + sticker->resize(_singleSize); + { + const auto animation = lifetime.make_state(); + 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( + 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( + 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 57dbaad872..7005a219b8 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 40ab1d705e..d48f32c005 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 337658af18..148f4e7d91 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 3243772420..75d98404e2 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 e77ee46b56..c218ce2fa8 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; 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 d5f76bf86d..662bfc9801 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) { diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 04a2e25f11..0f0fa264b8 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 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 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 15c0941c39..7259c410d3 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 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 now, + bool attached); + void requestFavedStickers(std::optional 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 9ed24fb381..8000355c23 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 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 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 148f4e7d91..4258edbacc 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 document) { +void StickersListWidget::showStickerSetBox( + not_null document, + uint64 setId) { if (document->sticker() && document->sticker()->set) { checkHideWithBox(Box( _show, document->sticker()->set, document->sticker()->setType)); + } else if ((setId == Data::Stickers::FavedSetId) + || (setId == Data::Stickers::RecentSetId)) { + const auto lifetime = std::make_shared(); + 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 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 f1a89b2108..c436273fcf 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 document); + void showStickerSetBox( + not_null document, + uint64 setId); void cancelSetsSearch(); void showSearchResults(); From 5c797d1f316f7e9cca5a3a18f7b9712b22c9df3f Mon Sep 17 00:00:00 2001 From: John Preston 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 7005a219b8..2311102df1 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 a76cb4b675..46a5b7fa3e 100644 --- a/Telegram/SourceFiles/api/api_media.cpp +++ b/Telegram/SourceFiles/api/api_media.cpp @@ -36,7 +36,8 @@ MTPVector 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 da16410b03..70d29f28ab 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 parent, not_null 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 424e2c4484..b8c943edcb 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 parent, not_null from, - int months); + GiftType type, + int count); MediaGiftBox( not_null parent, not_null from, diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index e43e6423ad..cf772208b7 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4533,7 +4533,8 @@ void Session::serviceNotification( MTPVector(), 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 38ea28aae6..edd4670d83 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 { + auto result = std::optional(); + 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 b318ce9fe8..4dfc7a7127 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 9e65eac39c..f9c9806d07 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 72824a2eef..9639730e42 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 d988fc00c3..4d3fa397c4 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 5d4c7e42db..62aaba5f7f 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 d2386e7e62..9cee2262e7 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, PrepareErrorText)); @@ -5072,6 +5094,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { _media = std::make_unique( 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( + 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 5a6538036f..8390d09fa8 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, const QString &name) { MTPVector(), 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 b832a6dc3f..885e74a62d 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 af5724dfac..7240dd49e3 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 _parent; diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp index 9e366ca549..bfcab3fc78 100644 --- a/Telegram/SourceFiles/main/main_account.cpp +++ b/Telegram/SourceFiles/main/main_account.cpp @@ -171,7 +171,8 @@ void Account::createSession( MTPVector(), 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 394a50efaf..2d35a0c524 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 bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.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 bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.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 = 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 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 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 = 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 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 countries_iso2:flags.2?Vector 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 = 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 = 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 = StarsTransaction; payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = 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 = bots.PopularAppBots; + +botPreviewMedia#23e91ba3 date:int media:MessageMedia = BotPreviewMedia; + +bots.previewInfo#ca71d64 media:Vector lang_codes:Vector = 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; @@ -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 = Vector; +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 = Bool; +bots.reorderPreviewMedias#b627f3aa bot:InputUser lang_code:string order:Vector = Bool; +bots.getPreviewInfo#423ab3ad bot:InputUser lang_code:string = bots.PreviewInfo; +bots.getPreviewMedias#a2a5594d bot:InputUser = Vector; 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 = payments.StarsStatus; +payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -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 c09b41c363..e9dec016bd 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(&_id.value)) { return MTP_inputInvoiceSlug(MTP_string(slug->slug)); } else if (const auto credits = std::get_if(&_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(_id.value); using Flag = MTPDpremiumGiftCodeOption::Flag; diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 59f2fb5056..1895a87035 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 dd87b0c5fe..efb527506b 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 2311102df1..931bbb1c0c 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 356e2cffaf..0b35a04eef 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 fdf1eda03f..5730d9b40a 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 75da4db5b1..d01becf072 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 9c2a692e78..86640fe5b3 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 1eaef45682..1f774b499b 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( 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>( + 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 e99a4d4eb3..61b43b8fc9 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -455,7 +455,9 @@ Fn)> 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 8fac1a3991..d72c74d077 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 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 f54b1f1d35..2e810dd6c4 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>(); 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 e722c19796..4c480fba3c 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 _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 1f774b499b..fdb7b74fe2 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>( content, object_ptr(content, peer, stUser))); } else { - const auto widget = content->add( - object_ptr>( + if (e.gift) { + using PlayerPtr = std::unique_ptr; + struct State final { + DocumentData *sticker = nullptr; + std::shared_ptr media; + std::unique_ptr lottie; + rpl::lifetime downloadLifetime; + }; + Ui::AddSkip( content, - object_ptr(content)))->entity(); - using Draw = Fn; - const auto draw = widget->lifetime().make_state( - 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(content); + icon->resize(Size(st::creditsHistoryEntryGiftStickerSize)); + const auto state = icon->lifetime().make_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>( + content, + object_ptr(content)))->entity(); + using Draw = Fn; + const auto draw = widget->lifetime().make_state( + 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 baab38fb77..cebc110c5e 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 0b35a04eef..3ef1d721f0 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -134,12 +134,10 @@ rpl::producer CreditsTopupOptions::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); - using TLOption = MTPStarsTopupOption; - _api.request(MTPpayments_GetStarsTopupOptions( - )).done([=](const MTPVector &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 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 &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 &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 e80f82a62d..2352536524 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 fdb7b74fe2..e9e0158490 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 show, not_null container, + not_null peer, int minimumCredits, Fn paid) { const auto options = container->add( @@ -351,8 +352,7 @@ void FillCreditOptions( }; using ApiOptions = Api::CreditsTopupOptions; - const auto apiCredits = content->lifetime().make_state( - show->session().user()); + const auto apiCredits = content->lifetime().make_state(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 47d5323c7a..701c60b7fe 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 show, not_null container, + not_null peer, int minCredits, Fn 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 e9e0158490..613bf9169a 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 3ef1d721f0..727cee565c 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -134,7 +134,9 @@ rpl::producer 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 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 d01becf072..ee8d948a75 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; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index e9dec016bd..8c0a44e10e 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(&_id.value)) { return MTP_inputInvoiceSlug(MTP_string(slug->slug)); } else if (const auto credits = std::get_if(&_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 c85d946ed2..42b6d00b37 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 613bf9169a..954c07c7b5 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 b9ee4785c2..8923211da4 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 931bbb1c0c..fad51ea323 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 0000000000..d03a736410 --- /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 box, + not_null peer, + Fn gifted) { + box->setStyle(st::creditsGiftBox); + box->setNoContentMargin(true); + box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); }); + + const auto content = box->setPinnedToTopContent( + object_ptr(box)); + + Ui::AddSkip(content); + Ui::AddSkip(content); + const auto &stUser = st::premiumGiftsUserpicButton; + const auto userpicWrap = content->add( + object_ptr>( + content, + object_ptr(content, peer, stUser))); + userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents); + Ui::AddSkip(content); + Ui::AddSkip(content); + + { + const auto widget = Ui::CreateChild(content); + using ColoredMiniStars = Ui::Premium::ColoredMiniStars; + const auto stars = widget->lifetime().make_state( + 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>( + 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(box)); +} + +void ShowGiftCreditsBox( + not_null controller, + Fn gifted) { + + class Controller final : public ContactsBoxController { + public: + Controller( + not_null session, + Fn)> choose) + : ContactsBoxController(session) + , _choose(std::move(choose)) { + } + + protected: + std::unique_ptr createRow( + not_null user) override { + if (user->isSelf() + || user->isBot() + || user->isServiceUser() + || user->isInaccessible()) { + return nullptr; + } + return ContactsBoxController::createRow(user); + } + + void rowClicked(not_null row) override { + _choose(row->peer()); + } + + private: + const Fn)> _choose; + + }; + auto initBox = [=](not_null 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->session(), + [=](not_null peer) { + show->showBox(Box(GiftCreditsBox, peer, gifted)); + }); + show->showBox( + Box(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 0000000000..43b8556f32 --- /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 controller, + Fn gifted); + +} // namespace Ui diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 2352536524..0d7d124906 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 cebc110c5e..b982687d65 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 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 754791e80d..35643637e7 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 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 9cee2262e7..4a8a413ccf 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 return fields; } +[[nodiscard]] TextWithEntities AmountAndStarCurrency( + not_null 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([=]( 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(); @@ -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 7e9fb6396b..1a5e140140 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 , 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 d950623137..7ebd961f3a 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::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 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 edd4670d83..d9c37de4e1 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 &Story::urlAreas() const { return _urlAreas; } +const std::vector &Story::weatherAreas() const { + return _weatherAreas; +} + void Story::applyChanges( StoryMedia media, const MTPDstoryItem &data, @@ -825,6 +829,7 @@ void Story::applyFields( auto suggestedReactions = std::vector(); auto channelPosts = std::vector(); auto urlAreas = std::vector(); + auto weatherAreas = std::vector(); 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 4dfc7a7127..bd508591c9 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 &; [[nodiscard]] auto urlAreas() const -> const std::vector &; + [[nodiscard]] auto weatherAreas() const + -> const std::vector &; void applyChanges( StoryMedia media, @@ -270,6 +273,7 @@ private: std::vector _suggestedReactions; std::vector _channelPosts; std::vector _urlAreas; + std::vector _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 c5fd13992d..ca558abfd4 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(); + const auto &weatherAreas = story + ? story->weatherAreas() + : std::vector(); 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(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([=] { + 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 3d486fa873..590dee3f67 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 reaction; + std::unique_ptr view; }; void initLayout(); @@ -303,6 +305,7 @@ private: std::vector _suggestedReactions; std::vector _channelPosts; std::vector _urlAreas; + std::vector _weatherAreas; mutable std::vector _areas; std::vector _cachedSourcesList; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index af45d91507..f5e2582ef4 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 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 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 document); + [[nodiscard]] QSize stickerSize() const; + + const not_null _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 _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 delegate, not_null 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 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 effect, @@ -457,6 +534,205 @@ void ReactionView::cacheBackground() { paintShape(_data.dark ? dark : QColor(255, 255, 255)); } +WeatherView::WeatherView( + QWidget *parent, + not_null 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 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 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( + ChatHelpers::LottiePlayerFromDocument( + media.get(), + ChatHelpers::StickerLottieSize::StickerSet, + stickerSize(), + Lottie::Quality::High)); + } else if (sticker->isWebm()) { + _sticker = std::make_shared( + media->owner()->location(), + media->bytes(), + stickerSize()); + } else { + _sticker = std::make_shared( + 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 { auto Reactions::makeSuggestedReactionWidget( const Data::SuggestedReaction &reaction) --> std::unique_ptr { +-> std::unique_ptr { return std::make_unique( _controller->wrap(), &_controller->uiShow()->session(), reaction); } +auto Reactions::makeWeatherAreaWidget(const Data::WeatherArea &data) +-> std::unique_ptr { + return std::make_unique( + _controller->wrap(), + &_controller->uiShow()->session(), + data); +} + void Reactions::setReplyFieldState( rpl::producer focused, rpl::producer hasSendText) { diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index de9f0e0ce6..b17e2e19da 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; + -> std::unique_ptr; + [[nodiscard]] auto makeWeatherAreaWidget(const Data::WeatherArea &data) + -> std::unique_ptr; void setReplyFieldState( rpl::producer focused, diff --git a/Telegram/SourceFiles/ui/color_int_conversion.cpp b/Telegram/SourceFiles/ui/color_int_conversion.cpp index a1b0bcb45a..5c9c57f073 100644 --- a/Telegram/SourceFiles/ui/color_int_conversion.cpp +++ b/Telegram/SourceFiles/ui/color_int_conversion.cpp @@ -22,4 +22,12 @@ std::optional 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 ed2bb6a182..1102863b2d 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 MaybeColorFromSerialized( quint32 serialized); +[[nodiscard]] QColor Color32FromSerialized(quint32 serialized); } // namespace Ui From a5ffd8b7cf7ecbe60f5e413a7698e189b6c302b9 Mon Sep 17 00:00:00 2001 From: John Preston 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 e7d842c64b..e0a609f7a3 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(_source) + ? (v::get(_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 6e11c87cd4..ecccdd0422 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 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 2e810dd6c4..f510540c2c 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 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 4c480fba3c..1e6c0e9349 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 885e74a62d..2a7a2f4b1e 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 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 fad51ea323..fedb32957c 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 5730d9b40a..3b8f6f1b5e 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 ea204a7704..6a816af963 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 4a8a413ccf..a38b9b33fe 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 2a7a2f4b1e..23d483c1ff 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 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([=](ClickContext context) { const auto my = context.other.value(); 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 954c07c7b5..a8c63b5f8b 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>( content, object_ptr(content, peer, stUser))); - } else { - if (e.gift) { - using PlayerPtr = std::unique_ptr; - struct State final { - DocumentData *sticker = nullptr; - std::shared_ptr media; - std::unique_ptr lottie; - rpl::lifetime downloadLifetime; - }; - Ui::AddSkip( - content, - st::creditsHistoryEntryGiftStickerSpace); - const auto icon = Ui::CreateChild(content); - icon->resize(Size(st::creditsHistoryEntryGiftStickerSize)); - const auto state = icon->lifetime().make_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>( - content, - object_ptr(content)))->entity(); - using Draw = Fn; - const auto draw = widget->lifetime().make_state( - 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 media; + std::unique_ptr lottie; + rpl::lifetime downloadLifetime; + }; + Ui::AddSkip( + content, + st::creditsHistoryEntryGiftStickerSpace); + const auto icon = Ui::CreateChild(content); + icon->resize(Size(st::creditsHistoryEntryGiftStickerSize)); + const auto state = icon->lifetime().make_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>( + content, + object_ptr(content)))->entity(); + using Draw = Fn; + const auto draw = widget->lifetime().make_state( + 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>( box, object_ptr( @@ -565,7 +562,7 @@ void ReceiptCreditsBox( auto &lifetime = content->lifetime(); const auto text = lifetime.make_state( 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 box, + not_null controller, + not_null from, + not_null 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 controller, FullMsgId refundItemId) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 701c60b7fe..e07262d6d0 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 controller, PeerData *premiumBot, const Data::CreditsHistoryEntry &e); +void GiftedCreditsBox( + not_null box, + not_null controller, + not_null from, + not_null to, + int count, + TimeId date); void ShowRefundInfoBox( not_null controller, FullMsgId refundItemId); diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 61b43b8fc9..018188cb3e 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 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 a19200d8ab..84b3f8b892 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); - - [[nodiscard]] not_null window() const { - return _window; - } - - bool rowTrackPress(not_null row) override; - void rowTrackPressCancel() override; - bool rowTrackPressSkipMouseSelection() override; - - bool processTouchEvent(not_null e); - void setupTouchChatPreview(not_null scroll); - -private: - const not_null _window; - - std::optional _chatPreviewTouchGlobal; - rpl::event_stream<> _touchCancelRequests; - -}; - -class RecentsController final : public ControllerWithPreviews { -public: - RecentsController( - not_null window, - RecentPeersList list); - - [[nodiscard]] rpl::producer count() const { - return _count.value(); - } - [[nodiscard]] rpl::producer> chosen() const { - return _chosen.events(); - } - - void prepare() override; - void rowClicked(not_null row) override; - base::unique_qptr rowContextMenu( - QWidget *parent, - not_null row) override; - Main::Session &session() const override; - - QString savedMessagesChatStatus() const override; - -private: - void setupDivider(); - void subscribeToEvents(); - [[nodiscard]] Fn removeAllCallback(); - - RecentPeersList _recent; - rpl::variable _count; - rpl::event_stream> _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); - - [[nodiscard]] rpl::producer count() const { - return _count.value(); - } - [[nodiscard]] rpl::producer> chosen() const { - return _chosen.events(); - } - - void prepare() override; - void rowClicked(not_null row) override; - base::unique_qptr rowContextMenu( - QWidget *parent, - not_null row) override; - Main::Session &session() const override; - -private: - void setupDivider(); - void appendRow(not_null channel); - void fill(bool force = false); - - std::vector> _channels; - rpl::variable _toggleExpanded = nullptr; - rpl::variable _count = 0; - rpl::variable _expanded = false; - rpl::event_stream> _chosen; - rpl::lifetime _lifetime; - -}; - -class RecommendationsController final : public ControllerWithPreviews { -public: - explicit RecommendationsController( - not_null window); - - [[nodiscard]] rpl::producer count() const { - return _count.value(); - } - [[nodiscard]] rpl::producer> chosen() const { - return _chosen.events(); - } - - void prepare() override; - void rowClicked(not_null row) override; - base::unique_qptr rowContextMenu( - QWidget *parent, - not_null row) override; - Main::Session &session() const override; - - void load(); - -private: - void fill(); - void setupDivider(); - void appendRow(not_null channel); - - rpl::variable _count; - History *_activeHistory = nullptr; - bool _requested = false; - rpl::event_stream> _chosen; - rpl::lifetime _lifetime; - -}; - struct EntryMenuDescriptor { not_null controller; not_null 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); + + [[nodiscard]] not_null window() const { + return _window; + } + [[nodiscard]] rpl::producer count() const { + return _count.value(); + } + [[nodiscard]] rpl::producer> chosen() const { + return _chosen.events(); + } + + bool rowTrackPress(not_null row) override; + void rowTrackPressCancel() override; + bool rowTrackPressSkipMouseSelection() override; + + bool processTouchEvent(not_null e); + void setupTouchChatPreview(not_null scroll); + +protected: + [[nodiscard]] int countCurrent() const; + void setCount(int count); + void choose(not_null peer); + +private: + const not_null _window; + + std::optional _chatPreviewTouchGlobal; + rpl::event_stream<> _touchCancelRequests; + rpl::event_stream> _chosen; + rpl::variable _count; + +}; + +class RecentsController final : public Suggestions::ObjectListController { +public: + RecentsController( + not_null window, + RecentPeersList list); + + void prepare() override; + void rowClicked(not_null row) override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + Main::Session &session() const override; + + QString savedMessagesChatStatus() const override; + +private: + void setupDivider(); + void subscribeToEvents(); + [[nodiscard]] Fn removeAllCallback(); + + RecentPeersList _recent; + rpl::lifetime _lifetime; + +}; + +class MyChannelsController final + : public Suggestions::ObjectListController { +public: + explicit MyChannelsController( + not_null window); + + void prepare() override; + void rowClicked(not_null row) override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + Main::Session &session() const override; + +private: + void setupDivider(); + void appendRow(not_null channel); + void fill(bool force = false); + + std::vector> _channels; + rpl::variable _toggleExpanded = nullptr; + rpl::variable _expanded = false; + rpl::lifetime _lifetime; + +}; + +class RecommendationsController final + : public Suggestions::ObjectListController { +public: + explicit RecommendationsController( + not_null window); + + void prepare() override; + void rowClicked(not_null row) override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + Main::Session &session() const override; + + void load(); + +private: + void fill(); + void setupDivider(); + void appendRow(not_null channel); + + History *_activeHistory = nullptr; + bool _requested = false; + rpl::lifetime _lifetime; + +}; + +Suggestions::ObjectListController::ObjectListController( not_null window) : _window(window) { } -bool ControllerWithPreviews::rowTrackPress(not_null row) { +bool Suggestions::ObjectListController::rowTrackPress( + not_null 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 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 e) { +bool Suggestions::ObjectListController::processTouchEvent( + not_null e) { const auto point = e->touchPoints().empty() ? std::optional() : e->touchPoints().front().screenPos().toPoint(); @@ -500,7 +493,7 @@ bool ControllerWithPreviews::processTouchEvent(not_null e) { return false; } -void ControllerWithPreviews::setupTouchChatPreview( +void Suggestions::ObjectListController::setupTouchChatPreview( not_null 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 peer) { + _chosen.fire_copy(peer); +} + RecentsController::RecentsController( not_null window, RecentPeersList list) -: ControllerWithPreviews(window) +: ObjectListController(window) , _recent(std::move(list)) { } @@ -523,13 +528,13 @@ void RecentsController::prepare() { delegate()->peerListAppendRow(std::make_unique(peer)); } delegate()->peerListRefreshRows(); - _count = _recent.list.size(); + setCount(_recent.list.size()); subscribeToEvents(); } void RecentsController::rowClicked(not_null row) { - _chosen.fire(row->peer()); + choose(row->peer()); } Fn RecentsController::removeAllCallback() { @@ -537,7 +542,7 @@ Fn 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 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(row.get())->refreshBadge()) { delegate()->peerListUpdateRow(row); @@ -660,7 +665,7 @@ void RecentsController::subscribeToEvents() { MyChannelsController::MyChannelsController( not_null 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 channel) { } void MyChannelsController::rowClicked(not_null row) { - _chosen.fire(row->peer()); + choose(row->peer()); } base::unique_qptr 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) -: 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 channel) { } void RecommendationsController::rowClicked(not_null row) { - _chosen.fire(row->peer()); + choose(row->peer()); } base::unique_qptr RecommendationsController::rowContextMenu( @@ -972,8 +977,6 @@ void RecommendationsController::setupDivider() { delegate()->peerListSetAboveWidget(std::move(result)); } -} // namespace - Suggestions::Suggestions( not_null parent, not_null controller, @@ -990,13 +993,13 @@ Suggestions::Suggestions( this, object_ptr(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(this)) , _channelsContent( _channelsScroll->setOwnedWidget(object_ptr(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 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 finish) { @@ -1432,36 +1436,25 @@ void Suggestions::resizeEvent(QResizeEvent *e) { _channelsContent->resizeToWidth(w); } -object_ptr> Suggestions::setupRecentPeers( - RecentPeersList recentPeers) { - auto &lifetime = _chatsContent->lifetime(); - const auto delegate = lifetime.make_state< - PeerListContentDelegateSimple - >(); - const auto controller = lifetime.make_state( +auto Suggestions::setupRecentPeers(RecentPeersList recentPeers) +-> std::unique_ptr { + const auto controller = lifetime().make_state( _controller, std::move(recentPeers)); - controller->setStyleOverrides(&st::recentPeersList); - _recentCount = controller->count(); - _recentProcessTouch = [=](not_null 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 peer) { - _controller->session().recentPeers().bump(peer); - _recentPeerChosen.fire_copy(peer); - }, lifetime); - - auto content = object_ptr(_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> 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> 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 peer) { + _controller->session().recentPeers().bump(peer); + }, list->lifetime()); - return object_ptr>(this, std::move(content)); + return result; } object_ptr> Suggestions::setupEmptyRecent() { @@ -1505,6 +1488,168 @@ object_ptr> Suggestions::setupEmptyRecent() { return setupEmpty(_chatsContent, icon, tr::lng_recent_none()); } +auto Suggestions::setupMyChannels() -> std::unique_ptr { + const auto controller = lifetime().make_state( + _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 { + const auto controller = lifetime().make_state( + _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 scroll, + not_null parent, + not_null controller, + Fn addToScroll) +-> std::unique_ptr { + auto &lifetime = parent->lifetime(); + const auto delegate = lifetime.make_state< + PeerListContentDelegateSimple + >(); + controller->setStyleOverrides(&st::recentPeersList); + + auto content = object_ptr(parent, controller); + const auto list = content.data(); + + auto result = std::make_unique(ObjectList{ + .wrap = parent->add(object_ptr>( + parent, + std::move(content))), + }); + const auto raw = result.get(); + + raw->count = controller->count(); + raw->processTouch = [=](not_null e) { + return controller->processTouchEvent(e); + }; + + controller->chosen( + ) | rpl::start_with_next([=](not_null 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> Suggestions::setupEmptyChannels() { const auto icon = SearchEmptyIcon::NoResults; return setupEmpty(_channelsContent, icon, tr::lng_channels_none_about()); @@ -1541,157 +1686,6 @@ object_ptr> Suggestions::setupEmpty( return result; } -object_ptr> Suggestions::setupMyChannels() { - auto &lifetime = _channelsContent->lifetime(); - const auto delegate = lifetime.make_state< - PeerListContentDelegateSimple - >(); - const auto controller = lifetime.make_state( - _controller); - controller->setStyleOverrides(&st::recentPeersList); - - _myChannelsCount = controller->count(); - _myChannelsProcessTouch = [=](not_null e) { - return controller->processTouchEvent(e); - }; - - controller->chosen( - ) | rpl::start_with_next([=](not_null peer) { - _persist = false; - _myChannelChosen.fire_copy(peer); - }, lifetime); - - auto content = object_ptr(_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>(this, std::move(content)); -} - -object_ptr> Suggestions::setupRecommendations() { - auto &lifetime = _channelsContent->lifetime(); - const auto delegate = lifetime.make_state< - PeerListContentDelegateSimple - >(); - const auto controller = lifetime.make_state( - _controller); - controller->setStyleOverrides(&st::recentPeersList); - - _recommendationsCount = controller->count(); - _recommendationsProcessTouch = [=](not_null 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 peer) { - _persist = true; - _recommendationChosen.fire_copy(peer); - }, lifetime); - - auto content = object_ptr(_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>(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 8b33718e6e..438c0b370e 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> { - return _recentPeerChosen.events(); + return _recent->chosen.events(); } [[nodiscard]] auto myChannelChosen() const -> rpl::producer> { - return _myChannelChosen.events(); + return _myChannels->chosen.events(); } [[nodiscard]] auto recommendationChosen() const -> rpl::producer> { - 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*> wrap; + rpl::variable count; + Fn choose; + Fn selectJump; + Fn updateFromParentDrag; + Fn dragLeft; + Fn)> processTouch; + rpl::event_stream> 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> setupRecentPeers( + [[nodiscard]] std::unique_ptr setupRecentPeers( RecentPeersList recentPeers); - [[nodiscard]] object_ptr> setupEmptyRecent(); - [[nodiscard]] object_ptr> setupMyChannels(); - [[nodiscard]] auto setupRecommendations() + [[nodiscard]] auto setupEmptyRecent() -> object_ptr>; + + [[nodiscard]] std::unique_ptr setupMyChannels(); + [[nodiscard]] std::unique_ptr setupRecommendations(); [[nodiscard]] auto setupEmptyChannels() -> object_ptr>; + + [[nodiscard]] std::unique_ptr setupObjectList( + not_null scroll, + not_null parent, + not_null controller, + Fn addToScroll = nullptr); + [[nodiscard]] object_ptr> setupEmpty( not_null parent, SearchEmptyIcon icon, @@ -131,44 +154,23 @@ private: const std::unique_ptr _chatsScroll; const not_null _chatsContent; + const not_null*> _topPeersWrap; const not_null _topPeers; + rpl::event_stream> _topPeerChosen; + + const std::unique_ptr _recent; - rpl::variable _recentCount; - Fn _recentPeersChoose; - Fn _recentSelectJump; - Fn _recentUpdateFromParentDrag; - Fn _recentDragLeft; - Fn)> _recentProcessTouch; - const not_null*> _recentPeers; const not_null*> _emptyRecent; const std::unique_ptr _channelsScroll; const not_null _channelsContent; - rpl::variable _myChannelsCount; - Fn _myChannelsChoose; - Fn _myChannelsSelectJump; - Fn _myChannelsUpdateFromParentDrag; - Fn _myChannelsDragLeft; - Fn)> _myChannelsProcessTouch; - const not_null*> _myChannels; - - rpl::variable _recommendationsCount; - Fn _recommendationsChoose; - Fn _recommendationsSelectJump; - Fn _recommendationsUpdateFromParentDrag; - Fn _recommendationsDragLeft; - Fn)> _recommendationsProcessTouch; - const not_null*> _recommendations; + const std::unique_ptr _myChannels; + const std::unique_ptr _recommendations; const not_null*> _emptyChannels; - rpl::event_stream> _topPeerChosen; - rpl::event_stream> _recentPeerChosen; - rpl::event_stream> _myChannelChosen; - rpl::event_stream> _recommendationChosen; - Ui::Animations::Simple _shownAnimation; Fn _showFinished; bool _hidden = false; From 6a8a85e39514242772a4c9b516bd0a21d23ff847 Mon Sep 17 00:00:00 2001 From: John Preston 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 fedb32957c..64bbb68d8f 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 f476869b66..57974febfe 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 session) -: _session(session) { +TopPeers::TopPeers(not_null 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 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 5f1250b531..7f834ad84f 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 session); + TopPeers(not_null session, TopPeerType type); ~TopPeers(); [[nodiscard]] std::vector> list() const; @@ -36,11 +41,13 @@ private: float64 rating = 0.; }; + void loadAfterChats(); void request(); [[nodiscard]] uint64 countHash() const; void updated(); const not_null _session; + const TopPeerType _type = {}; std::vector _list; rpl::event_stream<> _updates; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index cf772208b7..f88fb6abb1 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -721,6 +721,8 @@ not_null 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 67da9e3f54..8f84555d3f 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 922b673a06..2e7568fc02 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 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 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 bot) { + session().attachWebView().open({ + .bot = bot, + .context = { + .controller = controller(), + .maySkipConfirmation = true, + }, + .source = InlineBots::WebViewSourceBotProfile(), + }); +} + void Widget::changeOpenedSubsection( FnMut change, bool fromRight, diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 347683d0c5..a1ee3950de 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 bot); void changeOpenedSubsection( FnMut change, bool fromRight, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 84b3f8b892..18ae94b371 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 row) override; bool rowTrackPress(not_null 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 peer); + + [[nodiscard]] bool expandedCurrent() const; + [[nodiscard]] rpl::producer expanded() const; + + void setupPlainDivider(rpl::producer title); + void setupExpandDivider(rpl::producer title); private: const not_null _window; @@ -334,6 +347,8 @@ private: rpl::event_stream<> _touchCancelRequests; rpl::event_stream> _chosen; rpl::variable _count; + rpl::variable _toggleExpanded = nullptr; + rpl::variable _expanded = false; }; @@ -344,11 +359,9 @@ public: RecentPeersList list); void prepare() override; - void rowClicked(not_null row) override; base::unique_qptr rowContextMenu( QWidget *parent, not_null row) override; - Main::Session &session() const override; QString savedMessagesChatStatus() const override; @@ -369,20 +382,15 @@ public: not_null window); void prepare() override; - void rowClicked(not_null row) override; base::unique_qptr rowContextMenu( QWidget *parent, not_null row) override; - Main::Session &session() const override; private: - void setupDivider(); void appendRow(not_null channel); void fill(bool force = false); std::vector> _channels; - rpl::variable _toggleExpanded = nullptr; - rpl::variable _expanded = false; rpl::lifetime _lifetime; }; @@ -394,17 +402,11 @@ public: not_null window); void prepare() override; - void rowClicked(not_null row) override; - base::unique_qptr rowContextMenu( - QWidget *parent, - not_null row) override; - Main::Session &session() const override; void load(); private: void fill(); - void setupDivider(); void appendRow(not_null channel); History *_activeHistory = nullptr; @@ -413,6 +415,45 @@ private: }; +class RecentAppsController final + : public Suggestions::ObjectListController { +public: + explicit RecentAppsController( + not_null window); + + void prepare() override; + + void load(); + +private: + void appendRow(not_null bot); + void fill(); + + std::vector> _bots; + rpl::lifetime _lifetime; + +}; + +class PopularAppsController final + : public Suggestions::ObjectListController { +public: + explicit PopularAppsController( + not_null window); + + void prepare() override; + + void load(); + +private: + void fill(); + void appendRow(not_null bot); + + History *_activeHistory = nullptr; + bool _requested = false; + rpl::lifetime _lifetime; + +}; + Suggestions::ObjectListController::ObjectListController( not_null window) : _window(window) { @@ -510,8 +551,108 @@ void Suggestions::ObjectListController::setCount(int count) { _count = count; } -void Suggestions::ObjectListController::choose(not_null peer) { - _chosen.fire_copy(peer); +bool Suggestions::ObjectListController::expandedCurrent() const { + return _expanded.current(); +} + +rpl::producer Suggestions::ObjectListController::expanded() const { + return _expanded.value(); +} + +void Suggestions::ObjectListController::rowClicked( + not_null row) { + _chosen.fire(row->peer()); +} + +void Suggestions::ObjectListController::setupPlainDivider( + rpl::producer title) { + auto result = object_ptr( + (QWidget*)nullptr, + st::searchedBarHeight); + const auto raw = result.data(); + const auto label = Ui::CreateChild( + 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 title) { + auto result = object_ptr( + (QWidget*)nullptr, + st::searchedBarHeight); + const auto raw = result.data(); + const auto label = Ui::CreateChild( + 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( + 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 row) { - choose(row->peer()); -} - Fn RecentsController::removeAllCallback() { const auto weak = base::make_weak(this); const auto session = &this->session(); @@ -584,10 +721,6 @@ base::unique_qptr 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 channel) { delegate()->peerListAppendRow(std::move(row)); } -void MyChannelsController::rowClicked(not_null row) { - choose(row->peer()); -} - base::unique_qptr MyChannelsController::rowContextMenu( QWidget *parent, not_null row) { @@ -798,83 +927,13 @@ base::unique_qptr MyChannelsController::rowContextMenu( return result; } -Main::Session &MyChannelsController::session() const { - return window()->session(); -} - -void MyChannelsController::setupDivider() { - auto result = object_ptr( - (QWidget*)nullptr, - st::searchedBarHeight); - const auto raw = result.data(); - const auto label = Ui::CreateChild( - 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( - 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) : ObjectListController(window) { } void RecommendationsController::prepare() { - setupDivider(); + setupPlainDivider(tr::lng_channels_recommended()); fill(); } @@ -940,41 +999,115 @@ void RecommendationsController::appendRow(not_null channel) { delegate()->peerListAppendRow(std::move(row)); } -void RecommendationsController::rowClicked(not_null row) { - choose(row->peer()); +RecentAppsController::RecentAppsController( + not_null window) +: ObjectListController(window) { } -base::unique_qptr RecommendationsController::rowContextMenu( - QWidget *parent, - not_null 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( - (QWidget*)nullptr, - st::searchedBarHeight); - const auto raw = result.data(); - const auto label = Ui::CreateChild( - 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 bot) { + auto row = std::make_unique(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) +: 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 bot) { + auto row = std::make_unique(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(this))) , _myChannels(setupMyChannels()) , _recommendations(setupRecommendations()) -, _emptyChannels(_channelsContent->add(setupEmptyChannels())) { +, _emptyChannels(_channelsContent->add(setupEmptyChannels())) +, _appsScroll(std::make_unique(this)) +, _appsContent( + _appsScroll->setOwnedWidget(object_ptr(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 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 finish) { @@ -1322,7 +1570,8 @@ void Suggestions::hide(anim::type animated, Fn 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 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 { return result; } +auto Suggestions::setupRecentApps() -> std::unique_ptr { + const auto controller = lifetime().make_state( + _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 { + const auto controller = lifetime().make_state( + _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 scroll, not_null parent, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 438c0b370e..549851d72e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -79,6 +79,14 @@ public: -> rpl::producer> { return _recommendations->chosen.events(); } + [[nodiscard]] auto recentAppChosen() const + -> rpl::producer> { + return _recentApps->chosen.events(); + } + [[nodiscard]] auto popularAppChosen() const + -> rpl::producer> { + 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 setupRecentPeers( @@ -129,6 +141,9 @@ private: [[nodiscard]] auto setupEmptyChannels() -> object_ptr>; + [[nodiscard]] std::unique_ptr setupRecentApps(); + [[nodiscard]] std::unique_ptr setupPopularApps(); + [[nodiscard]] std::unique_ptr setupObjectList( not_null scroll, not_null parent, @@ -142,7 +157,7 @@ private: void switchTab(Tab tab); void startShownAnimation(bool shown, Fn finish); - void startSlideAnimation(); + void startSlideAnimation(Tab was, Tab now); void finishShow(); void handlePressForChatPreview(PeerId id, Fn callback); @@ -171,6 +186,12 @@ private: const not_null*> _emptyChannels; + const std::unique_ptr _appsScroll; + const not_null _appsContent; + + const std::unique_ptr _recentApps; + const std::unique_ptr _popularApps; + Ui::Animations::Simple _shownAnimation; Fn _showFinished; bool _hidden = false; @@ -181,6 +202,9 @@ private: QPixmap _slideLeft; QPixmap _slideRight; + int _slideLeftTop = 0; + int _slideRightTop = 0; + }; [[nodiscard]] rpl::producer TopPeersContent( diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 8e44a7a79c..4d5a9bc048 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 6e17ed5491..3c0af7c0d9 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 setupPersonalChannel(not_null user); object_ptr setupInfo(); object_ptr setupMuteToggle(); + void setupMainApp(); void setupMainButtons(); Ui::MultiSlideTracker fillTopicButtons(); Ui::MultiSlideTracker fillUserButtons( @@ -1209,10 +1211,21 @@ object_ptr 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>( result, object_ptr(result), - st::infoProfileSeparatorPadding) + padding) )->setDuration( st::infoSlideDuration )->toggleOn( @@ -1548,6 +1561,42 @@ object_ptr DetailsFiller::setupMuteToggle() { return result; } +void DetailsFiller::setupMainApp() { + const auto button = _wrap->add( + object_ptr( + _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 DetailsFiller::fill() { add(object_ptr(_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 e0a609f7a3..5810310faa 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 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 done) { close(); done(); }; + const auto cancel = [=](Fn close) { + botClose(); + close(); + }; _parentShow->show(Ui::MakeConfirmBox({ .text = tr::lng_allow_bot_webview( tr::now, @@ -764,7 +773,7 @@ void WebViewInstance::confirmOpen(Fn 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 session) _refreshTimer.callEach(kRefreshBotsTimeout); } -AttachWebView::~AttachWebView() = default; +AttachWebView::~AttachWebView() { + closeAll(); + _session->api().request(_popularAppBotsRequestId).cancel(); +} void AttachWebView::openByUsername( not_null 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>(); + 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> & { + 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 ecccdd0422..4a4f5ca08e 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 thread); - auto nonPanelPaymentFormFactory( Fn reactivate) -> Fn; @@ -352,6 +349,11 @@ public: void close(not_null instance); void closeAll(); + void loadPopularAppBots(); + [[nodiscard]] auto popularAppBots() const + -> const std::vector> &; + [[nodiscard]] rpl::producer<> popularAppBotsLoaded() const; + private: void resolveUsername( std::shared_ptr show, @@ -395,6 +397,10 @@ private: std::vector> _instances; + std::vector> _popularAppBots; + mtpRequestId _popularAppBotsRequestId = 0; + rpl::variable _popularAppBotsLoaded = false; + }; [[nodiscard]] std::unique_ptr MakeAttachBotsMenu( diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index cc09dacf52..5dd80e9888 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(this)) , _scheduledMessages(std::make_unique(this)) , _sponsoredMessages(std::make_unique(this)) -, _topPeers(std::make_unique(this)) +, _topPeers(std::make_unique(this, Data::TopPeerType::Chat)) +, _topBotApps( + std::make_unique(this, Data::TopPeerType::BotApp)) , _factchecks(std::make_unique(this)) , _locationPickers(std::make_unique()) , _cachedReactionIconFactory(std::make_unique()) diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index a69373a36f..ba5dbcd99f 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 _scheduledMessages; const std::unique_ptr _sponsoredMessages; const std::unique_ptr _topPeers; + const std::unique_ptr _topBotApps; const std::unique_ptr _factchecks; const std::unique_ptr _locationPickers; diff --git a/Telegram/SourceFiles/ui/vertical_list.cpp b/Telegram/SourceFiles/ui/vertical_list.cpp index 11347aa610..6666077dd2 100644 --- a/Telegram/SourceFiles/ui/vertical_list.cpp +++ b/Telegram/SourceFiles/ui/vertical_list.cpp @@ -28,31 +28,34 @@ void AddDivider(not_null container) { container->add(object_ptr(container)); } -void AddDividerText( +not_null AddDividerText( not_null container, rpl::producer text, const style::margins &margins, RectParts parts) { - AddDividerText( + return AddDividerText( container, std::move(text) | Ui::Text::ToWithEntities(), margins, parts); } -void AddDividerText( +not_null AddDividerText( not_null container, rpl::producer text, const style::margins &margins, RectParts parts) { + auto label = object_ptr( + container, + std::move(text), + st::boxDividerLabel); + const auto result = label.data(); container->add(object_ptr( container, - object_ptr( - container, - std::move(text), - st::boxDividerLabel), + std::move(label), margins, parts)); + return result; } not_null AddSubsectionTitle( diff --git a/Telegram/SourceFiles/ui/vertical_list.h b/Telegram/SourceFiles/ui/vertical_list.h index 7ab743bd3b..82934367f8 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 container); void AddSkip(not_null container, int skip); void AddDivider(not_null container); -void AddDividerText( +not_null AddDividerText( not_null container, rpl::producer text, const style::margins &margins = st::defaultBoxDividerLabelPadding, RectParts parts = RectPart::Top | RectPart::Bottom); -void AddDividerText( +not_null AddDividerText( not_null container, rpl::producer text, const style::margins &margins = st::defaultBoxDividerLabelPadding, From 3eeb01be61640c0062715fde3b851ea6b79d9a36 Mon Sep 17 00:00:00 2001 From: John Preston 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 efd0ba4d5a..16b2595de3 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() + : (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 3412c4588a..0ac4e94a50 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 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 _weatherInCelsius; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index ca558abfd4..fc75e366da 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) , _slider(std::make_unique(this)) , _replyArea(std::make_unique(this)) , _reactions(std::make_unique(this)) -, _recentViews(std::make_unique(this)) { +, _recentViews(std::make_unique(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([=] { - 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 590dee3f67..860908b967 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 session); @@ -307,6 +308,7 @@ private: std::vector _urlAreas; std::vector _weatherAreas; mutable std::vector _areas; + mutable rpl::variable _weatherInCelsius; std::vector _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 f5e2582ef4..a8e044cc9b 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 session, - const Data::WeatherArea &data); + const Data::WeatherArea &data, + rpl::producer 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 session, - const Data::WeatherArea &data) + const Data::WeatherArea &data, + rpl::producer 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 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 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 weatherInCelsius) -> std::unique_ptr { return std::make_unique( _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 b17e2e19da..11da4c456e 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; - [[nodiscard]] auto makeWeatherAreaWidget(const Data::WeatherArea &data) + [[nodiscard]] auto makeWeatherAreaWidget( + const Data::WeatherArea &data, + rpl::producer weatherInCelsius) -> std::unique_ptr; void setReplyFieldState( From 2dcf40817e39e52e744b423310362c2997c6658c Mon Sep 17 00:00:00 2001 From: John Preston 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 1bc5a1f852..bf1b494328 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 b3fb82a0d7..6720f3c93d 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> inChannelValues) { auto result = QByteArray(); diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h index 473813fcab..1f0ecaf9a9 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -76,6 +76,8 @@ public: base::flat_map> 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 a49ba88877..00cfaa615d 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, QString uri); + + [[nodiscard]] bool active() const; + + void moveTo(QString uri); + + void minimize(); + + [[nodiscard]] rpl::producer events() const { + return _events.events(); + } + + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + void createController(); + + void showWindowed(); + + const not_null _delegate; + QString _uri; + std::unique_ptr _controller; + + rpl::event_stream _events; + + rpl::lifetime _lifetime; + +}; + Shown::Shown( not_null delegate, not_null session, @@ -742,6 +775,50 @@ void Shown::minimize() { } } +TonSite::TonSite(not_null delegate, QString uri) +: _delegate(delegate) +, _uri(uri) { + showWindowed(); +} + +void TonSite::createController() { + Expects(!_controller); + + const auto showShareBox = [=](ShareBoxDescriptor &&descriptor) { + return ShareBoxResult(); + }; + _controller = std::make_unique( + _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) { } @@ -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(_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 session, const QString &id) { @@ -1085,23 +1214,30 @@ bool Instance::hasActiveWindow(not_null 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 9083734adf..c48cf569ee 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 session) const; @@ -97,6 +102,7 @@ private: QString _ivRequestUri; mtpRequestId _ivRequestId = 0; + std::unique_ptr _tonSite; rpl::lifetime _lifetime; From 4f37343e8b09d59bcb74b803708040c4c5c82d0f Mon Sep 17 00:00:00 2001 From: John Preston 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 16b2595de3..782f591564 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() : (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 0ac4e94a50..73f0f5eb0a 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 _weatherInCelsius; + QByteArray _tonsiteStorageToken; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 6720f3c93d..07ddd4e9d6 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 00cfaa615d..480d73573a 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 6349f6b6a3..253116dea8 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 83d8e52f4c..bfac715600 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 d1314cde2f..84533f93d5 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 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 bae02fe487..fda34772fe 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 c8c9c6b695..6a5757276e 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 07ddd4e9d6..1870011dc1 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(_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(_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 1f0ecaf9a9..b732ec57ae 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -127,11 +127,14 @@ private: std::unique_ptr _window; std::unique_ptr _subtitleWrap; + rpl::variable _url; rpl::variable _subtitleText; std::unique_ptr _subtitle; - Ui::Animations::Simple _subtitleLeft; + Ui::Animations::Simple _subtitleBackShift; + Ui::Animations::Simple _subtitleForwardShift; object_ptr _menuToggle = { nullptr }; object_ptr> _back = { nullptr }; + object_ptr> _forward = { nullptr }; base::unique_qptr _menu; Ui::RpWidget *_container = nullptr; std::unique_ptr _webview; diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index f578bce829..69e0ef89ec 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 363db4e49a..cc8f41d10b 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 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 a8dee5f762..b2159ebb11 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 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 8ee5170fb3..b77fe3e3c7 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -392,6 +392,9 @@ void InitMessageFieldHandlers( Fn customEmojiPaused, Fn)> allowPremiumEmoji, const style::InputField *fieldStyle) { + const auto paused = [customEmojiPaused] { + return customEmojiPaused && customEmojiPaused(); + }; field->setTagMimeProcessor( FieldTagMimeProcessor(session, allowPremiumEmoji)); field->setCustomTextContext([=](Fn 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 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 1870011dc1..487f9295cd 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>( + _container, + object_ptr( + _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 b732ec57ae..c92f4c4b2b 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; std::unique_ptr _window; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index d97628285e..2bc023e8c7 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 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 69e0ef89ec..c17eda5b8b 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 b2159ebb11..7b57464f7f 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 e25483b89a..f687c24e61 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 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 d03a736410..a2e36038ad 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(box)); } From fb9ce6d3a877f4773b40bb8d5c67eeec920fb887 Mon Sep 17 00:00:00 2001 From: John Preston 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 cc8f41d10b..2c95b169b3 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 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 45ec14889b..3d4040dd7a 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -37,6 +37,9 @@ + + + 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 487f9295cd..00b6455057 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 480d73573a..3b327e5a69 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 b68e2109c5..72913e85e2 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -38,6 +38,7 @@ CFBundleURLSchemes tg + tonsite From 8959679b3cfe29f756cb7437ff6fd4eeeab05987 Mon Sep 17 00:00:00 2001 From: John Preston 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 00b6455057..e369ab5c0c 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>{ { "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 24b7155ce9..3be7a6359b 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 &colors, const base::flat_map> &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 3d8a510669..5180964795 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 &colors, const base::flat_map> &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 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 94fa2ae4d9..39b7ac32fe 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 eca07e7519..373ba3546a 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 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 4ec94b0ab7..5a72a69847 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 SectionWidget::desiredHeight() const { From db80096e6b5d9b839d29abcabc6ad56e68884003 Mon Sep 17 00:00:00 2001 From: John Preston 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 08bd287ad8..aa2dcc1ecf 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 7a05070e72..f6f701eda7 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 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 aef0dd941d..d9d3b9a6fb 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 4258edbacc..e1e40cc628 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 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 5810310faa..e5c9ac6e31 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 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 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 915b12f4fd..b1ba3d166e 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(); if (!my.show) { diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 6777464c17..28aa998229 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 e369ab5c0c..067e90b485 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 97bfa6cef4..8db5d1aa53 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 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 3d4040dd7a..cffa28fb52 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.3.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 83eecf0a79..b9e1fb9160 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 40bb30f11b..0f70bef0ea 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 111881fd48..bb5f9b405e 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 f10750e38d..4829ec4b2e 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 5ec213f7e9..f8929141f4 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 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 2c95b169b3..4b23992321 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 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 17c758e2b9..721383d0b9 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 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 32437fb772..b49cef3bbe 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 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 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 3b8f6f1b5e..d478a7c6ea 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 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 a0d3d6066d..ee24c7eb42 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 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 e4694f88d6..a0578000f8 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -53,6 +53,12 @@ std::optional OnlineTextSpecial(not_null 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 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 18ae94b371..a1391e3ba5 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 peer) @@ -422,6 +423,9 @@ public: not_null window); void prepare() override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; void load(); @@ -1031,6 +1035,35 @@ void RecentAppsController::prepare() { }, _lifetime); } +base::unique_qptr RecentAppsController::rowContextMenu( + QWidget *parent, + not_null row) { + auto result = base::make_unique_q( + 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 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 a1391e3ba5..96529c2411 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 peer) const; + private: void appendRow(not_null bot); void fill(); std::vector> _bots; + rpl::event_stream<> _refreshed; rpl::lifetime _lifetime; }; @@ -442,7 +446,9 @@ class PopularAppsController final : public Suggestions::ObjectListController { public: explicit PopularAppsController( - not_null window); + not_null window, + Fn)> filterOut, + rpl::producer<> filterOutRefreshes); void prepare() override; @@ -452,6 +458,8 @@ private: void fill(); void appendRow(not_null bot); + Fn)> _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 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 bot) { @@ -1099,13 +1117,21 @@ void RecentAppsController::appendRow(not_null bot) { } PopularAppsController::PopularAppsController( - not_null window) -: ObjectListController(window) { + not_null window, + Fn)> 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 bot) { auto row = std::make_unique(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 { auto Suggestions::setupRecentApps() -> std::unique_ptr { const auto controller = lifetime().make_state( _controller); + _recentAppsShows = [=](not_null peer) { + return controller->shown(peer); + }; + _recentAppsRefreshed = controller->refreshed(); auto result = setupObjectList( _appsScroll.get(), @@ -1952,7 +1982,9 @@ auto Suggestions::setupRecentApps() -> std::unique_ptr { auto Suggestions::setupPopularApps() -> std::unique_ptr { const auto controller = lifetime().make_state( - _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 549851d72e..cf8b217800 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 _appsScroll; const not_null _appsContent; + rpl::producer<> _recentAppsRefreshed; + Fn)> _recentAppsShows; const std::unique_ptr _recentApps; const std::unique_ptr _popularApps; From 0af3028cd6868ad56d1d159e14f00f0ccce1edbd Mon Sep 17 00:00:00 2001 From: John Preston 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 e5c9ac6e31..3f549edb14 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 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 067e90b485..11df085275 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 c92f4c4b2b..30e975d023 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -132,6 +132,7 @@ private: std::unique_ptr _subtitleWrap; rpl::variable _url; rpl::variable _subtitleText; + rpl::variable _windowTitleText; std::unique_ptr _subtitle; Ui::Animations::Simple _subtitleBackShift; Ui::Animations::Simple _subtitleForwardShift; From 281ad01b850c07a0dc8c32fdc5cdbb8306c3b500 Mon Sep 17 00:00:00 2001 From: John Preston 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 568d6663af..82f184cb83 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 83e16284fd..67e7670036 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$<$: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 721383d0b9..6a1ac8a4ee 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 721383d0b901cc5d5cbeec29386671d145f5a1e5 +Subproject commit 6a1ac8a4eebb968ff6ca538088006a3d06d47421 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b5f71d99f2..8378b599f3 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 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 64bbb68d8f..3924a85f68 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 11df085275..759ef0db3c 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 #include +#include + 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(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 30e975d023..9b5af4e6b6 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -76,6 +76,7 @@ public: base::flat_map> 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 3b327e5a69..8078828d6b 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 602abf41c0..1d4edf9f5f 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 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 bf1b494328..60c9b00454 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 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 d9a08df0c0..0222032008 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 4b23992321..c27c69953d 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 d48f32c005..63cf5048a9 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 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 cffa28fb52..261fb09110 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.3.1.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index b9e1fb9160..eb9e2d265e 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 0f70bef0ea..9e4b2aced7 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 bb5f9b405e..146e4b1f00 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 4829ec4b2e..928f3047d9 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 f8929141f4..cf66163990 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 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 0222032008..8751e27d50 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 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 261fb09110..89f5f8761a 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.3.2.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index eb9e2d265e..eb4271d8a4 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 9e4b2aced7..d7e88ba045 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 146e4b1f00..9fdc8cf188 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 928f3047d9..37a420e7af 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 cf66163990..e43e0dbe29 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 6a1ac8a4ee..08de4f18f5 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 6a1ac8a4eebb968ff6ca538088006a3d06d47421 +Subproject commit 08de4f18f5e4459689957b3aa115e10d8cbef9d6