ui/MapSettings: sort location JsonArray inplace (#28886)
old-commit-hash: c3fe3c816298fc862b3136a7f52822a4c0525393
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <vector>
|
||||
|
||||
#include "common/util.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;
|
||||
}
|
||||
|
||||
MapSettings::MapSettings(bool closeable, QWidget *parent)
|
||||
: QFrame(parent), current_destination(nullptr) {
|
||||
QSize icon_size(100, 100);
|
||||
close_icon = loadPixmap("../assets/icons/close.svg", icon_size);
|
||||
|
||||
MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
|
||||
close_icon = loadPixmap("../assets/icons/close.svg", {100, 100});
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto *frame = new QVBoxLayout(this);
|
||||
@@ -68,7 +64,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent)
|
||||
|
||||
current_widget = new DestinationWidget(this);
|
||||
QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() {
|
||||
if (!current_destination) return;
|
||||
if (current_destination.empty()) return;
|
||||
params.remove("NavDestination");
|
||||
updateCurrentRoute();
|
||||
});
|
||||
@@ -85,7 +81,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -106,72 +102,37 @@ void MapSettings::updateCurrentRoute() {
|
||||
qWarning() << "JSON Parse failed on NavDestination" << dest;
|
||||
return;
|
||||
}
|
||||
auto destination = std::make_unique<NavDestination>(doc.object());
|
||||
if (current_destination && *destination == *current_destination) return;
|
||||
current_destination = std::move(destination);
|
||||
current_widget->set(current_destination.get(), true);
|
||||
current_destination = doc.object();
|
||||
current_widget->set(current_destination, true);
|
||||
} else {
|
||||
current_destination.reset(nullptr);
|
||||
current_destination = {};
|
||||
current_widget->unset("", true);
|
||||
}
|
||||
if (isVisible()) refresh();
|
||||
}
|
||||
|
||||
void MapSettings::parseResponse(const QString &response, bool success) {
|
||||
if (!success || response == cur_destinations) return;
|
||||
cur_destinations = response;
|
||||
void MapSettings::updateLocations(const QJsonArray &locations) {
|
||||
current_locations = locations;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void MapSettings::refresh() {
|
||||
bool has_home = false, has_work = 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));
|
||||
}
|
||||
}
|
||||
|
||||
setUpdatesEnabled(false);
|
||||
// TODO: should we build a new layout and swap it in?
|
||||
clearLayout(destinations_layout);
|
||||
|
||||
// Sort: HOME, WORK, alphabetical FAVORITES, and then most recent (as returned by API)
|
||||
std::stable_sort(destinations.begin(), destinations.end(), [](const auto &a, const auto &b) {
|
||||
if (a->isFavorite() && b->isFavorite()) {
|
||||
if (a->label() == NAV_FAVORITE_LABEL_HOME) return true;
|
||||
else if (b->label() == NAV_FAVORITE_LABEL_HOME) return false;
|
||||
else if (a->label() == NAV_FAVORITE_LABEL_WORK) return true;
|
||||
else if (b->label() == NAV_FAVORITE_LABEL_WORK) return false;
|
||||
else return a->name() < b->name();
|
||||
bool has_home = false, has_work = false;
|
||||
for (auto location : current_locations) {
|
||||
auto dest = location.toObject();
|
||||
if (dest["save_type"].toString() == NAV_TYPE_FAVORITE) {
|
||||
has_home = has_home || dest["label"].toString() == NAV_FAVORITE_LABEL_HOME;
|
||||
has_work = has_work || dest["label"].toString() == NAV_FAVORITE_LABEL_WORK;
|
||||
}
|
||||
else if (a->isFavorite()) return true;
|
||||
else if (b->isFavorite()) return false;
|
||||
return false;
|
||||
});
|
||||
if (dest == current_destination) continue;
|
||||
|
||||
for (auto &destination : destinations) {
|
||||
auto widget = new DestinationWidget(this);
|
||||
widget->set(destination.get(), false);
|
||||
|
||||
QObject::connect(widget, &QPushButton::clicked, [this, dest = destination->toJson()]() {
|
||||
widget->set(dest, false);
|
||||
QObject::connect(widget, &QPushButton::clicked, [this, dest]() {
|
||||
navigateTo(dest);
|
||||
emit closeSettings();
|
||||
});
|
||||
@@ -189,11 +150,12 @@ void MapSettings::refresh() {
|
||||
auto widget = new DestinationWidget(this);
|
||||
widget->unset(NAV_FAVORITE_LABEL_WORK);
|
||||
// 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->addStretch();
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
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("set", true);
|
||||
|
||||
auto icon_pixmap = current ? icons().directions : icons().recent;
|
||||
auto title_text = destination->name();
|
||||
auto subtitle_text = destination->details();
|
||||
auto title_text = destination["place_name"].toString();
|
||||
auto subtitle_text = destination["place_details"].toString();
|
||||
|
||||
if (destination->isFavorite()) {
|
||||
if (destination->label() == NAV_FAVORITE_LABEL_HOME) {
|
||||
if (destination["save_type"] == NAV_TYPE_FAVORITE) {
|
||||
if (destination["label"] == NAV_FAVORITE_LABEL_HOME) {
|
||||
icon_pixmap = icons().home;
|
||||
subtitle_text = title_text + ", " + subtitle_text;
|
||||
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;
|
||||
subtitle_text = title_text + ", " + subtitle_text;
|
||||
title_text = tr("Work");
|
||||
subtitle_text = destination->name() + ", " + destination->details();
|
||||
} else {
|
||||
icon_pixmap = icons().favorite;
|
||||
}
|
||||
@@ -332,9 +294,8 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) {
|
||||
// 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::locationsUpdated);
|
||||
QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavigationRequest::parseLocationsResponse);
|
||||
}
|
||||
|
||||
{
|
||||
// Destination set while offline
|
||||
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
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
@@ -20,7 +18,6 @@ const QString NAV_TYPE_RECENT = "recent";
|
||||
const QString NAV_FAVORITE_LABEL_HOME = "home";
|
||||
const QString NAV_FAVORITE_LABEL_WORK = "work";
|
||||
|
||||
class NavDestination;
|
||||
class DestinationWidget;
|
||||
|
||||
class NavigationRequest : public QObject {
|
||||
@@ -30,13 +27,15 @@ public:
|
||||
static NavigationRequest *instance();
|
||||
|
||||
signals:
|
||||
void locationsUpdated(const QString &response, bool success);
|
||||
void locationsUpdated(const QJsonArray &locations);
|
||||
void nextDestinationUpdated(const QString &response, bool success);
|
||||
|
||||
private:
|
||||
NavigationRequest(QObject *parent);
|
||||
void parseLocationsResponse(const QString &response, bool success);
|
||||
|
||||
Params params;
|
||||
QString prev_response;
|
||||
};
|
||||
|
||||
class MapSettings : public QFrame {
|
||||
@@ -45,7 +44,7 @@ public:
|
||||
explicit MapSettings(bool closeable = false, QWidget *parent = nullptr);
|
||||
|
||||
void navigateTo(const QJsonObject &place);
|
||||
void parseResponse(const QString &response, bool success);
|
||||
void updateLocations(const QJsonArray &locations);
|
||||
void updateCurrentRoute();
|
||||
|
||||
private:
|
||||
@@ -54,64 +53,21 @@ private:
|
||||
void refresh();
|
||||
|
||||
Params params;
|
||||
QString cur_destinations;
|
||||
QJsonArray current_locations;
|
||||
QJsonObject current_destination;
|
||||
QVBoxLayout *destinations_layout;
|
||||
std::unique_ptr<NavDestination> current_destination;
|
||||
DestinationWidget *current_widget;
|
||||
|
||||
QPixmap close_icon;
|
||||
|
||||
signals:
|
||||
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 {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DestinationWidget(QWidget *parent = nullptr);
|
||||
|
||||
void set(NavDestination *, bool current = false);
|
||||
void set(const QJsonObject &location, bool current = false);
|
||||
void unset(const QString &label, bool current = false);
|
||||
|
||||
signals:
|
||||
|
||||
Reference in New Issue
Block a user