ui/MapSettings: sort location JsonArray inplace (#28886)

pull/28916/head
Dean Lee 2 years ago committed by GitHub
parent daf80eaef5
commit c3fe3c8162
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 125
      selfdrive/ui/qt/maps/map_settings.cc
  2. 58
      selfdrive/ui/qt/maps/map_settings.h

@ -2,7 +2,6 @@
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
#include <vector>
#include "common/util.h" #include "common/util.h"
#include "selfdrive/ui/qt/request_repeater.h" #include "selfdrive/ui/qt/request_repeater.h"
@ -12,11 +11,8 @@ static QString shorten(const QString &str, int max_len) {
return str.size() > max_len ? str.left(max_len).trimmed() + "" : str; return str.size() > max_len ? str.left(max_len).trimmed() + "" : str;
} }
MapSettings::MapSettings(bool closeable, QWidget *parent) MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
: QFrame(parent), current_destination(nullptr) { close_icon = loadPixmap("../assets/icons/close.svg", {100, 100});
QSize icon_size(100, 100);
close_icon = loadPixmap("../assets/icons/close.svg", icon_size);
setContentsMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0);
auto *frame = new QVBoxLayout(this); auto *frame = new QVBoxLayout(this);
@ -68,7 +64,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent)
current_widget = new DestinationWidget(this); current_widget = new DestinationWidget(this);
QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() { QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() {
if (!current_destination) return; if (current_destination.empty()) return;
params.remove("NavDestination"); params.remove("NavDestination");
updateCurrentRoute(); updateCurrentRoute();
}); });
@ -85,7 +81,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent)
setStyleSheet("MapSettings { background-color: #333333; }"); setStyleSheet("MapSettings { background-color: #333333; }");
QObject::connect(NavigationRequest::instance(), &NavigationRequest::locationsUpdated, this, &MapSettings::parseResponse); QObject::connect(NavigationRequest::instance(), &NavigationRequest::locationsUpdated, this, &MapSettings::updateLocations);
QObject::connect(NavigationRequest::instance(), &NavigationRequest::nextDestinationUpdated, this, &MapSettings::updateCurrentRoute); QObject::connect(NavigationRequest::instance(), &NavigationRequest::nextDestinationUpdated, this, &MapSettings::updateCurrentRoute);
} }
@ -106,72 +102,37 @@ void MapSettings::updateCurrentRoute() {
qWarning() << "JSON Parse failed on NavDestination" << dest; qWarning() << "JSON Parse failed on NavDestination" << dest;
return; return;
} }
auto destination = std::make_unique<NavDestination>(doc.object()); current_destination = doc.object();
if (current_destination && *destination == *current_destination) return; current_widget->set(current_destination, true);
current_destination = std::move(destination);
current_widget->set(current_destination.get(), true);
} else { } else {
current_destination.reset(nullptr); current_destination = {};
current_widget->unset("", true); current_widget->unset("", true);
} }
if (isVisible()) refresh(); if (isVisible()) refresh();
} }
void MapSettings::parseResponse(const QString &response, bool success) { void MapSettings::updateLocations(const QJsonArray &locations) {
if (!success || response == cur_destinations) return; current_locations = locations;
cur_destinations = response;
refresh(); refresh();
} }
void MapSettings::refresh() { void MapSettings::refresh() {
bool has_home = false, has_work = false; setUpdatesEnabled(false);
auto destinations = std::vector<std::unique_ptr<NavDestination>>();
auto destinations_str = cur_destinations.trimmed();
if (!destinations_str.isEmpty()) {
QJsonDocument doc = QJsonDocument::fromJson(destinations_str.toUtf8());
if (doc.isNull()) {
qWarning() << "JSON Parse failed on navigation locations" << cur_destinations;
return;
}
for (auto el : doc.array()) {
auto destination = std::make_unique<NavDestination>(el.toObject());
// add home and work later if they are missing
if (destination->isFavorite()) {
if (destination->label() == NAV_FAVORITE_LABEL_HOME) has_home = true;
else if (destination->label() == NAV_FAVORITE_LABEL_WORK) has_work = true;
}
// skip current destination
if (current_destination && *destination == *current_destination) continue;
destinations.push_back(std::move(destination));
}
}
// TODO: should we build a new layout and swap it in? // TODO: should we build a new layout and swap it in?
clearLayout(destinations_layout); clearLayout(destinations_layout);
// Sort: HOME, WORK, alphabetical FAVORITES, and then most recent (as returned by API) bool has_home = false, has_work = false;
std::stable_sort(destinations.begin(), destinations.end(), [](const auto &a, const auto &b) { for (auto location : current_locations) {
if (a->isFavorite() && b->isFavorite()) { auto dest = location.toObject();
if (a->label() == NAV_FAVORITE_LABEL_HOME) return true; if (dest["save_type"].toString() == NAV_TYPE_FAVORITE) {
else if (b->label() == NAV_FAVORITE_LABEL_HOME) return false; has_home = has_home || dest["label"].toString() == NAV_FAVORITE_LABEL_HOME;
else if (a->label() == NAV_FAVORITE_LABEL_WORK) return true; has_work = has_work || dest["label"].toString() == NAV_FAVORITE_LABEL_WORK;
else if (b->label() == NAV_FAVORITE_LABEL_WORK) return false;
else return a->name() < b->name();
} }
else if (a->isFavorite()) return true; if (dest == current_destination) continue;
else if (b->isFavorite()) return false;
return false;
});
for (auto &destination : destinations) {
auto widget = new DestinationWidget(this); auto widget = new DestinationWidget(this);
widget->set(destination.get(), false); widget->set(dest, false);
QObject::connect(widget, &QPushButton::clicked, [this, dest]() {
QObject::connect(widget, &QPushButton::clicked, [this, dest = destination->toJson()]() {
navigateTo(dest); navigateTo(dest);
emit closeSettings(); emit closeSettings();
}); });
@ -189,11 +150,12 @@ void MapSettings::refresh() {
auto widget = new DestinationWidget(this); auto widget = new DestinationWidget(this);
widget->unset(NAV_FAVORITE_LABEL_WORK); widget->unset(NAV_FAVORITE_LABEL_WORK);
// TODO: refactor to remove this hack // TODO: refactor to remove this hack
int index = !has_home || (current_destination && current_destination->isFavorite() && current_destination->label() == NAV_FAVORITE_LABEL_HOME) ? 0 : 1; int index = !has_home || (current_destination["save_type"] == NAV_TYPE_FAVORITE && current_destination["label"] == NAV_FAVORITE_LABEL_HOME) ? 0 : 1;
destinations_layout->insertWidget(index, widget); destinations_layout->insertWidget(index, widget);
} }
destinations_layout->addStretch(); destinations_layout->addStretch();
setUpdatesEnabled(true);
} }
void MapSettings::navigateTo(const QJsonObject &place) { void MapSettings::navigateTo(const QJsonObject &place) {
@ -263,23 +225,23 @@ DestinationWidget::DestinationWidget(QWidget *parent) : QPushButton(parent) {
)"); )");
} }
void DestinationWidget::set(NavDestination *destination, bool current) { void DestinationWidget::set(const QJsonObject &destination, bool current) {
setProperty("current", current); setProperty("current", current);
setProperty("set", true); setProperty("set", true);
auto icon_pixmap = current ? icons().directions : icons().recent; auto icon_pixmap = current ? icons().directions : icons().recent;
auto title_text = destination->name(); auto title_text = destination["place_name"].toString();
auto subtitle_text = destination->details(); auto subtitle_text = destination["place_details"].toString();
if (destination->isFavorite()) { if (destination["save_type"] == NAV_TYPE_FAVORITE) {
if (destination->label() == NAV_FAVORITE_LABEL_HOME) { if (destination["label"] == NAV_FAVORITE_LABEL_HOME) {
icon_pixmap = icons().home; icon_pixmap = icons().home;
subtitle_text = title_text + ", " + subtitle_text;
title_text = tr("Home"); title_text = tr("Home");
subtitle_text = destination->name() + ", " + destination->details(); } else if (destination["label"] == NAV_FAVORITE_LABEL_WORK) {
} else if (destination->label() == NAV_FAVORITE_LABEL_WORK) {
icon_pixmap = icons().work; icon_pixmap = icons().work;
subtitle_text = title_text + ", " + subtitle_text;
title_text = tr("Work"); title_text = tr("Work");
subtitle_text = destination->name() + ", " + destination->details();
} else { } else {
icon_pixmap = icons().favorite; icon_pixmap = icons().favorite;
} }
@ -332,9 +294,8 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) {
// Fetch favorite and recent locations // Fetch favorite and recent locations
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations"; QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations";
RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true); RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true);
QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavigationRequest::locationsUpdated); QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavigationRequest::parseLocationsResponse);
} }
{ {
// Destination set while offline // Destination set while offline
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/next"; QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/next";
@ -358,3 +319,29 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) {
} }
} }
} }
static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); }
void NavigationRequest::parseLocationsResponse(const QString &response, bool success) {
if (!success || response == prev_response) return;
prev_response = response;
QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8());
if (doc.isNull()) {
qWarning() << "JSON Parse failed on navigation locations" << response;
return;
}
// Sort: HOME, WORK, alphabetical FAVORITES, and then most recent (as returned by API)
QJsonArray locations = doc.array();
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) {
QString a_label = a["label"].toString(), b_label = b["label"].toString();
return std::tuple(a["save_type"].toString(), (a_label.isEmpty() ? "xxx" : a_label), a["place_name"].toString()) <
std::tuple(b["save_type"].toString(), (b_label.isEmpty() ? "xxx" : b_label), b["place_name"].toString());
} else {
return false;
}
});
emit locationsUpdated(locations);
}

