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