MotorSignal-App/lib/services/sync/cloud_restore_controller.dart

172 lines
5.4 KiB
Dart

// ignore_for_file: prefer_initializing_formals
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../auth/auth_service.dart';
import '../../database/database.dart';
import '../../features/records/records_service.dart';
import 'cloud_restore_service.dart';
enum CloudRestoreStatus {
idle,
restoring,
success,
partialFailure,
authRequired,
unavailable,
failure,
}
class CloudRestoreUiState {
final CloudRestoreStatus status;
final CloudRestoreResult? result;
final String? message;
const CloudRestoreUiState({
this.status = CloudRestoreStatus.idle,
this.result,
this.message,
});
bool get isRestoring => status == CloudRestoreStatus.restoring;
}
class CloudRestoreController extends StateNotifier<CloudRestoreUiState> {
final CloudRestoreService _restoreService;
final AuthService _authService;
final RecordsService? _recordsService;
final AppDatabase? _db;
CloudRestoreController({
required CloudRestoreService restoreService,
required AuthService authService,
RecordsService? recordsService,
AppDatabase? db,
}) : _restoreService = restoreService,
_authService = authService,
_recordsService = recordsService,
_db = db,
super(const CloudRestoreUiState());
Future<CloudRestoreResult?> restoreFromMotorCloud() async {
if (state.isRestoring) return state.result;
final user = _authService.currentUser;
if (user == null || user.isAnonymous) {
state = const CloudRestoreUiState(
status: CloudRestoreStatus.authRequired,
message: 'Sign in with a MotorCloud account to restore ride history.',
);
return null;
}
if (!_authService.isConfigured) {
state = const CloudRestoreUiState(
status: CloudRestoreStatus.unavailable,
message:
'MotorCloud is unavailable. Try again when Firebase services are available.',
);
return null;
}
state = const CloudRestoreUiState(status: CloudRestoreStatus.restoring);
try {
final result = await _restoreService.restore();
if (_recordsService != null && _db != null) {
try {
await _recordsService.recalculateFromDatabase(_db);
} catch (_) {}
}
final hasFailures = result.failures > 0;
final hasRestoredOrMatched =
result.hasAnyRestoredData ||
result.existingRidesMatched > 0 ||
result.telemetryAlreadyPresent > 0 ||
result.telemetrySkipped > 0;
final status = result.requiresAuthentication && !hasRestoredOrMatched
? CloudRestoreStatus.authRequired
: (hasFailures && !hasRestoredOrMatched
? CloudRestoreStatus.failure
: (hasFailures
? CloudRestoreStatus.partialFailure
: CloudRestoreStatus.success));
state = CloudRestoreUiState(
status: status,
result: result,
message: _formatResult(result),
);
return result;
} on StateError catch (e) {
final message = e.message;
state = CloudRestoreUiState(
status: message.toLowerCase().contains('auth')
? CloudRestoreStatus.authRequired
: CloudRestoreStatus.unavailable,
message: message,
);
return null;
} catch (e) {
state = CloudRestoreUiState(
status: CloudRestoreStatus.failure,
message: 'MotorCloud restore failed: $e',
);
return null;
}
}
String _formatResult(CloudRestoreResult result) {
final parts = <String>[];
if (result.metadataRowsAdded > 0) {
parts.add('${result.metadataRowsAdded} ride(s) added');
}
if (result.telemetrySamplesRestored > 0) {
parts.add(
'${result.telemetrySamplesRestored} telemetry sample(s) restored',
);
}
if (result.performanceRunsRestored > 0) {
parts.add(
'${result.performanceRunsRestored} performance run(s) restored',
);
}
if (result.rideEventsRestored > 0) {
parts.add('${result.rideEventsRestored} ride event(s) restored');
}
if (result.existingRidesMatched > 0 || result.telemetryAlreadyPresent > 0) {
final alreadyPresent =
result.existingRidesMatched > result.telemetryAlreadyPresent
? result.existingRidesMatched
: result.telemetryAlreadyPresent;
parts.add('$alreadyPresent ride(s) already on this device');
}
final failureDetails = <String>[];
if (result.telemetryFailed > 0) {
failureDetails.add(
'${result.telemetryFailed} ride(s) pending telemetry upload from source device',
);
}
if (result.telemetryAuthFailures > 0) {
failureDetails.add('${result.telemetryAuthFailures} ride(s) require re-authentication');
}
if (result.metadata.conflicts > 0) {
failureDetails.add('${result.metadata.conflicts} conflict(s)');
}
if (result.metadata.skipped > 0) {
failureDetails.add('${result.metadata.skipped} skipped');
}
if (result.failures > 0) {
final explanation = failureDetails.isNotEmpty
? failureDetails.join('; ')
: '${result.failures} item(s) need attention';
parts.add('Unresolved: $explanation');
}
if (parts.isEmpty) {
return 'No restorable MotorCloud rides were found for this account.';
}
final prefix = result.failures > 0
? 'MotorCloud restore partially completed'
: 'MotorCloud restore complete';
return '$prefix: ${parts.join(', ')}.';
}
}