ui/MapSettings: sort destinations by recent activity (#29079)

old-commit-hash: 12073c07af
beeps
Dean Lee 2 years ago committed by GitHub
parent 3d04606233
commit 2e4a23510a
  1. 1
      common/params.cc
  2. 136
      selfdrive/ui/qt/maps/map_settings.cc
  3. 22
      selfdrive/ui/qt/maps/map_settings.h

@ -158,6 +158,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"LongitudinalPersonality", PERSISTENT},
{"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"NavDestinationWaypoints", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"NavPastDestinations", PERSISTENT},
{"NavSettingLeftSide", PERSISTENT},
{"NavSettingTime24h", PERSISTENT},
{"NavdRender", PERSISTENT},

@ -9,6 +9,12 @@
#include "selfdrive/ui/qt/request_repeater.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); }
static bool locationEqual(const QJsonValue &v1, const QJsonValue &v2) {
return v1["latitude"] == v2["latitude"] && v1["longitude"] == v2["longitude"];
}
MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
setContentsMargins(0, 0, 0, 0);
setAttribute(Qt::WA_NoMousePropagation);
@ -61,11 +67,8 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
frame->addSpacing(32);
current_widget = new DestinationWidget(this);
QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() {
if (current_destination.empty()) return;
params.remove("NavDestination");
updateCurrentRoute();
});
QObject::connect(current_widget, &DestinationWidget::actionClicked,
[]() { NavManager::instance()->setCurrentDestination({}); });
frame->addWidget(current_widget);
frame->addSpacing(32);
@ -84,40 +87,16 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
frame->addWidget(destinations_scroller);
setStyleSheet("MapSettings { background-color: #333333; }");
QObject::connect(NavigationRequest::instance(), &NavigationRequest::locationsUpdated, this, &MapSettings::updateLocations);
QObject::connect(NavigationRequest::instance(), &NavigationRequest::nextDestinationUpdated, this, &MapSettings::updateCurrentRoute);
current_locations = NavigationRequest::instance()->currentLocations();
QObject::connect(NavManager::instance(), &NavManager::updated, this, &MapSettings::refresh);
}
void MapSettings::showEvent(QShowEvent *event) {
updateCurrentRoute();
}
void MapSettings::updateCurrentRoute() {
auto dest = QString::fromStdString(params.get("NavDestination"));
if (dest.size()) {
QJsonDocument doc = QJsonDocument::fromJson(dest.trimmed().toUtf8());
if (doc.isNull()) {
qWarning() << "JSON Parse failed on NavDestination" << dest;
return;
}
current_destination = doc.object();
current_widget->set(current_destination, true);
} else {
current_destination = {};
current_widget->unset("", true);
}
if (isVisible()) refresh();
}
void MapSettings::updateLocations(const QJsonArray &locations) {
current_locations = locations;
refresh();
}
void MapSettings::refresh() {
if (!isVisible()) return;
setUpdatesEnabled(false);
auto get_w = [this](int i) {
@ -129,11 +108,17 @@ void MapSettings::refresh() {
return w;
};
const auto current_dest = NavManager::instance()->currentDestination();
if (!current_dest.isEmpty()) {
current_widget->set(current_dest, true);
} else {
current_widget->unset("", true);
}
home_widget->unset(NAV_FAVORITE_LABEL_HOME);
work_widget->unset(NAV_FAVORITE_LABEL_WORK);
int n = 0;
for (auto location : current_locations) {
for (auto location : NavManager::instance()->currentLocations()) {
DestinationWidget *w = nullptr;
auto dest = location.toObject();
if (dest["save_type"].toString() == NAV_TYPE_FAVORITE) {
@ -143,7 +128,7 @@ void MapSettings::refresh() {
}
w = w ? w : get_w(n++);
w->set(dest, false);
w->setVisible(dest != current_destination);
w->setVisible(!locationEqual(dest, current_dest));
}
for (; n < widgets.size(); ++n) widgets[n]->setVisible(false);
@ -151,9 +136,7 @@ void MapSettings::refresh() {
}
void MapSettings::navigateTo(const QJsonObject &place) {
QJsonDocument doc(place);
params.put("NavDestination", doc.toJson().toStdString());
updateCurrentRoute();
NavManager::instance()->setCurrentDestination(place);
emit closeSettings();
}
@ -279,24 +262,26 @@ void DestinationWidget::unset(const QString &label, bool current) {
setVisible(true);
}
// singleton NavigationRequest
// singleton NavManager
NavigationRequest *NavigationRequest::instance() {
static NavigationRequest *request = new NavigationRequest(qApp);
NavManager *NavManager::instance() {
static NavManager *request = new NavManager(qApp);
return request;
}
NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) {
NavManager::NavManager(QObject *parent) : QObject(parent) {
locations = QJsonDocument::fromJson(params.get("NavPastDestinations").c_str()).array();
current_dest = QJsonDocument::fromJson(params.get("NavDestination").c_str()).object();
if (auto dongle_id = getDongleId()) {
{
// Fetch favorite and recent locations
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations";
RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true);
QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavigationRequest::parseLocationsResponse);
QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavManager::parseLocationsResponse);
}
{
auto param_watcher = new ParamWatcher(this);
QObject::connect(param_watcher, &ParamWatcher::paramChanged, this, &NavigationRequest::nextDestinationUpdated);
QObject::connect(param_watcher, &ParamWatcher::paramChanged, this, &NavManager::updated);
// Destination set while offline
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/next";
@ -316,14 +301,14 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) {
// athena can set destination at any time
param_watcher->addParam("NavDestination");
current_dest = QJsonDocument::fromJson(params.get("NavDestination").c_str()).object();
emit updated();
});
}
}
}
static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); }
void NavigationRequest::parseLocationsResponse(const QString &response, bool success) {
void NavManager::parseLocationsResponse(const QString &response, bool success) {
if (!success || response == prev_response) return;
prev_response = response;
@ -333,13 +318,62 @@ void NavigationRequest::parseLocationsResponse(const QString &response, bool suc
return;
}
// Sort: alphabetical FAVORITES, and then most recent (as returned by API).
// set last activity time.
auto remote_locations = doc.array();
for (QJsonValueRef loc : remote_locations) {
auto obj = loc.toObject();
obj.insert("time", getLastActivity(obj));
loc = obj;
}
locations = remote_locations;
sortLocations();
emit updated();
}
void NavManager::sortLocations() {
// Sort: alphabetical FAVORITES, and then most recent.
// We don't need to care about the ordering of HOME and WORK. DestinationWidget always displays them at the top.
locations = doc.array();
std::stable_sort(locations.begin(), locations.end(), [](const QJsonValue &a, const QJsonValue &b) {
bool has_favorite = a["save_type"] == NAV_TYPE_FAVORITE || b["save_type"] == NAV_TYPE_FAVORITE;
return has_favorite && (std::tuple(a["save_type"].toString(), a["place_name"].toString()) <
if (a["save_type"] == NAV_TYPE_FAVORITE || b["save_type"] == NAV_TYPE_FAVORITE) {
return (std::tuple(a["save_type"].toString(), a["place_name"].toString()) <
std::tuple(b["save_type"].toString(), b["place_name"].toString()));
} else {
return a["time"].toVariant().toLongLong() > b["time"].toVariant().toLongLong();
}
});
write_param_future = std::async(std::launch::async, [destinations = QJsonArray(locations)]() {
Params().put("NavPastDestinations", QJsonDocument(destinations).toJson().toStdString());
});
emit locationsUpdated(locations);
}
qint64 NavManager::getLastActivity(const QJsonObject &loc) const {
qint64 last_activity = 0;
auto it = std::find_if(locations.begin(), locations.end(),
[&loc](const QJsonValue &l) { return locationEqual(loc, l); });
if (it != locations.end()) {
auto tm = it->toObject().value("time");
if (!tm.isUndefined() && !tm.isNull()) {
last_activity = tm.toVariant().toLongLong();
}
}
return last_activity;
}
void NavManager::setCurrentDestination(const QJsonObject &loc) {
current_dest = loc;
if (!current_dest.isEmpty()) {
current_dest["time"] = QDateTime::currentSecsSinceEpoch();
auto it = std::find_if(locations.begin(), locations.end(),
[&loc](const QJsonValue &l) { return locationEqual(loc, l); });
if (it != locations.end()) {
*it = current_dest;
sortLocations();
}
params.put("NavDestination", QJsonDocument(current_dest).toJson().toStdString());
} else {
params.remove("NavDestination");
}
emit updated();
}

@ -1,5 +1,6 @@
#pragma once
#include <future>
#include <vector>
#include <QFrame>
@ -22,42 +23,41 @@ const QString NAV_FAVORITE_LABEL_WORK = "work";
class DestinationWidget;
class NavigationRequest : public QObject {
class NavManager : public QObject {
Q_OBJECT
public:
static NavigationRequest *instance();
static NavManager *instance();
QJsonArray currentLocations() const { return locations; }
QJsonObject currentDestination() const { return current_dest; }
void setCurrentDestination(const QJsonObject &loc);
qint64 getLastActivity(const QJsonObject &loc) const;
signals:
void locationsUpdated(const QJsonArray &locations);
void nextDestinationUpdated();
void updated();
private:
NavigationRequest(QObject *parent);
NavManager(QObject *parent);
void parseLocationsResponse(const QString &response, bool success);
void sortLocations();
Params params;
QString prev_response;
QJsonArray locations;
QJsonObject current_dest;
std::future<void> write_param_future;
};
class MapSettings : public QFrame {
Q_OBJECT
public:
explicit MapSettings(bool closeable = false, QWidget *parent = nullptr);
void navigateTo(const QJsonObject &place);
void updateLocations(const QJsonArray &locations);
void updateCurrentRoute();
private:
void showEvent(QShowEvent *event) override;
void refresh();
Params params;
QJsonArray current_locations;
QJsonObject current_destination;
QVBoxLayout *destinations_layout;
DestinationWidget *current_widget;
DestinationWidget *home_widget;

Loading…
Cancel
Save