Phase 2 refinements: Firebase config, dev environment fixes, phase 3 requirement draft

- Integrated Firebase SDK in both Flutter apps (google-services, firebase_options)
- Fixed auth flow, API client, and pairing/status blocs for dev environment
- Added full Flutter project scaffolds (android, ios, web, etc.)
- Added phase 3 chat engine requirement document
- Added bugreport zip pattern to gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 19:16:34 +08:00
parent d668112edd
commit 844d7234e6
229 changed files with 10439 additions and 102 deletions

View File

@@ -1,5 +1,7 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_bloc/flutter_bloc.dart';
import '../api/api_client.dart';
@@ -51,6 +53,7 @@ class AuthError extends AuthState {
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final ApiClient apiClient;
final _auth = FirebaseAuth.instance;
ConfirmationResult? _webConfirmationResult;
AuthBloc({required this.apiClient}) : super(AuthInitial()) {
on<AppStarted>(_onAppStarted);
@@ -67,23 +70,50 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
Future<void> _onPhoneOtpRequested(PhoneOtpRequested event, Emitter<AuthState> emit) async {
emit(AuthLoading());
await _auth.verifyPhoneNumber(
phoneNumber: event.phone,
verificationCompleted: (_) {},
verificationFailed: (e) => emit(AuthError('Gagal mengirim OTP. Coba lagi.')),
codeSent: (verificationId, _) => emit(AuthOtpSent(verificationId)),
codeAutoRetrievalTimeout: (_) {},
);
if (kIsWeb) {
try {
final confirmationResult = await _auth.signInWithPhoneNumber(event.phone);
_webConfirmationResult = confirmationResult;
emit(AuthOtpSent('web'));
} catch (e) {
emit(AuthError('Gagal mengirim OTP. Coba lagi.'));
}
} else {
final completer = Completer<void>();
await _auth.verifyPhoneNumber(
phoneNumber: event.phone,
verificationCompleted: (_) {
if (!completer.isCompleted) completer.complete();
},
verificationFailed: (e) {
emit(AuthError('Gagal mengirim OTP. Coba lagi.'));
if (!completer.isCompleted) completer.complete();
},
codeSent: (verificationId, _) {
emit(AuthOtpSent(verificationId));
if (!completer.isCompleted) completer.complete();
},
codeAutoRetrievalTimeout: (_) {
if (!completer.isCompleted) completer.complete();
},
);
await completer.future;
}
}
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);
if (kIsWeb && _webConfirmationResult != null) {
await _webConfirmationResult!.confirm(event.smsCode);
} else {
final credential = PhoneAuthProvider.credential(
verificationId: event.verificationId,
smsCode: event.smsCode,
);
await _auth.signInWithCredential(credential);
}
await _verifyAndEmit(emit);
} catch (e) {
emit(AuthError('OTP tidak valid. Coba lagi.'));