From bf72557fc3a88817ab9690f7d36c068b571f82c8 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Sat, 10 Oct 2026 03:30:26 +0900 Subject: [PATCH] =?UTF-8?q?AI=20=ED=95=B4=EC=83=81=EB=8F=84=20=EC=98=AC?= =?UTF-8?q?=EB=A6=AC=EA=B8=B0=20=EB=B9=84=EA=B5=90=20=ED=99=94=EB=A9=B4:?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=ED=95=98=EC=A7=80=20=EC=95=8A=EA=B3=A0=20?= =?UTF-8?q?=EB=8B=AB=EC=9C=BC=EB=A9=B4=20=EB=AC=BB=EA=B8=B0=20(160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 닫으면 올린 그림이 지워지므로 [닫기] · 뒤로 키 · 뒤로 몸짓 모두 "저장하지 않고 닫을까요?" ([취소] · [저장하지 않고 닫기] · [저장]). 위쪽 [저장] 은 묻지 않는다. 시험하려고 UpscaleCompareView 로 공개. upscale_compare_test. Co-Authored-By: Claude Opus 5.5 --- assets/l10n/en.json | 3 ++ assets/l10n/ja.json | 3 ++ assets/l10n/zh-Hans.json | 3 ++ lib/ui/ai_upscale_ui.dart | 46 +++++++++++++++++++---- test/upscale_compare_test.dart | 67 ++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 test/upscale_compare_test.dart diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 60ee451..c6d7844 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -1266,6 +1266,7 @@ "올린 것": "Upscaled", "올린 것 (AI)": "Upscaled (AI)", "올린 것으로 계속 보기": "Keep viewing the upscaled one", + "올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.": "The upscaled image will be deleted. To see it again you will need to upscale again.", "올린 이 {0}": "Uploaded by {0}", "옮겼습니다.": "moved.", "옮기기": "Move", @@ -1593,6 +1594,8 @@ "저장하고 실행": "Save and run", "저장하는 중": "Saving", "저장하지 못했습니다: {0}": "Could not save: {0}", + "저장하지 않고 닫기": "Close without saving", + "저장하지 않고 닫을까요?": "Close without saving?", "저장하지 않은 변경": "Unsaved changes", "저장한 뒤": "After saving", "저장했습니다": "Saved", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 13f0866..972c2e4 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -1266,6 +1266,7 @@ "올린 것": "高解像度化後", "올린 것 (AI)": "高解像度化後 (AI)", "올린 것으로 계속 보기": "高解像度化した画像で表示を続ける", + "올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.": "高解像度化した画像は削除されます。もう一度見るには高解像度化をやり直す必要があります。", "올린 이 {0}": "投稿者 {0}", "옮겼습니다.": "移動しました。", "옮기기": "移動", @@ -1593,6 +1594,8 @@ "저장하고 실행": "保存して実行", "저장하는 중": "保存中", "저장하지 못했습니다: {0}": "保存できませんでした: {0}", + "저장하지 않고 닫기": "保存せずに閉じる", + "저장하지 않고 닫을까요?": "保存せずに閉じますか?", "저장하지 않은 변경": "未保存の変更", "저장한 뒤": "保存後", "저장했습니다": "保存しました", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 383bea4..582b9d2 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -1266,6 +1266,7 @@ "올린 것": "提高后", "올린 것 (AI)": "提高后 (AI)", "올린 것으로 계속 보기": "继续查看提高后的图片", + "올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.": "提升后的图片将被删除。要再次查看需要重新提升分辨率。", "올린 이 {0}": "上传者 {0}", "옮겼습니다.": "已移动。", "옮기기": "移动", @@ -1593,6 +1594,8 @@ "저장하고 실행": "保存并执行", "저장하는 중": "正在保存", "저장하지 못했습니다: {0}": "无法保存: {0}", + "저장하지 않고 닫기": "不保存并关闭", + "저장하지 않고 닫을까요?": "不保存就关闭吗?", "저장하지 않은 변경": "未保存的更改", "저장한 뒤": "保存后", "저장했습니다": "已保存", diff --git a/lib/ui/ai_upscale_ui.dart b/lib/ui/ai_upscale_ui.dart index 6d67268..d96c6cb 100644 --- a/lib/ui/ai_upscale_ui.dart +++ b/lib/ui/ai_upscale_ui.dart @@ -260,7 +260,7 @@ Future upscaleOne(BuildContext context, AppController c, {required Stri try { final save = await Navigator.of(context).push(MaterialPageRoute( fullscreenDialog: true, - builder: (_) => _CompareView(original: input, upscaled: four!, title: p.basename(nameFrom)), + builder: (_) => UpscaleCompareView(original: input, upscaled: four!, title: p.basename(nameFrom)), )); if (save != true || !context.mounted) return null; final saved = await saveUpscaled(four, source: nameFrom, original: input, o: upscaleOptionsOf(c, outDir: outDir)); @@ -353,18 +353,18 @@ Future upscaleAll(BuildContext context, AppController c, ReaderSource src) )); } -/// 원본 / 올린 것을 바꿔 보며 비교 (확대해서 볼 수 있음) -class _CompareView extends StatefulWidget { - const _CompareView({required this.original, required this.upscaled, required this.title}); +/// 원본 / 올린 것을 바꿔 보며 비교 (확대해서 볼 수 있음). [저장] 이면 true, 저장하지 않고 닫으면 false +class UpscaleCompareView extends StatefulWidget { + const UpscaleCompareView({super.key, required this.original, required this.upscaled, required this.title}); final String original; final String upscaled; final String title; @override - State<_CompareView> createState() => _CompareViewState(); + State createState() => UpscaleCompareViewState(); } -class _CompareViewState extends State<_CompareView> { +class UpscaleCompareViewState extends State { bool _up = true; final _zoom = TransformationController(); @@ -374,12 +374,44 @@ class _CompareViewState extends State<_CompareView> { super.dispose(); } + /// 160: 저장하지 않고 닫으면 올린 그림이 지워지므로 묻는다 (닫기 버튼 · 뒤로 키 · 뒤로 몸짓) + Future _close() async { + final r = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text(tr('저장하지 않고 닫을까요?')), + content: Text(tr('올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.')), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx), child: Text(tr('취소'))), + TextButton(onPressed: () => Navigator.pop(ctx, 'discard'), child: Text(tr('저장하지 않고 닫기'))), + FilledButton.icon( + onPressed: () => Navigator.pop(ctx, 'save'), + icon: const Icon(Icons.save_outlined, size: 18), + label: Text(tr('저장')), + ), + ], + ), + ); + if (!mounted || r == null) return; + Navigator.pop(context, r == 'save'); + } + @override Widget build(BuildContext context) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, _) { + if (!didPop) _close(); + }, + child: _page(context), + ); + } + + Widget _page(BuildContext context) { return Scaffold( backgroundColor: Colors.black, appBar: AppBar( - leading: IconButton(tooltip: tr('닫기'), icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context, false)), + leading: IconButton(tooltip: tr('닫기'), icon: const Icon(Icons.close), onPressed: _close), title: Text(widget.title, maxLines: 1, overflow: TextOverflow.ellipsis), actions: [ SegmentedButton( diff --git a/test/upscale_compare_test.dart b/test/upscale_compare_test.dart new file mode 100644 index 0000000..4a8835c --- /dev/null +++ b/test/upscale_compare_test.dart @@ -0,0 +1,67 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:image/image.dart' as img; +import 'package:jj_mkvmaker/ui/ai_upscale_ui.dart'; +import 'package:path/path.dart' as p; + +/// 160: 비교 화면을 저장하지 않고 닫으면 (올린 그림이 지워지므로) 묻는다 - 닫기 버튼 · 뒤로 키 +void main() { + late Directory dir; + late String a, b; + setUp(() { + dir = Directory.systemTemp.createTempSync('jj_cmp_'); + a = p.join(dir.path, 'a.png'); + b = p.join(dir.path, 'b.png'); + File(a).writeAsBytesSync(img.encodePng(img.Image(width: 4, height: 4))); + File(b).writeAsBytesSync(img.encodePng(img.Image(width: 16, height: 16))); + }); + tearDown(() => dir.deleteSync(recursive: true)); + + Future> open(WidgetTester tester) async { + late NavigatorState nav; + await tester.pumpWidget(MaterialApp(home: Builder(builder: (context) { + nav = Navigator.of(context); + return const SizedBox(); + }))); + final r = nav.push(MaterialPageRoute(builder: (_) => UpscaleCompareView(original: a, upscaled: b, title: 't'))); + await tester.pumpAndSettle(); + return r; + } + + testWidgets('[닫기] → 묻기: [취소] 면 그대로 · [저장하지 않고 닫기] 면 false', (tester) async { + final r = await open(tester); + await tester.tap(find.byTooltip('닫기')); + await tester.pumpAndSettle(); + expect(find.text('저장하지 않고 닫을까요?'), findsOneWidget); + await tester.tap(find.text('취소')); + await tester.pumpAndSettle(); + expect(find.byType(UpscaleCompareView), findsOneWidget, reason: '닫지 않음'); + await tester.tap(find.byTooltip('닫기')); + await tester.pumpAndSettle(); + await tester.tap(find.text('저장하지 않고 닫기')); + await tester.pumpAndSettle(); + expect(find.byType(UpscaleCompareView), findsNothing); + expect(await r, isFalse); + }); + + testWidgets('뒤로 키도 묻고, 묻는 창의 [저장] 이면 true', (tester) async { + final r = await open(tester); + await tester.binding.handlePopRoute(); + await tester.pumpAndSettle(); + expect(find.text('저장하지 않고 닫을까요?'), findsOneWidget); + expect(find.byType(UpscaleCompareView), findsOneWidget); + await tester.tap(find.descendant(of: find.byType(AlertDialog), matching: find.widgetWithText(FilledButton, '저장'))); + await tester.pumpAndSettle(); + expect(await r, isTrue); + }); + + testWidgets('위쪽 [저장] 은 묻지 않고 바로 true', (tester) async { + final r = await open(tester); + await tester.tap(find.widgetWithText(FilledButton, '저장')); + await tester.pumpAndSettle(); + expect(find.text('저장하지 않고 닫을까요?'), findsNothing); + expect(await r, isTrue); + }); +}