diff --git a/assets/l10n/en.json b/assets/l10n/en.json index a33a7b2..85d6bd1 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -1010,6 +1010,7 @@ "상하 반전": "Flip vertically", "새 마스터 비밀번호 정하기": "Set a new master password", "새 버전 v{0}": "New version v{0}", + "새 버전 v{0} 이 있습니다": "New version v{0} is available", "새 버전 확인: 지금 v{0} · 최신 v{1}": "Update check: current v{0} · latest v{1}", "새 버전을 확인할 수 없습니다: {0}": "Cannot check for a new version: {0}", "새 버전이 예전 기본값을 바꿨으면 켤 때 한 번 무엇이 바뀌었는지 알려 줍니다": "If a new version changed old defaults, shows once at startup what changed", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index d62f3ef..9263620 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -1010,6 +1010,7 @@ "상하 반전": "上下反転", "새 마스터 비밀번호 정하기": "新しいマスターパスワードを設定", "새 버전 v{0}": "新しいバージョン v{0}", + "새 버전 v{0} 이 있습니다": "新しいバージョン v{0} があります", "새 버전 확인: 지금 v{0} · 최신 v{1}": "新しいバージョンの確認: 現在 v{0} · 最新 v{1}", "새 버전을 확인할 수 없습니다: {0}": "新しいバージョンを確認できません: {0}", "새 버전이 예전 기본값을 바꿨으면 켤 때 한 번 무엇이 바뀌었는지 알려 줍니다": "新しいバージョンが以前の既定値を変更した場合、起動時に一度だけ何が変わったかを知らせます", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index bd1d11d..379434a 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -1010,6 +1010,7 @@ "상하 반전": "上下翻转", "새 마스터 비밀번호 정하기": "设置新的主密码", "새 버전 v{0}": "新版本 v{0}", + "새 버전 v{0} 이 있습니다": "有新版本 v{0}", "새 버전 확인: 지금 v{0} · 최신 v{1}": "检查新版本: 当前 v{0} · 最新 v{1}", "새 버전을 확인할 수 없습니다: {0}": "无法检查新版本: {0}", "새 버전이 예전 기본값을 바꿨으면 켤 때 한 번 무엇이 바뀌었는지 알려 줍니다": "如果新版本更改了旧的默认值,启动时提示一次更改了什么", diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index 176a8fe..ae34986 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -352,11 +352,8 @@ class _SettingsPageState extends State { subtitle: Text(tr('GitHub 에 올려 둔 모든 버전 중에서 골라 설치합니다 (새 버전으로 · 예전 정상 버전으로 되돌리기). ' '바꾸기 전 버전의 설정은 보관해 두었다가, 그 버전으로 돌아오면 되살립니다')), isThreeLine: true, - trailing: Wrap(spacing: 8, children: [ - OutlinedButton( - onPressed: () => checkForUpdate(context, c, manual: true), - child: Text(tr('최신 버전 확인')), - ), + trailing: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ + UpdateCheckButton(c: c), FilledButton.icon( onPressed: () => chooseVersion(context, c), icon: const Icon(Icons.system_update_alt, size: 18), @@ -989,6 +986,15 @@ class _SettingsPageState extends State { ), ]), _group('about', Icons.info_outline, tr('프로그램 정보'), [ + // 164: 지금 버전과 [최신 버전 확인] (결과는 버튼 옆에) + SettingTile( + leading: const Icon(Icons.info_outline, color: JjColors.accent), + title: FutureBuilder( + future: c.services.updater?.currentVersion() ?? Future.value(''), + builder: (_, v) => Text(v.data == null || v.data!.isEmpty ? 'JJ_MKVMaker' : 'JJ_MKVMaker v${v.data}'), + ), + trailing: c.services.updater == null ? null : UpdateCheckButton(c: c), + ), if (desk) ...[ SettingTile( title: Text(tr('필수 프로그램 점검')), diff --git a/lib/ui/update_dialog.dart b/lib/ui/update_dialog.dart index ab110b0..4424089 100644 --- a/lib/ui/update_dialog.dart +++ b/lib/ui/update_dialog.dart @@ -16,7 +16,8 @@ import '../l10n/tr.dart'; /// 새 버전 확인 → 알림 → 받기 · 검증 → 종료 후 설치 · 다시 시작 (하루 한 번 자동 확인 · 최신 버전만) /// /// [manual]: "지금 확인" (최신이어도 알려 주고, 건너뛴 버전도 보여 줌) -Future checkForUpdate(BuildContext context, AppController c, {bool manual = false}) async { +/// [onResult]: 164 - 직접 확인할 때 결과를 알림 대신 버튼 옆에 (최신 · 새 버전 · 확인 못 함) +Future checkForUpdate(BuildContext context, AppController c, {bool manual = false, void Function(String result)? onResult}) async { final up = c.services.updater; if (up == null) return; final s = c.settings; @@ -33,6 +34,7 @@ Future checkForUpdate(BuildContext context, AppController c, {bool manual } void snack(String t) { + if (onResult != null) return onResult(t); if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t))); } @@ -54,6 +56,7 @@ Future checkForUpdate(BuildContext context, AppController c, {bool manual return; } if (!manual && s.skippedVersion == r.version) return; + onResult?.call(trf('새 버전 v{0} 이 있습니다', [r.version])); if (!context.mounted) return; final choice = await showDialog( @@ -121,6 +124,41 @@ Future chooseVersion(BuildContext context, AppController c) async { ReleaseInfo initialVersionPick(List all, String current) => all.firstWhere((r) => r.zipUrl != null, orElse: () => all.firstWhere((r) => r.version == current, orElse: () => all.first)); +/// 164: [최신 버전 확인] 과 그 결과 (버튼 옆에 - 최신입니다 · 새 버전이 있습니다 · 확인할 수 없습니다) +class UpdateCheckButton extends StatefulWidget { + final AppController c; + const UpdateCheckButton({super.key, required this.c}); + + @override + State createState() => _UpdateCheckButtonState(); +} + +class _UpdateCheckButtonState extends State { + bool _busy = false; + String? _result; + + Future _check() async { + setState(() { + _busy = true; + _result = null; + }); + try { + await checkForUpdate(context, widget.c, manual: true, onResult: (r) { + if (mounted) setState(() => _result = r); + }); + } finally { + if (mounted) setState(() => _busy = false); + } + } + + @override + Widget build(BuildContext context) => Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ + OutlinedButton(onPressed: _busy ? null : _check, child: Text(tr('최신 버전 확인'))), + if (_busy) const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)), + if (_result != null) Text(_result!, style: const TextStyle(fontSize: 12, color: JjColors.textDim)), + ]); +} + /// 버전 고르기 창 class _VersionPicker extends StatefulWidget { final Updater up; diff --git a/test/update_ui_test.dart b/test/update_ui_test.dart index b7c980a..8b06fbe 100644 --- a/test/update_ui_test.dart +++ b/test/update_ui_test.dart @@ -94,6 +94,34 @@ void main() { mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), updater: up)); }); + testWidgets('164: [최신 버전 확인] 결과는 알림이 아니라 버튼 옆에 (최신 · 새 버전)', (tester) async { + tester.view.physicalSize = const Size(1200, 900); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + await tester.pumpWidget(MaterialApp(home: Scaffold(body: Center(child: UpdateCheckButton(c: c))))); + // 확인하는 동안 · 새 버전 창이 떠 있는 동안 도는 표시가 있어 pumpAndSettle 대신 될 때까지 + Future until(bool Function() ok) async { + for (var i = 0; i < 100 && !ok(); i++) { + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 50))); + await tester.pump(const Duration(milliseconds: 50)); + } + } + + await tester.tap(find.text('최신 버전 확인')); + await until(() => find.textContaining('최신 버전입니다').evaluate().isNotEmpty); + expect(find.text('최신 버전입니다 (v1.0.1)'), findsOneWidget); + expect(find.byType(SnackBar), findsNothing); + up.next = ReleaseInfo(version: '1.0.2', tag: 'v1.0.2', name: '', notes: '', pageUrl: '', zipUrl: 'https://x/a.zip'); + await tester.tap(find.text('최신 버전 확인')); + await until(() => find.textContaining('새 버전 v1.0.2 이 있습니다').evaluate().isNotEmpty); + expect(find.text('새 버전 v1.0.2 이 있습니다'), findsOneWidget); + // 새 버전이 있으면 예전처럼 안내 창도 뜬다 + expect(find.text('새 버전 v1.0.2'), findsWidgets); + await tester.tap(find.text('나중에').last); + await until(() => find.byType(CircularProgressIndicator).evaluate().isEmpty); + expect(find.byType(CircularProgressIndicator), findsNothing); + }); + Future open(WidgetTester tester, {bool manual = true}) async { tester.view.physicalSize = const Size(1200, 900); tester.view.devicePixelRatio = 1;