mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-28 01:52:06 +08:00
ui/MapSettings: sort destinations by recent activity (#29079)
old-commit-hash: 12073c07afbd0de42ed475c9368ad9f51fd0c631
This commit is contained in:
@@ -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).
|
||||
// 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()) <
|
||||
std::tuple(b["save_type"].toString(), b["place_name"].toString()));
|
||||
});
|
||||
emit locationsUpdated(locations);
|
||||
// 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.
|
||||
std::stable_sort(locations.begin(), locations.end(), [](const QJsonValue &a, const QJsonValue &b) {
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user