diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index 6a449b594d..0b09db7bc6 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -2,7 +2,6 @@ #include #include -#include #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(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>(); - - 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(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); +} diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index ed9d0c980b..2326fb3724 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -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 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: