Pipeline YouTube con relay, overlay e publisher sync lato backend; fix GL pauseGlDuringRotation su Android per evitare crash in rotazione durante lo streaming Flutter. Co-authored-by: Cursor <cursoragent@cursor.com>
142 lines
3.3 KiB
Dart
142 lines
3.3 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../core/auth_storage.dart';
|
|
import '../core/team_selection_storage.dart';
|
|
import '../shared/api_client.dart';
|
|
import '../shared/models/user.dart';
|
|
|
|
class AuthState {
|
|
const AuthState({
|
|
this.user,
|
|
this.accessToken,
|
|
this.refreshToken,
|
|
this.loading = false,
|
|
this.error,
|
|
});
|
|
|
|
final User? user;
|
|
final String? accessToken;
|
|
final String? refreshToken;
|
|
final bool loading;
|
|
final String? error;
|
|
|
|
bool get isAuthenticated =>
|
|
accessToken != null && accessToken!.isNotEmpty && user != null;
|
|
|
|
AuthState copyWith({
|
|
User? user,
|
|
String? accessToken,
|
|
String? refreshToken,
|
|
bool? loading,
|
|
String? error,
|
|
bool clearError = false,
|
|
}) {
|
|
return AuthState(
|
|
user: user ?? this.user,
|
|
accessToken: accessToken ?? this.accessToken,
|
|
refreshToken: refreshToken ?? this.refreshToken,
|
|
loading: loading ?? this.loading,
|
|
error: clearError ? null : (error ?? this.error),
|
|
);
|
|
}
|
|
}
|
|
|
|
class AuthNotifier extends StateNotifier<AuthState> {
|
|
AuthNotifier() : super(const AuthState());
|
|
|
|
void setSession({
|
|
required User user,
|
|
required String accessToken,
|
|
required String refreshToken,
|
|
}) {
|
|
state = AuthState(
|
|
user: user,
|
|
accessToken: accessToken,
|
|
refreshToken: refreshToken,
|
|
loading: false,
|
|
);
|
|
AuthStorage.save(
|
|
user: user,
|
|
accessToken: accessToken,
|
|
refreshToken: refreshToken,
|
|
);
|
|
}
|
|
|
|
Future<void> restoreSession() async {
|
|
final stored = await AuthStorage.load();
|
|
if (stored == null) return;
|
|
state = AuthState(
|
|
user: stored.user,
|
|
accessToken: stored.accessToken,
|
|
refreshToken: stored.refreshToken,
|
|
loading: false,
|
|
);
|
|
}
|
|
|
|
/// Dopo restore: verifica token o rinnova con refresh (evita 401 su /teams).
|
|
Future<bool> validateOrRefreshSession() async {
|
|
if (!state.isAuthenticated) return false;
|
|
|
|
final client = ApiClient(accessToken: state.accessToken);
|
|
try {
|
|
final user = await client.me();
|
|
state = state.copyWith(user: user);
|
|
await AuthStorage.save(
|
|
user: user,
|
|
accessToken: state.accessToken!,
|
|
refreshToken: state.refreshToken!,
|
|
);
|
|
return true;
|
|
} on DioException catch (e) {
|
|
if (e.response?.statusCode != 401) {
|
|
return true;
|
|
}
|
|
} catch (_) {
|
|
return true;
|
|
}
|
|
|
|
return refreshSession();
|
|
}
|
|
|
|
Future<bool> refreshSession() async {
|
|
final refreshToken = state.refreshToken;
|
|
if (refreshToken == null || refreshToken.isEmpty) {
|
|
logout();
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
final client = ApiClient();
|
|
final result = await client.refreshSession(refreshToken);
|
|
setSession(
|
|
user: result.user,
|
|
accessToken: result.tokens.accessToken,
|
|
refreshToken: result.tokens.refreshToken,
|
|
);
|
|
return true;
|
|
} catch (_) {
|
|
logout();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void setLoading(bool loading) {
|
|
state = state.copyWith(loading: loading, clearError: true);
|
|
}
|
|
|
|
void setError(String message) {
|
|
state = state.copyWith(loading: false, error: message);
|
|
}
|
|
|
|
void logout() {
|
|
state = const AuthState();
|
|
AuthStorage.clear();
|
|
TeamSelectionStorage.clear();
|
|
}
|
|
}
|
|
|
|
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>(
|
|
(ref) => AuthNotifier(),
|
|
);
|