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>
This commit is contained in:
151
lib/features/auth/presentation/bloc/auth_bloc.dart
Normal file
151
lib/features/auth/presentation/bloc/auth_bloc.dart
Normal file
@@ -0,0 +1,151 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user