glp-companion/lib/features/auth/presentation/controllers/auth_controller.dart

66 lines
2.5 KiB
Dart

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<IAuthService>(
(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<AuthState>((ref) {
if (!isSupabaseAvailable) return const Stream<AuthState>.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<AsyncValue<void>> {
AuthController(this._authService) : super(const AsyncValue.data(null));
final IAuthService _authService;
/// Dispatches a passwordless OTP/magic link to [email].
Future<void> signInWithMagicLink(String email) =>
_run(() => _authService.signInWithMagicLink(email));
Future<void> signInWithGoogle() => _run(_authService.signInWithGoogle);
Future<void> signInWithApple() => _run(_authService.signInWithApple);
Future<void> signInWithMicrosoft() => _run(_authService.signInWithMicrosoft);
Future<void> signOut() => _run(_authService.signOut);
Future<void> _run(Future<void> 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<AuthController, AsyncValue<void>>(
(ref) => AuthController(ref.watch(authServiceProvider)),
);