AI 해상도 올리기 비교 화면: 저장하지 않고 닫으면 묻기 (160)

닫으면 올린 그림이 지워지므로 [닫기] · 뒤로 키 · 뒤로 몸짓 모두 "저장하지 않고 닫을까요?" ([취소] · [저장하지 않고 닫기] · [저장]).
위쪽 [저장] 은 묻지 않는다. 시험하려고 UpscaleCompareView 로 공개. upscale_compare_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음4
RyuJongJin 2026-10-10 03:30:26 +09:00
parent 06b6169ad8
commit bf72557fc3
5 changed files with 115 additions and 7 deletions

View File

@ -1266,6 +1266,7 @@
"올린 것": "Upscaled", "올린 것": "Upscaled",
"올린 것 (AI)": "Upscaled (AI)", "올린 것 (AI)": "Upscaled (AI)",
"올린 것으로 계속 보기": "Keep viewing the upscaled one", "올린 것으로 계속 보기": "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}", "올린 이 {0}": "Uploaded by {0}",
"옮겼습니다.": "moved.", "옮겼습니다.": "moved.",
"옮기기": "Move", "옮기기": "Move",
@ -1593,6 +1594,8 @@
"저장하고 실행": "Save and run", "저장하고 실행": "Save and run",
"저장하는 중": "Saving", "저장하는 중": "Saving",
"저장하지 못했습니다: {0}": "Could not save: {0}", "저장하지 못했습니다: {0}": "Could not save: {0}",
"저장하지 않고 닫기": "Close without saving",
"저장하지 않고 닫을까요?": "Close without saving?",
"저장하지 않은 변경": "Unsaved changes", "저장하지 않은 변경": "Unsaved changes",
"저장한 뒤": "After saving", "저장한 뒤": "After saving",
"저장했습니다": "Saved", "저장했습니다": "Saved",

View File

@ -1266,6 +1266,7 @@
"올린 것": "高解像度化後", "올린 것": "高解像度化後",
"올린 것 (AI)": "高解像度化後 (AI)", "올린 것 (AI)": "高解像度化後 (AI)",
"올린 것으로 계속 보기": "高解像度化した画像で表示を続ける", "올린 것으로 계속 보기": "高解像度化した画像で表示を続ける",
"올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.": "高解像度化した画像は削除されます。もう一度見るには高解像度化をやり直す必要があります。",
"올린 이 {0}": "投稿者 {0}", "올린 이 {0}": "投稿者 {0}",
"옮겼습니다.": "移動しました。", "옮겼습니다.": "移動しました。",
"옮기기": "移動", "옮기기": "移動",
@ -1593,6 +1594,8 @@
"저장하고 실행": "保存して実行", "저장하고 실행": "保存して実行",
"저장하는 중": "保存中", "저장하는 중": "保存中",
"저장하지 못했습니다: {0}": "保存できませんでした: {0}", "저장하지 못했습니다: {0}": "保存できませんでした: {0}",
"저장하지 않고 닫기": "保存せずに閉じる",
"저장하지 않고 닫을까요?": "保存せずに閉じますか?",
"저장하지 않은 변경": "未保存の変更", "저장하지 않은 변경": "未保存の変更",
"저장한 뒤": "保存後", "저장한 뒤": "保存後",
"저장했습니다": "保存しました", "저장했습니다": "保存しました",

View File

@ -1266,6 +1266,7 @@
"올린 것": "提高后", "올린 것": "提高后",
"올린 것 (AI)": "提高后 (AI)", "올린 것 (AI)": "提高后 (AI)",
"올린 것으로 계속 보기": "继续查看提高后的图片", "올린 것으로 계속 보기": "继续查看提高后的图片",
"올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.": "提升后的图片将被删除。要再次查看需要重新提升分辨率。",
"올린 이 {0}": "上传者 {0}", "올린 이 {0}": "上传者 {0}",
"옮겼습니다.": "已移动。", "옮겼습니다.": "已移动。",
"옮기기": "移动", "옮기기": "移动",
@ -1593,6 +1594,8 @@
"저장하고 실행": "保存并执行", "저장하고 실행": "保存并执行",
"저장하는 중": "正在保存", "저장하는 중": "正在保存",
"저장하지 못했습니다: {0}": "无法保存: {0}", "저장하지 못했습니다: {0}": "无法保存: {0}",
"저장하지 않고 닫기": "不保存并关闭",
"저장하지 않고 닫을까요?": "不保存就关闭吗?",
"저장하지 않은 변경": "未保存的更改", "저장하지 않은 변경": "未保存的更改",
"저장한 뒤": "保存后", "저장한 뒤": "保存后",
"저장했습니다": "已保存", "저장했습니다": "已保存",

View File

@ -260,7 +260,7 @@ Future<String?> upscaleOne(BuildContext context, AppController c, {required Stri
try { try {
final save = await Navigator.of(context).push<bool>(MaterialPageRoute( final save = await Navigator.of(context).push<bool>(MaterialPageRoute(
fullscreenDialog: true, 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; if (save != true || !context.mounted) return null;
final saved = await saveUpscaled(four, source: nameFrom, original: input, o: upscaleOptionsOf(c, outDir: outDir)); final saved = await saveUpscaled(four, source: nameFrom, original: input, o: upscaleOptionsOf(c, outDir: outDir));
@ -353,18 +353,18 @@ Future<void> upscaleAll(BuildContext context, AppController c, ReaderSource src)
)); ));
} }
/// 원본 / 올린 것을 바꿔 보며 비교 (확대해서 볼 수 있음) /// 원본 / 올린 것을 바꿔 보며 비교 (확대해서 볼 수 있음). [저장] 이면 true, 저장하지 않고 닫으면 false
class _CompareView extends StatefulWidget { class UpscaleCompareView extends StatefulWidget {
const _CompareView({required this.original, required this.upscaled, required this.title}); const UpscaleCompareView({super.key, required this.original, required this.upscaled, required this.title});
final String original; final String original;
final String upscaled; final String upscaled;
final String title; final String title;
@override @override
State<_CompareView> createState() => _CompareViewState(); State<UpscaleCompareView> createState() => UpscaleCompareViewState();
} }
class _CompareViewState extends State<_CompareView> { class UpscaleCompareViewState extends State<UpscaleCompareView> {
bool _up = true; bool _up = true;
final _zoom = TransformationController(); final _zoom = TransformationController();
@ -374,12 +374,44 @@ class _CompareViewState extends State<_CompareView> {
super.dispose(); super.dispose();
} }
/// 160: 저장하지 않고 닫으면 올린 그림이 지워지므로 묻는다 (닫기 버튼 · 뒤로 키 · 뒤로 몸짓)
Future<void> _close() async {
final r = await showDialog<String>(
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) _close();
},
child: _page(context),
);
}
Widget _page(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar( 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), title: Text(widget.title, maxLines: 1, overflow: TextOverflow.ellipsis),
actions: [ actions: [
SegmentedButton<bool>( SegmentedButton<bool>(

View File

@ -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<Future<bool?>> 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<bool>(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);
});
}