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

View file

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

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