Phase 3.1: Remove flutter_bloc + equatable, delete old bloc files

- Remove flutter_bloc and equatable dependencies from both apps
- Delete all 10 old bloc files (5 per app)
- Fix 6 remaining screens that used context.read<ApiClient>() from
  flutter_bloc → converted to ConsumerStatefulWidget/ConsumerWidget
  with ref.read(apiClientProvider)
- Both apps now use Riverpod exclusively for state management

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 14:12:28 +08:00
parent 35d470b851
commit fa8c963d92
20 changed files with 38 additions and 2106 deletions

View File

@@ -1,236 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../api/api_client.dart';
// Events
abstract class AuthEvent extends Equatable {
@override
List<Object?> get props => [];
}
class AppStarted extends AuthEvent {}
class AnonymousLoginRequested extends AuthEvent {
final String displayName;
AnonymousLoginRequested(this.displayName);
@override List<Object?> get props => [displayName];
}
class GoogleLoginRequested extends AuthEvent {}
class AppleLoginRequested extends AuthEvent {}
class PhoneOtpRequested extends AuthEvent {
final String phone;
PhoneOtpRequested(this.phone);
@override List<Object?> get props => [phone];
}
class OtpVerified extends AuthEvent {
final String verificationId;
final String smsCode;
OtpVerified(this.verificationId, this.smsCode);
@override List<Object?> get props => [verificationId, smsCode];
}
class LinkAccountRequested extends AuthEvent {}
class LogoutRequested extends AuthEvent {}
// States
abstract class AuthState extends Equatable {
@override
List<Object?> get props => [];
}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final Map<String, dynamic> profile;
AuthAuthenticated(this.profile);
@override List<Object?> get props => [profile];
}
class AuthAnonymous extends AuthState {
final String customerId;
final String displayName;
AuthAnonymous({required this.customerId, required this.displayName});
@override List<Object?> get props => [customerId, displayName];
}
class AuthOtpSent extends AuthState {
final String verificationId;
AuthOtpSent(this.verificationId);
@override List<Object?> get props => [verificationId];
}
class AuthError extends AuthState {
final String message;
AuthError(this.message);
@override List<Object?> get props => [message];
}
class AuthForceRegister extends AuthState {
final String customerId;
final String displayName;
AuthForceRegister({required this.customerId, required this.displayName});
@override List<Object?> get props => [customerId, displayName];
}
// Bloc
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final ApiClient apiClient;
final _auth = FirebaseAuth.instance;
String? _pendingVerificationId;
AuthBloc({required this.apiClient}) : super(AuthLoading()) {
on<AppStarted>(_onAppStarted);
on<AnonymousLoginRequested>(_onAnonymousLogin);
on<GoogleLoginRequested>(_onGoogleLogin);
on<AppleLoginRequested>(_onAppleLogin);
on<PhoneOtpRequested>(_onPhoneOtpRequested);
on<OtpVerified>(_onOtpVerified);
on<LinkAccountRequested>(_onLinkAccount);
on<LogoutRequested>(_onLogout);
}
Future<void> _onAppStarted(AppStarted event, Emitter<AuthState> emit) async {
final prefs = await SharedPreferences.getInstance();
final customerId = prefs.getString('anonymous_customer_id');
final displayName = prefs.getString('anonymous_display_name');
final currentUser = _auth.currentUser;
if (currentUser != null && currentUser.isAnonymous && customerId != null && displayName != null) {
// Anonymous Firebase user — restore anonymous state
try {
final config = await apiClient.get('/api/shared/config/anonymity');
final anonymityEnabled = config['data']['anonymity_enabled'] as bool;
if (!anonymityEnabled) {
emit(AuthForceRegister(customerId: customerId, displayName: displayName));
} else {
emit(AuthAnonymous(customerId: customerId, displayName: displayName));
}
} catch (_) {
emit(AuthAnonymous(customerId: customerId, displayName: displayName));
}
} else if (currentUser != null && !currentUser.isAnonymous) {
// Fully registered Firebase user
await _verifyAndEmit(emit);
} else {
emit(AuthInitial());
}
}
Future<void> _onAnonymousLogin(AnonymousLoginRequested event, Emitter<AuthState> emit) async {
emit(AuthLoading());
try {
// Sign in anonymously with Firebase to get a real JWT
await _auth.signInAnonymously();
// Create/get customer record on backend linked to this Firebase UID
final response = await apiClient.post(
'/api/shared/customer/anonymous',
data: {'display_name': event.displayName},
);
final customer = response['data'] as Map<String, dynamic>;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('anonymous_customer_id', customer['id'] as String);
await prefs.setString('anonymous_display_name', customer['display_name'] as String);
emit(AuthAnonymous(customerId: customer['id'] as String, displayName: customer['display_name'] as String));
} catch (e) {
emit(AuthError('Failed to continue as guest. Please try again.'));
}
}
Future<void> _onGoogleLogin(GoogleLoginRequested event, Emitter<AuthState> emit) async {
emit(AuthLoading());
try {
final googleUser = await GoogleSignIn().signIn();
if (googleUser == null) { emit(AuthInitial()); return; }
final googleAuth = await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await _auth.signInWithCredential(credential);
await _verifyAndEmit(emit);
} catch (e) {
emit(AuthError('Google sign-in failed. Please try again.'));
}
}
Future<void> _onAppleLogin(AppleLoginRequested event, Emitter<AuthState> emit) async {
emit(AuthLoading());
try {
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [AppleIDAuthorizationScopes.email],
);
final oauthCredential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
accessToken: appleCredential.authorizationCode,
);
await _auth.signInWithCredential(oauthCredential);
await _verifyAndEmit(emit);
} catch (e) {
emit(AuthError('Apple sign-in failed. Please try again.'));
}
}
Future<void> _onPhoneOtpRequested(PhoneOtpRequested event, Emitter<AuthState> emit) async {
emit(AuthLoading());
await _auth.verifyPhoneNumber(
phoneNumber: event.phone,
verificationCompleted: (_) {},
verificationFailed: (e) => emit(AuthError('Failed to send OTP. Please try again.')),
codeSent: (verificationId, _) {
_pendingVerificationId = verificationId;
emit(AuthOtpSent(verificationId));
},
codeAutoRetrievalTimeout: (_) {},
);
}
Future<void> _onOtpVerified(OtpVerified event, Emitter<AuthState> emit) async {
emit(AuthLoading());
try {
final credential = PhoneAuthProvider.credential(
verificationId: event.verificationId,
smsCode: event.smsCode,
);
await _auth.signInWithCredential(credential);
await _verifyAndEmit(emit);
} catch (e) {
emit(AuthError('Invalid OTP. Please try again.'));
}
}
Future<void> _onLinkAccount(LinkAccountRequested event, Emitter<AuthState> emit) async {
// Called after anonymous user completes social/OTP login to link accounts
final prefs = await SharedPreferences.getInstance();
final customerId = prefs.getString('anonymous_customer_id');
if (customerId == null || _auth.currentUser == null) return;
emit(AuthLoading());
try {
await apiClient.post('/api/shared/customer/link', data: {
'customer_id': customerId,
'firebase_uid': _auth.currentUser!.uid,
});
await prefs.remove('anonymous_customer_id');
await prefs.remove('anonymous_display_name');
await _verifyAndEmit(emit);
} catch (e) {
emit(AuthError('Failed to link account. Please try again.'));
}
}
Future<void> _onLogout(LogoutRequested event, Emitter<AuthState> emit) async {
await _auth.signOut();
final prefs = await SharedPreferences.getInstance();
await prefs.remove('anonymous_customer_id');
await prefs.remove('anonymous_display_name');
emit(AuthInitial());
}
Future<void> _verifyAndEmit(Emitter<AuthState> emit) async {
try {
final response = await apiClient.post('/api/client/auth/verify');
emit(AuthAuthenticated(response['data'] as Map<String, dynamic>));
} catch (e) {
emit(AuthError('Failed to verify account. Please try again.'));
}
}
}