183 lines
5.7 KiB
Dart
183 lines
5.7 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:forui/forui.dart';
|
||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
|
import 'package:url_launcher/url_launcher.dart';
|
||
|
|
|
||
|
|
import '../../providers/update_provider.dart';
|
||
|
|
import '../../shared/app_info.dart';
|
||
|
|
import '../../shared/utils/format_utils.dart';
|
||
|
|
|
||
|
|
class UpdateDialog extends ConsumerStatefulWidget {
|
||
|
|
const UpdateDialog({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
ConsumerState<UpdateDialog> createState() => _UpdateDialogState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _UpdateDialogState extends ConsumerState<UpdateDialog> {
|
||
|
|
bool _installTriggered = false;
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
final state = ref.watch(updateProvider);
|
||
|
|
final info = state.info;
|
||
|
|
final appVersion =
|
||
|
|
ref.watch(appVersionProvider).value ?? kSmPlayerVersionFallback;
|
||
|
|
|
||
|
|
if (state.status == UpdateStatus.readyToInstall && !_installTriggered) {
|
||
|
|
_installTriggered = true;
|
||
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||
|
|
final navigator = Navigator.of(context);
|
||
|
|
await ref.read(updateProvider.notifier).install();
|
||
|
|
if (mounted && navigator.canPop()) {
|
||
|
|
navigator.pop();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (info == null) {
|
||
|
|
return FDialog.adaptive(
|
||
|
|
title: const Text('检查更新'),
|
||
|
|
body: _ErrorContent(message: state.error ?? '没有可用的更新信息'),
|
||
|
|
actions: [
|
||
|
|
FButton(
|
||
|
|
onPress: () => ref.read(updateProvider.notifier).manualCheck(),
|
||
|
|
child: const Text('重试'),
|
||
|
|
),
|
||
|
|
FButton(
|
||
|
|
onPress: () => Navigator.of(context).pop(),
|
||
|
|
variant: FButtonVariant.outline,
|
||
|
|
child: const Text('关闭'),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
final isDownloading = state.status == UpdateStatus.downloading;
|
||
|
|
final isReady = state.status == UpdateStatus.readyToInstall;
|
||
|
|
final hasError = state.status == UpdateStatus.error;
|
||
|
|
final noAsset = info.noAssetForPlatform;
|
||
|
|
|
||
|
|
return FDialog.adaptive(
|
||
|
|
title: Text('发现新版本 ${info.version}'),
|
||
|
|
body: SizedBox(
|
||
|
|
width: 460,
|
||
|
|
child: Column(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
Text('当前版本 $appVersion → 最新版本 ${info.version}'),
|
||
|
|
const SizedBox(height: 12),
|
||
|
|
ConstrainedBox(
|
||
|
|
constraints: const BoxConstraints(maxHeight: 220),
|
||
|
|
child: SingleChildScrollView(
|
||
|
|
child: Text(
|
||
|
|
info.releaseNotes.trim().isEmpty
|
||
|
|
? '暂无更新说明'
|
||
|
|
: info.releaseNotes.trim(),
|
||
|
|
style: Theme.of(context).textTheme.bodySmall,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
if (isDownloading) ...[
|
||
|
|
const SizedBox(height: 16),
|
||
|
|
FDeterminateProgress(
|
||
|
|
value: downloadProgressValue(
|
||
|
|
received: state.received,
|
||
|
|
total: state.total,
|
||
|
|
) ?? 0,
|
||
|
|
),
|
||
|
|
const SizedBox(height: 8),
|
||
|
|
Row(
|
||
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
|
|
children: [
|
||
|
|
Text(
|
||
|
|
'${formatDownloadBytes(state.received)} / ${formatDownloadBytes(state.total)}',
|
||
|
|
style: Theme.of(context).textTheme.bodySmall,
|
||
|
|
),
|
||
|
|
Text(
|
||
|
|
formatDownloadSpeed(state.speedBytesPerSec),
|
||
|
|
style: Theme.of(context).textTheme.bodySmall,
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
],
|
||
|
|
if (isReady) ...[
|
||
|
|
const SizedBox(height: 16),
|
||
|
|
const Text('安装程序已下载,正在打开。'),
|
||
|
|
],
|
||
|
|
if (hasError) ...[
|
||
|
|
const SizedBox(height: 16),
|
||
|
|
_ErrorContent(message: state.error ?? '更新失败'),
|
||
|
|
],
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
actions: [
|
||
|
|
if (isDownloading)
|
||
|
|
FButton(
|
||
|
|
onPress: () => ref.read(updateProvider.notifier).cancel(),
|
||
|
|
variant: FButtonVariant.outline,
|
||
|
|
child: const Text('取消'),
|
||
|
|
)
|
||
|
|
else ...[
|
||
|
|
FButton(
|
||
|
|
onPress: () => Navigator.of(context).pop(),
|
||
|
|
variant: FButtonVariant.outline,
|
||
|
|
child: const Text('稍后'),
|
||
|
|
),
|
||
|
|
if (hasError)
|
||
|
|
FButton(
|
||
|
|
onPress: () => ref.read(updateProvider.notifier).startDownload(),
|
||
|
|
child: const Text('重试'),
|
||
|
|
)
|
||
|
|
else if (noAsset)
|
||
|
|
FButton(
|
||
|
|
onPress: () => _openDownloadPage(info.htmlUrl),
|
||
|
|
child: const Text('前往下载页'),
|
||
|
|
)
|
||
|
|
else
|
||
|
|
FButton(
|
||
|
|
onPress: isReady
|
||
|
|
? null
|
||
|
|
: () => ref.read(updateProvider.notifier).startDownload(),
|
||
|
|
child: const Text('下载并安装'),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _openDownloadPage(Uri uri) async {
|
||
|
|
if (!uri.hasScheme) return;
|
||
|
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class _ErrorContent extends StatelessWidget {
|
||
|
|
final String message;
|
||
|
|
|
||
|
|
const _ErrorContent({required this.message});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Row(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
Icon(
|
||
|
|
Icons.error_outline,
|
||
|
|
size: 18,
|
||
|
|
color: Theme.of(context).colorScheme.error,
|
||
|
|
),
|
||
|
|
const SizedBox(width: 8),
|
||
|
|
Flexible(
|
||
|
|
child: Text(
|
||
|
|
message,
|
||
|
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|