/// 약속: 해상도 올린 결과를 원본과 나란히 보여 주고 (밀어서 비교), 고른 것을 저장한다. 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 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(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 createState() => UpscaleCompareViewState(); } class UpscaleCompareViewState extends State { bool _up = true; final _zoom = TransformationController(); @override void dispose() { _zoom.dispose(); 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, // 관리자 지시 (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( 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), ), ); } }