@ -1,7 +1,5 @@
#pragma once #pragma once
#include <memory>
#include <QFrame> #include <QFrame>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
@ -20,7 +18,6 @@ const QString NAV_TYPE_RECENT = "recent";
const QString NAV_FAVORITE_LABEL_HOME = "home"; const QString NAV_FAVORITE_LABEL_HOME = "home";
const QString NAV_FAVORITE_LABEL_WORK = "work"; const QString NAV_FAVORITE_LABEL_WORK = "work";
class NavDestination;
class DestinationWidget; class DestinationWidget;
class NavigationRequest : public QObject { class NavigationRequest : public QObject {
@ -30,13 +27,15 @@ public:
static NavigationRequest *instance(); static NavigationRequest *instance();
signals: signals:
void locationsUpdated(const QString &response, bool success); void locationsUpdated(const QJsonArray &locations);
void nextDestinationUpdated(const QString &response, bool success); void nextDestinationUpdated(const QString &response, bool success);
private: private:
NavigationRequest(QObject *parent); NavigationRequest(QObject *parent);
void parseLocationsResponse(const QString &response, bool success);
Params params; Params params;
QString prev_response;
}; };
class MapSettings : public QFrame { class MapSettings : public QFrame {
@ -45,7 +44,7 @@ public:
explicit MapSettings(bool closeable = false, QWidget *parent = nullptr); explicit MapSettings(bool closeable = false, QWidget *parent = nullptr);
void navigateTo(const QJsonObject &place); void navigateTo(const QJsonObject &place);
void parseResponse(const QString &response, bool success); void updateLocations(const QJsonArray &locations);
void updateCurrentRoute(); void updateCurrentRoute();
private: private:
@ -54,64 +53,21 @@ private:
void refresh(); void refresh();
Params params; Params params;
QString cur_destinations; QJsonArray current_locations;
QJsonObject current_destination;
QVBoxLayout *destinations_layout; QVBoxLayout *destinations_layout;
std::unique_ptr<NavDestination> current_destination;
DestinationWidget *current_widget; DestinationWidget *current_widget;
QPixmap close_icon; QPixmap close_icon;
signals: signals:
void closeSettings(); void closeSettings();
}; };
class NavDestination {
public:
explicit NavDestination(const QJsonObject &place)
: type_(place["save_type"].toString()), label_(place["label"].toString()),
name_(place["place_name"].toString()), details_(place["place_details"].toString()),
latitude_(place["latitude"].toDouble()), longitude_(place["longitude"].toDouble()) {
// if details starts with `name, ` remove it
if (details_.startsWith(name_ + ", ")) {
details_ = details_.mid(name_.length() + 2);
}
}
QString type() const { return type_; }
QString label() const { return label_; }
QString name() const { return name_; }
QString details() const { return details_; }
bool isFavorite() const { return type_ == NAV_TYPE_FAVORITE; }
bool isRecent() const { return type_ == NAV_TYPE_RECENT; }
bool operator==(const NavDestination &rhs) {
return type_ == rhs.type_ && label_ == rhs.label_ && name_ == rhs.name_ &&
details_ == rhs.details_ && latitude_ == rhs.latitude_ && longitude_ == rhs.longitude_;
}
QJsonObject toJson() const {
QJsonObject obj;
obj["save_type"] = type_;
obj["label"] = label_;
obj["place_name"] = name_;
obj["place_details"] = details_;
obj["latitude"] = latitude_;
obj["longitude"] = longitude_;
return obj;
}
private:
QString type_, label_, name_, details_;
double latitude_, longitude_;
};
class DestinationWidget : public QPushButton { class DestinationWidget : public QPushButton {
Q_OBJECT Q_OBJECT
public: public:
explicit DestinationWidget(QWidget *parent = nullptr); explicit DestinationWidget(QWidget *parent = nullptr);
void set(const QJsonObject &location, bool current = false);
void set(NavDestination *, bool current = false);
void unset(const QString &label, bool current = false); void unset(const QString &label, bool current = false);
signals: signals:

Loading…
Cancel
Save