@ -4,6 +4,7 @@ import datetime
from openpilot . common . time_helpers import system_time_valid
from openpilot . selfdrive . ui . ui_state import ui_state
from openpilot . system . ui . lib . application import gui_app
from openpilot . system . ui . lib . multilang import tr , trn
from openpilot . system . ui . widgets import Widget , DialogResult
from openpilot . system . ui . widgets . confirm_dialog import ConfirmDialog
from openpilot . system . ui . widgets . list_view import button_item , text_item , ListItem
@ -15,7 +16,7 @@ UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond
def time_ago ( date : datetime . datetime | None ) - > str :
if not date :
return " never "
return tr ( " never " )
if not system_time_valid ( ) :
return date . strftime ( " %a % b %d % Y " )
@ -26,16 +27,16 @@ def time_ago(date: datetime.datetime | None) -> str:
diff_seconds = int ( ( now - date ) . total_seconds ( ) )
if diff_seconds < 60 :
return " now "
return tr ( " now " )
if diff_seconds < 3600 :
m = diff_seconds / / 60
return f " { m } minute { ' s ' if m != 1 else ' ' } ago "
return trn ( " {} minute ago " , " {} minutes ago " , m ) . format ( m )
if diff_seconds < 86400 :
h = diff_seconds / / 3600
return f " { h } hour { ' s ' if h != 1 else ' ' } ago "
return trn ( " {} hour ago " , " {} hours ago " , h ) . format ( h )
if diff_seconds < 604800 :
d = diff_seconds / / 86400
return f " { d } day { ' s ' if d != 1 else ' ' } ago "
return trn ( " {} day ago " , " {} days ago " , d ) . format ( d )
return date . strftime ( " %a % b %d % Y " )
@ -43,12 +44,12 @@ class SoftwareLayout(Widget):
def __init__ ( self ) :
super ( ) . __init__ ( )
self . _onroad_label = ListItem ( title = " Updates are only downloaded while the car is off. " )
self . _version_item = text_item ( " Current Version " , ui_state . params . get ( " UpdaterCurrentDescription " ) or " " )
self . _download_btn = button_item ( " Download " , " CHECK " , callback = self . _on_download_update )
self . _onroad_label = ListItem ( title = tr ( " Updates are only downloaded while the car is off. " ) )
self . _version_item = text_item ( tr ( " Current Version " ) , ui_state . params . get ( " UpdaterCurrentDescription " ) or " " )
self . _download_btn = button_item ( tr ( " Download " ) , tr ( " CHECK " ) , callback = self . _on_download_update )
# Install button is initially hidden
self . _install_btn = button_item ( " Install Update " , " INSTALL " , callback = self . _on_install_update )
self . _install_btn = button_item ( tr ( " Install Update " ) , tr ( " INSTALL " ) , callback = self . _on_install_update )
self . _install_btn . set_visible ( False )
# Track waiting-for-updater transition to avoid brief re-enable while still idle
@ -66,7 +67,7 @@ class SoftwareLayout(Widget):
self . _install_btn ,
# TODO: implement branch switching
# button_item("Target Branch", "SELECT", callback=self._on_select_branch),
button_item ( " Uninstall " , " UNINSTALL " , callback = self . _on_uninstall ) ,
button_item ( " Uninstall " , tr ( " UNINSTALL " ) , callback = self . _on_uninstall ) ,
]
return items
@ -101,19 +102,19 @@ class SoftwareLayout(Widget):
self . _download_btn . action_item . set_value ( updater_state )
else :
if failed_count > 0 :
self . _download_btn . action_item . set_value ( " failed to check for update " )
self . _download_btn . action_item . set_text ( " CHECK " )
self . _download_btn . action_item . set_value ( tr ( " failed to check for update " ) )
self . _download_btn . action_item . set_text ( tr ( " CHECK " ) )
elif fetch_available :
self . _download_btn . action_item . set_value ( " update available " )
self . _download_btn . action_item . set_text ( " DOWNLOAD " )
self . _download_btn . action_item . set_value ( tr ( " update available " ) )
self . _download_btn . action_item . set_text ( tr ( " DOWNLOAD " ) )
else :
last_update = ui_state . params . get ( " LastUpdateTime " )
if last_update :
formatted = time_ago ( last_update )
self . _download_btn . action_item . set_value ( f " up to date, last checked { formatted } " )
self . _download_btn . action_item . set_value ( tr ( " up to date, last checked {} " ) . format ( formatted ) )
else :
self . _download_btn . action_item . set_value ( " up to date, last checked never " )
self . _download_btn . action_item . set_text ( " CHECK " )
self . _download_btn . action_item . set_value ( tr ( " up to date, last checked never " ) )
self . _download_btn . action_item . set_text ( tr ( " CHECK " ) )
# If we've been waiting too long without a state change, reset state
if self . _waiting_for_updater and ( time . monotonic ( ) - self . _waiting_start_ts > UPDATED_TIMEOUT ) :
@ -127,7 +128,7 @@ class SoftwareLayout(Widget):
if update_available :
new_desc = ui_state . params . get ( " UpdaterNewDescription " ) or " "
new_release_notes = ( ui_state . params . get ( " UpdaterNewReleaseNotes " ) or b " " ) . decode ( " utf-8 " , " replace " )
self . _install_btn . action_item . set_text ( " INSTALL " )
self . _install_btn . action_item . set_text ( tr ( " INSTALL " ) )
self . _install_btn . action_item . set_value ( new_desc )
self . _install_btn . set_description ( new_release_notes )
# Enable install button for testing (like Qt showEvent)
@ -138,7 +139,7 @@ class SoftwareLayout(Widget):
def _on_download_update ( self ) :
# Check if we should start checking or start downloading
self . _download_btn . action_item . set_enabled ( False )
if self . _download_btn . action_item . text == " CHECK " :
if self . _download_btn . action_item . text == tr ( " CHECK " ) :
# Start checking for updates
self . _waiting_for_updater = True
self . _waiting_start_ts = time . monotonic ( )
@ -154,7 +155,7 @@ class SoftwareLayout(Widget):
if result == DialogResult . CONFIRM :
ui_state . params . put_bool ( " DoUninstall " , True )
dialog = ConfirmDialog ( " Are you sure you want to uninstall? " , " Uninstall " )
dialog = ConfirmDialog ( tr ( " Are you sure you want to uninstall? " ) , tr ( " Uninstall " ) )
gui_app . set_modal_overlay ( dialog , callback = handle_uninstall_confirmation )
def _on_install_update ( self ) :