@ -9,6 +9,17 @@
# 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 " ] ;
}
static qint64 convertTimestampToEpoch ( const QString & timestamp ) {
QDateTime dt = QDateTime : : fromString ( timestamp , Qt : : ISODate ) ;
return dt . isValid ( ) ? dt . toSecsSinceEpoch ( ) : 0 ;
}
MapSettings : : MapSettings ( bool closeable , QWidget * parent ) : QFrame ( parent ) {
setContentsMargins ( 0 , 0 , 0 , 0 ) ;
setAttribute ( Qt : : WA_NoMousePropagation ) ;
@ -61,11 +72,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 +92,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 +113,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 +133,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 +141,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 +267,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 : : nextDestinationU pdated ) ;
QObject : : connect ( param_watcher , & ParamWatcher : : paramChanged , this , & NavManager : : u pdated ) ;
// Destination set while offline
QString url = CommaApi : : BASE_URL + " /v1/navigation/ " + * dongle_id + " /next " ;
@ -316,14 +306,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 +323,63 @@ 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 ( ) ;
auto serverTime = convertTimestampToEpoch ( obj [ " modified " ] . toString ( ) ) ;
obj . insert ( " time " , qMax ( serverTime , 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 ( ) ) <
std : : tuple ( b [ " save_type " ] . toString ( ) , b [ " 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 ( ) ;
}