import "dart:async"; import "package:flutter/foundation.dart"; import "package:supabase_flutter/supabase_flutter.dart"; SupabaseClient get supabase => Supabase.instance.client; class SupabaseProvider extends ChangeNotifier { Session? _session; StreamSubscription? _authSub; bool _isSessionValidated = false; bool _isValidatingSession = false; SupabaseProvider() { _session = supabase.auth.currentSession; unawaited(_validateSession(_session)); _authSub = supabase.auth.onAuthStateChange.listen((data) { unawaited(_validateSession(data.session)); }); } Session? get session => _session; bool get isAuthenticated => _session != null && _isSessionValidated; bool get isValidatingSession => _isValidatingSession; SupabaseClient get client => supabase; Future signInWithPassword({ required String email, required String password, }) async { await supabase.auth.signInWithPassword(email: email, password: password); } Future signUpWithPassword({ required String email, required String password, }) async { final res = await supabase.auth.signUp(email: email, password: password); // returns true if email confirmation is needed return res.session == null; } Future verifySignUpOtp({ required String email, required String token, }) async { await supabase.auth.verifyOTP( email: email, token: token, type: OtpType.signup, ); } Future resendSignUpOtp({required String email}) async { await supabase.auth.resend(type: OtpType.signup, email: email); } Future signOut() async { try { await supabase.auth.signOut(scope: SignOutScope.local); } catch (error, stackTrace) { debugPrint("[SupabaseProvider] signOut failed: $error"); debugPrintStack(stackTrace: stackTrace); } _session = null; _isSessionValidated = false; notifyListeners(); } Future _validateSession(Session? incomingSession) async { _session = incomingSession ?? supabase.auth.currentSession; if (_session == null) { _isSessionValidated = false; _isValidatingSession = false; notifyListeners(); return; } _isValidatingSession = true; notifyListeners(); try { final userResponse = await supabase.auth.getUser(); if (userResponse.user != null) { _session = supabase.auth.currentSession ?? _session; _isSessionValidated = true; _isValidatingSession = false; notifyListeners(); return; } } catch (error, stackTrace) { debugPrint("[SupabaseProvider] validateSession/getUser failed: $error"); debugPrintStack(stackTrace: stackTrace); } try { final refreshed = await supabase.auth.refreshSession(); _session = refreshed.session ?? supabase.auth.currentSession; final refreshedUser = await supabase.auth.getUser(); _isSessionValidated = refreshedUser.user != null; } catch (error, stackTrace) { debugPrint("[SupabaseProvider] validateSession/refresh failed: $error"); debugPrintStack(stackTrace: stackTrace); _isSessionValidated = false; } if (!_isSessionValidated) { try { await supabase.auth.signOut(scope: SignOutScope.local); } catch (error, stackTrace) { debugPrint("[SupabaseProvider] validateSession/signOut failed: $error"); debugPrintStack(stackTrace: stackTrace); } _session = null; } _isValidatingSession = false; notifyListeners(); } @override void dispose() { _authSub?.cancel(); super.dispose(); } }