|
|
|
|
|
// ignore_for_file: unused_import
|
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
import 'dart:developer';
|
|
|
|
|
|
import 'package:bloc/bloc.dart';
|
|
|
|
|
|
import 'package:equatable/equatable.dart';
|
|
|
|
|
|
import 'package:finger_print_verification/core/network/network.dart';
|
|
|
|
|
|
import 'package:finger_print_verification/fingerprint/utils/finger_print_quality_helper.dart';
|
|
|
|
|
|
import '../models/auth_response_model.dart';
|
|
|
|
|
|
import '../models/auth_result_ui_model.dart';
|
|
|
|
|
|
import '../models/fingerprint_verify_response_model.dart';
|
|
|
|
|
|
import '../models/login_response_model.dart';
|
|
|
|
|
|
import '../utils/zkt_finger_print_scanner.dart';
|
|
|
|
|
|
|
|
|
|
|
|
part 'fingerprint_event.dart';
|
|
|
|
|
|
part 'fingerprint_state.dart';
|
|
|
|
|
|
|
|
|
|
|
|
// ── Credentials ────────────────────────────────────────────────────────────
|
|
|
|
|
|
/// TODO: move to a secure config / env file before shipping.
|
|
|
|
|
|
const _kLoginUsername = 'admin';
|
|
|
|
|
|
const _kLoginPassword = 'Admin@123';
|
|
|
|
|
|
|
|
|
|
|
|
class FingerprintBloc extends Bloc<FingerprintEvent, FingerprintFormState> {
|
|
|
|
|
|
final ZktFingerPrintScanner _fingerprintScanner;
|
|
|
|
|
|
|
|
|
|
|
|
FingerprintBloc({ZktFingerPrintScanner? fingerprintScanner})
|
|
|
|
|
|
: _fingerprintScanner = fingerprintScanner ?? ZktFingerPrintScanner(),
|
|
|
|
|
|
super(const FingerprintFormState()) {
|
|
|
|
|
|
on<AppStarted>(_onAppStarted);
|
|
|
|
|
|
on<DocumentTypeChanged>(_onDocumentTypeChanged);
|
|
|
|
|
|
on<FingerTypeChanged>(_onFingerTypeChanged);
|
|
|
|
|
|
on<NationalIdChanged>(_onNationalIdChanged);
|
|
|
|
|
|
on<NationalityChanged>(_onNationalityChanged);
|
|
|
|
|
|
on<PassportNumberChanged>(_onPassportNumberChanged);
|
|
|
|
|
|
on<ConnectDevice>(_onConnectDevice);
|
|
|
|
|
|
on<ScanFingerprintEvent>(_onScanFingerprint);
|
|
|
|
|
|
on<ResetBlocEvent>(_onResetBloc);
|
|
|
|
|
|
on<SendFingerprintEvent>(_onSendFingerprint);
|
|
|
|
|
|
on<VerifyFingerprintEvent>(_onVerifyFingerprint);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── App startup: login ───────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// Called once at app startup to obtain the bearer token.
|
|
|
|
|
|
/// Runs silently in the background — the user sees no loading indicator.
|
|
|
|
|
|
Future<void> _onAppStarted(
|
|
|
|
|
|
AppStarted event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
emit(state.copyWith(status: FormStatus.loggingIn));
|
|
|
|
|
|
|
|
|
|
|
|
final result = await ApiClient.post<LoginResponseModel>(
|
|
|
|
|
|
url: 'api/Auth/login',
|
|
|
|
|
|
shouldKeepToken: false, // non-token endpoint — we're acquiring the token
|
|
|
|
|
|
body: {
|
|
|
|
|
|
'username': _kLoginUsername,
|
|
|
|
|
|
'password': _kLoginPassword,
|
|
|
|
|
|
},
|
|
|
|
|
|
fromJson: (json) =>
|
|
|
|
|
|
LoginResponseModel.fromJson(json as Map<String, dynamic>),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
|
case ApiSuccess(:final data):
|
|
|
|
|
|
if (data.success && (data.data?.token.isNotEmpty ?? false)) {
|
|
|
|
|
|
// Store the token so future calls can attach it automatically.
|
|
|
|
|
|
TokenManager.setToken(data.data!.token);
|
|
|
|
|
|
emit(state.copyWith(status: FormStatus.idle, clearError: true));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Login returned success=false — surface the message but don't block UI.
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.idle,
|
|
|
|
|
|
errorMessage: data.message.isNotEmpty
|
|
|
|
|
|
? data.message
|
|
|
|
|
|
: 'Login failed. Please restart the app.',
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
case ApiFailure(:final code, :final message):
|
|
|
|
|
|
if (code == -1) {
|
|
|
|
|
|
// Cancelled (e.g. BLoC closed during startup) — ignore silently.
|
|
|
|
|
|
emit(state.copyWith(status: FormStatus.idle));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.idle,
|
|
|
|
|
|
errorMessage: 'Background login failed: $message',
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Dropdown / field handlers ────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
void _onDocumentTypeChanged(
|
|
|
|
|
|
DocumentTypeChanged event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
documentType: event.documentType,
|
|
|
|
|
|
status: FormStatus.idle,
|
|
|
|
|
|
clearError: true,
|
|
|
|
|
|
clearNationalIdError: true,
|
|
|
|
|
|
clearNationalityError: true,
|
|
|
|
|
|
clearPassportNumberError: true,
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _onFingerTypeChanged(
|
|
|
|
|
|
FingerTypeChanged event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) {
|
|
|
|
|
|
emit(state.copyWith(selectedFingerIndex: event.fingerIndex));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _onNationalIdChanged(
|
|
|
|
|
|
NationalIdChanged event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) {
|
|
|
|
|
|
emit(state.copyWith(nationalIdInput: event.value, clearNationalIdError: true));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _onNationalityChanged(
|
|
|
|
|
|
NationalityChanged event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) {
|
|
|
|
|
|
emit(state.copyWith(nationality: event.value, clearNationalityError: true));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _onPassportNumberChanged(
|
|
|
|
|
|
PassportNumberChanged event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
passportNumber: event.value, clearPassportNumberError: true));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Validation ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// 10 digits, must start with 1 or 2.
|
|
|
|
|
|
String? _validateNationalId(String value) {
|
|
|
|
|
|
if (value.isEmpty) return 'National ID is required';
|
|
|
|
|
|
if (!RegExp(r'^[12]\d{9}$').hasMatch(value)) {
|
|
|
|
|
|
return 'Must be 10 digits and start with 1 or 2';
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Non-empty string.
|
|
|
|
|
|
String? _validateNationality(String value) {
|
|
|
|
|
|
if (value.trim().isEmpty) return 'Nationality is required';
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 6–9 alphanumeric characters (standard passport format).
|
|
|
|
|
|
String? _validatePassportNumber(String value) {
|
|
|
|
|
|
if (value.isEmpty) return 'Passport number is required';
|
|
|
|
|
|
if (!RegExp(r'^[A-Za-z0-9]{6,9}$').hasMatch(value)) {
|
|
|
|
|
|
return 'Must be 6–9 alphanumeric characters';
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns a state with field errors if invalid, null if everything is OK.
|
|
|
|
|
|
FingerprintFormState? _buildValidationErrorState() {
|
|
|
|
|
|
if (state.documentType == DocumentType.nationalId) {
|
|
|
|
|
|
final error = _validateNationalId(state.nationalIdInput);
|
|
|
|
|
|
if (error != null) return state.copyWith(nationalIdError: error);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
final natError = _validateNationality(state.nationality);
|
|
|
|
|
|
final passError = _validatePassportNumber(state.passportNumber);
|
|
|
|
|
|
if (natError != null || passError != null) {
|
|
|
|
|
|
return state.copyWith(
|
|
|
|
|
|
nationalityError: natError,
|
|
|
|
|
|
passportNumberError: passError,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Device / scan handlers ───────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> _onConnectDevice(
|
|
|
|
|
|
ConnectDevice event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.connecting,
|
|
|
|
|
|
isDeviceConnected: false,
|
|
|
|
|
|
isScanConnecting: false,
|
|
|
|
|
|
clearError: true,
|
|
|
|
|
|
clearDeviceStatus: true,
|
|
|
|
|
|
deviceStatusMessage: 'Connecting to device…',
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
final connectFuture = _fingerprintScanner.connect();
|
|
|
|
|
|
|
|
|
|
|
|
int polls = 0;
|
|
|
|
|
|
const maxPolls = 50;
|
|
|
|
|
|
while (polls < maxPolls) {
|
|
|
|
|
|
final done = await Future.any([
|
|
|
|
|
|
connectFuture.then((_) => true).catchError((_) => false),
|
|
|
|
|
|
Future.delayed(const Duration(seconds: 1)).then((_) => false),
|
|
|
|
|
|
]);
|
|
|
|
|
|
if (done) break;
|
|
|
|
|
|
polls++;
|
|
|
|
|
|
final statusMsg = _fingerprintScanner.lastStatus?.message;
|
|
|
|
|
|
if (statusMsg != null) emit(state.copyWith(deviceStatusMessage: statusMsg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await connectFuture;
|
|
|
|
|
|
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.idle,
|
|
|
|
|
|
isDeviceConnected: true,
|
|
|
|
|
|
deviceStatusMessage:
|
|
|
|
|
|
'Device connected – ${_fingerprintScanner.lastStatus?.message ?? "OK"}',
|
|
|
|
|
|
));
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.error,
|
|
|
|
|
|
isDeviceConnected: false,
|
|
|
|
|
|
errorMessage: e.toString().replaceFirst('Exception: ', ''),
|
|
|
|
|
|
deviceStatusMessage:
|
|
|
|
|
|
_fingerprintScanner.lastStatus?.message ?? 'No status received',
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> _onScanFingerprint(
|
|
|
|
|
|
ScanFingerprintEvent event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
|
|
|
|
|
|
final validationError = _buildValidationErrorState();
|
|
|
|
|
|
if (validationError != null) {
|
|
|
|
|
|
emit(validationError);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!_fingerprintScanner.isConnected) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.connecting,
|
|
|
|
|
|
isScanConnecting: true,
|
|
|
|
|
|
isDeviceConnected: false,
|
|
|
|
|
|
clearError: true,
|
|
|
|
|
|
deviceStatusMessage: 'Connecting to device…',
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
final connectFuture = _fingerprintScanner.connect();
|
|
|
|
|
|
|
|
|
|
|
|
int polls = 0;
|
|
|
|
|
|
const maxPolls = 50;
|
|
|
|
|
|
while (polls < maxPolls) {
|
|
|
|
|
|
final done = await Future.any([
|
|
|
|
|
|
connectFuture.then((_) => true).catchError((_) => false),
|
|
|
|
|
|
Future.delayed(const Duration(seconds: 1)).then((_) => false),
|
|
|
|
|
|
]);
|
|
|
|
|
|
if (done) break;
|
|
|
|
|
|
polls++;
|
|
|
|
|
|
final statusMsg = _fingerprintScanner.lastStatus?.message;
|
|
|
|
|
|
if (statusMsg != null) emit(state.copyWith(deviceStatusMessage: statusMsg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await connectFuture;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.scanning,
|
|
|
|
|
|
isScanConnecting: false,
|
|
|
|
|
|
isDeviceConnected: true,
|
|
|
|
|
|
clearError: true,
|
|
|
|
|
|
deviceStatusMessage: 'Connected',
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
final base64Data = await _fingerprintScanner.scan();
|
|
|
|
|
|
|
|
|
|
|
|
if (base64Data == null || base64Data.isEmpty) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.error,
|
|
|
|
|
|
errorMessage: 'No fingerprint data captured'));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.scanned,
|
|
|
|
|
|
base64Data: base64Data,
|
|
|
|
|
|
isScanConnecting: false,
|
|
|
|
|
|
isDeviceConnected: false,
|
|
|
|
|
|
deviceStatusMessage: 'Disconnected',
|
|
|
|
|
|
));
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.error,
|
|
|
|
|
|
errorMessage: 'Capture failed: ${e.toString()}'));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.error,
|
|
|
|
|
|
isScanConnecting: false,
|
|
|
|
|
|
isDeviceConnected: false,
|
|
|
|
|
|
errorMessage: e.toString().replaceFirst('Exception: ', ''),
|
|
|
|
|
|
deviceStatusMessage:
|
|
|
|
|
|
_fingerprintScanner.lastStatus?.message ?? 'No status received',
|
|
|
|
|
|
));
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await _fingerprintScanner.disconnect();
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> _onResetBloc(
|
|
|
|
|
|
ResetBlocEvent event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
emit(const FingerprintFormState(
|
|
|
|
|
|
isDeviceConnected: false, isScanConnecting: false));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> _onSendFingerprint(
|
|
|
|
|
|
SendFingerprintEvent event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.idle, clearBase64: true, clearError: true));
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.error,
|
|
|
|
|
|
errorMessage: 'Failed to send fingerprint: ${e.toString()}'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Verify fingerprint ───────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> _onVerifyFingerprint(
|
|
|
|
|
|
VerifyFingerprintEvent event,
|
|
|
|
|
|
Emitter<FingerprintFormState> emit,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
|
|
|
|
|
|
// FingerprintValidation validation = validateFingerprintBase64(
|
|
|
|
|
|
// state.base64Data!.split(",").last,
|
|
|
|
|
|
// );
|
|
|
|
|
|
// if(validation.isComplete == false)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// emit(state.copyWith(
|
|
|
|
|
|
// status: FormStatus.error,
|
|
|
|
|
|
// errorMessage: validation.reason));
|
|
|
|
|
|
// return;
|
|
|
|
|
|
// }
|
|
|
|
|
|
//
|
|
|
|
|
|
// if (state.base64Data == null || state.base64Data!.isEmpty) {
|
|
|
|
|
|
// emit(state.copyWith(
|
|
|
|
|
|
// status: FormStatus.error,
|
|
|
|
|
|
// errorMessage: 'No fingerprint data to verify'));
|
|
|
|
|
|
// return;
|
|
|
|
|
|
// }
|
|
|
|
|
|
emit(state.copyWith(status: FormStatus.verifying, clearError: true));
|
|
|
|
|
|
try {
|
|
|
|
|
|
final FingerprintVerifyResponseModel responseModel;
|
|
|
|
|
|
if (state.documentType == DocumentType.nationalId) {
|
|
|
|
|
|
final result = await ApiClient.post<FingerprintVerifyResponseModel>(
|
|
|
|
|
|
url: 'api/Fingerprint/authenticate',
|
|
|
|
|
|
shouldKeepToken: true,
|
|
|
|
|
|
body: {
|
|
|
|
|
|
'personId': state.nationalIdInput,
|
|
|
|
|
|
'fingerType': state.selectedFingerIndex,
|
|
|
|
|
|
'finger': "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
|
|
|
|
|
|
},
|
|
|
|
|
|
fromJson: (json) => FingerprintVerifyResponseModel.fromJson(
|
|
|
|
|
|
json as Map<String, dynamic>),
|
|
|
|
|
|
);
|
|
|
|
|
|
log(state.base64Data!.split(",").last);
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
|
case ApiSuccess(:final data):
|
|
|
|
|
|
responseModel = data;
|
|
|
|
|
|
case ApiFailure(:final code, :final message):
|
|
|
|
|
|
if (code == -1) return;
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.error,
|
|
|
|
|
|
errorMessage: 'Verification failed: $message'));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
final result = await ApiClient.post<FingerprintVerifyResponseModel>(
|
|
|
|
|
|
url: 'api/Fingerprint/authenticate-passport',
|
|
|
|
|
|
shouldKeepToken: true,
|
|
|
|
|
|
body: {
|
|
|
|
|
|
'passportNo': state.passportNumber,
|
|
|
|
|
|
'nationaltyCode': state.nationality,
|
|
|
|
|
|
'fingerType': state.selectedFingerIndex,
|
|
|
|
|
|
'finger': state.base64Data!.split(",").last,
|
|
|
|
|
|
},
|
|
|
|
|
|
fromJson: (json) => FingerprintVerifyResponseModel.fromJson(
|
|
|
|
|
|
json as Map<String, dynamic>),
|
|
|
|
|
|
);
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
|
case ApiSuccess(:final data):
|
|
|
|
|
|
responseModel = data;
|
|
|
|
|
|
case ApiFailure(:final code, :final message):
|
|
|
|
|
|
if (code == -1) return;
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.error,
|
|
|
|
|
|
errorMessage: 'Verification failed: $message'));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
final uiModel =
|
|
|
|
|
|
AuthResultUiModel.fromFingerprintVerifyResponse(responseModel);
|
|
|
|
|
|
emit(state.copyWith(status:uiModel.isSuccess? FormStatus.verified : FormStatus.error, authResult: uiModel, errorMessage: uiModel.statusMessage, base64Data: "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
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
emit(state.copyWith(
|
|
|
|
|
|
status: FormStatus.error,
|
|
|
|
|
|
errorMessage: 'Verification failed: ${e.toString()}'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Cancel any in-flight [ApiClient] requests when this BLoC is closed.
|
|
|
|
|
|
@override
|
|
|
|
|
|
Future<void> close() {
|
|
|
|
|
|
ApiClient.cancel();
|
|
|
|
|
|
return super.close();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|