|
|
|
@ -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).
|
|
|
|
|
// 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.
|
|
|
|
|
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()) < |
|
|
|
|
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()); |
|
|
|
|
}); |
|
|
|
|
emit locationsUpdated(locations); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
} |
|
|
|
|