import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../../../../core/network/supabase_status.dart'; import '../../data/supabase_auth_service.dart'; import '../../domain/auth_service.dart'; /// Provides the application-wide [IAuthService]. /// /// The concrete [SupabaseAuthService] is resolved behind the domain contract /// so widget/unit tests can override this provider with a fake without touching /// the presentation layer. final authServiceProvider = Provider( (ref) => SupabaseAuthService(), ); /// Raw GoTrue auth transitions (sign-in, sign-out, token refresh, ...). /// /// The router subscribes to this stream through `refreshListenable` so route /// guards react immediately after an OAuth or magic-link round trip completes. /// Falls back to an empty stream when the Supabase SDK has not been /// initialized (e.g. missing `.env` during local development). final authStateProvider = StreamProvider((ref) { if (!isSupabaseAvailable) return const Stream.empty(); return Supabase.instance.client.auth.onAuthStateChange; }); /// Orchestrates asynchronous passwordless authentication flows. /// /// Every method transitions through [AsyncValue.loading] and records failures /// in [state] as [AsyncValue.error], so the presentation layer only needs to /// watch/listen to the provider to render progress and error feedback. class AuthController extends StateNotifier> { AuthController(this._authService) : super(const AsyncValue.data(null)); final IAuthService _authService; /// Dispatches a passwordless OTP/magic link to [email]. Future signInWithMagicLink(String email) => _run(() => _authService.signInWithMagicLink(email)); Future signInWithGoogle() => _run(_authService.signInWithGoogle); Future signInWithApple() => _run(_authService.signInWithApple); Future signInWithMicrosoft() => _run(_authService.signInWithMicrosoft); Future signOut() => _run(_authService.signOut); Future _run(Future Function() action) async { state = const AsyncValue.loading(); try { await action(); if (mounted) state = const AsyncValue.data(null); } catch (error, stackTrace) { if (mounted) state = AsyncValue.error(error, stackTrace); } } } /// Exposes the [AuthController] to the widget tree. final authControllerProvider = StateNotifierProvider>( (ref) => AuthController(ref.watch(authServiceProvider)), );