Search for venues by location.

This commit is contained in:
John Preston 2024-07-11 18:15:17 +02:00
parent de52ac6b28
commit a130bb1be6
6 changed files with 213 additions and 36 deletions

View File

@ -1,7 +1,5 @@
:root {
--font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif;
--font-serif: Iowan Old Style, Apple Garamond, Baskerville, Georgia, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
--font-mono: Menlo, Cascadia Code, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
}
html {
@ -13,8 +11,6 @@ html {
body {
font-family: var(--font-sans);
font-size: 17px;
line-height: 25px;
width: 100%;
height: 100%;
padding: 0;
@ -68,3 +64,57 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
#marker_shadow {
position: absolute;
}
#search_venues {
position: absolute;
left: 50%;
transform: translateX(-50%);
z-index: 2;
top: -30px;
transition: top 200ms ease-in-out;
}
#search_venues.shown {
top: 6px;
}
#search_venues_inner {
position: relative;
overflow: hidden;
font-size: 13px;
font-weight: 500;
background: var(--td-window-bg);
color: var(--td-window-active-text-fg);
cursor: pointer;
border-radius: 14px;
padding: 5px 12px 6px;
box-shadow: 0 0 3px 0px var(--td-history-to-down-shadow);
}
#search_venues_inner:hover {
background: var(--td-window-bg-over);
}
#search_venues_content {
position: relative;
z-index: 2;
}
#search_venues_content:before {
content: var(--td-lng-maps-places-in-area);
}
#search_venues_inner .ripple .inner {
position: absolute;
border-radius: 50%;
transform: scale(0);
opacity: 1;
animation: ripple 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
background-color: var(--td-window-bg-ripple);
}
#search_venues_inner .ripple.hiding {
animation: fadeOut 200ms linear forwards;
}
@keyframes ripple {
to {
transform: scale(2);
}
}
@keyframes fadeOut {
to {
opacity: 0;
}
}

View File

@ -72,6 +72,7 @@ var LocationPicker = {
LocationPicker.map = new mapboxgl.Map(options);
LocationPicker.createMarker(center);
LocationPicker.trackMovement();
LocationPicker.initSearchVenueRipple();
},
marker: function() {
return document.getElementById('marker_drop');
@ -93,13 +94,14 @@ var LocationPicker = {
LocationPicker.map.on('movestart', function() {
LocationPicker.marker().classList.add('moving');
LocationPicker.clearMovingTimer();
LocationPicker.notify({ event: 'movestart' });
LocationPicker.toggleSearchVenues(false);
LocationPicker.notify({ event: 'move_start' });
});
LocationPicker.map.on('moveend', function() {
LocationPicker.startMovingTimer(function() {
LocationPicker.marker().classList.remove('moving');
LocationPicker.notify({
event: 'moveend',
event: 'move_end',
latitude: LocationPicker.map.getCenter().lat,
longitude: LocationPicker.map.getCenter().lng
});
@ -119,5 +121,79 @@ var LocationPicker = {
latitude: LocationPicker.map.getCenter().lat,
longitude: LocationPicker.map.getCenter().lng
});
},
addRipple: function (button, x, y) {
const ripple = document.createElement('span');
ripple.classList.add('ripple');
const inner = document.createElement('span');
inner.classList.add('inner');
var rect = button.getBoundingClientRect();
x -= rect.x;
y -= rect.y;
const mx = button.clientWidth - x;
const my = button.clientHeight - y;
const sq1 = x * x + y * y;
const sq2 = mx * mx + y * y;
const sq3 = x * x + my * my;
const sq4 = mx * mx + my * my;
const radius = Math.sqrt(Math.max(sq1, sq2, sq3, sq4));
inner.style.width = inner.style.height = `${2 * radius}px`;
inner.style.left = `${x - radius}px`;
inner.style.top = `${y - radius}px`;
inner.classList.add('inner');
ripple.addEventListener('animationend', function (e) {
if (e.animationName === 'fadeOut') {
ripple.remove();
}
});
ripple.appendChild(inner);
button.appendChild(ripple);
},
stopRipples: function (button) {
const id = button.id ? button.id : button;
button = document.getElementById(id);
const ripples = button.getElementsByClassName('ripple');
for (var i = 0; i < ripples.length; ++i) {
const ripple = ripples[i];
if (!ripple.classList.contains('hiding')) {
ripple.classList.add('hiding');
}
}
},
initSearchVenueRipple: function() {
var button = document.getElementById('search_venues_inner');
button.addEventListener('mousedown', function (e) {
LocationPicker.addRipple(e.currentTarget, e.clientX, e.clientY);
LocationPicker.searchVenuesPressed = true;
});
button.addEventListener('mouseup', function (e) {
const id = e.currentTarget.id;
setTimeout(function () {
LocationPicker.stopRipples(id);
}, 0);
if (LocationPicker.searchVenuesPressed) {
LocationPicker.searchVenuesPressed = false;
LocationPicker.toggleSearchVenues(false);
LocationPicker.notify({
event: 'search_venues',
latitude: LocationPicker.map.getCenter().lat,
longitude: LocationPicker.map.getCenter().lng
});
}
});
button.addEventListener('mouseleave', function (e) {
LocationPicker.stopRipples(e.currentTarget);
LocationPicker.searchVenuesPressed = false;
});
},
toggleSearchVenues: function(shown) {
var button = document.getElementById('search_venues');
button.classList.toggle('shown', shown);
},
};

View File

@ -148,7 +148,7 @@ void ResolveLocationAddressGeneric(
}
}
};
add({ u"address"_q, u"street"_q, u"neighborhood"_q });
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(", ") });
@ -215,4 +215,29 @@ void ResolveLocationAddress(
Platform::ResolveLocationAddress(location, language, std::move(done));
}
bool AreTheSame(const GeoLocation &a, const GeoLocation &b) {
if (a.accuracy != GeoLocationAccuracy::Exact
|| b.accuracy != GeoLocationAccuracy::Exact) {
return false;
}
const auto normalize = [](float64 value) {
value = std::fmod(value + 180., 360.);
return (value + (value < 0. ? 360. : 0.)) - 180.;
};
constexpr auto kEpsilon = 0.0001;
const auto lon1 = normalize(a.point.y());
const auto lon2 = normalize(b.point.y());
const auto diffLat = std::abs(a.point.x() - b.point.x());
if (std::abs(a.point.x()) >= (90. - kEpsilon)
|| std::abs(b.point.x()) >= (90. - kEpsilon)) {
return diffLat <= kEpsilon;
}
auto diffLon = std::abs(lon1 - lon2);
if (diffLon > 180.) {
diffLon = 360. - diffLon;
}
return diffLat <= kEpsilon && diffLon <= kEpsilon;
}
} // namespace Core

