feat(#350): pairing login flow with QR scan + manual entry (#421)

* feat(#350): pairing login flow with QR scan + manual entry

Adds PairingCodeEntryScreen with CameraX + ML Kit QR scanning
and manual code entry. The screen parses hermes://connect URIs,
Base64-encoded JSON, and raw token formats, validates via
/api/status, and saves credentials on success.

- New PairingCodeEntryScreen + PairingCodeEntryViewModel
- CameraX 1.4.1 + ML Kit barcode-scanning 17.3.0
- CAMERA permission + proguard rules
- Landing screen Pairing Login button wired correctly

* fix(#350): resolve smart-cast on delegated property for errorMessage

Co-authored-by: Hy4ri <hy4ri@users.noreply.github.com>

* refactor(#421): remove QR scanner from pairing login

- Remove CameraX + ML Kit barcode dependencies
- Drop CAMERA permission and camera feature from manifest
- Strip QrScannerTab, QrCodeScannerView, processFrame composables
- Remove camera permission state/methods from ViewModel
- Purge all QR/camera strings from strings.xml
- Delete ML Kit proguard rules

The pairing login now shows only the manual code entry UI.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-06-30 10:08:01 +03:00
1 parent 58bd78bd79
commit 7811a35d29
6 files changed
+346 -3

No files matched your search

@@ -54,6 +54,7 @@ import com.m57.hermescontrol.theme.LocalHermesStatusColors
import kotlinx.coroutines.launch
import com.m57.hermescontrol.ui.authlogin.AuthLoginScreen as AuthLoginScreenContent
import com.m57.hermescontrol.ui.landing.LandingScreen as LandingScreenContent
import com.m57.hermescontrol.ui.pairing.PairingCodeEntryScreen as PairingCodeEntryScreenContent
private fun resolveBottomNavItems(names: List<String>): List<ScreenDefinition> =
names.mapNotNull { name -> ScreenRegistry.ALL_SCREENS.firstOrNull { it.key::class.simpleName == name } }
@@ -70,7 +71,7 @@ private fun appEntryProvider(
NavigationController.backStack?.add(AuthLoginScreen)
},
onPairingLogin = {
NavigationController.backStack?.add(PairingScreen)
NavigationController.backStack?.add(PairingCodeEntryScreen)
},
)
}
@@ -86,6 +87,17 @@ private fun appEntryProvider(
)
}
entry<PairingCodeEntryScreen> {
PairingCodeEntryScreenContent(
onConnected = {
NavigationController.resetTo(ChatScreen)
},
onBack = {
NavigationController.goBack()
},
)
}
ScreenRegistry.ALL_SCREENS.forEach { screen ->
addEntryProvider(clazz = screen.key::class) {
screen.content(sessionId, openDrawer)
@@ -123,7 +135,8 @@ fun MainNavigation(sessionId: String? = null) {
val showBottomBar =
currentScreen != LandingScreen &&
currentScreen != AuthLoginScreen
currentScreen != AuthLoginScreen &&
currentScreen != PairingCodeEntryScreen
val gesturesEnabled = currentScreen in DRAWER_GESTURE_SCREENS
val openDrawer: () -> Unit = { scope.launch { drawerState.open() } }