|
|
|
@ -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); |
|
|
|
|
} |
|
|
|
|