Auth Method Selector Screen layout and method cards
epic-bankid-vipps-login-ui-task-001 — Build the Authentication Method Selector Screen with three method cards (BankID, Vipps, Biometric). Each card must display the provider logo, a short description, and a primary action button. The screen is the first screen users see after organization selection, so visual clarity and load speed are critical. Use Flutter with design token theming and Riverpod for state.
Acceptance Criteria
Technical Requirements
Implementation Notes
Build `AuthMethodCard` as a stateless widget with parameters: `logo` (Widget), `title` (String), `description` (String), `onTap` (VoidCallback), `ctaLabel` (String). Use `Semantics` wrapper on the card with `label` and `button: true` for TalkBack/VoiceOver. Implement `AuthMethodSelectorScreen` with a Riverpod `AsyncNotifierProvider` that loads biometric capability on init. Use `when(data:, loading:, error:)` to switch between skeleton, card list, and error state.
Logo assets: place BankID and Vipps SVGs in `assets/logos/` and declare in pubspec.yaml. Use `AppColors.bankIdRed`, `AppColors.vippsOrange` from the design token system — do not hardcode hex values. For the biometric icon, use `BiometricTypeIcon` that selects Face ID or fingerprint icon based on `BiometricCapability.supportedTypes`. Wrap the entire card list in a `FadeTransition` driven by an `AnimationController` that triggers once the biometric check resolves.
Ensure the Biometric card's absence is a `null` widget in a `Column` — use conditional rendering (`if (showBiometric) AuthMethodCard(...)`) rather than `Visibility` or `Opacity` to keep the widget tree clean.
Testing Requirements
Widget tests: (1) render with biometric available and enrolled → all three cards visible; (2) render with biometric unavailable → biometric card absent; (3) render with biometric available but not enrolled → biometric card absent; (4) tap BankID CTA → SelectBankIdMethod event emitted, navigation triggered; (5) tap Vipps CTA → SelectVippsMethod event emitted; (6) tap Biometric CTA → SelectBiometricMethod event emitted; (7) during biometric availability check (loading state) → skeleton shown, no cards visible; (8) biometric check error → gracefully degrades to two-card layout (no crash). Accessibility tests: use `tester.semantics` to verify all cards have correct semantic labels. Golden tests: render screen in light mode with design tokens and compare to approved golden file. Test on simulated 200% text scale to verify layout does not overflow.
Coverage target: 80% for the screen and AuthMethodCard widget.
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.