Implement venues search.

This commit is contained in:
John Preston 2024-07-12 15:07:21 +02:00
parent 917d1841c1
commit b4dfc25df5
3 changed files with 127 additions and 29 deletions

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/separate_panel.h" #include "ui/widgets/separate_panel.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
@ -48,6 +49,7 @@ namespace Ui {
namespace { namespace {
constexpr auto kResolveAddressDelay = 3 * crl::time(1000); constexpr auto kResolveAddressDelay = 3 * crl::time(1000);
constexpr auto kSearchDebounceDelay = crl::time(900);
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
const auto kProtocolOverride = "mapboxapihelper"; const auto kProtocolOverride = "mapboxapihelper";
@ -539,6 +541,12 @@ LocationPicker::LocationPicker(Descriptor &&descriptor)
, _geocoderResolveTimer([=] { resolveAddressByTimer(); }) , _geocoderResolveTimer([=] { resolveAddressByTimer(); })
, _venueState(PickerVenueLoading()) , _venueState(PickerVenueLoading())
, _session(descriptor.session) , _session(descriptor.session)
, _venuesSearchDebounceTimer([=] {
Expects(_venuesSearchLocation.has_value());
Expects(_venuesSearchQuery.has_value());
venuesRequest(*_venuesSearchLocation, *_venuesSearchQuery);
})
, _api(&_session->mtp()) { , _api(&_session->mtp()) {
std::move( std::move(
descriptor.closeRequests descriptor.closeRequests
@ -565,6 +573,7 @@ void LocationPicker::setup(const Descriptor &descriptor) {
if (LastExactLocation) { if (LastExactLocation) {
venuesRequest(LastExactLocation); venuesRequest(LastExactLocation);
resolveAddress(LastExactLocation); resolveAddress(LastExactLocation);
venuesSearchEnableAt(LastExactLocation);
} }
} }
@ -588,18 +597,27 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
_scroll = CreateChild<ScrollArea>(_body.get()); _scroll = CreateChild<ScrollArea>(_body.get());
const auto controls = _scroll->setOwnedWidget( const auto controls = _scroll->setOwnedWidget(
object_ptr<VerticalLayout>(_scroll)); object_ptr<VerticalLayout>(_scroll));
const auto toppad = controls->add(object_ptr<RpWidget>(controls));
const auto button = controls->add( _mapControlsWrap = controls->add(
MakeSendLocationButton(controls, _geocoderAddress.value()), object_ptr<SlideWrap<VerticalLayout>>(
controls,
object_ptr<VerticalLayout>(controls))
)->setDuration(0);
_mapControlsWrap->show(anim::type::instant);
const auto mapControls = _mapControlsWrap->entity();
const auto toppad = mapControls->add(object_ptr<RpWidget>(controls));
const auto button = mapControls->add(
MakeSendLocationButton(mapControls, _geocoderAddress.value()),
{ 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip }); { 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip });
button->setClickedCallback([=] { button->setClickedCallback([=] {
_webview->eval("LocationPicker.send();"); _webview->eval("LocationPicker.send();");
}); });
AddDivider(controls); AddDivider(mapControls);
AddSkip(controls); AddSkip(mapControls);
AddSubsectionTitle(controls, tr::lng_maps_or_choose()); AddSubsectionTitle(mapControls, tr::lng_maps_or_choose());
SetupVenues(controls, uiShow(), _venueState.value( SetupVenues(controls, uiShow(), _venueState.value(
) | rpl::filter([=](const PickerVenueState &state) { ) | rpl::filter([=](const PickerVenueState &state) {
@ -613,17 +631,19 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
rpl::combine( rpl::combine(
_body->sizeValue(), _body->sizeValue(),
_scroll->scrollTopValue() _scroll->scrollTopValue(),
) | rpl::start_with_next([=](QSize size, int scrollTop) { _venuesSearchShown.value()
) | rpl::start_with_next([=](QSize size, int scrollTop, bool search) {
const auto width = size.width(); const auto width = size.width();
const auto height = size.height(); const auto height = size.height();
const auto sub = std::min( const auto sub = std::min(
(st::pickLocationMapHeight - st::pickLocationCollapsedHeight), (st::pickLocationMapHeight - st::pickLocationCollapsedHeight),
scrollTop); scrollTop);
const auto mapHeight = st::pickLocationMapHeight - sub; const auto mapHeight = st::pickLocationMapHeight - sub;
const auto scrollHeight = height - mapHeight;
_container->setGeometry(0, 0, width, mapHeight); _container->setGeometry(0, 0, width, mapHeight);
_scroll->setGeometry(0, mapHeight, width, scrollHeight); const auto scrollWidgetTop = search ? 0 : mapHeight;
const auto scrollHeight = height - scrollWidgetTop;
_scroll->setGeometry(0, scrollWidgetTop, width, scrollHeight);
controls->resizeToWidth(width); controls->resizeToWidth(width);
toppad->resize(width, sub); toppad->resize(width, sub);
}, _container->lifetime()); }, _container->lifetime());
@ -662,7 +682,7 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) {
close(); close();
} else if (e->type() == QEvent::KeyPress) { } else if (e->type() == QEvent::KeyPress) {
const auto event = static_cast<QKeyEvent*>(e.get()); const auto event = static_cast<QKeyEvent*>(e.get());
if (event->key() == Qt::Key_Escape) { if (event->key() == Qt::Key_Escape && !_venuesSearchQuery) {
close(); close();
} }
} }
@ -722,6 +742,7 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) {
_webview->eval( _webview->eval(
"LocationPicker.toggleSearchVenues(true);"); "LocationPicker.toggleSearchVenues(true);");
} }
venuesSearchEnableAt(location);
} else if (event == u"search_venues"_q) { } else if (event == u"search_venues"_q) {
const auto lat = object.value("latitude").toDouble(); const auto lat = object.value("latitude").toDouble();
const auto lon = object.value("longitude").toDouble(); const auto lon = object.value("longitude").toDouble();
@ -835,26 +856,38 @@ void LocationPicker::mapReady() {
_scroll->show(); _scroll->show();
} }
void LocationPicker::venuesRequest( bool LocationPicker::venuesFromCache(
Core::GeoLocation location, Core::GeoLocation location,
QString query) { QString query) {
query = NormalizeVenuesQuery(query); const auto normalized = NormalizeVenuesQuery(query);
auto &cache = _venuesCache[query]; auto &cache = _venuesCache[normalized];
const auto i = ranges::find_if(cache, [&](const VenuesCacheEntry &v) { const auto i = ranges::find_if(cache, [&](const VenuesCacheEntry &v) {
return AreTheSame(v.location, location); return AreTheSame(v.location, location);
}); });
if (i != end(cache)) { if (i == end(cache)) {
_venueState = i->result; return false;
return; }
} else if (AreTheSame(_venuesRequestLocation, location) _venuesRequestLocation = location;
&& _venuesRequestQuery == query) { _venuesRequestQuery = normalized;
_venuesInitialQuery = query;
venuesApplyResults(i->result);
return true;
}
void LocationPicker::venuesRequest(
Core::GeoLocation location,
QString query) {
const auto normalized = NormalizeVenuesQuery(query);
if (AreTheSame(_venuesRequestLocation, location)
&& _venuesRequestQuery == normalized) {
return; return;
} else if (const auto oldRequestId = base::take(_venuesRequestId)) { } else if (const auto oldRequestId = base::take(_venuesRequestId)) {
_api.request(oldRequestId).cancel(); _api.request(oldRequestId).cancel();
} }
_venueState = PickerVenueLoading(); _venueState = PickerVenueLoading();
_venuesRequestLocation = location; _venuesRequestLocation = location;
_venuesRequestQuery = query; _venuesRequestQuery = normalized;
_venuesInitialQuery = query;
if (_venuesBot) { if (_venuesBot) {
venuesSendRequest(); venuesSendRequest();
} else if (_venuesBotRequestId) { } else if (_venuesBotRequestId) {
@ -904,16 +937,61 @@ void LocationPicker::venuesSendRequest() {
.location = _venuesRequestLocation, .location = _venuesRequestLocation,
.result = parsed, .result = parsed,
}); });
if (parsed.list.empty()) { venuesApplyResults(std::move(parsed));
_venueState = PickerVenueNothingFound{ _venuesRequestQuery };
} else {
_venueState = std::move(parsed);
}
}).fail([=] { }).fail([=] {
_venueState = PickerVenueNothingFound{ _venuesRequestQuery }; venuesApplyResults({});
}).send(); }).send();
} }
void LocationPicker::venuesApplyResults(PickerVenueList venues) {
_venuesRequestId = 0;
if (venues.list.empty()) {
_venueState = PickerVenueNothingFound{ _venuesInitialQuery };
} else {
_venueState = std::move(venues);
}
}
void LocationPicker::venuesSearchEnableAt(Core::GeoLocation location) {
if (!_venuesSearchLocation) {
_window->setSearchAllowed(
tr::lng_dlg_filter(),
[=](std::optional<QString> query) {
venuesSearchChanged(query);
});
}
_venuesSearchLocation = location;
}
void LocationPicker::venuesSearchChanged(
const std::optional<QString> &query) {
_venuesSearchQuery = query;
const auto shown = query && !query->trimmed().isEmpty();
_venuesSearchShown = shown;
if (_container->isHidden() != shown) {
_container->setVisible(!shown);
_mapControlsWrap->toggle(!shown, anim::type::instant);
if (shown) {
_venuesNoSearchLocation = _venuesRequestLocation;
_venueState = PickerVenueLoading();
} else if (_venuesNoSearchLocation) {
if (!venuesFromCache(_venuesNoSearchLocation)) {
venuesRequest(_venuesNoSearchLocation);
}
}
}
if (shown
&& !venuesFromCache(
*_venuesSearchLocation,
*_venuesSearchQuery)) {
_venuesSearchDebounceTimer.callOnce(kSearchDebounceDelay);
} else {
_venuesSearchDebounceTimer.cancel();
}
}
void LocationPicker::resolveCurrentLocation() { void LocationPicker::resolveCurrentLocation() {
using namespace Core; using namespace Core;
const auto window = _window.get(); const auto window = _window.get();
@ -924,7 +1002,9 @@ void LocationPicker::resolveCurrentLocation() {
} }
LastExactLocation = location; LastExactLocation = location;
if (location) { if (location) {
venuesRequest(location); if (_venuesSearchQuery.value_or(QString()).isEmpty()) {
venuesRequest(location);
}
resolveAddress(location); resolveAddress(location);
} }
if (_webview) { if (_webview) {
@ -940,7 +1020,11 @@ void LocationPicker::processKey(
const QString &key, const QString &key,
const QString &modifier) { const QString &modifier) {
const auto ctrl = ::Platform::IsMac() ? u"cmd"_q : u"ctrl"_q; const auto ctrl = ::Platform::IsMac() ? u"cmd"_q : u"ctrl"_q;
if (key == u"escape"_q || (key == u"w"_q && modifier == ctrl)) { if (key == u"escape"_q) {
if (!_window->closeSearch()) {
close();
}
} else if (key == u"w"_q && modifier == ctrl) {
close(); close();
} else if (key == u"m"_q && modifier == ctrl) { } else if (key == u"m"_q && modifier == ctrl) {
minimize(); minimize();

View file

@ -32,6 +32,9 @@ namespace Ui {
class SeparatePanel; class SeparatePanel;
class RpWidget; class RpWidget;
class ScrollArea; class ScrollArea;
class VerticalLayout;
template <typename Widget>
class SlideWrap;
struct PickerVenueLoading { struct PickerVenueLoading {
friend inline bool operator==( friend inline bool operator==(
@ -110,8 +113,12 @@ private:
void resolveAddress(Core::GeoLocation location); void resolveAddress(Core::GeoLocation location);
void mapReady(); void mapReady();
bool venuesFromCache(Core::GeoLocation location, QString query = {});
void venuesRequest(Core::GeoLocation location, QString query = {}); void venuesRequest(Core::GeoLocation location, QString query = {});
void venuesSendRequest(); void venuesSendRequest();
void venuesApplyResults(PickerVenueList venues);
void venuesSearchEnableAt(Core::GeoLocation location);
void venuesSearchChanged(const std::optional<QString> &query);
LocationPickerConfig _config; LocationPickerConfig _config;
Fn<void(Data::InputVenue)> _callback; Fn<void(Data::InputVenue)> _callback;
@ -119,6 +126,7 @@ private:
std::unique_ptr<SeparatePanel> _window; std::unique_ptr<SeparatePanel> _window;
not_null<RpWidget*> _body; not_null<RpWidget*> _body;
RpWidget *_container = nullptr; RpWidget *_container = nullptr;
SlideWrap<VerticalLayout> *_mapControlsWrap = nullptr;
ScrollArea *_scroll = nullptr; ScrollArea *_scroll = nullptr;
std::unique_ptr<Webview::Window> _webview; std::unique_ptr<Webview::Window> _webview;
SingleQueuedInvokation _updateStyles; SingleQueuedInvokation _updateStyles;
@ -133,13 +141,19 @@ private:
rpl::variable<PickerVenueState> _venueState; rpl::variable<PickerVenueState> _venueState;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
std::optional<Core::GeoLocation> _venuesSearchLocation;
std::optional<QString> _venuesSearchQuery;
base::Timer _venuesSearchDebounceTimer;
MTP::Sender _api; MTP::Sender _api;
UserData *_venuesBot = nullptr; UserData *_venuesBot = nullptr;
mtpRequestId _venuesBotRequestId = 0; mtpRequestId _venuesBotRequestId = 0;
mtpRequestId _venuesRequestId = 0; mtpRequestId _venuesRequestId = 0;
Core::GeoLocation _venuesRequestLocation; Core::GeoLocation _venuesRequestLocation;
QString _venuesRequestQuery; QString _venuesRequestQuery;
QString _venuesInitialQuery;
base::flat_map<QString, std::vector<VenuesCacheEntry>> _venuesCache; base::flat_map<QString, std::vector<VenuesCacheEntry>> _venuesCache;
Core::GeoLocation _venuesNoSearchLocation;
rpl::variable<bool> _venuesSearchShown = false;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;

@ -1 +1 @@
Subproject commit 9b69f3855ac9feb760aef92ce98e874129303d4d Subproject commit a95caea1adac69ca6d95b55fe920eac33cdf9580