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 001/134] 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 169fe8883b5a91..cbe32faf5b6d74 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 c16acc204c45b9..c96dac4eab840a 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 a176be565d46e7..a9649fb4de56c0 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 @@ class CreditsController final : public PeerListController { void applySlice(const Data::CreditsStatusSlice &slice); const not_null _session; - const not_null _premiumBot; Fn _entryClickedCallback; not_null const _creditIcon; @@ -903,11 +902,10 @@ class CreditsController final : public PeerListController { }; 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 2d381cb7cd8e1a..8423de55cc4d02 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 0890f2703d0c2d..5088ce62aadece 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 002/134] 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 1a302bd10cd339..7838dda794fdde 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 003/134] 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 6528a858e0ce65..6f3bc7a00209fe 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 5c461c1efdb7a1..122260d220fb7e 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 00000000000000..0c791008df94c0 --- /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 00000000000000..6a35060f00f70d --- /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 00000000000000..10a810aa919ce9 --- /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 ade419248fbabd..bf4d472fee75cb 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 2fdbad8437bc9b..c4bafc53780650 100644 --- a/Telegram/SourceFiles/api/api_sending.h +++ b/Telegram/SourceFiles/api/api_sending.h @@ -7,15 +7,19 @@ For license and copyright information please follow this link: */ #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 00000000000000..295bde82440079 --- /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 00000000000000..2715699eebe731 --- /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 a5e0090db8b593..6fb00d550d9a3d 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -45,6 +45,16 @@ class LocationPoint { }; +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 00000000000000..d4e3831219c037 --- /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 00000000000000..4f0944c81a9164 --- /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 ff263701120dce..c6b01395605357 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 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: #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 00000000000000..5ac0c49a78163e --- /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 00000000000000..6bb8195430d9cf --- /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 00000000000000..6bb8195430d9cf --- /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 00000000000000..03bf727ededaaf --- /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 00000000000000..245342fc36dfdb --- /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 00000000000000..37682c108df660 --- /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 00000000000000..6bb8195430d9cf --- /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 00000000000000..21c5b9861db18b --- /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 00000000000000..220c0c4264446e --- /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 3555c92189cb4d..b11016acbd06d8 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 004/134] 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 6a35060f00f70d..54eb60abae26fc 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 295bde82440079..eb2125bf5477db 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 03bf727ededaaf..2812a0c33386a4 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 @@ #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 21c5b9861db18b..804593b0776cdf 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -18,6 +18,7 @@ For license and copyright information please follow this link: #include "styles/style_dialogs.h" #include "styles/style_window.h" +#include #include #include #include @@ -27,6 +28,12 @@ For license and copyright information please follow this link: 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 53b8daf8b998fa..b68e2109c54ab2 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 46355b637bb513..050eea08f02960 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 97c1f6d587a058..af288322091dfd 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 4a4bc4cd34b3ad..78b441c9c6ad8a 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 005/134] Improve location picker design. --- .../chat_helpers/chat_helpers.style | 4 ++ .../ui/controls/location_picker.cpp | 72 ++++++++++++------- .../SourceFiles/ui/controls/location_picker.h | 5 +- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 0b1290dc8d58dc..8c7bcbb93703be 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 804593b0776cdf..5b77f017ad0df2 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -10,11 +10,14 @@ For license and copyright information please follow this link: #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 }); - - _container = Ui::CreateChild(window->body().get()); - const auto button = Ui::CreateChild( - window->body(), + window->setTitle(tr::lng_maps_point()); + window->move( + parent.x() + (parent.width() - window->width()) / 2, + 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)); + 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 220c0c4264446e..f18957b9dc69fe 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 006/134] 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 6f3bc7a00209fe..592f1329ff858c 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 122260d220fb7e..b15436b2b0da8c 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 54eb60abae26fc..cb936cf0b01778 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 8c7bcbb93703be..452a9f89b769ff 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 2021e294348f7c..f2ad99f46f5477 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -21,6 +21,7 @@ For license and copyright information please follow this link: #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 eb2125bf5477db..b649ea2ea7380b 100644 --- a/Telegram/SourceFiles/core/current_geo_location.cpp +++ b/Telegram/SourceFiles/core/current_geo_location.cpp @@ -8,10 +8,122 @@ For license and copyright information please follow this link: #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 2715699eebe731..e3b92222e98221 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 6fb00d550d9a3d..10f05adfbe40b3 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 869476243c5607..e43e6423ad42b6 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 12c4cba3a658b4..06b4b267d37810 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -559,6 +559,8 @@ class Session final { 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 @@ class Session final { 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 c6b01395605357..b9233313ceeb9e 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 3b30dc6dbbf659..4161fe87622c4e 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -21,6 +21,7 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: #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 ea611157407751..c14d490511e085 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 454339f494cfb8..1da5325ef88e04 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 8a4db80df9156e..289230b64480cb 100644 --- a/Telegram/SourceFiles/mtproto/mtproto_config.h +++ b/Telegram/SourceFiles/mtproto/mtproto_config.h @@ -11,9 +11,9 @@ For license and copyright information please follow this link: 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 5ac0c49a78163e..6f42c0afdb9bb9 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 2812a0c33386a4..452bc6e3aeadb9 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 245342fc36dfdb..9feb4b376e1c00 100644 --- a/Telegram/SourceFiles/platform/platform_current_geo_location.h +++ b/Telegram/SourceFiles/platform/platform_current_geo_location.h @@ -9,10 +9,14 @@ For license and copyright information please follow this link: 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 37682c108df660..a83dedb882e82c 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 @@ For license and copyright information please follow this link: #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 5b77f017ad0df2..a4658e7bee7348 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -7,16 +7,31 @@ For license and copyright information please follow this link: */ #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 f18957b9dc69fe..9a4ca56d59deea 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -9,8 +9,19 @@ For license and copyright information please follow this link: #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 @@ class LocationPicker final : public base::has_weak_ptr { 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 00000000000000..24b7155ce9dc01 --- /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 00000000000000..3d8a5106697867 --- /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 b11016acbd06d8..9bc3f6c7663ed6 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 007/134] 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 0c791008df94c0..4d8f378aedbf0b 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 cb936cf0b01778..27ffd3b6c6f29f 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 b649ea2ea7380b..818ea361aa3bcb 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 e3b92222e98221..dab4ffd00bdd57 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 6f42c0afdb9bb9..f87c343d36e970 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 452bc6e3aeadb9..1a5382f1994d61 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 9feb4b376e1c00..269ee81f320a9a 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 a83dedb882e82c..9c7967588e0517 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 a4658e7bee7348..2eef9d698a52d7 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -17,6 +17,7 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: 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 9a4ca56d59deea..e9c0d9801d813c 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #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 @@ class LocationPicker final : public base::has_weak_ptr { 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 008/134] 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 4d8f378aedbf0b..ac3d5912b1a272 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 27ffd3b6c6f29f..e44fd51a95ef47 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 818ea361aa3bcb..2ec4a9cfe4a319 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 dab4ffd00bdd57..3b495f11599001 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 2eef9d698a52d7..b5920e131032cc 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 @@ class LinksController final 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 e9c0d9801d813c..0adfc166fcf39b 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -129,6 +129,7 @@ class LocationPicker final : public base::has_weak_ptr { 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 009/134] 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 10f05adfbe40b3..5157f79a1c61aa 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 b9233313ceeb9e..d52e13b82dc4b3 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 2507dc8f1bd52c..6a4c63c35eca47 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 67b9853482b2ac..f09d5f120de78f 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -35,12 +35,11 @@ class AppConfig final { 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 @@ class AppConfig final { [[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 b5920e131032cc..9d926f980802a1 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 @@ class VenuesController final void rebuild(const std::vector &rows); const not_null _session; + const Fn _callback; rpl::variable> _rows; base::flat_map _icons; @@ -179,8 +179,10 @@ class VenuesController final 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 0adfc166fcf39b..b7996ee50d8dc6 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 010/134] 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 4161fe87622c4e..e72298f6c4f23d 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -9,7 +9,6 @@ For license and copyright information please follow this link: #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 d367e3382e2ef4..cbc27c0b85e796 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 011/134] 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 9d926f980802a1..d363bbe6d903e8 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -25,6 +25,7 @@ For license and copyright information please follow this link: #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 b7996ee50d8dc6..6014bd06bb2d70 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 9b69f3855ac9fe..a95caea1adac69 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 012/134] 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 b15436b2b0da8c..08368d74842cfe 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 452a9f89b769ff..d31d2919a33f50 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 d363bbe6d903e8..ed9d84f7a88265 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -17,11 +17,14 @@ For license and copyright information please follow this link: #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 a95caea1adac69..f046725ecde41b 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 013/134] Pass correct peer to venue search. --- .../chat_helpers/chat_helpers.style | 33 ++++++++----------- .../inline_bots/bot_attach_web_view.cpp | 1 + .../ui/controls/location_picker.cpp | 7 ++-- .../SourceFiles/ui/controls/location_picker.h | 2 ++ 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index d31d2919a33f50..a40d1e0ad354e1 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) { + height: pickLocationRowHeight; + photoSize: 42px; + photoPosition: point(18px, 5px); + namePosition: point(70px, 7px); + statusPosition: point(70px, 27px); button: OutlineButton(defaultPeerListButton) { + textBg: contactsBg; + textBgOver: contactsBgOver; font: normalFont; padding: margins(11px, 5px, 11px, 5px); + ripple: defaultRippleAnimation; } - height: 52px; - photoPosition: point(18px, 5px); - namePosition: point(70px, 7px); - statusPosition: point(70px, 27px); - photoSize: 42px; + 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 d52e13b82dc4b3..4dbd94e1d4277b 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 ed9d84f7a88265..5438d00d444758 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 6014bd06bb2d70..756e6b162337f7 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -80,6 +80,7 @@ class LocationPicker final : public base::has_weak_ptr { struct Descriptor { RpWidget *parent = nullptr; LocationPickerConfig config; + PeerData *recipient = nullptr; not_null session; Fn callback; Fn quit; @@ -145,6 +146,7 @@ class LocationPicker final : public base::has_weak_ptr { 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 014/134] 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 08368d74842cfe..fc725ff1d4642f 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 a40d1e0ad354e1..9b92f4815be6ad 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 5438d00d444758..b461a902b29ece 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -27,6 +27,7 @@ For license and copyright information please follow this link: #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 @@ class VenuesController final 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 015/134] 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 237c30b87af8e0..8f8b15ecf7a913 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 016/134] 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 a35cfd3c44e22d..9b8b959e0d68b5 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -58,6 +58,8 @@ For license and copyright information please follow this link: #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 0e03904f70c37b..ded5dd0d13f100 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 26f4783761ae75..9ebed92fcdcb8a 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.h +++ b/Telegram/SourceFiles/platform/win/windows_dlls.h @@ -11,6 +11,7 @@ For license and copyright information please follow this link: #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 b9fa8b84d8abe7..0c23e3803bc29e 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 e4f7d1fb11ce2e..00a3dc3b7817f2 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 6704bdbd28d60f..2f5986864f7418 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 @@ def runStages(): 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 @@ def runStages(): 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 881ad9952642ac..3f6938e721e54e 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 eb949654035694..81d01697c2ee2b 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit eb9496540356945e2c9fb700bcfa51444fd36f41 +Subproject commit 81d01697c2ee2b46c5e5c41ce1176c230264e4cb diff --git a/cmake b/cmake index 78b441c9c6ad8a..d50ff1a71e080e 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 78b441c9c6ad8a14a8f97a28825babcadc6bf781 +Subproject commit d50ff1a71e080e63f71c451f500a2f03f9d48539 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 033424e1d6fb79..b3c8d19054d85c 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 017/134] 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 | 206 ++++++++++++++++-- .../ui/controls/location_picker.cpp | 33 ++- .../SourceFiles/ui/controls/location_picker.h | 3 + 11 files changed, 265 insertions(+), 32 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fc725ff1d4642f..c692195bca2854 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 9f12728596ed6e..0ca37c62922621 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 e158c7a3a8e9b2..d9bb6ec7a9b043 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 7b99e4c8fc30d9..01993f22344821 100644 --- a/Telegram/SourceFiles/data/business/data_business_info.h +++ b/Telegram/SourceFiles/data/business/data_business_info.h @@ -22,6 +22,7 @@ class BusinessInfo final { 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 80727b3ba9694b..457bb641136a9d 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 5157f79a1c61aa..114d21c08edb98 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -16,6 +16,11 @@ class LocationPoint { 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 4dbd94e1d4277b..c42ecb3b999172 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 e9bc2f73ec27c5..688a6940d26adf 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 4a2f14e73ac2bb..e5bb0f8e387101 100644 --- a/Telegram/SourceFiles/settings/business/settings_location.cpp +++ b/Telegram/SourceFiles/settings/business/settings_location.cpp @@ -8,16 +8,30 @@ For license and copyright information please follow this link: #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 @@ class Location : public BusinessSection { void setupContent(not_null controller); void save(); + 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 b461a902b29ece..3f1c74fe00b054 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 756e6b162337f7..0a14851575d98b 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -80,8 +80,10 @@ class LocationPicker final : public base::has_weak_ptr { 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 018/134] 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 f046725ecde41b..58329bfe19c031 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 019/134] 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 1a5382f1994d61..0ea55446387b20 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 @@ */ #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 020/134] 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 3f1c74fe00b054..8cce44571114d9 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 0a14851575d98b..97ce724f343246 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -133,8 +133,8 @@ class LocationPicker final : public base::has_weak_ptr { 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 021/134] 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 592f1329ff858c..48cf60eff84145 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 2f5986864f7418..6300390d3dba01 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -26,7 +26,7 @@ def nativeToolsError(): 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 @@ def nativeToolsError(): '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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): """) 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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): -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 @@ def runStages(): -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 @@ def runStages(): -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 @@ def runStages(): -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 @@ def runStages(): -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 @@ def runStages(): -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 078006d29af000..c1d6b027365309 160000 --- a/Telegram/lib_crl +++ b/Telegram/lib_crl @@ -1 +1 @@ -Subproject commit 078006d29af0002e6cd8c61a405cdeaf65b37142 +Subproject commit c1d6b0273653095b10b4d0f4f7c30b614b690fd5 diff --git a/cmake b/cmake index d50ff1a71e080e..37d42a72255aab 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 022/134] 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 6d001d407500c4..75674299f8d81c 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 6f4e2d4663a381..1e38d721ad4229 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 1a50fd2300da31..54639131bf1e5d 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 023/134] 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 6300390d3dba01..e1e31ddfb2ce61 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -852,11 +852,11 @@ def runStages(): 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 @@ def runStages(): 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 024/134] 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 00a3dc3b7817f2..b5f7c86cd922c0 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 e1e31ddfb2ce61..0658703b782dba 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -457,7 +457,7 @@ def runStages(): 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 @@ def runStages(): 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 b3c8d19054d85c..0a3d3f8552678e 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 025/134] 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 0658703b782dba..ea305575e53758 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -621,7 +621,7 @@ def runStages(): 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 @@ def runStages(): 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 @@ def runStages(): """) 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 026/134] 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 a9649fb4de56c0..9c2a692e785869 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 027/134] 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 3f6938e721e54e..018a7cfb00930f 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 37d42a72255aab..3fe31233545307 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 028/134] 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 0c23e3803bc29e..2dc886d05e4b78 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 81d01697c2ee2b..839d9f0aade5e2 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 029/134] 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 b5f7c86cd922c0..d60e6d403ce5cb 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 030/134] 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 156ba726a5f370..e563e167534a53 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 fe95f60dbc8f12..777dde80c4ad73 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 3f3b65caaf0a0a..6dc539c61ecfc5 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 23e8143e31c64b..21183fa389a04b 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 4bcf93fe9bf05e..4e6f383527f860 100644 --- a/Telegram/build/release.py +++ b/Telegram/build/release.py @@ -220,6 +220,20 @@ def prepareSources(): '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 5c5ee763551f39..b0dd43f6571179 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 54639131bf1e5d..4ac8cf9d65e47e 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 031/134] 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 c692195bca2854..d3427dce9e4f76 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 cbc27c0b85e796..cb922a4ff5af3b 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 d9071c846e50e4..e25483b89a7e76 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 @@ class Panel final : public base::has_weak_ptr { 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 032/134] 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 451957c21aaf30..8febb5f42f1bd1 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 033/134] 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 0d93ee0b6cf1dd..4c6d4970dc210a 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 034/134] 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 d3427dce9e4f76..6c98281895294a 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 39d7983d7378da..a8771cc17b6cf4 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -23,6 +23,7 @@ For license and copyright information please follow this link: #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 dd32fa00f96aae..71b01fbf863e61 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 c42ecb3b999172..c8e88aed7dda42 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 87648b07e88b73..f9f59f1ae30a7f 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 @@ class AttachWebView final 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 cbb435389b6cf8..9a5daf9a4f9db3 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 3c7f112ca5d24e..e783bba58c36d1 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 @@ class CheckoutProcess final 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 69cf7abdea2222..b62987a828606e 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -24,6 +24,7 @@ For license and copyright information please follow this link: #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 e8ab9375c84bb3..53a31f81c692fe 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.h +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: 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 5088ce62aadece..e80f82a62d4503 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 282945d9efc732..1eaef456826c6f 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 406bfabcaf7564..47d5323c7a7936 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 cb922a4ff5af3b..ae27e4938932eb 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 035/134] 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 9f4860da364d63..675bb5edcf076b 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 036/134] 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 5d8be614450504..8ee5170fb3b7c7 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 58329bfe19c031..007e3a519ffbad 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 037/134] 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 2dc886d05e4b78..ce592ecab26770 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 839d9f0aade5e2..5d44a8acc341d5 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 038/134] 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 bf4d472fee75cb..c05a42e953b6ea 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 c8e88aed7dda42..b701fe21a74a23 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 d60e6d403ce5cb..0091881ed09e6e 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 018a7cfb00930f..44539b54b3845a 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 3fe31233545307..b3871c4efde378 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 039/134] 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 | 2081 ++++++++--------- .../inline_bots/bot_attach_web_view.h | 382 +-- .../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, 1383 insertions(+), 1265 deletions(-) diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 92905126113915..7514121eb31f6e 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 79dfd0977bcfc9..19f488b0b45ea1 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 eadb16181e122a..915b12f4fd9352 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 a6359419551a3e..b3aa0bae02f1ad 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 675bb5edcf076b..223cec0439ecbe 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 0a3c5a0647d340..789390bbfb3111 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 b701fe21a74a23..8e993f4f746af6 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 @@ class BotAction final : public Ui::Menu::ItemBase { 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 @@ class BotAction final : public Ui::Menu::ItemBase { 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,1150 +594,773 @@ 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; -}; +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(); +} -AttachWebView::AttachWebView(not_null session) -: _session(session) -, _refreshTimer([=] { requestBots(); }) { - _refreshTimer.callEach(kRefreshBotsTimeout); +WebViewInstance::~WebViewInstance() { + _session->api().request(base::take(_requestId)).cancel(); + _session->api().request(base::take(_prolongId)).cancel(); + base::take(_panel); } -AttachWebView::~AttachWebView() { - ActiveWebViews().remove(this); +Main::Session &WebViewInstance::session() const { + return *_session; } -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(); +not_null WebViewInstance::bot() const { + return _bot; +} - _context = std::make_unique(context); - _botUsername = botUsername; - _startCommand = startCommand; - resolve(); +WebViewSource WebViewInstance::source() const { + return _source; } -Webview::ThemeParams AttachWebView::botThemeParams() { - return Window::Theme::WebViewParams(); +void WebViewInstance::activate() { + if (_panel) { + _panel->requestActivate(); + } else { + PendingActivation = this; + } } -bool AttachWebView::botHandleLocalUri(QString uri, bool keepOpen) { - const auto local = Core::TryConvertUrlToLocal(uri); - if (uri == local || Core::InternalPassportLink(local)) { - return local.startsWith(u"tg://"_q); - } else if (!local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) { +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; } - if (!keepOpen) { - botClose(); + 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; } - crl::on_main([=, shownUrl = _lastShownUrl, bot = _bot] { - 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; - const auto variant = QVariant::fromValue(ClickHandlerContext{ - .attachBotWebviewUrl = shownUrl, - .sessionWindow = window, - }); - UrlClickHandler::Open(local, variant); - }); + resolveApp(appname, params.value(u"startapp"_q), true); return true; } -void AttachWebView::botHandleInvoice(QString slug) { - Expects(_panel != nullptr); - - using Result = Payments::CheckoutResult; - const auto weak = base::make_weak(_panel.get()); - const auto reactivate = [=](Result result) { - if (const auto strong = weak.get()) { - strong->invoiceClosed(slug, [&] { - switch (result) { - case Result::Paid: return "paid"; - case Result::Failed: return "failed"; - case Result::Pending: return "pending"; - case Result::Cancelled: return "cancelled"; - } - Unexpected("Payments::CheckoutResult value."); - }()); +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; } - }; - Payments::CheckoutProcess::Start( - &_bot->session(), - slug, - reactivate, - nonPanelPaymentFormFactory(reactivate)); -} + const auto confirm = data.is_inactive() || forceConfirmation; + const auto writeAccess = result.data().is_request_write_access(); -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); + // 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); } - }, [&](RealFormPresentedNotification) { - _panel->hideForPayment(); }); - }; + _session->attachWebView().requestAddToMenu(_bot, done); + }).fail([=] { + _parentShow->showToast(tr::lng_username_app_not_found(tr::now)); + close(); + }).send(); } -void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) { - Expects(_bot != nullptr); - Expects(_panel != nullptr); - - using Button = Ui::BotWebView::MenuButton; - const auto bot = _bot; - switch (button) { - case Button::OpenBot: - botClose(); - if (bot->session().windows().empty()) { - Core::App().domain().activate(&bot->session().account()); - } - if (!bot->session().windows().empty()) { - const auto window = bot->session().windows().front(); - window->showPeerHistory(bot); - window->window().activate(); - } - break; - case Button::RemoveFromMenu: - case Button::RemoveFromMainMenu: - const auto attached = ranges::find( - _attachBots, - not_null{ _bot }, - &AttachWebViewBot::user); - const auto name = (attached != end(_attachBots)) - ? attached->name - : _bot->name(); - const auto done = crl::guard(this, [=] { - removeFromMenu(bot); - botClose(); - if (const auto active = Core::App().activeWindow()) { - active->activate(); - } - }); - const auto main = (button == Button::RemoveFromMainMenu); - _panel->showBox(Ui::MakeConfirmBox({ - (main - ? tr::lng_bot_remove_from_side_menu_sure - : tr::lng_bot_remove_from_menu_sure)( - tr::now, - lt_bot, - Ui::Text::Bold(name), - Ui::Text::WithEntities), - done, - })); - break; +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(), + })); } -bool AttachWebView::botValidateExternalLink(QString uri) { - const auto lower = uri.toLower(); - const auto allowed = _session->appConfig().get>( - "web_app_allowed_protocols", - std::vector{ u"http"_q, u"https"_q }); - for (const auto &protocol : allowed) { - if (lower.startsWith(protocol + u"://"_q)) { - return true; +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(); } - } - return false; + })); } -void AttachWebView::botOpenIvLink(QString uri) { - const auto window = _context ? _context->controller.get() : nullptr; - if (window) { - Core::App().iv().openWithIvPreferred(window, uri); - } else { - Core::App().iv().openWithIvPreferred(_session, uri); - } -} +void WebViewInstance::requestButton() { + Expects(_context.action.has_value()); -void AttachWebView::botSendData(QByteArray data) { - if (!_context - || _context->fromSwitch - || _context->fromBotApp - || _context->fromMainMenu - || _context->action.history->peer != _bot - || _lastShownQueryId) { - return; - } - const auto randomId = base::RandomValue(); - _session->api().request(MTPmessages_SendWebViewData( + 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_long(randomId), - MTP_string(_lastShownButtonText), - MTP_bytes(data) - )).done([=](const MTPUpdates &result) { - _session->api().applyUpdates(result); - }).send(); - crl::on_main(this, [=] { cancel(); }); -} - -void AttachWebView::botSwitchInlineQuery( - std::vector chatTypes, - QString query) { - const auto controller = _context - ? _context->controller.get() - : nullptr; - const auto types = PeerTypesFromNames(chatTypes); - if (!_bot - || !_bot->isBot() - || _bot->botInfo->inlinePlaceholder.isEmpty() - || !controller) { - return; - } else if (!types) { - if (_context->dialogsEntryState.key.owningHistory()) { - controller->switchInlineQuery( - _context->dialogsEntryState, - _bot, - query); + 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(); } - } else { - const auto bot = _bot; - const auto done = [=](not_null thread) { - controller->switchInlineQuery(thread, bot, query); - }; - ShowChooseBox( - controller, - types, - done, - tr::lng_inline_switch_choose()); - } - crl::on_main(this, [=] { cancel(); }); -} - -void AttachWebView::botCheckWriteAccess(Fn callback) { - _session->api().request(MTPbots_CanSendMessage( - _bot->inputUser - )).done([=](const MTPBool &result) { - callback(mtpIsTrue(result)); - }).fail([=] { - callback(false); - }).send(); -} - -void AttachWebView::botAllowWriteAccess(Fn callback) { - _session->api().request(MTPbots_AllowSendMessage( - _bot->inputUser - )).done([=](const MTPUpdates &result) { - _session->api().applyUpdates(result); - callback(true); - }).fail([=] { - callback(false); + close(); }).send(); } -void AttachWebView::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()); - botSharePhone(callback); - } else { - callback(false); - } - }; - _bot->session().api().blockedPeers().unblock( - _bot, - crl::guard(this, done)); - return; - } - auto action = Api::SendAction(history); - action.clearDraft = false; - history->session().api().shareContact( - _bot->session().user(), - action, - std::move(callback)); -} - -void AttachWebView::botInvokeCustomMethod( - Ui::BotWebView::CustomMethodRequest request) { - const auto callback = request.callback; - _bot->session().api().request(MTPbots_InvokeWebViewCustomMethod( +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_string(request.method), - MTP_dataJSON(MTP_bytes(request.params)) - )).done([=](const MTPDataJSON &result) { - callback(result.data().vdata().v); + 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) { - callback(base::make_unexpected(error.type())); + _parentShow->showToast(error.type()); + close(); }).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(); }); -} - -AttachWebView::Context AttachWebView::LookupContext( - not_null controller, - const Api::SendAction &action) { - return { - .controller = controller, - .dialogsEntryState = controller->currentDialogsEntryState(), - .action = action, - }; -} - -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); -} - -void AttachWebView::request( - 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; - } - } - cancel(); - - _bot = bot; - _context = std::make_unique(context); - if (controllerForConfirm) { - confirmOpen(controllerForConfirm, [=] { - request(button); - }); - } else { - request(button); - } -} - -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; +void WebViewInstance::requestApp(bool allowWrite) { + Expects(_app != nullptr); + Expects(_context.action.has_value()); - using Flag = MTPmessages_RequestWebView::Flag; + using Flag = MTPmessages_RequestAppWebView::Flag; + const auto app = _app; 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( + | (_appStartParam.isEmpty() ? Flag(0) : Flag::f_start_param) + | (allowWrite ? Flag::f_write_allowed : Flag(0)); + _requestId = _session->api().request(MTPmessages_RequestAppWebView( 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()) + _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; - const auto &data = result.data(); - show( - data.vquery_id().value_or_empty(), - qs(data.vurl()), - button.text, - button.fromAttachMenu || button.url.isEmpty()); + show(qs(result.data().vurl())); }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.type() == u"BOT_INVALID"_q) { - requestBots(); + _session->attachWebView().requestBots(); } + close(); }).send(); } -void AttachWebView::cancel() { - Expects(!_catchingCancelInShowCall); +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); +} - 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(); +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 AttachWebView::requestBots(Fn callback) { - if (callback) { - _botsRequestCallbacks.push_back(std::move(callback)); - } - if (_botsRequestId) { +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; } - _botsRequestId = _session->api().request(MTPmessages_GetAttachMenuBots( - MTP_long(_botsHash) - )).done([=](const MTPAttachMenuBots &result) { - _botsRequestId = 0; - result.match([&](const MTPDattachMenuBotsNotModified &) { - }, [&](const MTPDattachMenuBots &data) { - _session->data().processUsers(data.vusers()); - _botsHash = data.vhash().v; - _attachBots.clear(); - _attachBots.reserve(data.vbots().v.size()); - for (const auto &bot : data.vbots().v) { - if (auto parsed = ParseAttachBot(_session, bot)) { - _attachBots.push_back(std::move(*parsed)); - } - } - _attachBotsUpdates.fire({}); - }); - for (const auto &callback : base::take(_botsRequestCallbacks)) { - callback(); - } - }).fail([=] { - _botsRequestId = 0; - for (const auto &callback : base::take(_botsRequestCallbacks)) { - callback(); + 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, + }, + }); } - }).send(); + }; + ShowChooseBox(_parentShow, _session, chooseFrom, done); + close(); } -bool AttachWebView::disclaimerAccepted(const AttachWebViewBot &bot) const { - return _disclaimerAccepted.contains(bot.user); +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(); + } + } } -bool AttachWebView::showMainMenuNewBadge( - const AttachWebViewBot &bot) const { - return bot.inMainMenu - && bot.disclaimerRequired - && !disclaimerAccepted(bot); +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 AttachWebView::requestAddToMenu( - not_null bot, - AddToMenuOpen open) { - requestAddToMenu(bot, open, nullptr, std::nullopt); +void WebViewInstance::close() { + _session->attachWebView().close(this); } -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)); - } +void WebViewInstance::started(uint64 queryId) { + Expects(_context.action.has_value()); - 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 (!queryId) { return; } - if (_addToMenuId) { - if (_addToMenuBot == bot) { - 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)); - } - } - } - } - }); - }).fail([=] { - _addToMenuId = 0; - _addToMenuBot = nullptr; - 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; - } - _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, - Fn)> done) { - if (const auto peer = _session->data().peerByUsername(username)) { - done(peer); - return; - } - _session->api().request(base::take(_requestId)).cancel(); - _requestId = _session->api().request(MTPcontacts_ResolveUsername( - MTP_string(username) - )).done([=](const MTPcontacts_ResolvedPeer &result) { - _requestId = 0; - result.match([&](const MTPDcontacts_resolvedPeer &data) { - _session->data().processUsers(data.vusers()); - _session->data().processChats(data.vchats()); - if (const auto peerId = peerFromMTP(data.vpeer())) { - done(_session->data().peer(peerId)); - } - }); - }).fail([=](const MTP::Error &error) { - _requestId = 0; - if (error.code() == 400) { - showToast( - tr::lng_username_not_found(tr::now, lt_user, username)); - } - }).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; - } + _session->data().webViewResultSent( + ) | rpl::filter([=](const Data::Session::WebViewResultSent &sent) { + return (sent.queryId == queryId); + }) | rpl::start_with_next([=] { + close(); + }, _panel->lifetime()); - 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)) + 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_string(url), - MTPstring(), // start_param - MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), - MTP_string("tdesktop"), + MTP_long(queryId), 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(); - } + )).done([=] { + _prolongId = 0; }).send(); - }); + }, _panel->lifetime()); } -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) { - 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(); +Webview::ThemeParams WebViewInstance::botThemeParams() { + return Window::Theme::WebViewParams(); } -void AttachWebView::confirmAppOpen(bool requestWriteAccess) { - const auto controller = _context ? _context->controller.get() : nullptr; - if (!controller || !_bot) { - return; +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; } - 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(); + const auto bot = _bot; + const auto context = std::make_shared(_context); + if (!keepOpen) { + botClose(); + } + 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{ + .sessionWindow = window, + .botWebviewContext = context, + }); + UrlClickHandler::Open(local, variant); + }); + return true; } -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(); +void WebViewInstance::botHandleInvoice(QString slug) { + Expects(_panel != nullptr); + + using Result = Payments::CheckoutResult; + const auto weak = base::make_weak(_panel.get()); + const auto reactivate = [=](Result result) { + if (const auto strong = weak.get()) { + strong->invoiceClosed(slug, [&] { + switch (result) { + case Result::Paid: return "paid"; + case Result::Failed: return "failed"; + case Result::Pending: return "pending"; + case Result::Cancelled: return "cancelled"; + } + Unexpected("Payments::CheckoutResult value."); + }()); } - }).send(); + }; + Payments::CheckoutProcess::Start( + &_bot->session(), + slug, + reactivate, + nonPanelPaymentFormFactory(reactivate)); } -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(); +auto WebViewInstance::nonPanelPaymentFormFactory( + Fn reactivate) +-> Fn { + using namespace Payments; + const auto panel = base::make_weak(_panel.get()); + const auto weak = _context.controller; + 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(); + }); }; - 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(), - })); } -void AttachWebView::acceptMainMenuDisclaimer( - not_null controller, - const WebViewButton &button) { - Expects(button.fromMainMenu); +void WebViewInstance::botHandleMenuButton( + Ui::BotWebView::MenuButton button) { + Expects(_panel != nullptr); - const auto local = _bot ? &_bot->session().local() : nullptr; - if (!local) { - return; - } - const auto i = ranges::find( - _attachBots, - not_null(_bot), - &AttachWebViewBot::user); - if (i == end(_attachBots)) { - _attachBotsUpdates.fire({}); - return; - } else if (i->inactive) { - requestAddToMenu(_bot, AddToMenuOpenMenu{ - .startCommand = button.startCommand, - }, controller, {}); - return; - } else if (!i->disclaimerRequired || disclaimerAccepted(*i)) { - requestSimple(button); - return; + using Button = Ui::BotWebView::MenuButton; + const auto bot = _bot; + switch (button) { + case Button::OpenBot: + botClose(); + if (bot->session().windows().empty()) { + Core::App().domain().activate(&bot->session().account()); + } + if (!bot->session().windows().empty()) { + const auto window = bot->session().windows().front(); + window->showPeerHistory(bot); + window->window().activate(); + } + break; + case Button::RemoveFromMenu: + case Button::RemoveFromMainMenu: + const auto &bots = _session->attachWebView().attachBots(); + const auto attached = ranges::find( + bots, + _bot, + &AttachWebViewBot::user); + const auto name = (attached != end(bots)) + ? attached->name + : _bot->name(); + const auto done = crl::guard(this, [=] { + const auto session = _session; + const auto was = _parentShow; + botClose(); + + const auto active = Core::App().activeWindow(); + const auto show = active ? active->uiShow() : was; + session->attachWebView().removeFromMenu(show, bot); + if (active) { + active->activate(); + } + }); + const auto main = (button == Button::RemoveFromMainMenu); + _panel->showBox(Ui::MakeConfirmBox({ + (main + ? tr::lng_bot_remove_from_side_menu_sure + : tr::lng_bot_remove_from_menu_sure)( + tr::now, + lt_bot, + Ui::Text::Bold(name), + Ui::Text::WithEntities), + done, + })); + break; } +} - const auto weak = base::make_weak(this); - controller->show(Box(FillDisclaimerBox, crl::guard(this, [=] { - _disclaimerAccepted.emplace(_bot); - _attachBotsUpdates.fire({}); - requestSimple(button); - }))); +bool WebViewInstance::botValidateExternalLink(QString uri) { + const auto lower = uri.toLower(); + const auto allowed = _session->appConfig().get>( + "web_app_allowed_protocols", + std::vector{ u"http"_q, u"https"_q }); + for (const auto &protocol : allowed) { + if (lower.startsWith(protocol + u"://"_q)) { + return true; + } + } + return false; } -void AttachWebView::ClearAll() { - while (!ActiveWebViews().empty()) { - ActiveWebViews().front()->cancel(); +void WebViewInstance::botOpenIvLink(QString uri) { + const auto window = _context.controller.get(); + if (window) { + Core::App().iv().openWithIvPreferred(window, uri); + } else { + Core::App().iv().openWithIvPreferred(_session, uri); } } -void AttachWebView::show( - uint64 queryId, - const QString &url, - const QString &buttonText, - bool allowClipboardRead, - const BotAppData *app, - bool fromMainMenu) { - Expects(_bot != nullptr && _context != nullptr); +void WebViewInstance::botSendData(QByteArray data) { + Expects(_context.action.has_value()); - auto title = Info::Profile::NameValue(_bot); - ActiveWebViews().emplace(this); + const auto button = std::get_if(&_source); + if (!button + || !button->simple + || _context.action->history->peer != _bot + || _dataSent) { + return; + } + _dataSent = true; + _session->api().request(MTPmessages_SendWebViewData( + _bot->inputUser, + MTP_long(base::RandomValue()), + MTP_string(_button.text), + MTP_bytes(data) + )).done([session = _session](const MTPUpdates &result) { + session->api().applyUpdates(result); + }).send(); + botClose(); +} - 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; +void WebViewInstance::botSwitchInlineQuery( + std::vector chatTypes, + QString query) { + const auto controller = _context.controller.get(); + const auto types = PeerTypesFromNames(chatTypes); + if (!_bot + || !_bot->isBot() + || _bot->botInfo->inlinePlaceholder.isEmpty() + || !controller) { + return; + } else if (!types) { + if (_context.dialogsEntryState.key.owningHistory()) { + controller->switchInlineQuery( + _context.dialogsEntryState, + _bot, + query); + } + } else { + const auto bot = _bot; + const auto done = [=](not_null thread) { + controller->switchInlineQuery(thread, bot, query); + }; + ShowChooseBox( + controller, + types, + done, + tr::lng_inline_switch_choose()); } - - _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); + botClose(); } -void AttachWebView::showGame(ShowGameParams &¶ms) { - ActiveWebViews().emplace(this); +void WebViewInstance::botCheckWriteAccess(Fn callback) { + _session->api().request(MTPbots_CanSendMessage( + _bot->inputUser + )).done([=](const MTPBool &result) { + callback(mtpIsTrue(result)); + }).fail([=] { + callback(false); + }).send(); +} - base::take(_panel); - _gameContext = params.context; +void WebViewInstance::botAllowWriteAccess(Fn callback) { + _session->api().request(MTPbots_AllowSendMessage( + _bot->inputUser + )).done([session = _session, callback](const MTPUpdates &result) { + session->api().applyUpdates(result); + callback(true); + }).fail([=] { + callback(false); + }).send(); +} - _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 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) { + if (success) { + botSharePhone(callback); + } else { + callback(false); + } + }); + _session->api().blockedPeers().unblock(_bot, done); + return; + } + auto action = Api::SendAction(history); + action.clearDraft = false; + _session->api().shareContact( + _session->user(), + action, + std::move(callback)); } -void AttachWebView::started(uint64 queryId) { - Expects(_bot != nullptr); - Expects(_context != nullptr); +void WebViewInstance::botInvokeCustomMethod( + Ui::BotWebView::CustomMethodRequest request) { + const auto callback = request.callback; + _session->api().request(MTPbots_InvokeWebViewCustomMethod( + _bot->inputUser, + MTP_string(request.method), + MTP_dataJSON(MTP_bytes(request.params)) + )).done([=](const MTPDataJSON &result) { + callback(result.data().vdata().v); + }).fail([=](const MTP::Error &error) { + callback(base::make_unexpected(error.type())); + }).send(); +} - if (_context->fromSwitch || !queryId) { +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(itemId)) { + FastShareMessage(uiShow(), item); + } else { + _panel->showToast({ tr::lng_message_not_found(tr::now) }); } +} - _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()); +void WebViewInstance::botClose() { + crl::on_main(this, [=] { close(); }); } -std::shared_ptr AttachWebView::uiShow() { +std::shared_ptr WebViewInstance::uiShow() { class Show final : public Main::SessionShow { public: - explicit Show(not_null that) : _that(that) { + explicit Show(not_null that) : _that(that) { } void showOrHideBoxOrLayer( @@ -1685,7 +1374,7 @@ std::shared_ptr AttachWebView::uiShow() { using ObjectBox = object_ptr; const auto panel = _that ? _that->_panel.get() : nullptr; if (v::is(layer)) { - Unexpected("Layers in AttachWebView are not implemented."); + Unexpected("Layers in WebView are not implemented."); } else if (auto box = std::get_if(&layer)) { if (panel) { panel->showBox(std::move(*box), options, animated); @@ -1709,39 +1398,281 @@ std::shared_ptr AttachWebView::uiShow() { [[nodiscard]] Main::Session &session() const override { Expects(_that.get() != nullptr); + return *_that->_session; } private: - const base::weak_ptr _that; + 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); +AttachWebView::AttachWebView(not_null session) +: _session(session) +, _refreshTimer([=] { requestBots(); }) { + _refreshTimer.callEach(kRefreshBotsTimeout); +} + +AttachWebView::~AttachWebView() = default; + +void AttachWebView::openByUsername( + not_null controller, + const Api::SendAction &action, + const QString &botUsername, + const QString &startCommand) { + if (botUsername.isEmpty() + || (_botUsername == botUsername && _startCommand == startCommand)) { + return; + } + cancel(); + + _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{}, + }); + })); +} + +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::closeAll() { + cancel(); + base::take(_instances); +} + +void AttachWebView::cancel() { + _session->api().request(base::take(_requestId)).cancel(); + _botUsername = QString(); + _startCommand = QString(); +} + +void AttachWebView::requestBots(Fn callback) { + if (callback) { + _botsRequestCallbacks.push_back(std::move(callback)); + } + if (_botsRequestId) { + return; + } + _botsRequestId = _session->api().request(MTPmessages_GetAttachMenuBots( + MTP_long(_botsHash) + )).done([=](const MTPAttachMenuBots &result) { + _botsRequestId = 0; + result.match([&](const MTPDattachMenuBotsNotModified &) { + }, [&](const MTPDattachMenuBots &data) { + _session->data().processUsers(data.vusers()); + _botsHash = data.vhash().v; + _attachBots.clear(); + _attachBots.reserve(data.vbots().v.size()); + for (const auto &bot : data.vbots().v) { + if (auto parsed = ParseAttachBot(_session, bot)) { + _attachBots.push_back(std::move(*parsed)); + } + } + _attachBotsUpdates.fire({}); + }); + for (const auto &callback : base::take(_botsRequestCallbacks)) { + callback(); + } + }).fail([=] { + _botsRequestId = 0; + for (const auto &callback : base::take(_botsRequestCallbacks)) { + callback(); + } + }).send(); +} + +bool AttachWebView::disclaimerAccepted(const AttachWebViewBot &bot) const { + return _disclaimerAccepted.contains(bot.user); +} + +bool AttachWebView::showMainMenuNewBadge( + const AttachWebViewBot &bot) const { + return bot.inMainMenu + && bot.disclaimerRequired + && !disclaimerAccepted(bot); +} + +void AttachWebView::requestAddToMenu( + not_null bot, + Fn done) { + auto &process = _addToMenu[bot]; + if (done) { + process.done.push_back(std::move(done)); + } + if (process.requestId) { + return; + } + + 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; + } + 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([=] { + finish(AddToMenuResult::Unsupported, {}); + }).send(); +} + +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)); + } + }); +} + +void AttachWebView::resolveUsername( + std::shared_ptr show, + Fn)> done) { + 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(_botUsername) + )).done([=](const MTPcontacts_ResolvedPeer &result) { + _requestId = 0; + result.match([&](const MTPDcontacts_resolvedPeer &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + if (const auto peerId = peerFromMTP(data.vpeer())) { + done(_session->data().peer(peerId)); + } + }); + }).fail([=](const MTP::Error &error) { + _requestId = 0; + if (error.code() == 400) { + show->showToast( + tr::lng_username_not_found(tr::now, lt_user, _botUsername)); + } + }).send(); +} + +void AttachWebView::open(WebViewDescriptor &&descriptor) { + for (const auto &instance : _instances) { + if (instance->bot() == descriptor.bot + && instance->source() == descriptor.source) { + instance->activate(); + return; + } + } + _instances.push_back( + std::make_unique(std::move(descriptor))); + _instances.back()->activate(); +} + +void AttachWebView::acceptMainMenuDisclaimer( + 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, std::move(done)); + return; + } else if (!i->disclaimerRequired || disclaimerAccepted(*i)) { + done(AddToMenuResult::AlreadyInMenu, i->types); + return; } + 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::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 f9f59f1ae30a7f..6e11c87cd42806 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 @@ For license and copyright information please follow this link: #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; +}; + +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 AttachWebView final +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 activate(); + void close(); - 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; + [[nodiscard]] std::shared_ptr uiShow(); - 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); +private: + void resolve(); - [[nodiscard]] std::shared_ptr uiShow(); + bool openAppFromBotMenuLink(); - static void ClearAll(); + void requestButton(); + void requestSimple(); + void requestApp(bool allowWrite); + void requestWithMainMenuDisclaimer(); + void requestWithMenuAdd(); + void maybeChooseAndRequestButton(PeerTypes supported); -private: - struct Context; + 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,36 +280,82 @@ class AttachWebView final 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; + + 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; + +}; - bool openAppFromMenuLink( +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); + Fn done); + void acceptMainMenuDisclaimer( + std::shared_ptr show, + not_null bot, + Fn done); - void resolve(); - void request(const WebViewButton &button); - void requestSimple(const WebViewButton &button); + void close(not_null instance); + void closeAll(); + +private: void resolveUsername( - const QString &username, + std::shared_ptr show, Fn)> done); - void confirmOpen( - not_null controller, - Fn done); - void acceptMainMenuDisclaimer( - not_null controller, - const WebViewButton &button); - enum class ToggledState { Removed, Added, @@ -232,67 +364,35 @@ class AttachWebView final 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 10c6236bf9676f..2418c0f8a2be04 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -31,6 +31,7 @@ For license and copyright information please follow this link: #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 df1f2ef2587a12..0cfdeece27025e 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 ae27e4938932eb..6a50d259050581 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 b70fb833ad1ea5..629b6cf16c818d 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 8380e458a10399..ebd403da488036 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 2c745712287d4e..74a783696f96e5 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 @@ For license and copyright information please follow this link: */ #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 040/134] 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 e72298f6c4f23d..b3fb82a0d713e2 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -9,6 +9,7 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: #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 6a50d259050581..a8dee5f76216fd 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 041/134] 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 d070c93d2a3c7b..a49ba8887773e4 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -9,6 +9,7 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: #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 042/134] 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 ce592ecab26770..358a7b0ec0e227 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 5d44a8acc341d5..1f0b2531d54a88 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 043/134] 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 8e993f4f746af6..27e37053a3268e 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 007e3a519ffbad..7dd187eeeb46b6 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 044/134] 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 0091881ed09e6e..463472041cc52f 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 ea305575e53758..f5a64b413ac184 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -540,7 +540,7 @@ def runStages(): 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 045/134] 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 f326275fdfd13c..d5f76bf86da5d3 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 acf09d84569e3f..214de059c43a64 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 046/134] 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 6d8a8ec54d731c..32437fb772fc1f 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -18,8 +18,10 @@ For license and copyright information please follow this link: #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 047/134] 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 463472041cc52f..f19f8f1e462635 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 0bf1aae0433660..e7a34e6aef6b3a 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 048/134] 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 f19f8f1e462635..143bf072ce68f8 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 049/134] 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 143bf072ce68f8..ca962d93532b22 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 050/134] 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 ca962d93532b22..0f101584b25537 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 051/134] 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 4ac8cf9d65e47e..21a8611ab764ac 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 7dd187eeeb46b6..45f3a330679b77 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 7dd187eeeb46b68dadc5752c3f3fc34e5e1d7e94 +Subproject commit 45f3a330679b775f0f56bbea7b40f83ebd8f5639 diff --git a/cmake b/cmake index b3871c4efde378..462b85cc62fd24 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 052/134] 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 358a7b0ec0e227..ce5bc41b27144d 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 1f0b2531d54a88..70b83ba7153701 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 053/134] 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 ce5bc41b27144d..0d88a47c7f6dce 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 21183fa389a04b..e3217ca40973ee 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 0f101584b25537..303aab47895c6e 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 462b85cc62fd24..17c758e2b9f09a 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 054/134] 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 46918013d2ffb5..27e1bbfe4bc095 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 c47677eb217519..a256817c411d5a 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 5594959fe574cf..8f3f05d4847405 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 e1ac92bf4e6718..45302d07a89722 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 4d2b8605937ff9..446ec84d7b20fb 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 c0298f855afe6b..8d5ac75b8fae72 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 055/134] 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 4b92ee0757bbe8..ea204a7704d022 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 84c33613104bac..927dba8f86948d 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 c6a7e9c6e66a6c..985bc03f9fbd8f 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 45f3a330679b77..03f250aab2e791 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 056/134] 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 303aab47895c6e..99816a198bde2a 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 f5a64b413ac184..b7c447019f7b61 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -457,7 +457,7 @@ def runStages(): 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 @@ def runStages(): 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 70b83ba7153701..d9a08df0c0b64e 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 0a3d3f8552678e..12fa23acb08aa6 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 057/134] 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 03f250aab2e791..99d9142130025e 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 058/134] 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 223cec0439ecbe..6777464c17a202 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 ebd403da488036..ef1bddf245c765 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 74a783696f96e5..d64d82c5359a7c 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 059/134] 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 48cf60eff84145..b9ee4785c20d3b 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 6c98281895294a..b902aa4f696ba7 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 cd8aa54e2d7fae..81f098d670c0e8 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 9b92f4815be6ad..47c1e8b47a90a8 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 00000000000000..4402e4ccf8a57d --- /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 00000000000000..ad10460952937a --- /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 27e37053a3268e..e7d842c64bf906 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 @@ For license and copyright information please follow this link: #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 c14d490511e085..cc09dacf5279d7 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -30,6 +30,7 @@ For license and copyright information please follow this link: #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 9581e7cd424dfb..a69373a36f1e83 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 @@ class Session final : public base::has_weak_ptr { [[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 @@ class Session final : public base::has_weak_ptr { 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 e5bb0f8e387101..6b659d0441d588 100644 --- a/Telegram/SourceFiles/settings/business/settings_location.cpp +++ b/Telegram/SourceFiles/settings/business/settings_location.cpp @@ -63,6 +63,7 @@ class Location : public BusinessSection { 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 8cce44571114d9..b6e3dc4411ff51 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -20,6 +20,7 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: #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 @@ class VenuesController final [[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 @@ class VenuesController final 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 97ce724f343246..943e7ac75f82d8 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 @@ class LocationPicker final : public base::has_weak_ptr { [[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 @@ class LocationPicker final : public base::has_weak_ptr { 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 @@ class LocationPicker final : public base::has_weak_ptr { 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 060/134] 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 0d88a47c7f6dce..9bf4065ea00cbe 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 061/134] 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 b6e3dc4411ff51..945e5cf8fe2982 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 062/134] 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 27e1bbfe4bc095..4716432a8aa92e 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 a256817c411d5a..91720d2351927f 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 8f3f05d4847405..829055b26bc787 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 45302d07a89722..fcfe70dd86436a 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 446ec84d7b20fb..3e6807e8b5eef2 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 8d5ac75b8fae72..09adb583503625 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 063/134] 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 bd51bb74a5b402..3a37311e67bfdc 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 064/134] 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 4716432a8aa92e..45ec14889b7462 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 91720d2351927f..83eecf0a79f858 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 829055b26bc787..40bb30f11b78bb 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 fcfe70dd86436a..111881fd487991 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 99816a198bde2a..568d6663afce9f 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 b7c447019f7b61..83e16284fd656f 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1718,7 +1718,7 @@ def runStages(): 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 3e6807e8b5eef2..f10750e38d861c 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 09adb583503625..5ec213f7e928ba 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 12fa23acb08aa6..b5f71d99f28e0e 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 065/134] 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 4c6d4970dc210a..e1d67952ebf45b 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 066/134] 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 feb6d395ed1f12..aea01c5924cd4b 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 221f6017f1fb80..bc42a9d1caa6d6 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 @@ For license and copyright information please follow this link: #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 067/134] 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 19f488b0b45ea1..b2c2ec4db10d1b 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 068/134] 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 8ef614d218a4f7..ec9fa548e9610b 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 069/134] 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 ce806928c38a1c..2853340a532f4e 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 070/134] 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 b902aa4f696ba7..47b46c6e91bd05 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 b3c589be793058..afd4d233f970e8 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -25,6 +25,7 @@ For license and copyright information please follow this link: #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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { : Data::StickersType::Stickers; } + [[nodiscard]] bool amSetCreator() const { + return _amSetCreator; + } + ~Inner(); protected: @@ -366,6 +371,7 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 47c1e8b47a90a8..be9d5f8e470bd0 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 99d9142130025e..97bfa6cef474b3 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 071/134] 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 afd4d233f970e8..e1906b0c8fd1de 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -15,6 +15,7 @@ For license and copyright information please follow this link: #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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { return _amSetCreator; } + void applySet(const TLStickerSet &set); + ~Inner(); protected: @@ -327,7 +331,6 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 be9d5f8e470bd0..40ab1d705ebcc8 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 072/134] 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 47b46c6e91bd05..3aa6ac586b7949 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 e1906b0c8fd1de..3cc09542193edf 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -34,6 +34,7 @@ For license and copyright information please follow this link: #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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { [[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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 21a8611ab764ac..ca4503b3075fca 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 073/134] 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 3cc09542193edf..48fe9828b377c4 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -342,6 +342,8 @@ class StickerSetBox::Inner final : public Ui::RpWidget { void installDone(const MTPmessages_StickerSetInstallResult &result); + void requestReorder(not_null document, int index); + void chosen( int index, not_null sticker, @@ -394,6 +396,8 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 074/134] 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 48fe9828b377c4..dd5a0118360c54 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -31,6 +31,7 @@ For license and copyright information please follow this link: #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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 075/134] 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 3aa6ac586b7949..57dbaad8724afe 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 dd5a0118360c54..aef0dd941db2f8 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -7,54 +7,55 @@ For license and copyright information please follow this link: */ #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 @@ class StickerSetBox::Inner final : public Ui::RpWidget { 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 076/134] 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 57dbaad8724afe..7005a219b80c3c 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 40ab1d705ebcc8..d48f32c0055f37 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 337658af18686a..148f4e7d91db78 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 32437724206bf9..75d98404e2e73d 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 e77ee46b56618c..c218ce2fa89f71 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 077/134] 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 d5f76bf86da5d3..662bfc98013be9 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 04a2e25f114448..0f0fa264b8957e 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 15c0941c39fc8d..7259c410d34e84 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -244,7 +244,10 @@ class ApiWrap final : public MTP::Sender { 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 @@ class ApiWrap final : public MTP::Sender { 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 9ed24fb381321e..8000355c239bb3 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 078/134] 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 148f4e7d91db78..4258edbacc0709 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #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 f1a89b21081bec..c436273fcfc6e4 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -350,7 +350,9 @@ class StickersListWidget final : public TabbedSelector::Inner { 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 079/134] 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 7005a219b80c3c..2311102df16b4c 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 a76cb4b67586fa..46a5b7fa3e711a 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 da16410b0366f2..70d29f28abbc10 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 424e2c4484bc3b..b8c943edcb0d02 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 @@ class MediaGiftBox final : public Media { 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 e43e6423ad42b6..cf772208b7fa12 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 38ea28aae66813..edd4670d835420 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -26,6 +26,7 @@ For license and copyright information please follow this link: #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 b318ce9fe885a4..4dfc7a7127932b 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 9e65eac39c6048..f9c9806d07402b 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 72824a2eef781e..9639730e423498 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 d988fc00c33b1f..4d3fa397c4cdab 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 5d4c7e42dbb89d..62aaba5f7f498a 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 d2386e7e625158..9cee2262e708d4 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 5a6538036fb40e..8390d09fa805b5 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 b832a6dc3fe98a..885e74a62d97e4 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 @@ For license and copyright information please follow this link: #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 af5724dfacf465..7240dd49e37f01 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 @@ class PremiumGift final : public ServiceBoxContent { [[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 9e366ca549a3c9..bfcab3fc7846af 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 394a50efaf381c..2d35a0c524ff49 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 c09b41c36351d5..e9dec016bd0987 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 59f2fb50569cac..1895a87035f15f 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 dd87b0c5fe92ca..efb527506b3efa 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 080/134] 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 2311102df16b4c..931bbb1c0c83a7 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 356e2cffaf2ba4..0b35a04eef9fc7 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 fdf1eda03f30bb..5730d9b40a9508 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 75da4db5b13f57..d01becf07266b6 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 9c2a692e785869..86640fe5b37b03 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 1eaef456826c6f..1f774b499b3e8f 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 e99a4d4eb37af1..61b43b8fc9f2d0 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 081/134] 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 8fac1a399189bd..d72c74d077fe4f 100644 --- a/Telegram/SourceFiles/settings/settings_common_session.cpp +++ b/Telegram/SourceFiles/settings/settings_common_session.cpp @@ -7,30 +7,9 @@ For license and copyright information please follow this link: */ #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 082/134] 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 f54b1f1d35db59..2e810dd6c42022 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 @@ For license and copyright information please follow this link: #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 e722c19796374d..4c480fba3c0d92 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 @@ For license and copyright information please follow this link: class DocumentData; +namespace Data { +struct FileOrigin; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -22,6 +26,7 @@ class GiftBoxPack final { void load(); [[nodiscard]] DocumentData *lookup(int months) const; + [[nodiscard]] Data::FileOrigin origin() const; private: using SetId = uint64; @@ -32,6 +37,7 @@ class GiftBoxPack final { 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 083/134] 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 1f774b499b3e8f..fdb7b74fe2e304 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -12,11 +12,14 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: #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 baab38fb77279c..cebc110c5e250f 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 084/134] 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 0b35a04eef9fc7..3ef1d721f0527f 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 085/134] 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 e80f82a62d4503..23525365240306 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 fdb7b74fe2e304..e9e01584905968 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 47d5323c7a7936..701c60b7fe8845 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 086/134] 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 e9e01584905968..613bf9169aec45 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 087/134] 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 3ef1d721f0527f..727cee565c7ab2 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 d01becf07266b6..ee8d948a75236c 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 e9dec016bd0987..8c0a44e10e799a 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 c85d946ed298ba..42b6d00b3704b9 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 613bf9169aec45..954c07c7b581ea 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 088/134] 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 b9ee4785c20d3b..8923211da4888c 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 931bbb1c0c83a7..fad51ea32357c8 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 00000000000000..d03a7364107dc7 --- /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 00000000000000..43b8556f32125c --- /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 23525365240306..0d7d1249061371 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #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 cebc110c5e250f..b982687d65955a 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 089/134] 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 754791e80d7ee1..35643637e727ff 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -17,6 +17,7 @@ For license and copyright information please follow this link: #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 9cee2262e708d4..4a8a413ccf480c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -39,6 +39,7 @@ For license and copyright information please follow this link: #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 7e9fb6396bac25..1a5e14014075cb 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 d950623137a696..7ebd961f3a3dc0 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 090/134] 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 edd4670d835420..d9c37de4e10e65 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 4dfc7a7127932b..bd508591c94ad6 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 @@ class Story final { -> 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 @@ class Story final { 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 c5fd13992d55e5..ca558abfd4f5ad 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 3d486fa873e57e..590dee3f675792 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 @@ class Controller final : public base::has_weak_ptr { }; 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 @@ class Controller final : public base::has_weak_ptr { 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 af45d91507f3b0..f5e2582ef4e373 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -11,6 +11,8 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: #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 @@ class ReactionView final 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 @@ class ReactionView final 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 @@ class ReactionView final }; +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 @@ class ReactionView final 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 de9f0e0ce61bea..b17e2e19da159c 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 @@ class Reactions final { [[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 a1b0bcb45afb11..5c9c57f0738150 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 ed2bb6a18259b0..1102863b2de38f 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 091/134] 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 e7d842c64bf906..e0a609f7a3d9d0 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 6e11c87cd42806..ecccdd04222ce5 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 @@ class WebViewInstance final 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 092/134] 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 2e810dd6c42022..f510540c2c3996 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 4c480fba3c0d92..1e6c0e9349ee05 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 @@ class GiftBoxPack final { ~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 885e74a62d97e4..2a7a2f4b1edef7 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 093/134] 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 | 176 ++++++++++-------- .../settings/settings_credits_graphics.h | 7 + .../ui/effects/credits_graphics.cpp | 4 +- 8 files changed, 153 insertions(+), 96 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fad51ea32357c8..fedb32957c72be 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 5730d9b40a9508..3b8f6f1b5e31ca 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 ea204a7704d022..6a816af963a408 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 4a8a413ccf480c..a38b9b33fe56ed 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 2a7a2f4b1edef7..23d483c1ffec5f 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 @@ For license and copyright information please follow this link: #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 954c07c7b581ea..a8c63b5f8b61b2 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) { + 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 { - 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( + const auto widget = content->add( + object_ptr>( 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()); - } + 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 701c60b7fe8845..e07262d6d06484 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 61b43b8fc9f2d0..018188cb3ec3a3 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 094/134] Remove some code duplication. --- .../dialogs/ui/dialogs_suggestions.cpp | 756 +++++++++--------- .../dialogs/ui/dialogs_suggestions.h | 70 +- 2 files changed, 411 insertions(+), 415 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index a19200d8ab27d2..84b3f8b892eef6 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -87,66 +87,6 @@ class RecentRow final : public PeerListRow { }; -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 @@ class ChannelRow final : public PeerListRow { }; -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); - }; - - 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(); + const auto addToScroll = [=] { + return _topPeersWrap->toggled() ? _topPeers->height() : 0; }; - _recentSelectJump = [raw](Qt::Key direction, int pageSize) { - const auto had = raw->hasSelection(); + auto result = setupObjectList( + _chatsScroll.get(), + _chatsContent, + 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) { @@ -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,96 +1488,45 @@ object_ptr> Suggestions::setupEmptyRecent() { return setupEmpty(_chatsContent, icon, tr::lng_recent_none()); } -object_ptr> Suggestions::setupEmptyChannels() { - const auto icon = SearchEmptyIcon::NoResults; - return setupEmpty(_channelsContent, icon, tr::lng_channels_none_about()); -} - -object_ptr> Suggestions::setupEmpty( - not_null parent, - SearchEmptyIcon icon, - rpl::producer text) { - auto content = object_ptr( - parent, - icon, - std::move(text) | Ui::Text::ToWithEntities()); - - const auto raw = content.data(); - rpl::combine( - _chatsScroll->heightValue(), - _topPeersWrap->heightValue() - ) | rpl::start_with_next([=](int height, int top) { - raw->setMinimalHeight(height - top); - }, raw->lifetime()); - - auto result = object_ptr>( - parent, - std::move(content)); - result->toggle(false, anim::type::instant); - - result->toggledValue() | rpl::filter([=](bool shown) { - return shown && _controller->session().data().chatsListLoaded(); - }) | rpl::start_with_next([=] { - raw->animate(); - }, raw->lifetime()); - - return result; -} - -object_ptr> Suggestions::setupMyChannels() { - auto &lifetime = _channelsContent->lifetime(); - const auto delegate = lifetime.make_state< - PeerListContentDelegateSimple - >(); - const auto controller = lifetime.make_state( +auto Suggestions::setupMyChannels() -> std::unique_ptr { + const auto controller = lifetime().make_state( _controller); - controller->setStyleOverrides(&st::recentPeersList); - _myChannelsCount = controller->count(); - _myChannelsProcessTouch = [=](not_null e) { - return controller->processTouchEvent(e); - }; + auto result = setupObjectList( + _channelsScroll.get(), + _channelsContent, + controller); + const auto raw = result.get(); + const auto list = raw->wrap->entity(); - 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(); + 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) { - raw->selectLast(); - return raw->hasSelection() + 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 = raw->selectedIndex(); + const auto was = list->selectedIndex(); 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); } if (had && delta > 0 - && _recommendationsCount.current() - && raw->selectedIndex() == was) { - raw->clearSelection(); + && raw->count.current() + && list->selectedIndex() == was) { + list->clearSelection(); return JumpResult::AppliedAndOut; } - return raw->hasSelection() + return list->hasSelection() ? JumpResult::Applied : had ? JumpResult::AppliedAndOut @@ -1602,58 +1534,33 @@ object_ptr> Suggestions::setupMyChannels() { } 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()); + raw->chosen.events( + ) | rpl::start_with_next([=] { + _persist = false; + }, list->lifetime()); - return object_ptr>(this, std::move(content)); + return result; } -object_ptr> Suggestions::setupRecommendations() { - auto &lifetime = _channelsContent->lifetime(); - const auto delegate = lifetime.make_state< - PeerListContentDelegateSimple - >(); - const auto controller = lifetime.make_state( +auto Suggestions::setupRecommendations() -> std::unique_ptr { + const auto controller = lifetime().make_state( _controller); - controller->setStyleOverrides(&st::recentPeersList); - _recommendationsCount = controller->count(); - _recommendationsProcessTouch = [=](not_null e) { - return controller->processTouchEvent(e); + const auto addToScroll = [=] { + const auto wrap = _myChannels->wrap; + return wrap->toggled() ? wrap->height() : 0; }; - - _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(); + 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) { @@ -1661,11 +1568,11 @@ object_ptr> Suggestions::setupRecommendations() { } 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 @@ -1673,23 +1580,110 @@ object_ptr> Suggestions::setupRecommendations() { } return JumpResult::NotApplied; }; - _recommendationsUpdateFromParentDrag = [=](QPoint globalPosition) { - return raw->updateFromParentDrag(globalPosition); - }; - _recommendationsDragLeft = [=] { - raw->dragLeft(); + + 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); }; - 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); + + controller->chosen( + ) | rpl::start_with_next([=](not_null peer) { + raw->chosen.fire_copy(peer); }, lifetime); - delegate->setContent(raw); + 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(_channelsScroll.get()); + controller->setupTouchChatPreview(scroll); + + return result; +} + +object_ptr> Suggestions::setupEmptyChannels() { + const auto icon = SearchEmptyIcon::NoResults; + return setupEmpty(_channelsContent, icon, tr::lng_channels_none_about()); +} + +object_ptr> Suggestions::setupEmpty( + not_null parent, + SearchEmptyIcon icon, + rpl::producer text) { + auto content = object_ptr( + parent, + icon, + std::move(text) | Ui::Text::ToWithEntities()); + + const auto raw = content.data(); + rpl::combine( + _chatsScroll->heightValue(), + _topPeersWrap->heightValue() + ) | rpl::start_with_next([=](int height, int top) { + raw->setMinimalHeight(height - top); + }, raw->lifetime()); - return object_ptr>(this, std::move(content)); + auto result = object_ptr>( + parent, + std::move(content)); + result->toggle(false, anim::type::instant); + + result->toggledValue() | rpl::filter([=](bool shown) { + return shown && _controller->session().data().chatsListLoaded(); + }) | rpl::start_with_next([=] { + raw->animate(); + }, raw->lifetime()); + + return result; } bool Suggestions::persist() const { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 8b33718e6e5cca..438c0b370e5d83 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -12,6 +12,8 @@ For license and copyright information please follow this link: #include "ui/effects/animations.h" #include "ui/rp_widget.h" +class PeerListContent; + namespace Data { class Thread; } // namespace Data @@ -67,17 +69,19 @@ class Suggestions final : public Ui::RpWidget { } [[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 @@ class Suggestions final : public Ui::RpWidget { 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 @@ class Suggestions final : public Ui::RpWidget { 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 @@ class Suggestions final : public Ui::RpWidget { 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 095/134] 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 fedb32957c72be..64bbb68d8fd31b 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 f476869b662a9d..57974febfe6308 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 5f1250b5315b30..7f834ad84f826d 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 @@ class TopPeers final { 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 cf772208b7fa12..f88fb6abb1c2ef 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 67da9e3f54edda..8f84555d3f3889 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 922b673a06756f..2e7568fc0275d3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -78,6 +78,7 @@ For license and copyright information please follow this link: #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 347683d0c5129a..a1ee3950de3e65 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -214,6 +214,7 @@ class Widget final : public Window::AbstractSectionWidget { 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 84b3f8b892eef6..18ae94b3710d69 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -23,6 +23,7 @@ For license and copyright information please follow this link: #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 @@ class Suggestions::ObjectListController 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 @@ class Suggestions::ObjectListController 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 @@ class Suggestions::ObjectListController rpl::event_stream<> _touchCancelRequests; rpl::event_stream> _chosen; rpl::variable _count; + rpl::variable _toggleExpanded = nullptr; + rpl::variable _expanded = false; }; @@ -344,11 +359,9 @@ class RecentsController final : public Suggestions::ObjectListController { 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 @@ class MyChannelsController final 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 @@ class RecommendationsController final 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 @@ class RecommendationsController final }; +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 438c0b370e5d83..549851d72ea9b1 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -79,6 +79,14 @@ class Suggestions final : public Ui::RpWidget { -> 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 @@ class Suggestions final : public Ui::RpWidget { enum class Tab : uchar { Chats, Channels, + Apps, }; enum class JumpResult : uchar { NotApplied, @@ -110,13 +119,16 @@ class Suggestions final : public Ui::RpWidget { 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 @@ class Suggestions final : public Ui::RpWidget { [[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 @@ class Suggestions final : public Ui::RpWidget { 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 @@ class Suggestions final : public Ui::RpWidget { 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 @@ class Suggestions final : public Ui::RpWidget { 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 8e44a7a79cd64f..4d5a9bc048c057 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 6e17ed54914ec4..3c0af7c0d9565a 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -45,6 +45,7 @@ For license and copyright information please follow this link: #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 @@ class DetailsFiller { 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 e0a609f7a3d9d0..5810310faa0df7 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 ecccdd04222ce5..4a4f5ca08e61a1 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 @@ class WebViewInstance final void showGame(); void started(uint64 queryId); - [[nodiscard]] Window::SessionController *windowForThread( - not_null thread); - auto nonPanelPaymentFormFactory( Fn reactivate) -> Fn; @@ -352,6 +349,11 @@ class AttachWebView final : public base::has_weak_ptr { 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 @@ class AttachWebView final : public base::has_weak_ptr { 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 cc09dacf5279d7..5dd80e98883b2a 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 a69373a36f1e83..ba5dbcd99f8ce7 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -129,6 +129,9 @@ class Session final : public base::has_weak_ptr { [[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 @@ class Session final : public base::has_weak_ptr { 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 11347aa6101123..6666077dd22b68 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 7ab743bd3b734f..82934367f81f64 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 096/134] 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 efd0ba4d5a36f2..16b2595de3a88f 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 3412c4588a9bca..0ac4e94a50873f 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -891,6 +891,13 @@ class Settings final { _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 @@ class Settings final { 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 ca558abfd4f5ad..fc75e366dadba9 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #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 590dee3f675792..860908b96737d1 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -229,6 +229,7 @@ class Controller final : public base::has_weak_ptr { 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 @@ class Controller final : public base::has_weak_ptr { 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 f5e2582ef4e373..a8e044cc9b330f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -75,7 +75,6 @@ class ReactionView final 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 @@ class WeatherView final : public Ui::RpWidget, public StoryAreaView { 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 b17e2e19da159c..11da4c456edc55 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -49,7 +49,6 @@ class StoryAreaView { 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 @@ class Reactions final { [[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 097/134] 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 1bc5a1f852305f..bf1b4943283468 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 b3fb82a0d713e2..6720f3c93d1295 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 473813fcab00d1..1f0ecaf9a9dfc9 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -76,6 +76,8 @@ class Controller final { 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 a49ba8887773e4..00cfaa615defdd 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -171,6 +171,39 @@ class Shown final : public base::has_weak_ptr { }; +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 9083734adf2fbf..c48cf569ee363d 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 @@ class Instance final { QString uri, QVariant context = {}); + void showTonSite( + const QString &uri, + QVariant context = {}); + [[nodiscard]] bool hasActiveWindow( not_null session) const; @@ -97,6 +102,7 @@ class Instance final { 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 098/134] 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 16b2595de3a88f..782f5915645eef 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 0ac4e94a50873f..73f0f5eb0a6c43 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -898,6 +898,13 @@ class Settings final { _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 @@ class Settings final { 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 6720f3c93d1295..07ddd4e9d60a60 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 00cfaa615defdd..480d73573a7b1a 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 6349f6b6a30869..253116dea86b31 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 83d8e52f4c8ad2..bfac71560072db 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 d1314cde2fa7e1..84533f93d556d1 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -327,4 +327,6 @@ class Account final { }; +[[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 099/134] 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 bae02fe487bc2f..fda34772fe060d 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 c8c9c6b69560c2..6a5757276e0feb 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 07ddd4e9d60a60..1870011dc117f4 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 1f0ecaf9a9dfc9..b732ec57ae5781 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -127,11 +127,14 @@ class Controller final { 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 f578bce829fa3b..69e0ef89ec0131 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -24,6 +24,7 @@ For license and copyright information please follow this link: #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 363db4e49a0b78..cc8f41d10b66c1 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 100/134] 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 a8dee5f76216fd..b2159ebb116335 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 101/134] 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 8ee5170fb3b7c7..b77fe3e3c7d74b 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 102/134] 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 1870011dc117f4..487f9295cd1e9a 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -11,8 +11,10 @@ For license and copyright information please follow this link: #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 @@ For license and copyright information please follow this link: #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 b732ec57ae5781..c92f4c4b2b48f7 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -123,6 +123,9 @@ class Controller final { 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 d97628285e9d96..2bc023e8c79fb1 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -16,6 +16,7 @@ For license and copyright information please follow this link: #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 69e0ef89ec0131..c17eda5b8b07d1 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -24,7 +24,6 @@ For license and copyright information please follow this link: #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 b2159ebb116335..7b57464f7f1094 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 e25483b89a7e76..f687c24e61a554 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 103/134] 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 d03a7364107dc7..a2e36038adb272 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 104/134] 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 cc8f41d10b66c1..2c95b169b30035 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 105/134] 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 45ec14889b7462..3d4040dd7a9178 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 487f9295cd1e9a..00b64550574f5a 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 480d73573a7b1a..3b327e5a69970b 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 b68e2109c54ab2..72913e85e2edd5 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 106/134] 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 00b64550574f5a..e369ab5c0cd085 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 24b7155ce9dc01..3be7a6359bec35 100644 --- a/Telegram/SourceFiles/ui/webview_helpers.cpp +++ b/Telegram/SourceFiles/ui/webview_helpers.cpp @@ -10,24 +10,30 @@ For license and copyright information please follow this link: #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 3d8a5106697867..5180964795456d 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 107/134] 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 94fa2ae4d92b32..39b7ac32fe2183 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 eca07e75193ffc..373ba3546a6c51 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -378,6 +378,7 @@ class Application final : public QObject { 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 108/134] 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 4ec94b0ab71d59..5a72a69847c410 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 109/134] 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 08bd287ad8d740..aa2dcc1ecf40bd 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 7a05070e72f0a2..f6f701eda70f71 100644 --- a/Telegram/build/updates.py +++ b/Telegram/build/updates.py @@ -81,7 +81,7 @@ def finish(code, error = ''): 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 110/134] 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 aef0dd941db2f8..d9d3b9a6fb81ab 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 4258edbacc0709..e1e40cc628e407 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 111/134] 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 5810310faa0df7..e5c9ac6e31a19a 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 112/134] 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 915b12f4fd9352..b1ba3d166ea41f 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 6777464c17a202..28aa9982295b32 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 e369ab5c0cd085..067e90b485176d 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 97bfa6cef474b3..8db5d1aa533334 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 113/134] 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 3d4040dd7a9178..cffa28fb52d370 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 83eecf0a79f858..b9e1fb916098c3 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 40bb30f11b78bb..0f70bef0eaa497 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 111881fd487991..bb5f9b405e3eb3 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 f10750e38d861c..4829ec4b2ea96f 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 5ec213f7e928ba..f8929141f4dd0b 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 114/134] 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 2c95b169b30035..4b239923219d6a 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 115/134] Version 5.3: Update cmake_helpers submodule. --- cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake b/cmake index 17c758e2b9f09a..721383d0b901cc 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 116/134] 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 32437fb772fc1f..b49cef3bbe6104 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 117/134] 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 3b8f6f1b5e31ca..d478a7c6eae373 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 118/134] 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 a0d3d6066ded86..ee24c7eb42b2f5 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 119/134] 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 e4694f88d63e7e..a0578000f805ab 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 120/134] 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 18ae94b3710d69..a1391e3ba5728a 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 @@ class RecentAppsController final 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 121/134] 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 a1391e3ba5728a..96529c2411b33a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -429,11 +429,15 @@ class RecentAppsController final 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 @@ class PopularAppsController final 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 549851d72ea9b1..cf8b2178003b7b 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -189,6 +189,8 @@ class Suggestions final : public Ui::RpWidget { 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 122/134] 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 e5c9ac6e31a19a..3f549edb148180 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 123/134] 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 067e90b485176d..11df085275c512 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 c92f4c4b2b48f7..30e975d0238ecb 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -132,6 +132,7 @@ class Controller final { 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 124/134] 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 568d6663afce9f..82f184cb83436d 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 83e16284fd656f..67e7670036988f 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1831,6 +1831,30 @@ def runStages(): 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 721383d0b901cc..6a1ac8a4eebb96 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 721383d0b901cc5d5cbeec29386671d145f5a1e5 +Subproject commit 6a1ac8a4eebb968ff6ca538088006a3d06d47421 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b5f71d99f28e0e..8378b599f3f526 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 125/134] 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 64bbb68d8fd31b..3924a85f684788 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 11df085275c512..759ef0db3c400e 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -44,6 +44,8 @@ For license and copyright information please follow this link: #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 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(); + 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('.'); + }; + 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 30e975d0238ecb..9b5af4e6b6a15d 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -76,6 +76,7 @@ class Controller final { 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 3b327e5a69970b..8078828d6bd0ef 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -42,6 +42,7 @@ For license and copyright information please follow this link: #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 602abf41c0d865..1d4edf9f5fb8bb 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 126/134] 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 bf1b4943283468..60c9b00454e2e1 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 127/134] 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 d9a08df0c0b64e..0222032008a93f 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 4b239923219d6a..c27c69953db52c 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 128/134] 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 d48f32c0055f37..63cf5048a9426b 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 129/134] 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 cffa28fb52d370..261fb0911075d6 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 b9e1fb916098c3..eb9e2d265e4aae 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 0f70bef0eaa497..9e4b2aced766b7 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 bb5f9b405e3eb3..146e4b1f0073f2 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 4829ec4b2ea96f..928f3047d92f66 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 f8929141f4dd0b..cf6616399005c7 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 130/134] 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 0222032008a93f..8751e27d50d2f2 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 131/134] 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 261fb0911075d6..89f5f8761a6463 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 eb9e2d265e4aae..eb4271d8a40bd4 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 9e4b2aced766b7..d7e88ba045a551 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 146e4b1f0073f2..9fdc8cf1883f17 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 928f3047d92f66..37a420e7af5f52 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 cf6616399005c7..e43e0dbe29cbc7 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 6a1ac8a4eebb96..08de4f18f5e445 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 6a1ac8a4eebb968ff6ca538088006a3d06d47421 +Subproject commit 08de4f18f5e4459689957b3aa115e10d8cbef9d6 From 1301c42afaf779330c50f6fbf8f45167d4a70235 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 3 Aug 2024 03:04:26 +0400 Subject: [PATCH 132/134] Add ada to macOS packaged action --- .github/workflows/mac_packaged.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml index 3a37311e67bfdc..bc9f5dd4aed70a 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 openh264 openssl opus ninja pkg-config python qt yasm xz + brew install ada-url 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 From 2d1b1fbd449facb8ed4b65a2d6722c0cfe0ed516 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 3 Aug 2024 03:04:45 +0400 Subject: [PATCH 133/134] Ensure tdesktop builds after ada in snap --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 8378b599f3f526..11f78a28afa207 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -156,6 +156,7 @@ parts: craftctl default rm -rf "$CRAFT_PART_INSTALL/usr/share/icons" after: + - ada - ffmpeg - libjxl - qt From 9eb15f7b177589f33f8733d2807361276d160f7c Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 3 Aug 2024 03:06:01 +0400 Subject: [PATCH 134/134] Move ada snap part to maintain alphabetical order --- snap/snapcraft.yaml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 11f78a28afa207..dfba0b01e99871 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -210,6 +210,24 @@ parts: after: - telegram + 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 + ffmpeg: plugin: nil build-packages: @@ -489,21 +507,3 @@ 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