135 lines
7.7 KiB
Dart
135 lines
7.7 KiB
Dart
// 보기 화면 위 막대의 버튼 목록 모듈
|
|
//
|
|
// 약속
|
|
// - 입력: 앱 ([AppController] - 설정을 읽고 바꿈) · 그림 원본 · 지금 상태 ([ReaderBarState]) · 누를 때 부를 일 ([ReaderBarHandlers])
|
|
// - 출력: 위 막대 칸 목록 ([ReaderBarItem]) - 앞의 것일수록 먼저 보이고, 못 들어가면 ⋮ 메뉴로 ([ReaderTopBar])
|
|
// - 오류: 던지지 않는다. 쓸 수 없는 버튼은 onTap 이 null (흐리게)
|
|
// 버튼의 순서 · 글 · 아이콘은 이 모듈 한 곳에서 정한다. 다른 보기 화면도 다시 쓸 수 있다.
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../../app/app_controller.dart';
|
|
import '../../core/reader_sources.dart';
|
|
import '../../l10n/tr.dart';
|
|
import '../ai_image_page.dart';
|
|
import '../ai_upscale_ui.dart';
|
|
import 'reader_bars.dart';
|
|
|
|
/// 버튼 모양을 정하는 지금 상태
|
|
typedef ReaderBarState = ({bool halfShown, bool playing, int turns, bool flipV, String? aiSource});
|
|
|
|
/// 버튼을 누를 때 부를 일 (화면이 준다)
|
|
class ReaderBarHandlers {
|
|
const ReaderBarHandlers({
|
|
required this.splitOff,
|
|
required this.play,
|
|
required this.setSeconds,
|
|
required this.save,
|
|
required this.deletePage,
|
|
required this.insertImages,
|
|
required this.photoInfo,
|
|
required this.toggleGallery,
|
|
required this.cycleSplit,
|
|
required this.rotate,
|
|
required this.toggleFlip,
|
|
required this.updateSettings,
|
|
required this.applySystemBars,
|
|
required this.upscalePage,
|
|
required this.qualityPage,
|
|
});
|
|
|
|
final VoidCallback splitOff;
|
|
final void Function(bool on) play;
|
|
final void Function(int d) setSeconds;
|
|
final VoidCallback save;
|
|
final VoidCallback deletePage;
|
|
final VoidCallback insertImages;
|
|
final VoidCallback photoInfo;
|
|
final VoidCallback toggleGallery;
|
|
final VoidCallback cycleSplit;
|
|
final void Function(int d) rotate;
|
|
final VoidCallback toggleFlip;
|
|
|
|
/// 설정을 바꾸고 화면을 다시 그린다
|
|
final Future<void> Function(void Function(dynamic s) f) updateSettings;
|
|
final VoidCallback applySystemBars;
|
|
final VoidCallback upscalePage;
|
|
final VoidCallback qualityPage;
|
|
}
|
|
|
|
/// 위 막대의 버튼들 (앞의 것일수록 먼저 보이고, 못 들어가면 ⋮ 메뉴로). [context] 는 AI 창을 띄울 화면
|
|
List<ReaderBarItem> readerBarItems(BuildContext context, AppController c, ReaderSource src, ReaderBarState st,
|
|
ReaderBarHandlers on) {
|
|
final s = c.settings;
|
|
final pdf = src is PdfSource ? src : null;
|
|
final pictures = src is ImageFilesSource || src is ZipImagesSource;
|
|
final aiOn = c.settings.components.contains('aiimage');
|
|
final playing = st.playing;
|
|
final secs = s.readerAutoSeconds;
|
|
ReaderBarItem one(ReaderAct a) => ReaderBarItem(48, [a]);
|
|
final split = switch (s.readerSplit) {
|
|
'on' => tr('두 쪽 나눠 보기: 켜기 (누르면 끄기)'),
|
|
'off' => tr('두 쪽 나눠 보기: 끄기 (누르면 자동)'),
|
|
_ => tr('두 쪽 나눠 보기: 자동 - 두 쪽 스캔으로 보이는 그림만 (누르면 켜기)'),
|
|
};
|
|
return [
|
|
// 223: 나눠 보고 있으면 바로 한 장으로 (자동이 사진을 잘라 보인다고 여기지 않게 맨 앞에)
|
|
if (st.halfShown) one(ReaderAct(const Icon(Icons.crop_original), tr('두 쪽 나눔 끄기 (한 장으로 보기)'), on.splitOff)),
|
|
// 117: 계속 보기 · 간격 (▲ 늘리기 · ▼ 줄이기)
|
|
one(ReaderAct(Icon(playing ? Icons.pause : Icons.play_arrow),
|
|
playing ? tr('계속 보기 멈추기') : trf('계속 보기 ({0}초마다 다음 장)', [secs]), () => on.play(!playing),
|
|
on: playing)),
|
|
ReaderBarItem(112, [
|
|
ReaderAct(const Icon(Icons.keyboard_arrow_up), trf('계속 보기 간격 늘리기 (지금 {0}초)', [secs]),
|
|
secs < 60 ? () => on.setSeconds(1) : null),
|
|
ReaderAct(const Icon(Icons.keyboard_arrow_down), trf('계속 보기 간격 줄이기 (지금 {0}초)', [secs]),
|
|
secs > 1 ? () => on.setSeconds(-1) : null),
|
|
], seconds: secs),
|
|
if (pdf != null) ...[
|
|
one(ReaderAct(const Icon(Icons.save_outlined), tr('저장'), pdf.dirty ? on.save : null)),
|
|
one(ReaderAct(const Icon(Icons.delete_outline), tr('이 페이지 지우기'), on.deletePage)),
|
|
one(ReaderAct(const Icon(Icons.add_photo_alternate_outlined), tr('그림을 페이지로 넣기 (이 페이지 뒤)'), on.insertImages)),
|
|
],
|
|
// X-plore 맞추기 (관리자 지시 10/20): 사진 정보 (Exif) - 폴더의 그림만
|
|
if (src is ImageFilesSource) one(ReaderAct(const Icon(Icons.info_outline), tr('사진 정보 (Exif)'), on.photoInfo)),
|
|
// X-plore 맞추기 (관리자 지시 10/20): 아래 갤러리 줄 켜고 끄기 (그림 원본만)
|
|
if (pictures)
|
|
one(ReaderAct(const Icon(Icons.view_carousel_outlined), s.readerGallery ? tr('아래 갤러리 줄 숨기기') : tr('아래 갤러리 줄 보이기'),
|
|
on.toggleGallery, on: s.readerGallery)),
|
|
// 119 · 120
|
|
one(ReaderAct(const Icon(Icons.vertical_split_outlined), split, on.cycleSplit, on: s.readerSplit != 'off')),
|
|
one(ReaderAct(const Icon(Icons.rotate_right), tr('90° 회전 (시계 방향)'), () => on.rotate(1), on: st.turns != 0)),
|
|
one(ReaderAct(const Icon(Icons.rotate_left), tr('반대로 90° 회전'), () => on.rotate(3), on: st.turns != 0)),
|
|
one(ReaderAct(const RotatedBox(quarterTurns: 1, child: Icon(Icons.flip)), tr('상하 반전'), on.toggleFlip, on: st.flipV)),
|
|
one(ReaderAct(const Icon(Icons.fit_screen_outlined), tr('한 쪽 맞추기'), () => on.updateSettings((x) => x.readerFit = 'page'),
|
|
on: s.readerFit == 'page')),
|
|
one(ReaderAct(const Icon(Icons.width_full_outlined), tr('좌우 맞추기 (세로로 밀어 봄)'),
|
|
() => on.updateSettings((x) => x.readerFit = 'width'), on: s.readerFit == 'width')),
|
|
one(ReaderAct(const Icon(Icons.swap_horiz),
|
|
s.readerRtl ? tr('넘기는 방향: 오른쪽 → 왼쪽 (만화)') : tr('넘기는 방향: 왼쪽 → 오른쪽'),
|
|
() => on.updateSettings((x) => x.readerRtl = !x.readerRtl), on: s.readerRtl)),
|
|
one(ReaderAct(const Icon(Icons.brightness_low), tr('어둡게'),
|
|
() => on.updateSettings((x) => x.readerBrightness = (x.readerBrightness - 0.1).clamp(-0.7, 0.7)))),
|
|
one(ReaderAct(const Icon(Icons.brightness_high), tr('밝게'),
|
|
() => on.updateSettings((x) => x.readerBrightness = (x.readerBrightness + 0.1).clamp(-0.7, 0.7)))),
|
|
// 30: 시계 · 알림 · 뒤로 막대를 그대로 / 화면 가득
|
|
one(ReaderAct(Icon(s.readerSystemBars ? Icons.fullscreen : Icons.fullscreen_exit),
|
|
s.readerSystemBars ? tr('화면 가득 (시스템 막대 숨기기)') : tr('시스템 막대 보이기 (시계 · 알림 · 뒤로)'), () async {
|
|
await on.updateSettings((x) => x.readerSystemBars = !x.readerSystemBars);
|
|
on.applySystemBars();
|
|
}, on: s.readerSystemBars)),
|
|
// AI 는 막대가 좁으면 먼저 ⋮ 로 (늘 쓰는 버튼이 막대에 남게)
|
|
// 121: AI 해상도 올리기 (이 장 · 모든 장) - 새 파일로 저장, 원본 · ZIP 은 그대로
|
|
if (aiOn && src is! PdfSource) ...[
|
|
one(ReaderAct(const Icon(Icons.hd_outlined), tr('AI 해상도 올리기 (이 장)'), on.upscalePage)),
|
|
one(ReaderAct(const Icon(Icons.burst_mode_outlined), tr('AI 해상도 올리기 (모든 장)'), () => upscaleAll(context, c, src))),
|
|
// 관리자 판단 (10/17): [품질 올리기] - 설정으로 켰을 때만
|
|
if (c.settings.aiQualityButton) one(ReaderAct(const Icon(Icons.auto_fix_high), tr('품질 올리기 (이 장)'), on.qualityPage)),
|
|
],
|
|
// 123: 이 그림을 바탕으로 AI 그림 (그림 → 그림, 원본은 그대로)
|
|
if (st.aiSource case final path?)
|
|
one(ReaderAct(const Icon(Icons.auto_awesome_outlined), tr('AI 그림으로 (이 그림을 바탕으로)'),
|
|
() => AiImagePage.open(Navigator.of(context), c: c, initImage: path))),
|
|
];
|
|
}
|