diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6528a858e..6f3bc7a00 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1614,6 +1614,7 @@ PRIVATE qrc/telegram/animations.qrc qrc/telegram/export.qrc qrc/telegram/iv.qrc + qrc/telegram/picker.qrc qrc/telegram/telegram.qrc qrc/telegram/sounds.qrc winrc/Telegram.rc diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5c461c1ef..122260d22 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3194,6 +3194,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_unread_bar_some" = "Unread messages"; "lng_maps_point" = "Location"; +"lng_maps_point_send" = "Send This Location"; "lng_live_location" = "Live Location"; "lng_live_location_now" = "updated just now"; "lng_live_location_minutes#one" = "updated {count} minute ago"; diff --git a/Telegram/Resources/picker_html/picker.css b/Telegram/Resources/picker_html/picker.css new file mode 100644 index 000000000..0c791008d --- /dev/null +++ b/Telegram/Resources/picker_html/picker.css @@ -0,0 +1,60 @@ +:root { + --font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif; + --font-serif: Iowan Old Style, Apple Garamond, Baskerville, Georgia, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; + --font-mono: Menlo, Cascadia Code, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; +} + +html { + width: 100%; + height: 100%; + padding: 0; + margin: 0; +} + +body { + font-family: var(--font-sans); + font-size: 17px; + line-height: 25px; + width: 100%; + height: 100%; + padding: 0; + margin: 0; + background-color: var(--td-window-bg); + color: var(--td-window-fg); +} + +html.custom_scroll ::-webkit-scrollbar { + border-radius: 5px !important; + border: 3px solid transparent !important; + background-color: var(--td-scroll-bg) !important; + background-clip: content-box !important; + width: 10px !important; +} +html.custom_scroll ::-webkit-scrollbar:hover { + background-color: var(--td-scroll-bg-over) !important; +} +html.custom_scroll ::-webkit-scrollbar-thumb { + border-radius: 5px !important; + border: 3px solid transparent !important; + background-color: var(--td-scroll-bar-bg) !important; + background-clip: content-box !important; +} +html.custom_scroll ::-webkit-scrollbar-thumb:hover { + background-color: var(--td-scroll-bar-bg-over) !important; +} + +#map { + position: relative; + width: 100%; + height: 100%; +} +#marker { + pointer-events: none; + display: flex; + z-index: 2; + position: absolute; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +} diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js new file mode 100644 index 000000000..6a35060f0 --- /dev/null +++ b/Telegram/Resources/picker_html/picker.js @@ -0,0 +1,80 @@ +var LocationPicker = { + startZoom: 14, + flySpeed: 2.4, + notify: function(message) { + if (window.external && window.external.invoke) { + window.external.invoke(JSON.stringify(message)); + } + }, + frameKeyDown: function (e) { + const keyW = (e.key === 'w') + || (e.code === 'KeyW') + || (e.keyCode === 87); + const keyQ = (e.key === 'q') + || (e.code === 'KeyQ') + || (e.keyCode === 81); + const keyM = (e.key === 'm') + || (e.code === 'KeyM') + || (e.keyCode === 77); + if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) { + e.preventDefault(); + LocationPicker.notify({ + event: 'keydown', + modifier: e.ctrlKey ? 'ctrl' : 'cmd', + key: keyW ? 'w' : keyQ ? 'q' : 'm', + }); + } else if (e.key === 'Escape' || e.keyCode === 27) { + e.preventDefault(); + LocationPicker.notify({ + event: 'keydown', + key: 'escape', + }); + } + }, + updateStyles: function (styles) { + if (LocationPicker.styles !== styles) { + LocationPicker.styles = styles; + document.getElementsByTagName('html')[0].style = styles; + } + }, + init: function (token, center, bounds) { + mapboxgl.accessToken = token; + + var options = { container: 'map' }; + if (center) { + center = [center[1], center[0]]; + options.center = center; + options.zoom = LocationPicker.startZoom; + } else if (bounds) { + options.bounds = bounds; + center = new mapboxgl.LngLatBounds(bounds).getCenter(); + } else { + center = [0, 0]; + } + LocationPicker.map = new mapboxgl.Map(options); + + const marker = new mapboxgl.Marker() + .setLngLat(center) + .addTo(LocationPicker.map); + const drop = document.getElementById('marker_drop'); + const element = marker.getElement(); + drop.innerHTML = element.innerHTML; + const offset = marker.getOffset(); + drop.style.transform = 'translate(' + offset.x + 'px, ' + offset.y + 'px)'; + marker.remove(); + }, + narrowTo: function (point) { + LocationPicker.map.flyTo({ + center: [point[1], point[0]], + zoom: LocationPicker.startZoom, + speed: LocationPicker.flySpeed, + }); + }, + send: function () { + LocationPicker.notify({ + event: 'send', + latitude: LocationPicker.map.getCenter().lat, + longitude: LocationPicker.map.getCenter().lng + }); + } +}; diff --git a/Telegram/Resources/qrc/telegram/picker.qrc b/Telegram/Resources/qrc/telegram/picker.qrc new file mode 100644 index 000000000..10a810aa9 --- /dev/null +++ b/Telegram/Resources/qrc/telegram/picker.qrc @@ -0,0 +1,6 @@ + + + ../../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 ade419248..bf4d472fe 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -62,6 +62,85 @@ void InnerFillMessagePostFlags( } } +void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { + const auto history = action.history; + const auto peer = history->peer; + const auto session = &history->session(); + const auto api = &session->api(); + + action.clearDraft = false; + action.generateLocal = false; + api->sendAction(action); + + const auto randomId = base::RandomValue(); + + 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 2fdbad843..c4bafc537 100644 --- a/Telegram/SourceFiles/api/api_sending.h +++ b/Telegram/SourceFiles/api/api_sending.h @@ -7,15 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -namespace Main { -class Session; -} // namespace Main - class History; class PhotoData; class DocumentData; struct FilePrepareResult; +namespace Data { +struct InputVenue; +} // namespace Data + +namespace Main { +class Session; +} // namespace Main + namespace Api { struct MessageToSend; @@ -33,6 +37,13 @@ void SendExistingPhoto( bool SendDice(MessageToSend &message); +// We can't create Data::LocationPoint() and use it +// for a local sending message, because we can't request +// map thumbnail in messages history without access hash. +void SendLocation(SendAction action, float64 lat, float64 lon); + +void SendVenue(SendAction action, Data::InputVenue venue); + void FillMessagePostFlags( const SendAction &action, not_null peer, diff --git a/Telegram/SourceFiles/core/current_geo_location.cpp b/Telegram/SourceFiles/core/current_geo_location.cpp new file mode 100644 index 000000000..295bde824 --- /dev/null +++ b/Telegram/SourceFiles/core/current_geo_location.cpp @@ -0,0 +1,50 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "core/current_geo_location.h" + +#include "base/platform/base_platform_info.h" +#include "data/raw/raw_countries_bounds.h" +#include "platform/platform_current_geo_location.h" + +namespace Core { + +GeoLocation ResolveCurrentCountryLocation() { + const auto iso2 = Platform::SystemCountry().toUpper(); + const auto &bounds = Raw::CountryBounds(); + const auto i = bounds.find(iso2); + if (i == end(bounds)) { + return { + .accuracy = GeoLocationAccuracy::Failed, + }; + } + return { + .point = { + (i->second.minLat + i->second.maxLat) / 2., + (i->second.minLon + i->second.maxLon) / 2., + }, + .bounds = { + i->second.minLat, + i->second.minLon, + i->second.maxLat - i->second.minLat, + i->second.maxLon - i->second.minLon, + }, + .accuracy = GeoLocationAccuracy::Country, + }; +} + +void ResolveCurrentGeoLocation(Fn callback) { + using namespace Platform; + return ResolveCurrentExactLocation([done = std::move(callback)]( + GeoLocation result) { + done(result.accuracy != GeoLocationAccuracy::Failed + ? result + : ResolveCurrentCountryLocation()); + }); +} + +} // namespace Core diff --git a/Telegram/SourceFiles/core/current_geo_location.h b/Telegram/SourceFiles/core/current_geo_location.h new file mode 100644 index 000000000..2715699ee --- /dev/null +++ b/Telegram/SourceFiles/core/current_geo_location.h @@ -0,0 +1,41 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Core { + +enum class GeoLocationAccuracy : uchar { + Exact, + Country, + Failed, +}; + +struct GeoLocation { + QPointF point; + QRectF bounds; + GeoLocationAccuracy accuracy = GeoLocationAccuracy::Failed; + + [[nodiscard]] bool exact() const { + return accuracy == GeoLocationAccuracy::Exact; + } + [[nodiscard]] bool country() const { + return accuracy == GeoLocationAccuracy::Country; + } + [[nodiscard]] bool failed() const { + return accuracy == GeoLocationAccuracy::Failed; + } + + explicit operator bool() const { + return !failed(); + } +}; + +[[nodiscard]] GeoLocation ResolveCurrentCountryLocation(); +void ResolveCurrentGeoLocation(Fn callback); + +} // namespace Core diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index a5e0090db..6fb00d550 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -45,6 +45,16 @@ private: }; +struct InputVenue { + float64 lat = 0.; + float64 lon = 0.; + QString title; + QString address; + QString provider; + QString id; + QString venueType; +}; + [[nodiscard]] GeoPointLocation ComputeLocation(const LocationPoint &point); } // namespace Data diff --git a/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp b/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp new file mode 100644 index 000000000..d4e383121 --- /dev/null +++ b/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp @@ -0,0 +1,193 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/raw/raw_countries_bounds.h" + +// Source: https://github.com/sandstrom/country-bounding-boxes + +namespace Raw { + +const base::flat_map &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 000000000..4f0944c81 --- /dev/null +++ b/Telegram/SourceFiles/data/raw/raw_countries_bounds.h @@ -0,0 +1,23 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include + +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 ff2637011..c6b013956 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -9,9 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_blocked_peers.h" #include "api/api_common.h" +#include "api/api_sending.h" #include "base/qthelp_url.h" #include "boxes/share_box.h" #include "core/click_handler_types.h" +#include "core/shortcuts.h" #include "data/data_bot_app.h" #include "data/data_changes.h" #include "data/data_user.h" @@ -28,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "iv/iv_instance.h" #include "ui/boxes/confirm_box.h" #include "ui/chat/attach/attach_bot_webview.h" +#include "ui/controls/location_picker.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/dropdown_menu.h" #include "ui/widgets/popup_menu.h" @@ -155,6 +158,10 @@ constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); return result; } +[[nodiscard]] QString ResolveMapsToken(not_null 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 000000000..5ac0c49a7 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.cpp @@ -0,0 +1,18 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/linux/current_geo_location_linux.h" + +#include "core/current_geo_location.h" + +namespace Platform { + +void ResolveCurrentExactLocation(Fn callback) { + callback({}); +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/current_geo_location_linux.h b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.h new file mode 100644 index 000000000..6bb819543 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/current_geo_location_linux.h @@ -0,0 +1,10 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "platform/platform_current_geo_location.h" diff --git a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.h b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.h new file mode 100644 index 000000000..6bb819543 --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.h @@ -0,0 +1,10 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "platform/platform_current_geo_location.h" diff --git a/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm new file mode 100644 index 000000000..03bf727ed --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm @@ -0,0 +1,18 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/mac/current_geo_location_mac.h" + +#include "core/current_geo_location.h" + +namespace Platform { + +void ResolveCurrentExactLocation(Fn callback) { + callback({}); +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/platform_current_geo_location.h b/Telegram/SourceFiles/platform/platform_current_geo_location.h new file mode 100644 index 000000000..245342fc3 --- /dev/null +++ b/Telegram/SourceFiles/platform/platform_current_geo_location.h @@ -0,0 +1,18 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Core { +struct GeoLocation; +} // namespace Core + +namespace Platform { + +void ResolveCurrentExactLocation(Fn callback); + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp b/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp new file mode 100644 index 000000000..37682c108 --- /dev/null +++ b/Telegram/SourceFiles/platform/win/current_geo_location_win.cpp @@ -0,0 +1,59 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/win/current_geo_location_win.h" + +#include "base/platform/win/base_windows_winrt.h" +#include "core/current_geo_location.h" + +#include +#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 000000000..6bb819543 --- /dev/null +++ b/Telegram/SourceFiles/platform/win/current_geo_location_win.h @@ -0,0 +1,10 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "platform/platform_current_geo_location.h" diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp new file mode 100644 index 000000000..21c5b9861 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -0,0 +1,350 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/controls/location_picker.h" + +#include "base/platform/base_platform_info.h" +#include "core/current_geo_location.h" +#include "lang/lang_keys.h" +#include "ui/widgets/rp_window.h" +#include "ui/widgets/buttons.h" +#include "webview/webview_data_stream_memory.h" +#include "webview/webview_embed.h" +#include "webview/webview_interface.h" +#include "styles/style_dialogs.h" +#include "styles/style_window.h" + +#include +#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 000000000..220c0c426 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -0,0 +1,67 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/invoke_queued.h" +#include "base/weak_ptr.h" +#include "webview/webview_common.h" + +namespace Webview { +class Window; +} // namespace Webview + +namespace Ui { + +class RpWindow; +class RpWidget; + +struct LocationInfo { + float64 lat = 0.; + float64 lon = 0.; +}; + +class LocationPicker final : public base::has_weak_ptr { +public: + struct Descriptor { + RpWidget *parent = nullptr; + Fn 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 3555c9218..b11016acb 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -70,6 +70,8 @@ PRIVATE chat_helpers/stickers_emoji_image_loader.cpp chat_helpers/stickers_emoji_image_loader.h + core/current_geo_location.cpp + core/current_geo_location.h core/file_location.cpp core/file_location.h core/mime_type.cpp @@ -78,6 +80,8 @@ PRIVATE countries/countries_instance.cpp countries/countries_instance.h + data/raw/raw_countries_bounds.cpp + data/raw/raw_countries_bounds.h data/data_birthday.cpp data/data_birthday.h data/data_channel_earn.h @@ -194,9 +198,16 @@ PRIVATE payments/ui/payments_panel_data.h payments/ui/payments_panel_delegate.h + platform/linux/current_geo_location_linux.cpp + platform/linux/current_geo_location_linux.h platform/mac/file_bookmark_mac.h platform/mac/file_bookmark_mac.mm + platform/mac/current_geo_location_mac.h + platform/mac/current_geo_location_mac.mm + platform/win/current_geo_location_win.cpp + platform/win/current_geo_location_win.h platform/platform_file_bookmark.h + platform/platform_current_geo_location.h settings/settings_common.cpp settings/settings_common.h @@ -344,6 +355,8 @@ PRIVATE ui/controls/invite_link_buttons.h ui/controls/invite_link_label.cpp ui/controls/invite_link_label.h + ui/controls/location_picker.cpp + ui/controls/location_picker.h ui/controls/peer_list_dummy.cpp ui/controls/peer_list_dummy.h ui/controls/send_as_button.cpp @@ -439,6 +452,12 @@ PRIVATE ui/ui_pch.h ) +nice_target_sources(td_ui ${res_loc} +PRIVATE + picker_html/picker.css + picker_html/picker.js +) + if (DESKTOP_APP_SPECIAL_TARGET) remove_target_sources(td_ui ${src_loc} ui/controls/window_outdated_bar_dummy.cpp