66 lines
2.5 KiB
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)),
|
|
);
|