Focus existing location picker.

This commit is contained in:
John Preston 2024-07-26 17:23:41 +02:00
parent db0856f71c
commit 3f0f3a3c11
12 changed files with 297 additions and 35 deletions

View File

@ -475,6 +475,8 @@ PRIVATE
data/business/data_shortcut_messages.h
data/components/factchecks.cpp
data/components/factchecks.h
data/components/location_pickers.cpp
data/components/location_pickers.h
data/components/recent_peers.cpp
data/components/recent_peers.h
data/components/scheduled_messages.cpp

View File

@ -3197,6 +3197,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_unread_bar_some" = "Unread messages";
"lng_maps_point" = "Location";
"lng_maps_select_on_map" = "Select on the Map";
"lng_maps_point_send" = "Send This Location";
"lng_maps_point_set" = "Set This Location";
"lng_maps_or_choose" = "Or choose a venue";

View File

@ -30,6 +30,10 @@ struct SendOptions {
bool invertCaption = false;
bool hideViaBot = false;
crl::time ttlSeconds = 0;
friend inline bool operator==(
const SendOptions &,
const SendOptions &) = default;
};
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
@ -52,6 +56,10 @@ struct SendAction {
MsgId replaceMediaOf = 0;
[[nodiscard]] MTPInputReplyTo mtpReplyTo() const;
friend inline bool operator==(
const SendAction &,
const SendAction &) = default;
};
struct MessageToSend {

View File

@ -1475,3 +1475,9 @@ pickLocationLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
thickness: 4px;
}
pickLocationPromoHeight: 32px;
pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
height: 44px;
textTop: 11px;
width: -96px;
font: font(15px semibold);
}

View File

@ -0,0 +1,44 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/components/location_pickers.h"
#include "api/api_common.h"
#include "ui/controls/location_picker.h"
namespace Data {
struct LocationPickers::Entry {
Api::SendAction action;
base::weak_ptr<Ui::LocationPicker> picker;
};
LocationPickers::LocationPickers() = default;
LocationPickers::~LocationPickers() = default;
Ui::LocationPicker *LocationPickers::lookup(const Api::SendAction &action) {
for (auto i = begin(_pickers); i != end(_pickers);) {
if (const auto strong = i->picker.get()) {
if (i->action == action) {
return i->picker.get();
}
++i;
} else {
i = _pickers.erase(i);
}
}
return nullptr;
}
void LocationPickers::emplace(
const Api::SendAction &action,
not_null<Ui::LocationPicker*> picker) {
_pickers.push_back({ action, picker });
}
} // namespace Data

View File

