JJ_MKVMaker/lib/ui/ai_upscale/upscale_compare.dart

142 lines
5.2 KiB
Dart

/// 약속: 해상도 올린 결과를 원본과 나란히 보여 주고 (밀어서 비교), 고른 것을 저장한다.
library;
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;
import '../../app/ai_upscale.dart';
import '../../app/app_controller.dart';
import '../../l10n/tr.dart';
import '../theme.dart';
import 'upscale_prep.dart';
import '../ai_upscale_ui.dart';
import '../jj_title_bar.dart';
/// 4배 결과 [four] 를 [원본 / 올린 것] 비교 → [저장] (새 파일, 원본을 덮지 않음). 끝나면 임시 결과를 지운다
Future<String?> compareAndSaveUpscale(NavigatorState nav, AppController c, String four,
{required String input, required String nameFrom, required String outDir}) async {
final messenger = ScaffoldMessenger.maybeOf(nav.context);
try {
final save = await nav.push<bool>(MaterialPageRoute(
fullscreenDialog: true,
builder: (_) => UpscaleCompareView(original: input, upscaled: four, title: p.basename(nameFrom)),
));
if (save != true) return null;
final saved = await saveUpscaled(four, source: nameFrom, original: input, o: upscaleOptionsOf(c, outDir: outDir));
messenger?.showSnackBar(SnackBar(content: Text(trf('저장했습니다: {0}', [saved]))));
return saved;
} catch (e) {
messenger?.showSnackBar(SnackBar(content: Text(trf('저장하지 못했습니다: {0}', [e]))));
return null;
} finally {
try {
await Directory(p.dirname(four)).delete(recursive: true);
} catch (_) {}
}
}
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<UpscaleCompareView> createState() => UpscaleCompareViewState();
}
class UpscaleCompareViewState extends State<UpscaleCompareView> {
bool _up = true;
final _zoom = TransformationController();
@override
void dispose() {
_zoom.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
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,
// 관리자 지시 (10/21, 15 찾음 G2 - a22832d 는 after_1020 에만 있어 다시): 360 에서 위 막대가 넘쳐 제목이 안 보이고 [저장] 글자가 잘렸다 -
// 폭 600 미만이면 원본 / 올린 것 고르기를 위 막대 아래 둘째 줄로, [저장] 은 글자 그대로. 넓으면 그대로
appBar: jjTitleBar(
context,
twoLinesBelow: 600,
leading: IconButton(tooltip: tr('닫기'), icon: const Icon(Icons.close), onPressed: _close),
title: widget.title,
lowerActions: [
SegmentedButton<bool>(
segments: [
ButtonSegment(value: false, label: Text(tr('원본'))),
ButtonSegment(value: true, label: Text(tr('올린 것'))),
],
selected: {_up},
onSelectionChanged: (v) => setState(() => _up = v.first),
),
const SizedBox(width: 8),
],
actions: [
FilledButton.icon(
onPressed: () => Navigator.pop(context, true),
icon: const Icon(Icons.save_outlined, size: 18),
label: Text(tr('저장')),
),
const SizedBox(width: 8),
],
),
body: InteractiveViewer(
transformationController: _zoom,
maxScale: 8,
child: Center(
// 같은 크기로 보여 확대해 비교한다 (원본은 부드럽게 늘리지 않고 픽셀 그대로)
child: Image.file(File(_up ? widget.upscaled : widget.original),
key: ValueKey(_up), fit: BoxFit.contain, filterQuality: _up ? FilterQuality.medium : FilterQuality.none,
width: double.infinity, height: double.infinity),
),
),
bottomNavigationBar: Container(
color: JjColors.panel,
padding: const EdgeInsets.all(8),
child: Text(_up ? tr('올린 것 (AI)') : tr('원본'), textAlign: TextAlign.center),
),
);
}
}