Mirror the GPS-disabled-vs-permission-denied distinction for the BLE and
network transports:
- core/ui: add isBluetoothDisabled() (adapter off) + isWifiUnavailable()
(no Wi-Fi/Ethernet), plus rememberOpenBluetoothSettings()/rememberOpenWifiSettings()
— alongside the existing isGpsDisabled()/rememberOpenLocationSettings()
- extract a reusable RecoveryCard from PermissionRecoveryCard (errorContainer
message box + one recovery action); PermissionRecoveryCard now delegates to it
- ConnectionsScreen: when a transport's permission is granted but its adapter is
off, show an inline recovery banner. The BLE scan toggle routes to Bluetooth
settings when the radio is off (scanning can't work); the network banner is
informational (manual TCP can still work off-Wi-Fi)
- detection refreshes on ON_RESUME, so the banner clears after the user returns
from the adapter settings screen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AndroidScannerViewModel: keep main's armTransport bonding-retry structure;
apply the SecurityException + USB string-resource conversions on top; drop the
now-moot bonding_failed string (main no longer errors on flaky bonding)
- NodeDetailScreens: wrap onRequestPosition/node in rememberUpdatedState for the
grant LaunchedEffect (LambdaParameterInRestartableEffect)
- spotless: blank lines between multiline when-conditions (TakPermissionUtil,
ConnectionsScreen)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- migrate all remaining permission call sites (PrivacySection, NodeDetailScreens,
TakPermissionUtil, ConnectionsScreen) to rememberXxxPermissionState(); delete the
old rememberRequestXxx/isXxx wrappers — single source of truth, resolves the
FINE-vs-FINE|COARSE location-grant conflict (F-5)
- add in-context Bluetooth + local-network requests with permanent-denial→settings
recovery on the Connections scan toggles (F-1)
- pre-Android-12 Bluetooth state reports granted instead of delegating to location,
fixing the intro Bluetooth-screen regression (F-2)
- null Activity no longer collapses to PERMANENTLY_DENIED (F-13)
- extract + test isPermissionGroupGranted (requireAll coarse-location logic) (F-7)
- make granular PermissionRecoveryCard overload internal (F-14)
- order PermissionStatus by lifecycle (F-16)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- PermissionUiState: drop misleading data class, mark @Stable (F-4)
- share grantedPermissionUiState() across platform stubs (F-12)
- remove refreshTrigger indirection; update state in result callback (F-11)
- guard rememberOpenLocationSettings against ActivityNotFoundException (F-6)
- barcode: reopen scanner after grant from recovery card; add dialog heading (F-3, F-8)
- strings: bonding message gains recovery action; camera rationale active voice;
internationalize generic bonding-failure message (F-9, F-10, F-17)
- parallel KDoc bullets in PermissionRecoveryCard (F-15)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migrate intro, both map flavors, and the barcode scanner to the native
rememberXxxPermissionState() helpers; remove accompanist-permissions from
the version catalog, feature convention plugin, and module build files.
Add user-facing denial recovery where it was previously silent: barcode
camera shows a PermissionRecoveryCard, USB permission denial surfaces an
error message, and the map location button routes permanent denial to app
settings. Convert the Bluetooth bonding error to a string resource.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add PermissionStatus enum, pure computePermissionStatus classifier,
PermissionUiState holder, reactive rememberXxxPermissionState()
composables (location/bluetooth/notification/local-network/camera),
rememberOpenAppSettings(), and a shared PermissionRecoveryCard.
The has-been-requested flag is persisted synchronously (SharedPreferences,
written from the launcher result callback) and combined with
shouldShowRequestPermissionRationale to distinguish first-request from
permanent denial. Pre-Android-12 Bluetooth state delegates to location.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
configureCommon() applied setMultipleConnectionPool(maxNumOfReaders = 4)
to every database, including the in-memory ones used by tests. A read on
a pooled reader connection can observe a snapshot older than the latest
write on the writer connection, so a read immediately after a write may
return stale rows.
DeviceLinkRepositoryImplTest.reconcilePrunesShortCodesNoLongerInCatalog
read [a, b] (the pre-prune state) instead of [a] after a deleteNotIn —
passing locally but flaking on CI depending on connection-assignment
timing (failed shard-core on #5738; the identical code passed on #5780).
In-memory builders now pass multiConnection = false so reads serialize
behind writes on one connection. Production/file databases keep the
multi-reader pool.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The google-flavor AppFunctionsModule registered AppFunctionStateSync
with createdAtStart = true. Eager creation needs the androidContext
binding and immediately spawns the prefs-observing sync coroutine —
so any Koin graph built outside a running app failed with
NoDefinitionFoundException for android.content.Context. That broke
KoinVerificationTest.verifyTypedBootstrapLoadsModuleGraph (the typed
koinApplication<AndroidKoinApp>() bootstrap instantiates eager
singletons), failing the shard-app CI job on this branch.
The definition is now a plain @Single (the graph stays lazily
constructible) and GoogleMeshUtilApplication.onCreate resolves it once
after startKoin has bound androidContext — same production behavior,
explicit instead of implicit. It was the repo's only createdAtStart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: James Rich <james.a.rich@gmail.com>