Vipps Auth Screen — OAuth redirect launch and loading state
epic-bankid-vipps-login-ui-task-003 — Implement the Vipps Authentication Screen that triggers the Vipps OAuth redirect flow. The screen must show the Vipps logo, a branded call-to-action button, and transition to a loading/progress indicator immediately after the user taps the button. The loading state must persist until the deep-link callback returns or a timeout error is raised. Wire the screen to VippsAuthService via Riverpod.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Use a StateNotifierProvider (Riverpod) to hold VippsAuthState so the screen subscribes reactively. The StateNotifier should expose initiateLogin() which: (1) emits loading state, (2) generates CSRF state token, (3) calls url_launcher to open the Vipps OAuth URL, (4) starts a timer for the 120 s timeout. The timeout timer must be cancelled if a deep-link callback arrives first — use a Completer that can be completed by either the deep-link handler or the timer. Handle the edge case where the Vipps app is not installed: url_launcher.canLaunchUrl() should be checked; if false, show a browser-based fallback.
The back-button interception during loading state should call a cancel() method on the notifier that sets state back to idle and cancels the pending timer before popping the route. Do not use Navigator.pop() directly in the widget — route the cancel action through the notifier to ensure state is clean. Use const for the Vipps logo widget and static text to prevent unnecessary rebuilds during the loading animation.
Testing Requirements
Widget tests: mock VippsAuthService provider, tap the CTA button, assert (1) button becomes disabled, (2) CircularProgressIndicator appears, (3) VippsAuthService.initiateLogin() was called exactly once. Widget test: simulate back-button press during loading state, assert screen pops and cancellation method on VippsAuthService is called. Unit tests for VippsAuthService: assert OAuth state parameter is unique across successive calls (generate 100, check no duplicates). Integration test on a simulator: launch to Vipps screen, tap button, assert the device attempts to open the Vipps OAuth URL (check url_launcher invocation via a fake).
Manual testing on physical device: verify Vipps app opens or browser fallback activates; verify loading state is displayed during the handshake wait.
BankID on mobile uses a WebView or external app redirect that has known compatibility issues with Flutter's WebView package on certain Android versions. BankID's JavaScript-heavy broker pages may also trigger CSP or mixed-content errors in a Flutter WebView, preventing the authentication flow from completing.
Mitigation & Contingency
Mitigation: Use the flutter_inappwebview package (more mature than webview_flutter for complex OAuth pages) and validate BankID WebView rendering on the broker's test environment before integrating with the service layer. Prefer external browser redirect where the broker supports it.
Contingency: If WebView approach fails for certain BankID brokers, implement the full external browser redirect + deep link callback pattern as the primary flow and treat WebView as a fallback only.
The OAuth redirect flows (both Vipps and BankID) temporarily move the user outside the Flutter app into an external browser or the Vipps/BankID app. Screen reader users may lose focus context during this transition and become disoriented when the app callback returns them to the loading state, failing the WCAG 2.2 AA mandate.
Mitigation & Contingency
Mitigation: Implement explicit accessibility announcements (live region announcements) at each transition point: when launching the external flow ('Opening Vipps'), during the loading wait state ('Waiting for Vipps confirmation'), and on return ('Login successful' or 'Login failed — please try again'). Test with VoiceOver on iOS and TalkBack on Android during development.
Contingency: If OAuth transition accessibility is unresolvable on a specific platform, add an explicit accessibility user guide in the onboarding flow explaining the external app redirect behavior to set user expectations.
Biometric UI varies significantly across devices — Face ID (iPhone), fingerprint sensor (most Android), front-facing camera biometrics (some Android), and devices with no biometrics at all. Flutter's local_auth handles the OS dialog but the surrounding UI must gracefully handle all these cases, and testing coverage for all permutations is difficult.
Mitigation & Contingency
Mitigation: Use local_auth's getAvailableBiometrics() to detect the exact biometric type and render appropriate iconography (Face ID icon vs. fingerprint icon). For devices with no biometrics, skip the biometric screen entirely and route directly to full re-authentication.
Contingency: If a specific device configuration produces unexpected local_auth behavior in production, implement a user-accessible toggle in Settings to disable biometric login entirely, routing those users to the standard BankID/Vipps flow without biometrics.