Files
Immich/mobile/lib/widgets/settings/app_update_settings.dart
T

210 lines
5.8 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/services/app_update.service.dart';
import 'package:package_info_plus/package_info_plus.dart';
class AppUpdateSettings extends StatefulWidget {
final AppUpdateService? service;
const AppUpdateSettings({super.key, this.service});
@override
State<AppUpdateSettings> createState() => _AppUpdateSettingsState();
}
class _AppUpdateSettingsState extends State<AppUpdateSettings> {
late final _service = widget.service ?? AppUpdateService();
PackageInfo? _installed;
AppRelease? _release;
File? _apk;
bool _busy = false;
bool _downloading = false;
bool _cancelled = false;
bool _permissionRequired = false;
double _progress = 0;
String? _message;
bool _error = false;
@override
void initState() {
super.initState();
unawaited(_loadInstalled());
}
Future<void> _loadInstalled() async {
try {
final installed = await _service.installedVersion();
if (mounted) {
setState(() => _installed = installed);
}
} catch (_) {
if (mounted) {
setState(() {
_error = true;
_message = 'local_update_error';
});
}
}
}
Future<void> _check() async {
setState(() {
_busy = true;
_error = false;
_message = null;
_release = null;
_apk = null;
_permissionRequired = false;
});
try {
final installed = await _service.installedVersion();
final release = await _service.check();
if (!mounted) {
return;
}
setState(() {
_installed = installed;
_release = release != null && release.isNewerThan(installed.buildNumber) ? release : null;
_message = _release == null ? 'local_update_current' : 'local_update_available';
});
} catch (_) {
if (mounted) {
setState(() {
_error = true;
_message = 'local_update_error';
});
}
} finally {
if (mounted) {
setState(() => _busy = false);
}
}
}
Future<void> _downloadAndInstall() async {
final release = _release;
if (release == null) {
return;
}
setState(() {
_busy = true;
_downloading = _apk == null;
_cancelled = false;
_progress = 0;
_error = false;
_message = null;
});
try {
_apk ??= await _service.download(release, (progress) {
if (mounted) {
setState(() => _progress = progress);
}
});
if (!mounted || _cancelled) {
return;
}
setState(() => _downloading = false);
final started = await _service.install(_apk!, release);
if (mounted) {
setState(() {
_permissionRequired = !started;
_message = started ? 'local_update_installer_opened' : 'local_update_permission';
});
}
} catch (_) {
if (mounted) {
setState(() {
_error = !_cancelled;
_message = _cancelled ? 'local_update_cancelled' : 'local_update_download_error';
});
}
} finally {
if (mounted) {
setState(() {
_busy = false;
_downloading = false;
});
}
}
}
Future<void> _openPermission() async {
try {
await _service.openInstallPermission();
} catch (_) {
if (mounted) {
setState(() {
_error = true;
_message = 'local_update_error';
});
}
}
}
@override
void dispose() {
_service.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final t = context.t;
final release = _release;
return ListView(
padding: const EdgeInsets.all(20),
children: [
Text(t.local_update_description),
const SizedBox(height: 16),
if (_installed != null)
Text('${t.local_update_installed}: ${_installed!.version} (${_installed!.buildNumber})'),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _busy ? null : _check,
icon: const Icon(Icons.system_update),
label: Text(t.local_update_check),
),
if (_busy && !_downloading)
const Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator()),
),
if (_message != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(
_message!.tr(context: context),
style: TextStyle(color: _error ? Theme.of(context).colorScheme.error : null),
),
),
if (release != null) ...[
Text('${t.local_update_available}: ${release.version}', style: Theme.of(context).textTheme.titleMedium),
Text('${(release.size / (1024 * 1024)).toStringAsFixed(1)} MB'),
if (release.notes.isNotEmpty)
Padding(padding: const EdgeInsets.symmetric(vertical: 16), child: Text(release.notes)),
if (_downloading) ...[
LinearProgressIndicator(value: _progress),
Text('${(_progress * 100).floor()}%'),
TextButton(
onPressed: () {
_cancelled = true;
_service.cancel();
},
child: Text(t.cancel),
),
],
if (_permissionRequired)
OutlinedButton(onPressed: _busy ? null : _openPermission, child: Text(t.local_update_allow)),
FilledButton(
onPressed: _busy ? null : _downloadAndInstall,
child: Text(_apk == null ? t.local_update_download : t.local_update_install),
),
],
],
);
}
}