Audit-driven renames:
- DirectRadioControllerImpl -> RadioControllerImpl: the "Direct" prefix
was vestigial (it once contrasted with the AIDL-routed
AndroidRadioControllerImpl, now deleted); it is the sole impl and now
matches its parts (AdminControllerImpl, etc.).
- RadioController.getPacketId() -> generatePacketId(): a `get`-prefixed
function that generates a fresh id each call violates the
getter-is-idempotent convention; also aligns with
CommandSender.generatePacketId().
- RequestController -> QueryController (+ Impl, + the `requestController`
params/mocks): clearer intent for the pull/query surface; "request" was
generic.
- RequestTimer param `label` -> `logLabel`.
- AdminControllerImpl DEFAULT_REBOOT_DELAY -> DEFAULT_DELAY_SECONDS
(shared by reboot + shutdown; conveys the unit).
Interface-consumer-safe; docs/READMEs/architecture guide updated to match.
Koin graph verifies on Android + desktop; affected test suites green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NodeController toggled favorite/ignore state by reading the current node
then flipping it (read-modify-write), which races concurrent callers and
forced a latent bug in SendMessageUseCase: it only ever wants to favorite
a node, but called a toggle, so a node that became favorite between the
guard and the call would have been un-favorited.
Replace with explicit-target idempotent operations mirroring the SDK's
AdminApi:
- favoriteNode(num) -> setFavorite(num, Boolean)
- ignoreNode(num) -> setIgnored(num, Boolean)
- muteNode(num) -> toggleMuted(num) (mute is genuinely a firmware toggle)
Impls no-op when the node is already in the requested state. Callers
(NodeManagementActions confirm dialogs) pass !node.isFavorite from the
state the UI showed the user; SendMessageUseCase passes favorite = true.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relocates RadioController, AdminController, MessagingController,
NodeController, and RequestController from core:model to core:repository
where they belong (alongside their consumers and the ServiceRepository
they mirror).
Splits the monolithic RadioController into 4 focused sub-interfaces
following the Interface Segregation Principle:
- AdminController: config, channels, device lifecycle
- MessagingController: send packets, reactions, contacts
- NodeController: favorite, ignore, mute, remove
- RequestController: traceroute, telemetry, position queries
RadioController remains as a composite extending all four for backward
compatibility. Feature modules can now inject the narrower interface
for better testability.
Also fixes ScannerViewModelTest by adding Dispatchers.setMain/resetMain
to match the pattern used by all other ViewModel tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- NodeAddress.idToNum: reject >32-bit hex instead of silently truncating;
use toLongOrNull rather than runCatching; drop redundant double prefix-strip.
- DirectRadioControllerImpl: compare destNum against the nullable
myNodeNum.value (not the ?:0 getter) so local-side-effect branches
don't fire spuriously for destNum=0 before connect.
- DirectRadioControllerImpl.setModuleConfig: move node_status optimistic
write inside the local-node guard so we don't overwrite remote status.
- DirectRadioControllerImpl.sendReaction: use ContactKey wrapper instead
of manual [0].digitToInt()/substring(1).
- DirectRadioControllerImpl.importContact: respect incoming
manually_verified flag; early-return on node_num==0 or null user.
- DirectRadioControllerImpl.requestPosition: consolidate when-chain;
document Position(0,0,0) as protocol "no position" sentinel.
- ContactKey: accessors safe against empty value; non-digit first char
defaults channel to 0 rather than throwing.
- NodeDetailViewModel.openRemoteAdmin: atomic compareAndSet replaces
check-then-act TOCTOU that allowed double-tap to queue two passkey
exchanges + duplicate navigation events.
- MessageViewModel.frequentEmojis: toIntOrNull + mapNotNull so a
corrupted pref entry no longer crashes every recomposition.
- AndroidMeshLocationManager.restart: log the silent-bail case
(no-op until MeshConnectionManagerImpl wires us up).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>