critical priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

The screen renders three method cards in a vertical list: BankID (top), Vipps (middle), Biometric (bottom, conditionally shown).
The Biometric card is only shown when BiometricCapability.isAvailable is true AND the user has a prior biometric enrollment (isBiometricEnrolled returns true); otherwise the biometric card is hidden entirely.
Each card displays: the provider logo (BankID red logo, Vipps orange logo, biometric icon), a one-line description, and a full-width primary CTA button.
Tapping the BankID card CTA dispatches a SelectBankIdMethod event and navigates to the BankID login flow.
Tapping the Vipps card CTA dispatches a SelectVippsMethod event and navigates to the Vipps login flow.
Tapping the Biometric card CTA dispatches a SelectBiometricMethod event and triggers the biometric session resumption flow.
All text elements pass WCAG 2.2 AA contrast ratio (minimum 4.5:1 for normal text, 3:1 for large text) against the card background.
All interactive elements have a minimum touch target of 48x48dp as per WCAG 2.2 and Flutter accessibility guidelines.
The screen uses no hardcoded color values — all colors, spacings, and typography come from the design token system.
The screen renders correctly on: iPhone SE (375px wide), iPhone 14 Pro (390px), Samsung Galaxy S21 (360px), and large-font accessibility settings (text scale 200%).
A loading indicator is shown during the biometric availability check; once resolved, the card list animates in with a subtle fade.
VoiceOver (iOS) and TalkBack (Android) correctly announce each card's role and CTA — screen reader semantic labels are explicit and descriptive.
The screen loads and first paint completes within 300ms from navigation; the biometric check does not block the initial render.

Technical Requirements

frameworks
Flutter
Riverpod
Dart
Design Token System
apis
BiometricAuthService.checkBiometricCapability() — from task-010
BiometricAuthService.isBiometricEnrolled(userId) — from task-011
Riverpod Provider for BiometricCapability state
Router (GoRouter or equivalent) for navigation to BankID/Vipps/biometric flows
data models
BiometricCapability (isAvailable, supportedTypes) — read via Riverpod
AuthMethodSelection (enum: bankId, vipps, biometric)
AuthMethodSelectorState (loading, ready(showBiometric: bool), error)
performance requirements
First paint within 300ms — biometric check must not block initial widget build
Use FutureProvider or AsyncNotifier for biometric check to show skeleton/loading state, not a blocking await
Logo assets must be SVG or 2x/3x PNG, pre-bundled in assets/ — no network image fetching on this screen
security requirements
The biometric CTA must be hidden (not disabled) when enrollment is absent — hidden prevents confusion and reduces attack surface
No authentication credentials or session tokens are displayed or logged on this screen
The screen must not cache the biometric availability check result for more than one app session
ui components
AuthMethodCard (logo, description, CTA button — reusable for all three methods)
AuthMethodSelectorScreen (scaffold, list of AuthMethodCards, loading skeleton)
BiometricTypeIcon (adaptive: Face ID icon on iOS if supported, fingerprint icon on Android)
MethodCardSkeleton (loading placeholder matching card dimensions)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

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.

Epic Risks (3)
high impact medium prob technical

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.

medium impact medium prob technical

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.

low impact high prob technical

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.