mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-03 21:54:05 +02:00
Add venues list and chosen place name.
This commit is contained in:
parent
8e6d7bb190
commit
310837c9e1
27 changed files with 1066 additions and 141 deletions
|
@ -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
|
||||
|
|
BIN
Telegram/Resources/icons/chat/filled_location.png
Normal file
BIN
Telegram/Resources/icons/chat/filled_location.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 536 B |
BIN
Telegram/Resources/icons/chat/filled_location@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/filled_location@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 987 B |
BIN
Telegram/Resources/icons/chat/filled_location@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/filled_location@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -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";
|
||||
|
|
|
@ -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]];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_document_media.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "menu/menu_send.h" // SendMenu::FillSendMenu
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "ui/controls/tabbed_search.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
|
@ -48,7 +49,6 @@ namespace ChatHelpers {
|
|||
namespace {
|
||||
|
||||
constexpr auto kSearchRequestDelay = 400;
|
||||
constexpr auto kSearchBotUsername = "gif"_cs;
|
||||
constexpr auto kMinRepaintDelay = crl::time(33);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
|
||||
|
@ -864,13 +864,11 @@ void GifsListWidget::searchForGifs(const QString &query) {
|
|||
}
|
||||
|
||||
if (!_searchBot && !_searchBotRequestId) {
|
||||
auto username = kSearchBotUsername.utf16();
|
||||
const auto username = session().serverConfig().gifSearchUsername;
|
||||
_searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||
|
||||
auto &data = result.c_contacts_resolvedPeer();
|
||||
auto &data = result.data();
|
||||
session().data().processUsers(data.vusers());
|
||||
session().data().processChats(data.vchats());
|
||||
const auto peer = session().data().peerLoaded(
|
||||
|
|
|
@ -8,10 +8,122 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/current_geo_location.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/timer.h"
|
||||
#include "data/raw/raw_countries_bounds.h"
|
||||
#include "platform/platform_current_geo_location.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDestroyManagerTimeout = 20 * crl::time(1000);
|
||||
|
||||
void ResolveLocationAddressGeneric(
|
||||
const GeoLocation &location,
|
||||
const QString &token,
|
||||
Fn<void(GeoAddress)> callback) {
|
||||
const auto partialUrl = u"https://api.mapbox.com/search/geocode/v6"
|
||||
"/reverse?longitude=%1&latitude=%2&access_token=%3"_q
|
||||
.arg(location.point.y())
|
||||
.arg(location.point.x());
|
||||
static auto Cache = base::flat_map<QString, GeoAddress>();
|
||||
const auto i = Cache.find(partialUrl);
|
||||
if (i != end(Cache)) {
|
||||
callback(i->second);
|
||||
return;
|
||||
}
|
||||
const auto finishWith = [=](GeoAddress result) {
|
||||
Cache[partialUrl] = result;
|
||||
callback(result);
|
||||
};
|
||||
|
||||
struct State final : QObject {
|
||||
explicit State(QObject *parent)
|
||||
: QObject(parent)
|
||||
, manager(this)
|
||||
, destroyer([=] { if (sent.empty()) delete this; }) {
|
||||
}
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
std::vector<QPointer<QNetworkReply>> sent;
|
||||
base::Timer destroyer;
|
||||
};
|
||||
|
||||
static auto state = QPointer<State>();
|
||||
if (!state) {
|
||||
state = Ui::CreateChild<State>(qApp);
|
||||
}
|
||||
const auto destroyReplyDelayed = [](QNetworkReply *reply) {
|
||||
InvokeQueued(reply, [=] {
|
||||
for (auto i = begin(state->sent); i != end(state->sent);) {
|
||||
if (!*i || *i == reply) {
|
||||
i = state->sent.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
delete reply;
|
||||
if (state->sent.empty()) {
|
||||
state->destroyer.callOnce(kDestroyManagerTimeout);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
auto request = QNetworkRequest(partialUrl.arg(token));
|
||||
request.setRawHeader("Referer", "http://desktop-app-resource/");
|
||||
|
||||
const auto reply = state->manager.get(request);
|
||||
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
||||
destroyReplyDelayed(reply);
|
||||
|
||||
const auto json = QJsonDocument::fromJson(reply->readAll());
|
||||
if (!json.isObject()) {
|
||||
finishWith({});
|
||||
return;
|
||||
}
|
||||
const auto features = json["features"].toArray();
|
||||
if (features.isEmpty()) {
|
||||
finishWith({});
|
||||
return;
|
||||
}
|
||||
const auto feature = features.at(0).toObject();
|
||||
const auto properties = feature["properties"].toObject();
|
||||
const auto context = properties["context"].toObject();
|
||||
auto names = QStringList();
|
||||
auto add = [&](std::vector<QString> keys) {
|
||||
for (const auto &key : keys) {
|
||||
const auto value = context[key];
|
||||
if (value.isObject()) {
|
||||
const auto name = value.toObject()["name"].toString();
|
||||
if (!name.isEmpty()) {
|
||||
names.push_back(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
add({ u"address"_q, u"street"_q, u"neighborhood"_q });
|
||||
add({ u"place"_q, u"region"_q });
|
||||
add({ u"country"_q });
|
||||
finishWith({ .name = names.join(", ") });
|
||||
});
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred, [=] {
|
||||
destroyReplyDelayed(reply);
|
||||
|
||||
finishWith({});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GeoLocation ResolveCurrentCountryLocation() {
|
||||
const auto iso2 = Platform::SystemCountry().toUpper();
|
||||
|
@ -47,4 +159,18 @@ void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback) {
|
|||
});
|
||||
}
|
||||
|
||||
void ResolveLocationAddress(
|
||||
const GeoLocation &location,
|
||||
const QString &token,
|
||||
Fn<void(GeoAddress)> callback) {
|
||||
auto done = [=, done = std::move(callback)](GeoAddress result) mutable {
|
||||
if (!result && !token.isEmpty()) {
|
||||
ResolveLocationAddressGeneric(location, token, std::move(done));
|
||||
} else {
|
||||
done(result);
|
||||
}
|
||||
};
|
||||
Platform::ResolveLocationAddress(location, std::move(done));
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -33,9 +33,29 @@ struct GeoLocation {
|
|||
explicit operator bool() const {
|
||||
return !failed();
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
const GeoLocation&,
|
||||
const GeoLocation&) = default;
|
||||
};
|
||||
|
||||
struct GeoAddress {
|
||||
QString name;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return name.isEmpty();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] GeoLocation ResolveCurrentCountryLocation();
|
||||
void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback);
|
||||
|
||||
void ResolveLocationAddress(
|
||||
const GeoLocation &location,
|
||||
const QString &token,
|
||||
Fn<void(GeoAddress)> callback);
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3328,6 +3328,22 @@ void Session::documentApplyFields(
|
|||
}
|
||||
}
|
||||
|
||||
not_null<DocumentData*> Session::venueIconDocument(const QString &icon) {
|
||||
const auto i = _venueIcons.find(icon);
|
||||
if (i != end(_venueIcons)) {
|
||||
return i->second;
|
||||
}
|
||||
const auto result = documentFromWeb(MTP_webDocumentNoProxy(
|
||||
MTP_string(u"https://ss3.4sqi.net/img/categories_v2/"_q
|
||||
+ icon
|
||||
+ u"_64.png"_q),
|
||||
MTP_int(0),
|
||||
MTP_string("image/png"),
|
||||
MTP_vector<MTPDocumentAttribute>()), {}, {});
|
||||
_venueIcons.emplace(icon, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<WebPageData*> Session::webpage(WebPageId id) {
|
||||
auto i = _webpages.find(id);
|
||||
if (i == _webpages.cend()) {
|
||||
|
|
|
@ -559,6 +559,8 @@ public:
|
|||
const MTPWebDocument &data,
|
||||
const ImageLocation &thumbnailLocation,
|
||||
const ImageLocation &videoThumbnailLocation);
|
||||
[[nodiscard]] not_null<DocumentData*> venueIconDocument(
|
||||
const QString &icon);
|
||||
|
||||
[[nodiscard]] not_null<WebPageData*> webpage(WebPageId id);
|
||||
not_null<WebPageData*> processWebpage(const MTPWebPage &data);
|
||||
|
@ -1002,6 +1004,7 @@ private:
|
|||
FullStoryId,
|
||||
base::flat_set<not_null<HistoryItem*>>> _storyItems;
|
||||
base::flat_map<uint64, not_null<HistoryItem*>> _highlightings;
|
||||
base::flat_map<QString, not_null<DocumentData*>> _venueIcons;
|
||||
|
||||
base::flat_set<not_null<WebPageData*>> _webpagesUpdated;
|
||||
base::flat_set<not_null<GameData*>> _gamesUpdated;
|
||||
|
|
|
@ -162,6 +162,10 @@ constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000);
|
|||
return u""_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ResolveGeocodingToken(not_null<Main::Session*> session) {
|
||||
return u""_q;
|
||||
}
|
||||
|
||||
void ShowChooseBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PeerTypes types,
|
||||
|
@ -1808,6 +1812,7 @@ void ChooseAndSendLocation(
|
|||
};
|
||||
Ui::LocationPicker::Show({
|
||||
.parent = controller->widget(),
|
||||
.session = &controller->session(),
|
||||
.callback = crl::guard(controller, callback),
|
||||
.quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); },
|
||||
.storageId = controller->session().local().resolveStorageIdBots(),
|
||||
|
@ -1872,7 +1877,9 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|
|||
const auto session = &controller->session();
|
||||
const auto locationType = ChatRestriction::SendOther;
|
||||
if (Data::CanSendAnyOf(peer, locationType)
|
||||
&& Ui::LocationPicker::Available(ResolveMapsToken(session))) {
|
||||
&& Ui::LocationPicker::Available(
|
||||
ResolveMapsToken(session),
|
||||
ResolveGeocodingToken(session))) {
|
||||
raw->addAction(tr::lng_maps_point(tr::now), [=] {
|
||||
ChooseAndSendLocation(controller, actionFactory());
|
||||
}, &st::menuIconAddress);
|
||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/webview_helpers.h"
|
||||
#include "webview/webview_data_stream_memory.h"
|
||||
#include "webview/webview_embed.h"
|
||||
#include "webview/webview_interface.h"
|
||||
|
@ -39,8 +40,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QtGui/QWindow>
|
||||
#include <charconv>
|
||||
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
namespace Iv {
|
||||
namespace {
|
||||
|
||||
|
@ -79,67 +78,7 @@ namespace {
|
|||
static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{
|
||||
{ "iv-join-channel", tr::lng_iv_join_channel },
|
||||
};
|
||||
static const auto serialize = [](const style::color *color) {
|
||||
const auto qt = (*color)->c;
|
||||
if (qt.alpha() == 255) {
|
||||
return '#'
|
||||
+ QByteArray::number(qt.red(), 16).right(2)
|
||||
+ QByteArray::number(qt.green(), 16).right(2)
|
||||
+ QByteArray::number(qt.blue(), 16).right(2);
|
||||
}
|
||||
return "rgba("
|
||||
+ QByteArray::number(qt.red()) + ","
|
||||
+ QByteArray::number(qt.green()) + ","
|
||||
+ QByteArray::number(qt.blue()) + ","
|
||||
+ QByteArray::number(qt.alpha() / 255.) + ")";
|
||||
};
|
||||
static const auto escape = [](tr::phrase<> phrase) {
|
||||
const auto text = phrase(tr::now);
|
||||
|
||||
auto result = QByteArray();
|
||||
for (auto i = 0; i != text.size(); ++i) {
|
||||
uint ucs4 = text[i].unicode();
|
||||
if (QChar::isHighSurrogate(ucs4) && i + 1 != text.size()) {
|
||||
ushort low = text[i + 1].unicode();
|
||||
if (QChar::isLowSurrogate(low)) {
|
||||
ucs4 = QChar::surrogateToUcs4(ucs4, low);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (ucs4 == '\'' || ucs4 == '\"' || ucs4 == '\\') {
|
||||
result.append('\\').append(char(ucs4));
|
||||
} else if (ucs4 < 32 || ucs4 > 127) {
|
||||
result.append('\\' + QByteArray::number(ucs4, 16) + ' ');
|
||||
} else {
|
||||
result.append(char(ucs4));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto result = QByteArray();
|
||||
for (const auto &[name, phrase] : phrases) {
|
||||
result += "--td-lng-" + name + ":'" + escape(phrase) + "'; ";
|
||||
}
|
||||
for (const auto &[name, color] : map) {
|
||||
result += "--td-" + name + ':' + serialize(color) + ';';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value) {
|
||||
return value
|
||||
.replace('&', "&")
|
||||
.replace('"', """)
|
||||
.replace('\'', "'")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">");
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value) {
|
||||
return value
|
||||
.replace('\\', "\\\\")
|
||||
.replace('"', "\\\"")
|
||||
.replace('\'', "\\\'");
|
||||
return Ui::ComputeStyles(map, phrases);
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray WrapPage(const Prepared &page) {
|
||||
|
@ -159,7 +98,7 @@ namespace {
|
|||
<html)"_q
|
||||
+ classAttribute
|
||||
+ R"( style=")"
|
||||
+ EscapeForAttribute(ComputeStyles())
|
||||
+ Ui::EscapeForAttribute(ComputeStyles())
|
||||
+ R"(">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -194,7 +133,7 @@ Controller::Controller(
|
|||
Fn<ShareBoxResult(ShareBoxDescriptor)> showShareBox)
|
||||
: _delegate(delegate)
|
||||
, _updateStyles([=] {
|
||||
const auto str = EscapeForScriptString(ComputeStyles());
|
||||
const auto str = Ui::EscapeForScriptString(ComputeStyles());
|
||||
if (_webview) {
|
||||
_webview->eval("IV.updateStyles('" + str + "');");
|
||||
}
|
||||
|
@ -612,7 +551,7 @@ QByteArray Controller::navigateScript(int index, const QString &hash) {
|
|||
return "IV.navigateTo("
|
||||
+ QByteArray::number(index)
|
||||
+ ", '"
|
||||
+ EscapeForScriptString(qthelp::url_decode(hash).toUtf8())
|
||||
+ Ui::EscapeForScriptString(qthelp::url_decode(hash).toUtf8())
|
||||
+ "');";
|
||||
}
|
||||
|
||||
|
@ -679,7 +618,7 @@ bool Controller::active() const {
|
|||
void Controller::showJoinedTooltip() {
|
||||
if (_webview && _ready) {
|
||||
_webview->eval("IV.showTooltip('"
|
||||
+ EscapeForScriptString(
|
||||
+ Ui::EscapeForScriptString(
|
||||
tr::lng_action_you_joined(tr::now).toUtf8())
|
||||
+ "');");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,18 +16,30 @@ namespace {
|
|||
|
||||
constexpr auto kVersion = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
QString ConfigDefaultReactionEmoji() {
|
||||
[[nodiscard]] QString ConfigDefaultReactionEmoji() {
|
||||
static const auto result = QString::fromUtf8("\xf0\x9f\x91\x8d");
|
||||
return result;
|
||||
}
|
||||
|
||||
Config::Config(Environment environment) : _dcOptions(environment) {
|
||||
_fields.webFileDcId = _dcOptions.isTestMode() ? 2 : 4;
|
||||
_fields.txtDomainString = _dcOptions.isTestMode()
|
||||
? u"tapv3.stel.com"_q
|
||||
: u"apv3.stel.com"_q;
|
||||
} // namespace
|
||||
|
||||
ConfigFields::ConfigFields(Environment environment)
|
||||
: webFileDcId(environment == Environment::Test ? 2 : 4)
|
||||
, txtDomainString(environment == Environment::Test
|
||||
? u"tapv3.stel.com"_q
|
||||
: u"apv3.stel.com"_q)
|
||||
, reactionDefaultEmoji(ConfigDefaultReactionEmoji())
|
||||
, gifSearchUsername(environment == Environment::Test
|
||||
? u"izgifbot"_q
|
||||
: u"gif"_q)
|
||||
, venueSearchUsername(environment == Environment::Test
|
||||
? u"foursquarebot"_q
|
||||
: u"foursquare"_q) {
|
||||
}
|
||||
|
||||
Config::Config(Environment environment)
|
||||
: _dcOptions(environment)
|
||||
, _fields(environment) {
|
||||
}
|
||||
|
||||
Config::Config(const Config &other)
|
||||
|
@ -46,7 +58,9 @@ QByteArray Config::serialize() const {
|
|||
+ 3 * sizeof(qint32)
|
||||
+ Serialize::stringSize(_fields.reactionDefaultEmoji)
|
||||
+ sizeof(quint64)
|
||||
+ sizeof(qint32);
|
||||
+ sizeof(qint32)
|
||||
+ Serialize::stringSize(_fields.gifSearchUsername)
|
||||
+ Serialize::stringSize(_fields.venueSearchUsername);
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
|
@ -91,7 +105,9 @@ QByteArray Config::serialize() const {
|
|||
<< qint32(_fields.captionLengthMax)
|
||||
<< _fields.reactionDefaultEmoji
|
||||
<< quint64(_fields.reactionDefaultCustom)
|
||||
<< qint32(_fields.ratingDecay);
|
||||
<< qint32(_fields.ratingDecay)
|
||||
<< _fields.gifSearchUsername
|
||||
<< _fields.venueSearchUsername;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -190,6 +206,10 @@ std::unique_ptr<Config> Config::FromSerialized(const QByteArray &serialized) {
|
|||
if (!stream.atEnd()) {
|
||||
read(raw->_fields.ratingDecay);
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
read(raw->_fields.gifSearchUsername);
|
||||
read(raw->_fields.venueSearchUsername);
|
||||
}
|
||||
|
||||
if (stream.status() != QDataStream::Ok
|
||||
|| !raw->_dcOptions.constructFromSerialized(dcOptionsSerialized)) {
|
||||
|
@ -256,8 +276,12 @@ void Config::apply(const MTPDconfig &data) {
|
|||
_fields.autologinToken = qs(data.vautologin_token().value_or_empty());
|
||||
_fields.ratingDecay = data.vrating_e_decay().v;
|
||||
if (_fields.ratingDecay <= 0) {
|
||||
_fields.ratingDecay = ConfigFields().ratingDecay;
|
||||
_fields.ratingDecay = ConfigFields(
|
||||
_dcOptions.environment()
|
||||
).ratingDecay;
|
||||
}
|
||||
_fields.gifSearchUsername = qs(data.vgif_search_username().value_or_empty());
|
||||
_fields.venueSearchUsername = qs(data.vvenue_search_username().value_or_empty());
|
||||
|
||||
if (data.vdc_options().v.empty()) {
|
||||
LOG(("MTP Error: config with empty dc_options received!"));
|
||||
|
|
|
@ -11,9 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace MTP {
|
||||
|
||||
[[nodiscard]] QString ConfigDefaultReactionEmoji();
|
||||
|
||||
struct ConfigFields {
|
||||
explicit ConfigFields(Environment environment);
|
||||
|
||||
int chatSizeMax = 200;
|
||||
int megagroupSizeMax = 10000;
|
||||
int forwardedCountMax = 100;
|
||||
|
@ -40,9 +40,12 @@ struct ConfigFields {
|
|||
bool blockedMode = false;
|
||||
int captionLengthMax = 1024;
|
||||
int ratingDecay = 2419200;
|
||||
QString reactionDefaultEmoji = ConfigDefaultReactionEmoji();
|
||||
uint64 reactionDefaultCustom;
|
||||
QString reactionDefaultEmoji;
|
||||
uint64 reactionDefaultCustom = 0;
|
||||
QString autologinToken;
|
||||
|
||||
QString gifSearchUsername;
|
||||
QString venueSearchUsername;
|
||||
};
|
||||
|
||||
class Config final {
|
||||
|
|
|
@ -14,5 +14,10 @@ namespace Platform {
|
|||
void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) {
|
||||
callback({});
|
||||
}
|
||||
void ResolveLocationAddress(
|
||||
const Core::GeoLocation &location,
|
||||
Fn<void(Core::GeoAddress)> callback) {
|
||||
callback({});
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
@ -116,4 +116,10 @@ void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) {
|
|||
[[LocationDelegate alloc] initWithCallback:std::move(callback)];
|
||||
}
|
||||
|
||||
void ResolveLocationAddress(
|
||||
const Core::GeoLocation &location,
|
||||
Fn<void(Core::GeoAddress)> callback) {
|
||||
callback({});
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
@ -9,10 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace Core {
|
||||
struct GeoLocation;
|
||||
struct GeoAddress;
|
||||
} // namespace Core
|
||||
|
||||
namespace Platform {
|
||||
|
||||
void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback);
|
||||
void ResolveLocationAddress(
|
||||
const Core::GeoLocation &location,
|
||||
Fn<void(Core::GeoAddress)> callback);
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <winrt/Windows.Devices.Geolocation.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
|
||||
#include <winrt/Windows.Services.Maps.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
namespace Platform {
|
||||
|
||||
void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) {
|
||||
|
@ -56,4 +59,10 @@ void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) {
|
|||
}
|
||||
}
|
||||
|
||||
void ResolveLocationAddress(
|
||||
const Core::GeoLocation &location,
|
||||
Fn<void(Core::GeoAddress)> callback) {
|
||||
callback({});
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
@ -7,16 +7,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "ui/controls/location_picker.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "core/current_geo_location.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_location.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/session/session_show.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/separate_panel.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/webview_helpers.h"
|
||||
#include "webview/webview_data_stream_memory.h"
|
||||
#include "webview/webview_embed.h"
|
||||
#include "webview/webview_interface.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_window.h"
|
||||
|
@ -39,6 +54,246 @@ const auto kProtocolOverride = "";
|
|||
|
||||
Core::GeoLocation LastExactLocation;
|
||||
QString MapsProviderToken;
|
||||
QString GeocodingProviderToken;
|
||||
|
||||
using VenueData = Data::InputVenue;
|
||||
|
||||
class VenueRowDelegate {
|
||||
public:
|
||||
virtual void rowPaintIcon(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
const QString &type) = 0;
|
||||
};
|
||||
|
||||
class VenueRow final : public PeerListRow {
|
||||
public:
|
||||
VenueRow(not_null<VenueRowDelegate*> delegate, const VenueData &data);
|
||||
|
||||
void update(const VenueData &data);
|
||||
|
||||
[[nodiscard]] VenueData data() const;
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
|
||||
private:
|
||||
const not_null<VenueRowDelegate*> _delegate;
|
||||
VenueData _data;
|
||||
|
||||
};
|
||||
|
||||
VenueRow::VenueRow(
|
||||
not_null<VenueRowDelegate*> delegate,
|
||||
const VenueData &data)
|
||||
: PeerListRow(UniqueRowIdFromString(data.id))
|
||||
, _delegate(delegate)
|
||||
, _data(data) {
|
||||
setCustomStatus(data.address);
|
||||
}
|
||||
|
||||
void VenueRow::update(const VenueData &data) {
|
||||
_data = data;
|
||||
setCustomStatus(data.address);
|
||||
}
|
||||
|
||||
VenueData VenueRow::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
QString VenueRow::generateName() {
|
||||
return _data.title;
|
||||
}
|
||||
|
||||
QString VenueRow::generateShortName() {
|
||||
return generateName();
|
||||
}
|
||||
|
||||
PaintRoundImageCallback VenueRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) {
|
||||
_delegate->rowPaintIcon(p, x, y, size, _data.venueType);
|
||||
};
|
||||
}
|
||||
|
||||
class LinksController final
|
||||
: public PeerListController
|
||||
, public VenueRowDelegate
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
LinksController(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<std::vector<VenueData>> content);
|
||||
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
void rowPaintIcon(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
const QString &type) override;
|
||||
|
||||
private:
|
||||
struct VenueIcon {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
uint32 paletteVersion : 31 = 0;
|
||||
uint32 iconLoaded : 1 = 0;
|
||||
QImage image;
|
||||
QImage icon;
|
||||
};
|
||||
|
||||
void appendRow(const VenueData &data);
|
||||
|
||||
void rebuild(const std::vector<VenueData> &rows);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::variable<std::vector<VenueData>> _rows;
|
||||
|
||||
base::flat_map<QString, VenueIcon> _icons;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] QString NormalizeVenuesQuery(QString query) {
|
||||
return query.trimmed().toLower();
|
||||
}
|
||||
|
||||
LinksController::LinksController(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<std::vector<VenueData>> content)
|
||||
: _session(session)
|
||||
, _rows(std::move(content)) {
|
||||
}
|
||||
|
||||
void LinksController::prepare() {
|
||||
_rows.value(
|
||||
) | rpl::start_with_next([=](const std::vector<VenueData> &rows) {
|
||||
rebuild(rows);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void LinksController::rebuild(const std::vector<VenueData> &rows) {
|
||||
auto i = 0;
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
while (i < rows.size()) {
|
||||
if (i < count) {
|
||||
const auto row = delegate()->peerListRowAt(i);
|
||||
static_cast<VenueRow*>(row.get())->update(rows[i]);
|
||||
} else {
|
||||
appendRow(rows[i]);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
while (i < count) {
|
||||
delegate()->peerListRemoveRow(delegate()->peerListRowAt(i));
|
||||
--count;
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void LinksController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto venue = static_cast<VenueRow*>(row.get())->data();
|
||||
venue;
|
||||
}
|
||||
|
||||
void LinksController::rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListShowRowMenu(row, true);
|
||||
}
|
||||
|
||||
Main::Session &LinksController::session() const {
|
||||
return *_session;
|
||||
}
|
||||
|
||||
void LinksController::appendRow(const VenueData &data) {
|
||||
delegate()->peerListAppendRow(std::make_unique<VenueRow>(this, data));
|
||||
}
|
||||
|
||||
void LinksController::rowPaintIcon(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
const QString &icon) {
|
||||
auto i = _icons.find(icon);
|
||||
if (i == end(_icons)) {
|
||||
i = _icons.emplace(icon, VenueIcon{
|
||||
.document = _session->data().venueIconDocument(icon),
|
||||
}).first;
|
||||
i->second.media = i->second.document->createMediaView();
|
||||
i->second.document->forceToCache(true);
|
||||
i->second.document->save({}, QString(), LoadFromCloudOrLocal, true);
|
||||
}
|
||||
auto &data = i->second;
|
||||
const auto version = uint32(style::PaletteVersion());
|
||||
const auto loaded = (!data.media || data.media->loaded()) ? 1 : 0;
|
||||
const auto prepare = data.image.isNull()
|
||||
|| (data.iconLoaded != loaded)
|
||||
|| (data.paletteVersion != version);
|
||||
if (prepare) {
|
||||
const auto skip = st::pickLocationIconSkip;
|
||||
const auto inner = size - skip * 2;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
|
||||
if (loaded && data.media) {
|
||||
const auto bytes = base::take(data.media)->bytes();
|
||||
data.icon = Images::Read({ .content = bytes }).image;
|
||||
if (!data.icon.isNull()) {
|
||||
data.icon = data.icon.scaled(
|
||||
QSize(inner, inner) * ratio,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
}
|
||||
|
||||
const auto full = QSize(size, size) * ratio;
|
||||
auto image = (data.image.size() == full)
|
||||
? base::take(data.image)
|
||||
: QImage(full, QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(Qt::transparent);
|
||||
image.setDevicePixelRatio(ratio);
|
||||
|
||||
const auto bg = EmptyUserpic::UserpicColor(
|
||||
EmptyUserpic::ColorIndex(UniqueRowIdFromString(icon)));
|
||||
auto p = QPainter(&image);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
{
|
||||
auto gradient = QLinearGradient(0, 0, 0, size);
|
||||
gradient.setStops({
|
||||
{ 0., bg.color1->c },
|
||||
{ 1., bg.color2->c }
|
||||
});
|
||||
p.setBrush(gradient);
|
||||
}
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawEllipse(QRect(0, 0, size, size));
|
||||
if (!data.icon.isNull()) {
|
||||
p.drawImage(
|
||||
QRect(skip, skip, inner, inner),
|
||||
style::colorizeImage(data.icon, st::historyPeerUserpicFg));
|
||||
}
|
||||
p.end();
|
||||
|
||||
data.paletteVersion = version;
|
||||
data.iconLoaded = loaded;
|
||||
data.image = std::move(image);
|
||||
}
|
||||
p.drawImage(x, y, data.image);
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray DefaultCenter() {
|
||||
if (!LastExactLocation) {
|
||||
|
@ -68,23 +323,16 @@ QString MapsProviderToken;
|
|||
}
|
||||
|
||||
[[nodiscard]] QByteArray ComputeStyles() {
|
||||
return "";
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value) {
|
||||
return value
|
||||
.replace('&', "&")
|
||||
.replace('"', """)
|
||||
.replace('\'', "'")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">");
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value) {
|
||||
return value
|
||||
.replace('\\', "\\\\")
|
||||
.replace('"', "\\\"")
|
||||
.replace('\'', "\\\'");
|
||||
static const auto map = base::flat_map<QByteArray, const style::color*>{
|
||||
{ "window-bg", &st::windowBg },
|
||||
{ "window-bg-over", &st::windowBgOver },
|
||||
{ "window-bg-ripple", &st::windowBgRipple },
|
||||
{ "window-active-text-fg", &st::windowActiveTextFg },
|
||||
};
|
||||
static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{
|
||||
{ "maps-places-in-area", tr::lng_maps_places_in_area },
|
||||
};
|
||||
return Ui::ComputeStyles(map, phrases, Window::Theme::IsNightMode());
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray ReadResource(const QString &name) {
|
||||
|
@ -115,6 +363,129 @@ QString MapsProviderToken;
|
|||
)"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<AbstractButton> MakeSendLocationButton(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> address) {
|
||||
auto result = object_ptr<FlatButton>(
|
||||
parent,
|
||||
QString(),
|
||||
st::pickLocationButton);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto st = &st::pickLocationVenue;
|
||||
const auto icon = CreateChild<RpWidget>(raw);
|
||||
icon->setGeometry(
|
||||
st->photoPosition.x(),
|
||||
st->photoPosition.y(),
|
||||
st->photoSize,
|
||||
st->photoSize);
|
||||
icon->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBgActive);
|
||||
p.drawEllipse(icon->rect());
|
||||
st::pickLocationSendIcon.paintInCenter(p, icon->rect());
|
||||
}, icon->lifetime());
|
||||
icon->show();
|
||||
|
||||
const auto hadAddress = std::make_shared<bool>(false);
|
||||
auto statusText = std::move(
|
||||
address
|
||||
) | rpl::map([=](const QString &text) {
|
||||
if (!text.isEmpty()) {
|
||||
*hadAddress = true;
|
||||
return text;
|
||||
}
|
||||
return *hadAddress ? tr::lng_contacts_loading(tr::now) : QString();
|
||||
});
|
||||
const auto name = CreateChild<FlatLabel>(
|
||||
raw,
|
||||
tr::lng_maps_point_send(tr::now),
|
||||
st::pickLocationButtonText);
|
||||
name->show();
|
||||
const auto status = CreateChild<FlatLabel>(
|
||||
raw,
|
||||
rpl::duplicate(statusText),
|
||||
st::pickLocationButtonStatus);
|
||||
status->showOn(std::move(
|
||||
statusText
|
||||
) | rpl::map([](const QString &text) {
|
||||
return !text.isEmpty();
|
||||
}) | rpl::distinct_until_changed());
|
||||
rpl::combine(
|
||||
result->widthValue(),
|
||||
status->shownValue()
|
||||
) | rpl::start_with_next([=](int width, bool statusShown) {
|
||||
const auto available = width
|
||||
- st->namePosition.x()
|
||||
- st->button.padding.right();
|
||||
const auto namePosition = st->namePosition;
|
||||
const auto statusPosition = st->statusPosition;
|
||||
name->resizeToWidth(available);
|
||||
const auto nameTop = statusShown
|
||||
? namePosition.y()
|
||||
: (st->height - name->height()) / 2;
|
||||
name->moveToLeft(namePosition.x(), nameTop, width);
|
||||
status->resizeToWidth(available);
|
||||
status->moveToLeft(statusPosition.x(), statusPosition.y(), width);
|
||||
}, name->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SetupVenues(
|
||||
not_null<VerticalLayout*> container,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
rpl::producer<std::vector<VenueData>> value) {
|
||||
auto &lifetime = container->lifetime();
|
||||
const auto delegate = lifetime.make_state<PeerListContentDelegateShow>(
|
||||
show);
|
||||
const auto controller = lifetime.make_state<LinksController>(
|
||||
&show->session(),
|
||||
std::move(value));
|
||||
controller->setStyleOverrides(&st::pickLocationVenueList);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
delegate->setContent(content);
|
||||
controller->setDelegate(delegate);
|
||||
|
||||
show->session().downloaderTaskFinished() | rpl::start_with_next([=] {
|
||||
content->update();
|
||||
}, content->lifetime());
|
||||
}
|
||||
|
||||
[[nodiscard]] PickerVenueList ParseVenues(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_BotResults &venues) {
|
||||
const auto &data = venues.data();
|
||||
session->data().processUsers(data.vusers());
|
||||
|
||||
auto &list = data.vresults().v;
|
||||
auto result = PickerVenueList();
|
||||
result.list.reserve(list.size());
|
||||
for (const auto &found : list) {
|
||||
found.match([&](const auto &data) {
|
||||
data.vsend_message().match([&](
|
||||
const MTPDbotInlineMessageMediaVenue &data) {
|
||||
data.vgeo().match([&](const MTPDgeoPoint &geo) {
|
||||
result.list.push_back({
|
||||
.lat = geo.vlat().v,
|
||||
.lon = geo.vlong().v,
|
||||
.title = qs(data.vtitle()),
|
||||
.address = qs(data.vaddress()),
|
||||
.provider = qs(data.vprovider()),
|
||||
.id = qs(data.vvenue_id()),
|
||||
.venueType = qs(data.vvenue_type()),
|
||||
});
|
||||
}, [](const auto &) {});
|
||||
}, [](const auto &) {});
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LocationPicker::LocationPicker(Descriptor &&descriptor)
|
||||
|
@ -127,9 +498,12 @@ LocationPicker::LocationPicker(Descriptor &&descriptor)
|
|||
, _updateStyles([=] {
|
||||
const auto str = EscapeForScriptString(ComputeStyles());
|
||||
if (_webview) {
|
||||
_webview->eval("IV.updateStyles('" + str + "');");
|
||||
_webview->eval("LocationPicker.updateStyles('" + str + "');");
|
||||
}
|
||||
}) {
|
||||
})
|
||||
, _venueState(PickerVenueLoading())
|
||||
, _session(descriptor.session)
|
||||
, _api(&_session->mtp()) {
|
||||
std::move(
|
||||
descriptor.closeRequests
|
||||
) | rpl::start_with_next([=] {
|
||||
|
@ -140,15 +514,26 @@ LocationPicker::LocationPicker(Descriptor &&descriptor)
|
|||
setup(descriptor);
|
||||
}
|
||||
|
||||
bool LocationPicker::Available(const QString &token) {
|
||||
std::shared_ptr<Main::SessionShow> LocationPicker::uiShow() {
|
||||
return Main::MakeSessionShow(nullptr, _session);
|
||||
}
|
||||
|
||||
bool LocationPicker::Available(
|
||||
const QString &mapsToken,
|
||||
const QString &geocodingToken) {
|
||||
static const auto Supported = Webview::NavigateToDataSupported();
|
||||
MapsProviderToken = token;
|
||||
MapsProviderToken = mapsToken;
|
||||
GeocodingProviderToken = geocodingToken;
|
||||
return Supported && !MapsProviderToken.isEmpty();
|
||||
}
|
||||
|
||||
void LocationPicker::setup(const Descriptor &descriptor) {
|
||||
setupWindow(descriptor);
|
||||
setupWebview(descriptor);
|
||||
if (LastExactLocation) {
|
||||
venuesRequest(LastExactLocation);
|
||||
resolveAddress(LastExactLocation);
|
||||
}
|
||||
}
|
||||
|
||||
void LocationPicker::setupWindow(const Descriptor &descriptor) {
|
||||
|
@ -168,24 +553,32 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
|
|||
parent.y() + (parent.height() - window->height()) / 2);
|
||||
|
||||
_container = CreateChild<RpWidget>(_body.get());
|
||||
const auto scroll = CreateChild<ScrollArea>(_body.get());
|
||||
const auto controls = scroll->setOwnedWidget(
|
||||
object_ptr<VerticalLayout>(scroll));
|
||||
_scroll = CreateChild<ScrollArea>(_body.get());
|
||||
const auto controls = _scroll->setOwnedWidget(
|
||||
object_ptr<VerticalLayout>(_scroll));
|
||||
const auto toppad = controls->add(object_ptr<RpWidget>(controls));
|
||||
|
||||
const auto button = controls->add(object_ptr<FlatButton>(
|
||||
controls,
|
||||
tr::lng_maps_point_send(tr::now),
|
||||
st::dialogsUpdateButton));
|
||||
const auto button = controls->add(
|
||||
MakeSendLocationButton(controls, _geocoderAddress.value()),
|
||||
{ 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip });
|
||||
button->setClickedCallback([=] {
|
||||
_webview->eval("LocationPicker.send();");
|
||||
});
|
||||
controls->add(object_ptr<RpWidget>(controls))->resize(
|
||||
st::pickLocationWindow);
|
||||
|
||||
AddDivider(controls);
|
||||
AddSkip(controls);
|
||||
AddSubsectionTitle(controls, tr::lng_maps_or_choose());
|
||||
|
||||
SetupVenues(controls, uiShow(), _venueState.value(
|
||||
) | rpl::filter([=](const PickerVenueState &state) {
|
||||
return v::is<PickerVenueList>(state);
|
||||
}) | rpl::map([=](PickerVenueState &&state) {
|
||||
return std::move(v::get<PickerVenueList>(state).list);
|
||||
}));
|
||||
|
||||
rpl::combine(
|
||||
_body->sizeValue(),
|
||||
scroll->scrollTopValue()
|
||||
_scroll->scrollTopValue()
|
||||
) | rpl::start_with_next([=](QSize size, int scrollTop) {
|
||||
const auto width = size.width();
|
||||
const auto height = size.height();
|
||||
|
@ -194,9 +587,9 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
|
|||
scrollTop);
|
||||
const auto mapHeight = st::pickLocationMapHeight - sub;
|
||||
const auto scrollHeight = height - mapHeight;
|
||||
button->resizeToWidth(width);
|
||||
_container->setGeometry(0, 0, width, mapHeight);
|
||||
scroll->setGeometry(0, mapHeight, width, scrollHeight);
|
||||
_scroll->setGeometry(0, mapHeight, width, scrollHeight);
|
||||
controls->resizeToWidth(width);
|
||||
toppad->resize(width, sub);
|
||||
}, _container->lifetime());
|
||||
|
||||
|
@ -205,7 +598,7 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
|
|||
}, _container->lifetime());
|
||||
|
||||
_container->show();
|
||||
scroll->show();
|
||||
_scroll->hide();
|
||||
controls->show();
|
||||
button->show();
|
||||
window->show();
|
||||
|
@ -256,7 +649,7 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) {
|
|||
const auto object = message.object();
|
||||
const auto event = object.value("event").toString();
|
||||
if (event == u"ready"_q) {
|
||||
initMap();
|
||||
mapReady();
|
||||
resolveCurrentLocation();
|
||||
} else if (event == u"keydown"_q) {
|
||||
const auto key = object.value("key").toString();
|
||||
|
@ -321,7 +714,31 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) {
|
|||
raw->navigateToData("location/picker.html");
|
||||
}
|
||||
|
||||
void LocationPicker::initMap() {
|
||||
void LocationPicker::resolveAddress(Core::GeoLocation location) {
|
||||
if (_geocoderResolvingFor == location) {
|
||||
return;
|
||||
}
|
||||
_geocoderResolvingFor = location;
|
||||
const auto done = [=](Core::GeoAddress address) {
|
||||
if (_geocoderResolvingFor != location) {
|
||||
return;
|
||||
} else if (address) {
|
||||
_geocoderAddress = address.name;
|
||||
} else {
|
||||
_geocoderAddress = u"(%1, %2)"_q
|
||||
.arg(location.point.x(), 0, 'f')
|
||||
.arg(location.point.y(), 0, 'f');
|
||||
}
|
||||
};
|
||||
Core::ResolveLocationAddress(
|
||||
location,
|
||||
GeocodingProviderToken,
|
||||
crl::guard(this, done));
|
||||
}
|
||||
|
||||
void LocationPicker::mapReady() {
|
||||
Expects(_scroll != nullptr);
|
||||
|
||||
const auto token = MapsProviderToken.toUtf8();
|
||||
const auto center = DefaultCenter();
|
||||
const auto bounds = DefaultBounds();
|
||||
|
@ -333,16 +750,103 @@ void LocationPicker::initMap() {
|
|||
+ ", bounds: " + bounds
|
||||
+ ", protocol: " + protocol;
|
||||
_webview->eval("LocationPicker.init({ " + params + " });");
|
||||
|
||||
_scroll->show();
|
||||
}
|
||||
|
||||
void LocationPicker::venuesRequest(
|
||||
Core::GeoLocation location,
|
||||
QString query) {
|
||||
query = NormalizeVenuesQuery(query);
|
||||
auto &cache = _venuesCache[query];
|
||||
const auto i = ranges::find(
|
||||
cache,
|
||||
location,
|
||||
&VenuesCacheEntry::location);
|
||||
if (i != end(cache)) {
|
||||
_venueState = i->result;
|
||||
return;
|
||||
} else if (_venuesRequestLocation == location
|
||||
&& _venuesRequestQuery == query) {
|
||||
return;
|
||||
} else if (const auto oldRequestId = base::take(_venuesRequestId)) {
|
||||
_api.request(oldRequestId).cancel();
|
||||
}
|
||||
_venueState = PickerVenueLoading();
|
||||
_venuesRequestLocation = location;
|
||||
_venuesRequestQuery = query;
|
||||
if (_venuesBot) {
|
||||
venuesSendRequest();
|
||||
} else if (_venuesBotRequestId) {
|
||||
return;
|
||||
}
|
||||
const auto username = _session->serverConfig().venueSearchUsername;
|
||||
_venuesBotRequestId = _api.request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
auto &data = result.data();
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
const auto peer = _session->data().peerLoaded(
|
||||
peerFromMTP(data.vpeer()));
|
||||
const auto user = peer ? peer->asUser() : nullptr;
|
||||
if (user && user->isBotInlineGeo()) {
|
||||
_venuesBot = user;
|
||||
venuesSendRequest();
|
||||
} else {
|
||||
LOG(("API Error: Bad peer returned by: %1").arg(username));
|
||||
}
|
||||
}).fail([=] {
|
||||
LOG(("API Error: Error returned on lookup: %1").arg(username));
|
||||
}).send();
|
||||
}
|
||||
|
||||
void LocationPicker::venuesSendRequest() {
|
||||
Expects(_venuesBot != nullptr);
|
||||
|
||||
if (_venuesRequestId || !_venuesRequestLocation) {
|
||||
return;
|
||||
}
|
||||
_venuesRequestId = _api.request(MTPmessages_GetInlineBotResults(
|
||||
MTP_flags(MTPmessages_GetInlineBotResults::Flag::f_geo_point),
|
||||
_venuesBot->inputUser,
|
||||
MTP_inputPeerEmpty(),
|
||||
MTP_inputGeoPoint(
|
||||
MTP_flags(0),
|
||||
MTP_double(_venuesRequestLocation.point.x()),
|
||||
MTP_double(_venuesRequestLocation.point.y()),
|
||||
MTP_int(0)), // accuracy_radius
|
||||
MTP_string(_venuesRequestQuery),
|
||||
MTP_string() // offset
|
||||
)).done([=](const MTPmessages_BotResults &result) {
|
||||
auto parsed = ParseVenues(_session, result);
|
||||
_venuesCache[_venuesRequestQuery].push_back({
|
||||
.location = _venuesRequestLocation,
|
||||
.result = parsed,
|
||||
});
|
||||
if (parsed.list.empty()) {
|
||||
_venueState = PickerVenueNothingFound{ _venuesRequestQuery };
|
||||
} else {
|
||||
_venueState = std::move(parsed);
|
||||
}
|
||||
}).fail([=] {
|
||||
_venueState = PickerVenueNothingFound{ _venuesRequestQuery };
|
||||
}).send();
|
||||
}
|
||||
|
||||
void LocationPicker::resolveCurrentLocation() {
|
||||
using namespace Core;
|
||||
const auto window = _window.get();
|
||||
ResolveCurrentGeoLocation(crl::guard(window, [=](GeoLocation location) {
|
||||
if (location.accuracy != GeoLocationAccuracy::Exact) {
|
||||
const auto changed = (LastExactLocation != location);
|
||||
if (location.accuracy != GeoLocationAccuracy::Exact || !changed) {
|
||||
return;
|
||||
}
|
||||
LastExactLocation = location;
|
||||
if (location) {
|
||||
venuesRequest(location);
|
||||
resolveAddress(location);
|
||||
}
|
||||
if (_webview) {
|
||||
const auto point = QByteArray::number(location.point.x())
|
||||
+ ","_q
|
||||
|
|
|
@ -9,8 +9,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "core/current_geo_location.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "webview/webview_common.h"
|
||||
|
||||
namespace Data {
|
||||
struct InputVenue;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
class SessionShow;
|
||||
} // namespace Main
|
||||
|
||||
namespace Webview {
|
||||
class Window;
|
||||
} // namespace Webview
|
||||
|
@ -19,23 +30,61 @@ namespace Ui {
|
|||
|
||||
class SeparatePanel;
|
||||
class RpWidget;
|
||||
class ScrollArea;
|
||||
|
||||
struct LocationInfo {
|
||||
float64 lat = 0.;
|
||||
float64 lon = 0.;
|
||||
};
|
||||
|
||||
struct PickerVenueLoading {
|
||||
friend inline bool operator==(
|
||||
PickerVenueLoading,
|
||||
PickerVenueLoading) = default;
|
||||
};
|
||||
|
||||
struct PickerVenueNothingFound {
|
||||
QString query;
|
||||
|
||||
friend inline bool operator==(
|
||||
const PickerVenueNothingFound&,
|
||||
const PickerVenueNothingFound&) = default;
|
||||
};
|
||||
|
||||
struct PickerVenueWaitingForLocation {
|
||||
friend inline bool operator==(
|
||||
PickerVenueWaitingForLocation,
|
||||
PickerVenueWaitingForLocation) = default;
|
||||
};
|
||||
|
||||
struct PickerVenueList {
|
||||
std::vector<Data::InputVenue> list;
|
||||
|
||||
friend inline bool operator==(
|
||||
const PickerVenueList&,
|
||||
const PickerVenueList&) = default;
|
||||
};
|
||||
|
||||
using PickerVenueState = std::variant<
|
||||
PickerVenueLoading,
|
||||
PickerVenueNothingFound,
|
||||
PickerVenueWaitingForLocation,
|
||||
PickerVenueList>;
|
||||
|
||||
class LocationPicker final : public base::has_weak_ptr {
|
||||
public:
|
||||
struct Descriptor {
|
||||
RpWidget *parent = nullptr;
|
||||
not_null<Main::Session*> session;
|
||||
Fn<void(LocationInfo)> callback;
|
||||
Fn<void()> quit;
|
||||
Webview::StorageId storageId;
|
||||
rpl::producer<> closeRequests;
|
||||
};
|
||||
|
||||
[[nodiscard]] static bool Available(const QString &token);
|
||||
[[nodiscard]] static bool Available(
|
||||
const QString &mapsToken,
|
||||
const QString &geocodingToken);
|
||||
static not_null<LocationPicker*> Show(Descriptor &&descriptor);
|
||||
|
||||
void close();
|
||||
|
@ -43,14 +92,25 @@ public:
|
|||
void quit();
|
||||
|
||||
private:
|
||||
struct VenuesCacheEntry {
|
||||
Core::GeoLocation location;
|
||||
PickerVenueList result;
|
||||
};
|
||||
|
||||
explicit LocationPicker(Descriptor &&descriptor);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Main::SessionShow> uiShow();
|
||||
|
||||
void setup(const Descriptor &descriptor);
|
||||
void setupWindow(const Descriptor &descriptor);
|
||||
void setupWebview(const Descriptor &descriptor);
|
||||
void processKey(const QString &key, const QString &modifier);
|
||||
void resolveCurrentLocation();
|
||||
void initMap();
|
||||
void resolveAddress(Core::GeoLocation location);
|
||||
void mapReady();
|
||||
|
||||
void venuesRequest(Core::GeoLocation location, QString query = {});
|
||||
void venuesSendRequest();
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
@ -59,10 +119,25 @@ private:
|
|||
std::unique_ptr<SeparatePanel> _window;
|
||||
not_null<RpWidget*> _body;
|
||||
RpWidget *_container = nullptr;
|
||||
ScrollArea *_scroll = nullptr;
|
||||
std::unique_ptr<Webview::Window> _webview;
|
||||
SingleQueuedInvokation _updateStyles;
|
||||
bool _subscribedToColors = false;
|
||||
|
||||
Core::GeoLocation _geocoderResolvingFor;
|
||||
rpl::variable<QString> _geocoderAddress;
|
||||
|
||||
rpl::variable<PickerVenueState> _venueState;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
UserData *_venuesBot = nullptr;
|
||||
mtpRequestId _venuesBotRequestId = 0;
|
||||
mtpRequestId _venuesRequestId = 0;
|
||||
Core::GeoLocation _venuesRequestLocation;
|
||||
QString _venuesRequestQuery;
|
||||
base::flat_map<QString, std::vector<VenuesCacheEntry>> _venuesCache;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
|
|
82
Telegram/SourceFiles/ui/webview_helpers.cpp
Normal file
82
Telegram/SourceFiles/ui/webview_helpers.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/webview_helpers.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
[[nodiscard]] QByteArray ComputeStyles(
|
||||
const base::flat_map<QByteArray, const style::color*> &colors,
|
||||
const base::flat_map<QByteArray, tr::phrase<>> &phrases,
|
||||
bool nightTheme) {
|
||||
static const auto serialize = [](const style::color *color) {
|
||||
const auto qt = (*color)->c;
|
||||
if (qt.alpha() == 255) {
|
||||
return '#'
|
||||
+ QByteArray::number(qt.red(), 16).right(2)
|
||||
+ QByteArray::number(qt.green(), 16).right(2)
|
||||
+ QByteArray::number(qt.blue(), 16).right(2);
|
||||
}
|
||||
return "rgba("
|
||||
+ QByteArray::number(qt.red()) + ","
|
||||
+ QByteArray::number(qt.green()) + ","
|
||||
+ QByteArray::number(qt.blue()) + ","
|
||||
+ QByteArray::number(qt.alpha() / 255.) + ")";
|
||||
};
|
||||
static const auto escape = [](tr::phrase<> phrase) {
|
||||
const auto text = phrase(tr::now);
|
||||
|
||||
auto result = QByteArray();
|
||||
for (auto i = 0; i != text.size(); ++i) {
|
||||
uint ucs4 = text[i].unicode();
|
||||
if (QChar::isHighSurrogate(ucs4) && i + 1 != text.size()) {
|
||||
ushort low = text[i + 1].unicode();
|
||||
if (QChar::isLowSurrogate(low)) {
|
||||
ucs4 = QChar::surrogateToUcs4(ucs4, low);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (ucs4 == '\'' || ucs4 == '\"' || ucs4 == '\\') {
|
||||
result.append('\\').append(char(ucs4));
|
||||
} else if (ucs4 < 32 || ucs4 > 127) {
|
||||
result.append('\\' + QByteArray::number(ucs4, 16) + ' ');
|
||||
} else {
|
||||
result.append(char(ucs4));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto result = QByteArray();
|
||||
for (const auto &[name, phrase] : phrases) {
|
||||
result += "--td-lng-"_q + name + ":'"_q + escape(phrase) + "'; "_q;
|
||||
}
|
||||
for (const auto &[name, color] : colors) {
|
||||
result += "--td-"_q + name + ':' + serialize(color) + ';';
|
||||
}
|
||||
result += "--td-night:"_q + (nightTheme ? "1" : "0") + ';';
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray EscapeForAttribute(QByteArray value) {
|
||||
return value
|
||||
.replace('&', "&")
|
||||
.replace('"', """)
|
||||
.replace('\'', "'")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">");
|
||||
}
|
||||
|
||||
QByteArray EscapeForScriptString(QByteArray value) {
|
||||
return value
|
||||
.replace('\\', "\\\\")
|
||||
.replace('"', "\\\"")
|
||||
.replace('\'', "\\\'");
|
||||
}
|
||||
|
||||
} // namespace Ui
|
27
Telegram/SourceFiles/ui/webview_helpers.h
Normal file
27
Telegram/SourceFiles/ui/webview_helpers.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/flat_map.h"
|
||||
|
||||
namespace tr {
|
||||
template <typename ...Tags>
|
||||
struct phrase;
|
||||
} // namespace tr
|
||||
|
||||
namespace Ui {
|
||||
|
||||
[[nodiscard]] QByteArray ComputeStyles(
|
||||
const base::flat_map<QByteArray, const style::color*> &colors,
|
||||
const base::flat_map<QByteArray, tr::phrase<>> &phrases,
|
||||
bool nightTheme = false);
|
||||
|
||||
[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value);
|
||||
[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value);
|
||||
|
||||
} // namespace Ui
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue