Initial commit
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user