프로그램 정보에 버전 · [최신 버전 확인], 결과는 버튼 옆에 (164)

환경 설정 > 프로그램 정보 맨 위에 "JJ_MKVMaker v<지금>" 과 [최신 버전 확인]. 결과 (최신 · 새 버전 · 확인 못 함) 는 알림이 아니라
버튼 옆에, 확인하는 동안 도는 표시. 업데이트 묶음의 버튼도 같은 위젯 (UpdateCheckButton). 새 버전이면 안내 창은 예전처럼.
checkForUpdate 에 onResult. update_ui_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음4
RyuJongJin 2026-10-10 04:03:52 +09:00
parent ecb1cf93b7
commit 968d62979c
6 changed files with 81 additions and 6 deletions

View File

@ -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",

View File

@ -1010,6 +1010,7 @@
"상하 반전": "上下反転",
"새 마스터 비밀번호 정하기": "新しいマスターパスワードを設定",
"새 버전 v{0}": "新しいバージョン v{0}",
"새 버전 v{0} 이 있습니다": "新しいバージョン v{0} があります",
"새 버전 확인: 지금 v{0} · 최신 v{1}": "新しいバージョンの確認: 現在 v{0} · 最新 v{1}",
"새 버전을 확인할 수 없습니다: {0}": "新しいバージョンを確認できません: {0}",
"새 버전이 예전 기본값을 바꿨으면 켤 때 한 번 무엇이 바뀌었는지 알려 줍니다": "新しいバージョンが以前の既定値を変更した場合、起動時に一度だけ何が変わったかを知らせます",

View File

@ -1010,6 +1010,7 @@
"상하 반전": "上下翻转",
"새 마스터 비밀번호 정하기": "设置新的主密码",
"새 버전 v{0}": "新版本 v{0}",
"새 버전 v{0} 이 있습니다": "有新版本 v{0}",
"새 버전 확인: 지금 v{0} · 최신 v{1}": "检查新版本: 当前 v{0} · 最新 v{1}",
"새 버전을 확인할 수 없습니다: {0}": "无法检查新版本: {0}",
"새 버전이 예전 기본값을 바꿨으면 켤 때 한 번 무엇이 바뀌었는지 알려 줍니다": "如果新版本更改了旧的默认值,启动时提示一次更改了什么",

View File

@ -352,11 +352,8 @@ class _SettingsPageState extends State<SettingsPage> {
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<SettingsPage> {
),
]),
_group('about', Icons.info_outline, tr('프로그램 정보'), [
// 164: 지금 버전과 [최신 버전 확인] (결과는 버튼 옆에)
SettingTile(
leading: const Icon(Icons.info_outline, color: JjColors.accent),
title: FutureBuilder<String>(
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('필수 프로그램 점검')),

View File

@ -16,7 +16,8 @@ import '../l10n/tr.dart';
/// 새 버전 확인 → 알림 → 받기 · 검증 → 종료 후 설치 · 다시 시작 (하루 한 번 자동 확인 · 최신 버전만)
///
/// [manual]: "지금 확인" (최신이어도 알려 주고, 건너뛴 버전도 보여 줌)
Future<void> checkForUpdate(BuildContext context, AppController c, {bool manual = false}) async {
/// [onResult]: 164 - 직접 확인할 때 결과를 알림 대신 버튼 옆에 (최신 · 새 버전 · 확인 못 함)
Future<void> 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<void> 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<void> 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<String>(
@ -121,6 +124,41 @@ Future<void> chooseVersion(BuildContext context, AppController c) async {
ReleaseInfo initialVersionPick(List<ReleaseInfo> 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<UpdateCheckButton> createState() => _UpdateCheckButtonState();
}
class _UpdateCheckButtonState extends State<UpdateCheckButton> {
bool _busy = false;
String? _result;
Future<void> _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;

View File

@ -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<void> until(bool Function() ok) async {
for (var i = 0; i < 100 && !ok(); i++) {
await tester.runAsync(() => Future<void>.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<void> open(WidgetTester tester, {bool manual = true}) async {
tester.view.physicalSize = const Size(1200, 900);
tester.view.devicePixelRatio = 1;