Files
focusflow/lib/features/auth/presentation/bloc/auth_bloc.dart
Oracle Public Cloud User 50931d839d Initial scaffold: FocusFlow ADHD Task Manager Flutter app
BLoC/Cubit state management, ADHD-friendly theme (calming teal, no red),
GetIt DI, GoRouter navigation. Screens: task dashboard, focus mode,
task create/detail, streaks, time perception, settings, onboarding, auth.
Custom widgets: TaskCard, RewardPopup, StreakRing, GentleNudgeCard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:53:58 +00:00

152 lines
4.7 KiB
Dart

import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:focusflow_shared/focusflow_shared.dart';
import '../../../../core/network/api_client.dart';
import '../../../../core/network/interceptors/auth_interceptor.dart';
import '../../../../main.dart';
// ── Events ─────────────────────────────────────────────────────────
sealed class AuthEvent extends Equatable {
const AuthEvent();
@override
List<Object?> get props => [];
}
class AuthLoginRequested extends AuthEvent {
final String email;
final String password;
const AuthLoginRequested({required this.email, required this.password});
@override
List<Object?> get props => [email, password];
}
class AuthSignupRequested extends AuthEvent {
final String displayName;
final String email;
final String password;
const AuthSignupRequested({
required this.displayName,
required this.email,
required this.password,
});
@override
List<Object?> get props => [displayName, email, password];
}
class AuthLogoutRequested extends AuthEvent {
const AuthLogoutRequested();
}
class AuthCheckRequested extends AuthEvent {}
// ── States ─────────────────────────────────────────────────────────
sealed class AuthState extends Equatable {
const AuthState();
@override
List<Object?> get props => [];
}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final User user;
const AuthAuthenticated(this.user);
@override
List<Object?> get props => [user];
}
class AuthUnauthenticated extends AuthState {}
class AuthError extends AuthState {
final String message;
const AuthError(this.message);
@override
List<Object?> get props => [message];
}
// ── BLoC ───────────────────────────────────────────────────────────
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final ApiClient _api = getIt<ApiClient>();
AuthBloc() : super(AuthInitial()) {
on<AuthCheckRequested>(_onCheck);
on<AuthLoginRequested>(_onLogin);
on<AuthSignupRequested>(_onSignup);
on<AuthLogoutRequested>(_onLogout);
}
Future<void> _onCheck(AuthCheckRequested event, Emitter<AuthState> emit) async {
final hasToken = await AuthInterceptor.hasToken();
if (hasToken) {
// In a production app we would validate the token and fetch the user profile.
// For now we emit a placeholder user.
emit(AuthAuthenticated(
User(
id: 'local',
email: '',
displayName: 'Friend',
createdAt: DateTime.now(),
),
));
} else {
emit(AuthUnauthenticated());
}
}
Future<void> _onLogin(AuthLoginRequested event, Emitter<AuthState> emit) async {
emit(AuthLoading());
try {
final response = await _api.login(event.email, event.password);
if (response.status == 'success' && response.data != null) {
final data = response.data!;
await AuthInterceptor.saveTokens(
accessToken: data['accessToken'] as String,
refreshToken: data['refreshToken'] as String,
);
final user = User.fromJson(data['user'] as Map<String, dynamic>);
emit(AuthAuthenticated(user));
} else {
emit(AuthError(response.error?.message ?? 'Login failed'));
}
} catch (e) {
emit(AuthError('Something went wrong. Please try again.'));
}
}
Future<void> _onSignup(AuthSignupRequested event, Emitter<AuthState> emit) async {
emit(AuthLoading());
try {
final response = await _api.register(event.displayName, event.email, event.password);
if (response.status == 'success' && response.data != null) {
final data = response.data!;
await AuthInterceptor.saveTokens(
accessToken: data['accessToken'] as String,
refreshToken: data['refreshToken'] as String,
);
final user = User.fromJson(data['user'] as Map<String, dynamic>);
emit(AuthAuthenticated(user));
} else {
emit(AuthError(response.error?.message ?? 'Sign up failed'));
}
} catch (e) {
emit(AuthError('Something went wrong. Please try again.'));
}
}
Future<void> _onLogout(AuthLogoutRequested event, Emitter<AuthState> emit) async {
try {
await _api.logout();
} catch (_) {
// Best-effort server logout — clear local tokens regardless.
}
await AuthInterceptor.clearTokens();
emit(AuthUnauthenticated());
}
}