import 'dart:io'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:umbrix/core/localization/translations.dart'; import 'package:umbrix/features/app_update/model/remote_version_entity.dart'; import 'package:umbrix/features/app_update/notifier/app_update_notifier.dart'; import 'package:umbrix/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:dio/dio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:open_file/open_file.dart'; class NewVersionDialog extends HookConsumerWidget with PresLogger { NewVersionDialog( this.currentVersion, this.newVersion, { this.canIgnore = true, }) : super(key: _dialogKey); final String currentVersion; final RemoteVersionEntity newVersion; final bool canIgnore; static final _dialogKey = GlobalKey(debugLabel: 'new version dialog'); Future show(BuildContext context) async { if (_dialogKey.currentContext == null) { return showDialog( context: context, builder: (context) => this, ); } else { loggy.warning("new version dialog is already open"); } } @override Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); final theme = Theme.of(context); final isDownloading = useState(false); final downloadProgress = useState(0.0); Future downloadAndInstallUpdate() async { // Для Android - просто открываем браузер (в production - ссылка на Google Play) if (Platform.isAndroid) { await UriUtils.tryLaunch(Uri.parse(newVersion.url)); if (context.mounted) context.pop(); return; } // Для Desktop (Windows/macOS/Linux) - скачиваем с прогресс-баром try { isDownloading.value = true; downloadProgress.value = 0.0; final tempDir = await getTemporaryDirectory(); String fileExt = ''; if (Platform.isWindows) fileExt = '.exe'; else if (Platform.isMacOS) fileExt = '.dmg'; else if (Platform.isLinux) fileExt = '.AppImage'; final savePath = '${tempDir.path}/umbrix-${newVersion.version}$fileExt'; final file = File(savePath); if (await file.exists()) await file.delete(); final dio = Dio(); await dio.download(newVersion.url, savePath, onReceiveProgress: (received, total) { if (total != -1) downloadProgress.value = received / total; }); loggy.info('Update downloaded to: $savePath'); final result = await OpenFile.open(savePath); if (result.type != ResultType.done && context.mounted) { CustomToast.error('Не удалось открыть: ${result.message}').show(context); } else if (context.mounted) { context.pop(); } } catch (e, st) { loggy.error('Download failed', e, st); if (context.mounted) CustomToast.error('Ошибка: $e').show(context); } finally { isDownloading.value = false; } } return AlertDialog( title: Text(t.appUpdate.dialogTitle), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(t.appUpdate.updateMsg), const Gap(8), Text.rich(TextSpan(children: [ TextSpan(text: "${t.appUpdate.currentVersionLbl}: ", style: theme.textTheme.bodySmall), TextSpan(text: currentVersion, style: theme.textTheme.labelMedium), ])), Text.rich(TextSpan(children: [ TextSpan(text: "${t.appUpdate.newVersionLbl}: ", style: theme.textTheme.bodySmall), TextSpan(text: newVersion.presentVersion, style: theme.textTheme.labelMedium), ])), if (isDownloading.value) ...[ const Gap(16), LinearProgressIndicator(value: downloadProgress.value), const Gap(8), Text('Скачивание: ${(downloadProgress.value * 100).toStringAsFixed(0)}%', style: theme.textTheme.bodySmall), ], ], ), actions: [ if (canIgnore && !isDownloading.value) TextButton( onPressed: () async { await ref.read(appUpdateNotifierProvider.notifier).ignoreRelease(newVersion); if (context.mounted) context.pop(); }, child: Text(t.appUpdate.ignoreBtnTxt), ), if (!isDownloading.value) TextButton(onPressed: context.pop, child: Text(t.appUpdate.laterBtnTxt)), TextButton( onPressed: isDownloading.value ? null : downloadAndInstallUpdate, child: Text(isDownloading.value ? 'Скачивание...' : t.appUpdate.updateNowBtnTxt), ), ], ); } }