View File

@ -33,12 +33,10 @@ struct GeoLocation {
explicit operator bool() const {
return !failed();
}
friend inline bool operator==(
const GeoLocation&,
const GeoLocation&) = default;
};
[[nodiscard]] bool AreTheSame(const GeoLocation &a, const GeoLocation &b);
struct GeoAddress {
QString name;

View File

@ -102,6 +102,7 @@ VenueRow::VenueRow(
void VenueRow::update(const VenueData &data) {
_data = data;
setCustomStatus(data.address);
refreshName(st::pickLocationVenueItem);
}
VenueData VenueRow::data() const {
@ -128,12 +129,12 @@ PaintRoundImageCallback VenueRow::generatePaintUserpicCallback(
};
}
class LinksController final
class VenuesController final
: public PeerListController
, public VenueRowDelegate
, public base::has_weak_ptr {
public:
LinksController(
VenuesController(
not_null<Main::Session*> session,
rpl::producer<std::vector<VenueData>> content);
@ -176,21 +177,21 @@ private:
return query.trimmed().toLower();
}
LinksController::LinksController(
VenuesController::VenuesController(
not_null<Main::Session*> session,
rpl::producer<std::vector<VenueData>> content)
: _session(session)
, _rows(std::move(content)) {
}
void LinksController::prepare() {
void VenuesController::prepare() {
_rows.value(
) | rpl::start_with_next([=](const std::vector<VenueData> &rows) {
rebuild(rows);
}, _lifetime);
}
void LinksController::rebuild(const std::vector<VenueData> &rows) {
void VenuesController::rebuild(const std::vector<VenueData> &rows) {
auto i = 0;
auto count = delegate()->peerListFullRowsCount();
while (i < rows.size()) {
@ -209,24 +210,24 @@ void LinksController::rebuild(const std::vector<VenueData> &rows) {
delegate()->peerListRefreshRows();
}
void LinksController::rowClicked(not_null<PeerListRow*> row) {
void VenuesController::rowClicked(not_null<PeerListRow*> row) {
const auto venue = static_cast<VenueRow*>(row.get())->data();
venue;
}
void LinksController::rowRightActionClicked(not_null<PeerListRow*> row) {
void VenuesController::rowRightActionClicked(not_null<PeerListRow*> row) {
delegate()->peerListShowRowMenu(row, true);
}
Main::Session &LinksController::session() const {
Main::Session &VenuesController::session() const {
return *_session;
}
void LinksController::appendRow(const VenueData &data) {
void VenuesController::appendRow(const VenueData &data) {
delegate()->peerListAppendRow(std::make_unique<VenueRow>(this, data));
}
void LinksController::rowPaintIcon(
void VenuesController::rowPaintIcon(
QPainter &p,
int x,
int y,
@ -331,6 +332,7 @@ void LinksController::rowPaintIcon(
{ "window-bg-over", &st::windowBgOver },
{ "window-bg-ripple", &st::windowBgRipple },
{ "window-active-text-fg", &st::windowActiveTextFg },
{ "history-to-down-shadow", &st::historyToDownShadow },
};
static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{
{ "maps-places-in-area", tr::lng_maps_places_in_area },
@ -358,6 +360,9 @@ void LinksController::rowPaintIcon(
<link href='https://api.mapbox.com/mapbox-gl-js/v3.4.0/mapbox-gl.css' rel='stylesheet' />
</head>
<body>
<div id="search_venues">
<div id="search_venues_inner"><span id="search_venues_content"></span></div>
</div>
<div id="marker">
<div id="marker_shadow" style="transform: translate(0px, -14px);">
<svg display="block" height="41px" width="27px" viewBox="0 0 27 41">
@ -467,7 +472,7 @@ void SetupVenues(
auto &lifetime = container->lifetime();
const auto delegate = lifetime.make_state<PeerListContentDelegateShow>(
show);
const auto controller = lifetime.make_state<LinksController>(
const auto controller = lifetime.make_state<VenuesController>(
&show->session(),
std::move(value));
controller->setStyleOverrides(&st::pickLocationVenueList);
@ -687,18 +692,41 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) {
const auto lon = object.value("longitude").toDouble();
_callback({ lat, lon });
close();
} else if (event == u"movestart"_q) {
} else if (event == u"move_start"_q) {
if (const auto now = _geocoderAddress.current()
; !now.isEmpty()) {
_geocoderSavedAddress = now;
_geocoderAddress = QString();
}
base::take(_geocoderResolvePostponed);
_geocoderResolveTimer.cancel();
} else if (event == u"moveend"_q) {
} else if (event == u"move_end"_q) {
const auto lat = object.value("latitude").toDouble();
const auto lon = object.value("longitude").toDouble();
_geocoderResolvePostponed = Core::GeoLocation{
const auto location = Core::GeoLocation{
.point = { lat, lon },
.accuracy = Core::GeoLocationAccuracy::Exact,
};
if (AreTheSame(_geocoderResolvingFor, location)
&& !_geocoderSavedAddress.isEmpty()) {
_geocoderAddress = base::take(_geocoderSavedAddress);
_geocoderResolveTimer.cancel();
} else {
_geocoderResolvePostponed = location;
_geocoderResolveTimer.callOnce(kResolveAddressDelay);
}
if (!AreTheSame(_venuesRequestLocation, location)) {
_webview->eval(
"LocationPicker.toggleSearchVenues(true);");
}
} else if (event == u"search_venues"_q) {
const auto lat = object.value("latitude").toDouble();
const auto lon = object.value("longitude").toDouble();
venuesRequest({
.point = { lat, lon },
.accuracy = Core::GeoLocationAccuracy::Exact,
});
}
});
});
raw->setDataRequestHandler([=](Webview::DataRequest request) {
@ -759,12 +787,12 @@ void LocationPicker::resolveAddressByTimer() {
}
void LocationPicker::resolveAddress(Core::GeoLocation location) {
if (_geocoderResolvingFor == location) {
if (AreTheSame(_geocoderResolvingFor, location)) {
return;
}
_geocoderResolvingFor = location;
const auto done = [=](Core::GeoAddress address) {
if (_geocoderResolvingFor != location) {
if (!AreTheSame(_geocoderResolvingFor, location)) {
return;
} else if (address) {
_geocoderAddress = address.name;
@ -809,14 +837,13 @@ void LocationPicker::venuesRequest(
QString query) {
query = NormalizeVenuesQuery(query);
auto &cache = _venuesCache[query];
const auto i = ranges::find(
cache,
location,
&VenuesCacheEntry::location);
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 (_venuesRequestLocation == location
} else if (AreTheSame(_venuesRequestLocation, location)
&& _venuesRequestQuery == query) {
return;
} else if (const auto oldRequestId = base::take(_venuesRequestId)) {
@ -888,7 +915,7 @@ void LocationPicker::resolveCurrentLocation() {
using namespace Core;
const auto window = _window.get();
ResolveCurrentGeoLocation(crl::guard(window, [=](GeoLocation location) {
const auto changed = (LastExactLocation != location);
const auto changed = !AreTheSame(LastExactLocation, location);
if (location.accuracy != GeoLocationAccuracy::Exact || !changed) {
return;
}

View File

@ -129,6 +129,7 @@ private:
base::Timer _geocoderResolveTimer;
Core::GeoLocation _geocoderResolvePostponed;
Core::GeoLocation _geocoderResolvingFor;
QString _geocoderSavedAddress;
rpl::variable<QString> _geocoderAddress;
rpl::variable<PickerVenueState> _venueState;