188 lines
6.7 KiB
Python
188 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
|
|
import pyray as rl
|
|
|
|
from openpilot.system.hardware.base import Profile
|
|
from openpilot.system.hardware.tici.esim_manager import EsimManager, EsimUiState, get_esim_manager
|
|
from openpilot.system.ui.lib.application import gui_app
|
|
from openpilot.system.ui.widgets import DialogResult, NavWidget
|
|
from openpilot.system.ui.widgets.esim_scanner import EsimQrScannerDialog
|
|
from openpilot.selfdrive.ui.mici.widgets.button import NeonBigButton
|
|
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog, BigMultiOptionDialog, BigConfirmationDialogV2
|
|
from openpilot.system.ui.widgets.scroller import Scroller
|
|
|
|
|
|
class EsimUIMici(NavWidget):
|
|
def __init__(self, back_callback: Callable):
|
|
super().__init__()
|
|
self._manager: EsimManager = get_esim_manager()
|
|
self._state = EsimUiState()
|
|
self._callback_registered = False
|
|
self._scroller = Scroller([], snap_items=False)
|
|
self._rebuild_scroller()
|
|
self.set_back_callback(back_callback)
|
|
|
|
def show_event(self):
|
|
super().show_event()
|
|
self._scroller.show_event()
|
|
if not self._callback_registered:
|
|
self._manager.add_callback(self._on_state_update)
|
|
self._callback_registered = True
|
|
self._manager.refresh_profiles()
|
|
|
|
def hide_event(self):
|
|
super().hide_event()
|
|
if self._callback_registered:
|
|
self._manager.remove_callback(self._on_state_update)
|
|
self._callback_registered = False
|
|
|
|
def _is_busy(self) -> bool:
|
|
return self._state.busy
|
|
|
|
def _status_text(self) -> str:
|
|
if self._state.message:
|
|
return self._state.message
|
|
return self._state.state.value
|
|
|
|
def _on_state_update(self, state: EsimUiState):
|
|
self._state = state
|
|
self._rebuild_scroller()
|
|
|
|
def _show_choice_dialog(self, title: str, options: list[str], callback: Callable[[str], None]) -> None:
|
|
if not options:
|
|
return
|
|
dlg = BigMultiOptionDialog(
|
|
options,
|
|
options[0],
|
|
right_btn="check",
|
|
right_btn_callback=lambda: callback(dlg.get_selected_option()),
|
|
)
|
|
gui_app.set_modal_overlay(dlg)
|
|
|
|
def _rebuild_scroller(self):
|
|
widgets = []
|
|
|
|
status_btn = NeonBigButton("status", chips=[self._status_text()])
|
|
status_btn.set_enabled(False)
|
|
widgets.append(status_btn)
|
|
|
|
refresh_btn = NeonBigButton("refresh profiles")
|
|
refresh_btn.set_enabled(lambda: not self._is_busy())
|
|
refresh_btn.set_click_callback(lambda: self._manager.refresh_profiles())
|
|
widgets.append(refresh_btn)
|
|
|
|
add_btn = NeonBigButton("add profile", chips=["scan qr / enter code"])
|
|
add_btn.set_enabled(lambda: not self._is_busy())
|
|
add_btn.set_click_callback(self._on_add_profile)
|
|
widgets.append(add_btn)
|
|
|
|
profiles = self._state.profiles or []
|
|
for p in profiles:
|
|
widgets.append(self._make_profile_button(p))
|
|
|
|
self._scroller = Scroller(widgets, snap_items=False)
|
|
|
|
def _make_profile_button(self, profile: Profile) -> NeonBigButton:
|
|
title = profile.nickname if profile.nickname else profile.iccid
|
|
value = f"{profile.provider or 'provider unknown'}{' • active' if profile.enabled else ''}"
|
|
btn = NeonBigButton(title, chips=[value])
|
|
btn.set_enabled(lambda: not self._is_busy())
|
|
btn.set_click_callback(lambda profile=profile: self._on_profile_selected(profile))
|
|
return btn
|
|
|
|
def _on_add_profile(self):
|
|
options = ["scan qr", "enter code"]
|
|
|
|
def _selected(option: str):
|
|
if option == "scan qr":
|
|
self._scan_qr()
|
|
elif option == "enter code":
|
|
self._manual_entry()
|
|
|
|
self._show_choice_dialog("add esim profile", options, _selected)
|
|
|
|
def _scan_qr(self):
|
|
scanner = EsimQrScannerDialog()
|
|
self._manager.set_scanning_state(True)
|
|
|
|
def _done(result: int):
|
|
self._manager.set_scanning_state(False)
|
|
if result != DialogResult.CONFIRM or not scanner.code:
|
|
return
|
|
self._prompt_nickname_and_add(scanner.code)
|
|
|
|
gui_app.set_modal_overlay(scanner, _done)
|
|
|
|
def _manual_entry(self):
|
|
dlg = BigInputDialog("enter LPA activation code...", "", minimum_length=1,
|
|
confirm_callback=lambda code: self._prompt_nickname_and_add(code.strip()) if code.strip() else None)
|
|
gui_app.set_modal_overlay(dlg)
|
|
|
|
def _prompt_nickname_and_add(self, code: str):
|
|
dlg = BigInputDialog("optional nickname...", "", minimum_length=0,
|
|
confirm_callback=lambda nickname: self._manager.add_profile(code, nickname.strip() or None))
|
|
gui_app.set_modal_overlay(dlg)
|
|
|
|
def _on_profile_selected(self, profile: Profile):
|
|
options = []
|
|
if not profile.enabled:
|
|
options.append("activate")
|
|
options.append("rename")
|
|
if self._manager.is_comma_profile(profile.iccid):
|
|
options.append("remove comma psim")
|
|
elif not profile.enabled:
|
|
options.append("delete")
|
|
|
|
def _selected(option: str):
|
|
if option == "activate":
|
|
self._manager.switch_profile(profile.iccid)
|
|
elif option == "rename":
|
|
self._rename_profile(profile)
|
|
elif option == "remove comma psim":
|
|
self._remove_comma_profile()
|
|
elif option == "delete":
|
|
self._manager.delete_profile(profile.iccid)
|
|
|
|
self._show_choice_dialog("profile actions", options, _selected)
|
|
|
|
def _rename_profile(self, profile: Profile):
|
|
dlg = BigInputDialog("rename profile...", profile.nickname or "", minimum_length=1,
|
|
confirm_callback=lambda nickname: self._manager.rename_profile(profile.iccid, nickname.strip()) if nickname.strip() else None)
|
|
gui_app.set_modal_overlay(dlg)
|
|
|
|
def _remove_comma_profile(self):
|
|
dlg = BigDialog(
|
|
"Warning",
|
|
"This will permanently wipe the Comma pSIM profile from the SIM.",
|
|
right_btn="check",
|
|
right_btn_callback=self._remove_comma_profile_final_warning,
|
|
)
|
|
gui_app.set_modal_overlay(dlg)
|
|
|
|
def _remove_comma_profile_final_warning(self):
|
|
dlg = BigDialog(
|
|
"Final Warning",
|
|
"You must use your own eSIM profile after this. You cannot use Comma Prime again unless you buy a new SIM from comma.",
|
|
right_btn="check",
|
|
right_btn_callback=self._confirm_remove_comma_profile,
|
|
)
|
|
gui_app.set_modal_overlay(dlg)
|
|
|
|
def _confirm_remove_comma_profile(self):
|
|
dlg = BigConfirmationDialogV2(
|
|
"slide to remove\ncomma psim",
|
|
"icons_mici/settings/network/new/trash.png",
|
|
red=True,
|
|
confirm_callback=self._manager.bootstrap,
|
|
)
|
|
gui_app.set_modal_overlay(dlg)
|
|
|
|
def _render(self, rect: rl.Rectangle):
|
|
if not self._manager.is_supported():
|
|
from openpilot.system.ui.widgets.label import gui_label
|
|
gui_label(rect, "Insert the original comma SIM card that came with the device to use eSIM", 48, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
|
return
|
|
self._scroller.render(rect)
|