@ -0,0 +1,39 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/weak_ptr.h"
namespace Api {
struct SendAction;
} // namespace Api
namespace Ui {
class LocationPicker;
} // namespace Ui
namespace Data {
class LocationPickers final {
public:
LocationPickers();
~LocationPickers();
Ui::LocationPicker *lookup(const Api::SendAction &action);
void emplace(
const Api::SendAction &action,
not_null<Ui::LocationPicker*> picker);
private:
struct Entry;
std::vector<Entry> _pickers;
};
} // namespace Data

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/share_box.h"
#include "core/click_handler_types.h"
#include "core/shortcuts.h"
#include "data/components/location_pickers.h"
#include "data/data_bot_app.h"
#include "data/data_changes.h"
#include "data/data_user.h"
@ -1776,6 +1777,11 @@ void ChooseAndSendLocation(
not_null<Window::SessionController*> controller,
const Ui::LocationPickerConfig &config,
Api::SendAction action) {
const auto session = &controller->session();
if (const auto picker = session->locationPickers().lookup(action)) {
picker->activate();
return;
}
const auto callback = [=](Data::InputVenue venue) {
if (venue.justLocation()) {
Api::SendLocation(action, venue.lat, venue.lon);
@ -1783,17 +1789,18 @@ void ChooseAndSendLocation(
Api::SendVenue(action, venue);
}
};
Ui::LocationPicker::Show({
const auto picker = Ui::LocationPicker::Show({
.parent = controller->widget(),
.config = config,
.chooseLabel = tr::lng_maps_point_send(),
.recipient = action.history->peer,
.session = &controller->session(),
.callback = crl::guard(controller, callback),
.session = session,
.callback = crl::guard(session, callback),
.quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); },
.storageId = controller->session().local().resolveStorageIdBots(),
.storageId = session->local().resolveStorageIdBots(),
.closeRequests = controller->content()->death(),
});
session->locationPickers().emplace(action, picker);
}
std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(

View File

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_account.h"
#include "storage/storage_facade.h"
#include "data/components/factchecks.h"
#include "data/components/location_pickers.h"
#include "data/components/recent_peers.h"
#include "data/components/scheduled_messages.h"
#include "data/components/sponsored_messages.h"
@ -111,6 +112,7 @@ Session::Session(
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
, _topPeers(std::make_unique<Data::TopPeers>(this))
, _factchecks(std::make_unique<Data::Factchecks>(this))
, _locationPickers(std::make_unique<Data::LocationPickers>())
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
, _supportHelper(Support::Helper::Create(this))
, _saveSettingsTimer([=] { saveSettings(); }) {

View File

@ -36,6 +36,7 @@ class ScheduledMessages;
class SponsoredMessages;
class TopPeers;
class Factchecks;
class LocationPickers;
} // namespace Data
namespace HistoryView::Reactions {
@ -131,6 +132,9 @@ public:
[[nodiscard]] Data::Factchecks &factchecks() const {
return *_factchecks;
}
[[nodiscard]] Data::LocationPickers &locationPickers() const {
return *_locationPickers;
}
[[nodiscard]] Api::Updates &updates() const {
return *_updates;
}
@ -259,6 +263,7 @@ private:
const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages;
const std::unique_ptr<Data::TopPeers> _topPeers;
const std::unique_ptr<Data::Factchecks> _factchecks;
const std::unique_ptr<Data::LocationPickers> _locationPickers;
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
const std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;

View File

@ -63,6 +63,7 @@ private:
const Ui::LocationPickerConfig _config;
rpl::variable<Data::BusinessLocation> _data;
rpl::variable<Data::CloudImage*> _map = nullptr;
base::weak_ptr<Ui::LocationPicker> _picker;
std::shared_ptr<QImage> _view;
Ui::RoundRect _bottomSkipRounding;
@ -232,6 +233,10 @@ void Location::setupPicker(not_null<Ui::VerticalLayout*> content) {
}
void Location::chooseOnMap() {
if (const auto strong = _picker.get()) {
strong->activate();
return;
}
const auto callback = [=](Data::InputVenue venue) {
auto copy = _data.current();
copy.point = Data::LocationPoint(
@ -249,7 +254,7 @@ void Location::chooseOnMap() {
.accuracy = Core::GeoLocationAccuracy::Exact,
}
: Core::GeoLocation();
Ui::LocationPicker::Show({
_picker = Ui::LocationPicker::Show({
.parent = controller()->widget(),
.config = _config,
.chooseLabel = tr::lng_maps_point_set(),
@ -258,7 +263,7 @@ void Location::chooseOnMap() {
.callback = crl::guard(this, callback),
.quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); },
.storageId = session->local().resolveStorageIdBots(),
.closeRequests = controller()->content()->death(),
.closeRequests = death(),
});
}

View File

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/ui/chat_search_empty.h" // Dialogs::SearchEmpty.
#include "lang/lang_instance.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/session/session_show.h"
#include "main/main_session.h"
#include "mtproto/mtproto_config.h"
@ -41,6 +42,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
#include "styles/style_dialogs.h"
#include "styles/style_window.h"
#include "styles/style_settings.h" // settingsCloudPasswordIconSize
#include "styles/style_layers.h" // boxDividerHeight
#include <QtCore/QFile>
#include <QtCore/QJsonDocument>
@ -185,11 +188,12 @@ private:
[[nodiscard]] object_ptr<RpWidget> MakeFoursquarePromo() {
auto result = object_ptr<RpWidget>((QWidget*)nullptr);
const auto skip = st::defaultVerticalListSkip;
const auto raw = result.data();
raw->resize(0, st::pickLocationPromoHeight);
raw->resize(0, skip + st::pickLocationPromoHeight);
const auto shadow = CreateChild<PlainShadow>(raw);
raw->widthValue() | rpl::start_with_next([=](int width) {
shadow->setGeometry(0, 0, width, st::lineWidth);
shadow->setGeometry(0, skip, width, st::lineWidth);
}, raw->lifetime());
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(raw);
@ -197,7 +201,7 @@ private:
p.setPen(st::windowSubTextFg);
p.setFont(st::normalFont);
p.drawText(
raw->rect(),
raw->rect().marginsRemoved({ 0, skip, 0, 0 }),
tr::lng_maps_venues_source(tr::now),
style::al_center);
}, raw->lifetime());
@ -544,7 +548,7 @@ void SetupEmptyView(
(query ? Icon::NoResults : Icon::Search),
(query
? tr::lng_maps_no_places
: tr::lng_maps_choose_to_search)(Ui::Text::WithEntities));
: tr::lng_maps_choose_to_search)(Text::WithEntities));
view->setMinimalHeight(st::recentPeersEmptyHeightMin);
view->show();
@ -636,6 +640,96 @@ void SetupVenues(
return result;
}
not_null<RpWidget*> SetupMapPlaceholder(
not_null<RpWidget*> parent,
int minHeight,
int maxHeight,
Fn<void()> choose) {
const auto result = CreateChild<RpWidget>(parent);
const auto top = CreateChild<BoxContentDivider>(result);
const auto bottom = CreateChild<BoxContentDivider>(result);
const auto icon = CreateChild<RpWidget>(result);
const auto iconSize = st::settingsCloudPasswordIconSize;
auto ownedLottie = Lottie::MakeIcon({
.name = u"location"_q,
.sizeOverride = { iconSize, iconSize },
.limitFps = true,
});
const auto lottie = ownedLottie.get();
icon->lifetime().add([kept = std::move(ownedLottie)] {});
icon->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(icon);
const auto left = (icon->width() - iconSize) / 2;
const auto scale = icon->height() / float64(iconSize);
auto hq = std::optional<PainterHighQualityEnabler>();
if (scale < 1.) {
const auto center = QPointF(
icon->width() / 2.,
icon->height() / 2.);
hq.emplace(p);
p.translate(center);
p.scale(scale, scale);
p.translate(-center);
p.setOpacity(scale);
}
lottie->paint(p, left, 0);
}, icon->lifetime());
InvokeQueued(icon, [=] {
const auto till = lottie->framesCount() - 1;
lottie->animate([=] { icon->update(); }, 0, till);
});
const auto button = CreateChild<RoundButton>(
result,
tr::lng_maps_select_on_map(),
st::pickLocationChooseOnMap);
button->setFullRadius(true);
button->setTextTransform(RoundButton::TextTransform::NoTransform);
button->setClickedCallback(choose);
parent->sizeValue() | rpl::start_with_next([=](QSize size) {
result->setGeometry(QRect(QPoint(), size));
const auto width = size.width();
top->setGeometry(0, 0, width, top->height());
bottom->setGeometry(QRect(
QPoint(0, size.height() - bottom->height()),
QSize(width, bottom->height())));
const auto dividers = top->height() + bottom->height();
const auto ratio = (size.height() - minHeight)
/ float64(maxHeight - minHeight);
const auto iconHeight = int(base::SafeRound(ratio * iconSize));
const auto available = size.height() - dividers;
const auto maxDelta = (maxHeight
- dividers
- iconSize
- button->height()) / 2;
const auto minDelta = (minHeight - dividers - button->height()) / 2;
const auto delta = anim::interpolate(minDelta, maxDelta, ratio);
button->move(
(width - button->width()) / 2,
size.height() - bottom->height() - delta - button->height());
const auto wide = available - delta - button->height();
const auto skip = (wide - iconHeight) / 2;
icon->setGeometry(0, top->height() + skip, width, iconHeight);
}, result->lifetime());
top->show();
icon->show();
bottom->show();
result->show();
return result;
}
} // namespace
LocationPicker::LocationPicker(Descriptor &&descriptor)
@ -646,6 +740,8 @@ LocationPicker::LocationPicker(Descriptor &&descriptor)
, _body((_window->setInnerSize(st::pickLocationWindow)
, _window->showInner(base::make_unique_q<RpWidget>(_window.get()))
, _window->inner()))
, _chooseButtonLabel(std::move(descriptor.chooseLabel))
, _webviewStorageId(descriptor.storageId)
, _updateStyles([=] {
const auto str = EscapeForScriptString(ComputeStyles());
if (_webview) {
@ -684,7 +780,6 @@ bool LocationPicker::Available(const LocationPickerConfig &config) {
void LocationPicker::setup(const Descriptor &descriptor) {
setupWindow(descriptor);
setupWebview(descriptor);
_initialProvided = descriptor.initial;
const auto initial = _initialProvided.exact()
@ -695,6 +790,9 @@ void LocationPicker::setup(const Descriptor &descriptor) {
resolveAddress(initial);
venuesSearchEnableAt(initial);
}
if (!_initialProvided) {
resolveCurrentLocation();
}
}
void LocationPicker::setupWindow(const Descriptor &descriptor) {
@ -714,6 +812,15 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
parent.y() + (parent.height() - window->height()) / 2);
_container = CreateChild<RpWidget>(_body.get());
_mapPlaceholderAdded = st::pickLocationButtonSkip
+ st::pickLocationButton.height
+ st::pickLocationButtonSkip
+ st::boxDividerHeight;
const auto min = st::pickLocationCollapsedHeight + _mapPlaceholderAdded;
const auto max = st::pickLocationMapHeight + _mapPlaceholderAdded;
_mapPlaceholder = SetupMapPlaceholder(_container, min, max, [=] {
setupWebview();
});
_scroll = CreateChild<ScrollArea>(_body.get());
const auto controls = _scroll->setOwnedWidget(
object_ptr<VerticalLayout>(_scroll));
@ -727,17 +834,6 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
const auto toppad = mapControls->add(object_ptr<RpWidget>(controls));
const auto button = mapControls->add(
MakeChooseLocationButton(
mapControls,
std::move(descriptor.chooseLabel),
_geocoderAddress.value()),
{ 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip });
button->setClickedCallback([=] {
_webview->eval("LocationPicker.send();");
});
AddDivider(mapControls);
AddSkip(mapControls);
AddSubsectionTitle(mapControls, tr::lng_maps_or_choose());
@ -757,7 +853,9 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
const auto sub = std::min(
(st::pickLocationMapHeight - st::pickLocationCollapsedHeight),
scrollTop);
const auto mapHeight = st::pickLocationMapHeight - sub;
const auto mapHeight = st::pickLocationMapHeight
- sub
+ (_mapPlaceholder ? _mapPlaceholderAdded : 0);
_container->setGeometry(0, 0, width, mapHeight);
const auto scrollWidgetTop = search ? 0 : mapHeight;
const auto scrollHeight = height - scrollWidgetTop;
@ -771,21 +869,52 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
}, _container->lifetime());
_container->show();
_scroll->hide();
_scroll->show();
controls->show();
button->show();
window->show();
}
void LocationPicker::setupWebview(const Descriptor &descriptor) {
void LocationPicker::setupWebview() {
Expects(!_webview);
delete base::take(_mapPlaceholder);
const auto mapControls = _mapControlsWrap->entity();
mapControls->insert(
1,
object_ptr<BoxContentDivider>(mapControls)
)->show();
_mapButton = mapControls->insert(
1,
MakeChooseLocationButton(
mapControls,
_chooseButtonLabel.value(),
_geocoderAddress.value()),
{ 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip });
_mapButton->setClickedCallback([=] {
_webview->eval("LocationPicker.send();");
});
_mapButton->hide();
_scroll->scrollToY(0);
_venuesSearchShown.force_assign(_venuesSearchShown.current());
_mapLoading = CreateChild<RpWidget>(_body.get());
_container->geometryValue() | rpl::start_with_next([=](QRect rect) {
_mapLoading->setGeometry(rect);
}, _mapLoading->lifetime());
SetupLoadingView(_mapLoading);
_mapLoading->show();
const auto window = _window.get();
_webview = std::make_unique<Webview::Window>(
_container,
Webview::WindowConfig{
.opaqueBg = st::windowBg->c,
.storageId = descriptor.storageId,
.storageId = _webviewStorageId,
.dataProtocolOverride = kProtocolOverride,
});
const auto raw = _webview.get();
@ -823,12 +952,6 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) {
const auto event = object.value("event").toString();
if (event == u"ready"_q) {
mapReady();
if (!_initialProvided) {
resolveCurrentLocation();
}
if (_webview) {
_webview->focus();
}
} else if (event == u"keydown"_q) {
const auto key = object.value("key").toString();
const auto modifier = object.value("modifier").toString();
@ -968,6 +1091,8 @@ void LocationPicker::resolveAddress(Core::GeoLocation location) {
void LocationPicker::mapReady() {
Expects(_scroll != nullptr);
delete base::take(_mapLoading);
const auto token = _config.mapsToken.toUtf8();
const auto center = DefaultCenter(_initialProvided);
const auto bounds = DefaultBounds();
@ -980,7 +1105,11 @@ void LocationPicker::mapReady() {
+ ", protocol: " + protocol;
_webview->eval("LocationPicker.init({ " + params + " });");
_scroll->show();
const auto handle = _window->window()->windowHandle();
if (handle && QGuiApplication::focusWindow() == handle) {
_webview->focus();
}
_mapButton->show();
}
bool LocationPicker::venuesFromCache(
@ -1163,6 +1292,12 @@ void LocationPicker::processKey(
}
}
void LocationPicker::activate() {
if (_window) {
_window->activateWindow();
}
}
void LocationPicker::close() {
crl::on_main(this, [=] {
_window = nullptr;

View File

@ -29,6 +29,7 @@ class Window;
namespace Ui {
class AbstractButton;
class SeparatePanel;
class RpWidget;
class ScrollArea;
@ -93,6 +94,7 @@ public:
[[nodiscard]] static bool Available(const LocationPickerConfig &config);
static not_null<LocationPicker*> Show(Descriptor &&descriptor);
void activate();
void close();
void minimize();
void quit();
@ -109,7 +111,7 @@ private:
void setup(const Descriptor &descriptor);
void setupWindow(const Descriptor &descriptor);
void setupWebview(const Descriptor &descriptor);
void setupWebview();
void processKey(const QString &key, const QString &modifier);
void resolveCurrentLocation();
void resolveAddressByTimer();
@ -129,11 +131,17 @@ private:
std::unique_ptr<SeparatePanel> _window;
not_null<RpWidget*> _body;
RpWidget *_container = nullptr;
RpWidget *_mapPlaceholder = nullptr;
RpWidget *_mapLoading = nullptr;
AbstractButton *_mapButton = nullptr;
SlideWrap<VerticalLayout> *_mapControlsWrap = nullptr;
rpl::variable<QString> _chooseButtonLabel;
ScrollArea *_scroll = nullptr;
Webview::StorageId _webviewStorageId;
std::unique_ptr<Webview::Window> _webview;
SingleQueuedInvokation _updateStyles;
Core::GeoLocation _initialProvided;
int _mapPlaceholderAdded = 0;
bool _subscribedToColors = false;
base::Timer _geocoderResolveTimer;