Implement UrlLauncherUtil wrapper
epic-no-access-screen-foundation-task-005 — Create the UrlLauncherUtil class as a thin, platform-safe wrapper around Flutter's url_launcher package. Expose a single async launchUrl(String url) method that validates the URI, attempts to open it with the external application mode, and returns a typed result (success/unsupported/error) rather than throwing exceptions.
Acceptance Criteria
Technical Requirements
Implementation Notes
Define a `UrlLaunchResult` sealed class using Dart 3 `sealed class` syntax with three subclasses: `UrlLaunchSuccess`, `UrlLaunchUnsupported`, and `UrlLaunchError(String message)`. This enables exhaustive `switch` pattern matching in callers. The wrapper should accept an abstract `UrlLauncherInterface` (or use `url_launcher_platform_interface`) in its constructor with a default value of the real implementation, enabling mock injection in tests. Avoid a static `instance` pattern as it complicates testing.
Keep the class stateless—all state lives in the caller. The URI validation (`Uri.tryParse` + scheme allowlist check) should happen before any async platform call to fail fast synchronously. Follow the no-access screen's design requirement that admin portal links open reliably even on constrained devices.
Testing Requirements
Covered by `epic-no-access-screen-foundation-task-007`. This task focuses on implementation only. However, add a basic smoke assertion in a `main()` debug entry point or document the manual test steps (open a valid HTTPS URL, observe browser opening) to confirm platform wiring before the test task runs.
Supabase remote config may be unavailable at app startup (network error, cold start), causing the repository to return no blocked-role list. If the fallback is empty, blocked users could access the app.
Mitigation & Contingency
Mitigation: Define a local constants fallback list of blocked roles compiled into the app binary. Remote config enriches or overrides this list when available.
Contingency: If remote config repeatedly fails in production, pin the blocked-role list to local constants only and disable remote override until the Supabase config endpoint is stabilised.
url_launcher package behaviour differs between iOS and Android (e.g. canLaunchUrl returning false on some Android configurations), leading to silent failures when the admin portal link is tapped.
Mitigation & Contingency
Mitigation: Run canLaunchUrl check before every launch attempt and surface a descriptive inline error message (e.g. 'Could not open link — visit admin.example.org manually') when the check fails.
Contingency: If canLaunchUrl is consistently unreliable on a target platform, replace the tap-to-open pattern with a copyable text field showing the URL as a fallback.