feat: fix debug updater
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:ota_update/ota_update.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import '../utils/app_config.dart';
|
||||
import '../utils/app_update_util.dart';
|
||||
|
||||
enum OtaPhase { downloading, installing, done, error }
|
||||
|
||||
class UpdateProgressViewModel extends ChangeNotifier {
|
||||
final UpdateInfo update;
|
||||
final bool simulate;
|
||||
final bool simulateError;
|
||||
|
||||
OtaPhase _phase = OtaPhase.downloading;
|
||||
double _progress = 0;
|
||||
String _statusText = 'Preparing download…';
|
||||
String? _errorMessage;
|
||||
bool _done = false;
|
||||
|
||||
StreamSubscription? _subscription;
|
||||
Timer? _simTimer;
|
||||
|
||||
OtaPhase get phase => _phase;
|
||||
double get progress => _progress;
|
||||
String get statusText => _statusText;
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
UpdateProgressViewModel({
|
||||
required this.update,
|
||||
this.simulate = false,
|
||||
this.simulateError = false,
|
||||
});
|
||||
|
||||
Future<void> start() async {
|
||||
if (simulate) {
|
||||
_runSimulation();
|
||||
return;
|
||||
}
|
||||
|
||||
final url = update.download.startsWith('http')
|
||||
? update.download
|
||||
: '$kUpdateServerUrl${update.download}';
|
||||
|
||||
debugPrint('UpdateProgressViewModel: starting download from $url');
|
||||
|
||||
_subscription = OtaUpdate()
|
||||
.execute(
|
||||
url,
|
||||
destinationFilename: 'update.apk',
|
||||
sha256checksum: update.sha256,
|
||||
)
|
||||
.listen(
|
||||
_onEvent,
|
||||
onError: _onError,
|
||||
onDone: _onDone,
|
||||
);
|
||||
}
|
||||
|
||||
void _runSimulation() {
|
||||
debugPrint('UpdateProgressViewModel: running simulated download');
|
||||
|
||||
int step = 0;
|
||||
const totalSteps = 20;
|
||||
const stepDuration = Duration(milliseconds: 500);
|
||||
|
||||
_simTimer = Timer.periodic(stepDuration, (timer) {
|
||||
step++;
|
||||
|
||||
if (simulateError && step == 10) {
|
||||
timer.cancel();
|
||||
_phase = OtaPhase.error;
|
||||
_errorMessage = 'Simulated download error (network timeout)';
|
||||
_statusText = 'Download failed';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
if (step < totalSteps) {
|
||||
_progress = step / totalSteps;
|
||||
_statusText = 'Downloading ${(_progress * 100).toStringAsFixed(0)}%';
|
||||
_phase = OtaPhase.downloading;
|
||||
notifyListeners();
|
||||
} else {
|
||||
timer.cancel();
|
||||
_startInstallPhase();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _startInstallPhase() {
|
||||
_phase = OtaPhase.installing;
|
||||
_progress = 1.0;
|
||||
_statusText = 'Installing update…';
|
||||
notifyListeners();
|
||||
|
||||
_simTimer = Timer(const Duration(seconds: 3), () {
|
||||
_phase = OtaPhase.done;
|
||||
_progress = 1.0;
|
||||
_statusText = 'Update installed! Restarting…';
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
void _onDone() {
|
||||
if (_done) return;
|
||||
_done = true;
|
||||
_phase = OtaPhase.done;
|
||||
_progress = 1.0;
|
||||
_statusText = 'Update installed! Restarting…';
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _onEvent(OtaEvent event) {
|
||||
switch (event.status) {
|
||||
case OtaStatus.DOWNLOADING:
|
||||
final raw = double.tryParse(event.value ?? '0') ?? 0;
|
||||
_progress = (raw / 100).clamp(0.0, 1.0);
|
||||
_statusText = 'Downloading ${_progress.toStringAsFixed(0)}%';
|
||||
_phase = OtaPhase.downloading;
|
||||
notifyListeners();
|
||||
|
||||
case OtaStatus.INSTALLING:
|
||||
_phase = OtaPhase.installing;
|
||||
_progress = 1.0;
|
||||
_statusText = 'Installing update…';
|
||||
notifyListeners();
|
||||
|
||||
case OtaStatus.INSTALLATION_DONE:
|
||||
_onDone();
|
||||
return;
|
||||
|
||||
case OtaStatus.DOWNLOAD_ERROR:
|
||||
_phase = OtaPhase.error;
|
||||
_errorMessage = 'Download failed: ${event.value}';
|
||||
_statusText = 'Download failed';
|
||||
Sentry.captureMessage('OTA download error: ${event.value}');
|
||||
notifyListeners();
|
||||
|
||||
case OtaStatus.INSTALLATION_ERROR:
|
||||
_phase = OtaPhase.error;
|
||||
_errorMessage = 'Installation failed: ${event.value}';
|
||||
_statusText = 'Installation failed';
|
||||
Sentry.captureMessage('OTA installation error: ${event.value}');
|
||||
notifyListeners();
|
||||
|
||||
case OtaStatus.ALREADY_RUNNING_ERROR:
|
||||
_phase = OtaPhase.error;
|
||||
_errorMessage = 'Update already in progress';
|
||||
_statusText = 'Update already running';
|
||||
notifyListeners();
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _onError(Object error, StackTrace stack) {
|
||||
debugPrint('UpdateProgressViewModel: stream error: $error');
|
||||
_phase = OtaPhase.error;
|
||||
_errorMessage = error.toString();
|
||||
_statusText = 'Update failed';
|
||||
Sentry.captureException(error, stackTrace: stack);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
_simTimer?.cancel();
|
||||
_simTimer = null;
|
||||
_subscription?.cancel();
|
||||
_subscription = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user