이동 경고 문구 (222) · 자동 두 쪽 나누기는 두 쪽 스캔일 때만 (223) · 보는 동안 지운 그림 (224)
- 222 (관리자 지시): 원본이 대상 안일 때 이동이면 "옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로)" - 예전에는 이동일 때도 "복사됩니다 · 원본 폴더 그대로". - 223 (관리자 지시): 보기 화면 "두 쪽 나눠 보기: 자동" 이 평범한 가로 사진도 반으로 잘랐다. ZIP · CBZ · PDF 묶음의 가로 그림은 예전처럼 나누고, 폴더의 그림은 다른 그림 대부분 (3/4 이상, 2장 이상) 이 세로일 때만 나눈다. 나눠 볼 때 위 막대 맨 앞에 [두 쪽 나눔 끄기]. 설정 값 · 처음 값은 그대로. - 224 (관리자 지시): 보기 화면이 앱으로 돌아올 때 폴더의 그림 중 없어진 것을 목록에서 빼고, 보던 그림이 없어졌으면 "파일이 없어졌습니다" 와 다음 그림, 다 없으면 닫는다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>fix_mkv
parent
3ffaf23955
commit
f59e2f75a1
|
|
@ -750,9 +750,11 @@
|
|||
"되살리고 다시 시작": "Restore and restart",
|
||||
"되살리기": "Restore",
|
||||
"두 번 넣은 비밀번호가 다릅니다": "The two passwords don't match",
|
||||
"두 쪽 나눔 끄기 (한 장으로 보기)": "Turn off two-page split (show as one)",
|
||||
"두 쪽 나눠 보기": "Split two-page spreads",
|
||||
"두 쪽 나눠 보기: 끄기 (누르면 자동)": "Split two-page spreads: off (tap for auto)",
|
||||
"두 쪽 나눠 보기: 자동 - 가로로 긴 그림만 (누르면 켜기)": "Split two-page spreads: auto - wide images only (tap to turn on)",
|
||||
"두 쪽 나눠 보기: 자동 - 두 쪽 스캔으로 보이는 그림만 (누르면 켜기)": "Two-page split: Auto - only images that look like two-page scans (tap to turn on)",
|
||||
"두 쪽 나눠 보기: 켜기 (누르면 끄기)": "Split two-page spreads: on (tap to turn off)",
|
||||
"두 창": "Two panes",
|
||||
"두 창 배치": "Two-pane arrangement",
|
||||
|
|
@ -1368,6 +1370,7 @@
|
|||
"원본이 대상 안에 있어도 맞추기 (안쪽 → 바깥)": "Sync even when the source is inside the target (inner → outer)",
|
||||
"원본이 대상 안에 있으면 지우기 (--delete) 를 함께 쓸 수 없습니다 (대상의 다른 파일이 모두 지워집니다).": "When the source is inside the target, delete (--delete) can't be used (all other files in the target would be deleted).",
|
||||
"원본이 대상 안에 있으면 지우기 포함으로 맞출 수 없습니다": "Can't sync with delete when the source is inside the target",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래로 옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로).": "The source is inside the destination folder: its contents are moved directly into the destination folder (source files are removed; the source folder is handled as chosen below).",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).": "The source is inside the target folder: its contents are copied directly under the target folder (the source folder stays).",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (지우기는 쓸 수 없음).": "The source is inside the target folder: its contents are copied directly under the target folder (delete can't be used).",
|
||||
"원본이 바뀌면 대상에 맞춥니다. 일정을 정하면 그 시간에만 맞추고, 그 밖의 시간에는 바뀐 것을 모아 두었다가 다음 시작 시간에 맞춥니다.": "When the source changes, the target is synced. With a schedule, sync happens only in that time; changes outside it are collected and synced at the next start time.",
|
||||
|
|
@ -1907,6 +1910,7 @@
|
|||
"파일이 보이지 않습니다: \"모든 파일에 대한 접근\" 권한이 없습니다": "Files are not shown: \"All files access\" is not allowed",
|
||||
"파일이 손상되었습니다 (SHA-256 다름): {0}": "The file is damaged (SHA-256 mismatch): {0}",
|
||||
"파일이 없습니다 (지워졌거나 옮겨짐)": "File not found (deleted or moved)",
|
||||
"파일이 없어졌습니다": "The file is gone",
|
||||
"팝업 끄기": "Close pop-up",
|
||||
"팝업 보기": "Pop-up view",
|
||||
"패키지 라이선스": "Package licenses",
|
||||
|
|
|
|||
|
|
@ -750,9 +750,11 @@
|
|||
"되살리고 다시 시작": "復元して再起動",
|
||||
"되살리기": "元に戻す",
|
||||
"두 번 넣은 비밀번호가 다릅니다": "2 回入力したパスワードが違います",
|
||||
"두 쪽 나눔 끄기 (한 장으로 보기)": "見開き分割をオフ (1 枚で表示)",
|
||||
"두 쪽 나눠 보기": "見開きを分けて表示",
|
||||
"두 쪽 나눠 보기: 끄기 (누르면 자동)": "見開きを分けて表示: オフ (押すと自動)",
|
||||
"두 쪽 나눠 보기: 자동 - 가로로 긴 그림만 (누르면 켜기)": "見開きを分けて表示: 自動 - 横長の画像のみ (押すとオン)",
|
||||
"두 쪽 나눠 보기: 자동 - 두 쪽 스캔으로 보이는 그림만 (누르면 켜기)": "見開き分割: 自動 - 見開きスキャンらしい画像だけ (押すとオン)",
|
||||
"두 쪽 나눠 보기: 켜기 (누르면 끄기)": "見開きを分けて表示: オン (押すとオフ)",
|
||||
"두 창": "2 ペイン",
|
||||
"두 창 배치": "2 ペインの配置",
|
||||
|
|
@ -1368,6 +1370,7 @@
|
|||
"원본이 대상 안에 있어도 맞추기 (안쪽 → 바깥)": "元が対象の中にあっても同期 (内側 → 外側)",
|
||||
"원본이 대상 안에 있으면 지우기 (--delete) 를 함께 쓸 수 없습니다 (대상의 다른 파일이 모두 지워집니다).": "元が対象の中にある場合、削除 (--delete) は使えません (対象の他のファイルがすべて削除されます)。",
|
||||
"원본이 대상 안에 있으면 지우기 포함으로 맞출 수 없습니다": "元が対象の中にある場合、削除を含めて同期できません",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래로 옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로).": "元のフォルダーが移動先の中にあります: 中身は移動先フォルダーの直下に移動します (元のファイルは削除され、元のフォルダーは下で選んだとおり)。",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).": "元が対象フォルダーの中にあります: 元の内容が対象フォルダーの直下にコピーされます (元フォルダーはそのまま)。",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (지우기는 쓸 수 없음).": "元が対象フォルダーの中にあります: 元の内容が対象フォルダーの直下にコピーされます (削除は使えません)。",
|
||||
"원본이 바뀌면 대상에 맞춥니다. 일정을 정하면 그 시간에만 맞추고, 그 밖의 시간에는 바뀐 것을 모아 두었다가 다음 시작 시간에 맞춥니다.": "元が変わると対象に合わせます。予定を決めるとその時間だけ同期し、それ以外は変更を貯めて次の開始時刻に同期します。",
|
||||
|
|
@ -1907,6 +1910,7 @@
|
|||
"파일이 보이지 않습니다: \"모든 파일에 대한 접근\" 권한이 없습니다": "ファイルが表示されません: 「すべてのファイルへのアクセス」権限がありません",
|
||||
"파일이 손상되었습니다 (SHA-256 다름): {0}": "ファイルが破損しています (SHA-256 不一致): {0}",
|
||||
"파일이 없습니다 (지워졌거나 옮겨짐)": "ファイルがありません (削除または移動)",
|
||||
"파일이 없어졌습니다": "ファイルがなくなりました",
|
||||
"팝업 끄기": "ポップアップを閉じる",
|
||||
"팝업 보기": "ポップアップ表示",
|
||||
"패키지 라이선스": "パッケージのライセンス",
|
||||
|
|
|
|||
|
|
@ -750,9 +750,11 @@
|
|||
"되살리고 다시 시작": "恢复并重新启动",
|
||||
"되살리기": "恢复",
|
||||
"두 번 넣은 비밀번호가 다릅니다": "两次输入的密码不一致",
|
||||
"두 쪽 나눔 끄기 (한 장으로 보기)": "关闭双页拆分(整张显示)",
|
||||
"두 쪽 나눠 보기": "拆分双页",
|
||||
"두 쪽 나눠 보기: 끄기 (누르면 자동)": "拆分双页: 关闭 (点按切换为自动)",
|
||||
"두 쪽 나눠 보기: 자동 - 가로로 긴 그림만 (누르면 켜기)": "拆分双页: 自动 - 仅横向图片 (点按开启)",
|
||||
"두 쪽 나눠 보기: 자동 - 두 쪽 스캔으로 보이는 그림만 (누르면 켜기)": "双页拆分:自动 - 仅拆分看起来像双页扫描的图片(点按开启)",
|
||||
"두 쪽 나눠 보기: 켜기 (누르면 끄기)": "拆分双页: 开启 (点按关闭)",
|
||||
"두 창": "双窗格",
|
||||
"두 창 배치": "双窗格排列",
|
||||
|
|
@ -1368,6 +1370,7 @@
|
|||
"원본이 대상 안에 있어도 맞추기 (안쪽 → 바깥)": "源位于目标内时也同步 (内 → 外)",
|
||||
"원본이 대상 안에 있으면 지우기 (--delete) 를 함께 쓸 수 없습니다 (대상의 다른 파일이 모두 지워집니다).": "源位于目标内时不能使用删除 (--delete) (会删除目标中的其他所有文件)。",
|
||||
"원본이 대상 안에 있으면 지우기 포함으로 맞출 수 없습니다": "源位于目标内时无法包含删除同步",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래로 옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로).": "源文件夹位于目标文件夹内:其内容将移到目标文件夹下(源文件会被删除,源文件夹按下方的选择处理)。",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).": "源位于目标文件夹内: 源的内容会复制到目标文件夹下 (源文件夹保留)。",
|
||||
"원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (지우기는 쓸 수 없음).": "源位于目标文件夹内: 源的内容会复制到目标文件夹下 (不能使用删除)。",
|
||||
"원본이 바뀌면 대상에 맞춥니다. 일정을 정하면 그 시간에만 맞추고, 그 밖의 시간에는 바뀐 것을 모아 두었다가 다음 시작 시간에 맞춥니다.": "源变化时同步到目标。设定计划后只在该时间同步,其他时间的变化会累积到下次开始时间再同步。",
|
||||
|
|
@ -1907,6 +1910,7 @@
|
|||
"파일이 보이지 않습니다: \"모든 파일에 대한 접근\" 권한이 없습니다": "文件不可见: 没有“所有文件访问”权限",
|
||||
"파일이 손상되었습니다 (SHA-256 다름): {0}": "文件已损坏(SHA-256 不一致): {0}",
|
||||
"파일이 없습니다 (지워졌거나 옮겨짐)": "找不到文件 (已删除或移动)",
|
||||
"파일이 없어졌습니다": "文件已不存在",
|
||||
"팝업 끄기": "关闭弹出窗口",
|
||||
"팝업 보기": "弹出窗口观看",
|
||||
"패키지 라이선스": "软件包许可",
|
||||
|
|
|
|||
|
|
@ -2112,8 +2112,12 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
],
|
||||
Text(tr('폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).'),
|
||||
style: const TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||
// 222: 이동이면 동작대로 (예전에는 이동일 때도 "복사 · 원본 폴더는 그대로" 라고 썼다)
|
||||
if (nested)
|
||||
Text(tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).'),
|
||||
Text(
|
||||
removeSource && !both
|
||||
? tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래로 옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로).')
|
||||
: tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).'),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
||||
// 1280×720 등 낮은 화면에서도 보이게: 원본 지우기 체크 · 경고는 비교 목록보다 위에 (178)
|
||||
if (both) ...[
|
||||
|
|
|
|||
|
|
@ -29,6 +29,17 @@ typedef ReaderSlot = ({int page, int part});
|
|||
/// (오른쪽에서 왼쪽으로 넘기기를 켜면 반대). 좌우로 밀어도 넘어간다.
|
||||
/// - 위 막대: 계속 보기 (117) · 두 쪽 나누기 (119) · 회전 · 상하 반전 (120) · 한 쪽 맞추기 · 좌우 맞추기, 넘기는 방향,
|
||||
/// 어둡게 · 밝게, (PDF) 페이지 지우기 · 그림 넣기 · 저장. 좁으면 못 들어간 버튼은 ⋮ 메뉴로.
|
||||
/// 223: "두 쪽 나눠 보기: 자동" 이 이 장 ([size], 회전 반영) 을 나눌지 - 두 쪽 스캔일 가능성이 높을 때만.
|
||||
/// [bundle] (ZIP · CBZ · PDF 로 연 묶음): 가로로 긴 그림은 나눈다 (만화 두 쪽 스캔이 흔함).
|
||||
/// 폴더의 그림들: 같은 폴더의 다른 그림 ([others], 크기를 아는 것) 대부분 (3/4 이상, 2장 이상) 이 세로인데 이것만 가로일 때만 -
|
||||
/// 평범한 가로 사진은 나누지 않는다 (예전에는 모두 반으로 잘라 "그림이 잘렸다" 고 보였다)
|
||||
bool autoSplitsPage(Size size, List<Size> others, {required bool bundle}) {
|
||||
if (size.width <= size.height) return false;
|
||||
if (bundle) return true;
|
||||
final tall = others.where((s) => s.height > s.width).length;
|
||||
return others.length >= 2 && tall * 4 >= others.length * 3;
|
||||
}
|
||||
|
||||
class ReaderPage extends StatefulWidget {
|
||||
final AppController c;
|
||||
final ReaderSource source;
|
||||
|
|
@ -64,6 +75,59 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
_index = i < 0 ? 0 : i;
|
||||
_pages = PageController(initialPage: _index);
|
||||
_applySystemBars();
|
||||
// 224: 앱으로 돌아올 때 그사이 밖에서 지운 그림을 목록에서 뺀다
|
||||
_life = AppLifecycleListener(onResume: () => unawaited(dropMissingFiles()));
|
||||
}
|
||||
|
||||
late final AppLifecycleListener _life;
|
||||
|
||||
/// 224: 폴더의 그림 중 없어진 것 (앱 밖에서 지움) 을 목록에서 뺀다. 보던 그림이 없어졌으면 알리고 다음 그림으로,
|
||||
/// 남은 것이 없으면 닫는다 (예전에는 지운 그림이 그대로 보였다). 시험에서도 부른다
|
||||
@visibleForTesting
|
||||
Future<void> dropMissingFiles() async {
|
||||
final s = src;
|
||||
if (s is! ImageFilesSource) return;
|
||||
final gone = <int>{
|
||||
for (var i = 0; i < s.paths.length; i++)
|
||||
if (vMissingSync(s.paths[i])) i,
|
||||
};
|
||||
if (gone.isEmpty || !mounted) return;
|
||||
final curPage = _cur.page;
|
||||
final curGone = gone.contains(curPage);
|
||||
// 보던 그림 (없어졌으면 그 뒤의 남은 그림) 의 새 번호
|
||||
var keep = curPage;
|
||||
while (keep < s.paths.length && gone.contains(keep)) {
|
||||
keep++;
|
||||
}
|
||||
if (keep >= s.paths.length) {
|
||||
keep = curPage;
|
||||
while (keep >= 0 && gone.contains(keep)) {
|
||||
keep--;
|
||||
}
|
||||
}
|
||||
final newPage = keep < 0 ? -1 : keep - gone.where((g) => g < keep).length;
|
||||
for (final g in gone.toList()..sort((a, b) => b - a)) {
|
||||
s.paths.removeAt(g);
|
||||
}
|
||||
for (final f in [..._cache.values, ..._cacheHalf.values]) {
|
||||
unawaited(f.then((x) => x.dispose(), onError: (_) {}));
|
||||
}
|
||||
_cache.clear();
|
||||
_cacheHalf.clear();
|
||||
_dims.clear();
|
||||
if (s.paths.isEmpty) {
|
||||
_snack(tr('파일이 없어졌습니다'));
|
||||
Navigator.of(context).maybePop();
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_v = const [];
|
||||
_rebuildPages();
|
||||
final i = _v.indexWhere((e) => e.page == newPage);
|
||||
_index = i < 0 ? 0 : i;
|
||||
});
|
||||
_jumpLater();
|
||||
if (curGone) _snack(tr('파일이 없어졌습니다'));
|
||||
}
|
||||
|
||||
/// 보기 화면은 위 · 아래 시스템 막대까지 그림으로 (30: 설정으로 막대를 그대로 둘 수 있다)
|
||||
|
|
@ -83,7 +147,7 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
int _turns = 0;
|
||||
bool _flipV = false;
|
||||
|
||||
/// 이 장을 반씩 나눠 볼지 (자동: 회전한 뒤 가로가 세로보다 긴 그림만)
|
||||
/// 이 장을 반씩 나눠 볼지. 자동은 두 쪽 스캔일 가능성이 높을 때만 ([autoSplitsPage])
|
||||
bool _splits(int page) {
|
||||
switch (c.settings.readerSplit) {
|
||||
case 'on':
|
||||
|
|
@ -93,7 +157,19 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
}
|
||||
final d = _dims[page];
|
||||
if (d == null) return false;
|
||||
return _turns.isOdd ? d.height > d.width : d.width > d.height;
|
||||
Size turned(Size s) => _turns.isOdd ? Size(s.height, s.width) : s;
|
||||
return autoSplitsPage(turned(d), [for (final e in _dims.entries) if (e.key != page) turned(e.value)],
|
||||
bundle: src is! ImageFilesSource);
|
||||
}
|
||||
|
||||
/// 223: 나눠 본 장에서 바로 한 장으로 (설정을 "끄기" 로)
|
||||
Future<void> _splitOff() async {
|
||||
await c.updateSettings((x) => x.readerSplit = 'off');
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_resetZoom();
|
||||
_rebuildPages();
|
||||
});
|
||||
}
|
||||
|
||||
/// 보이는 쪽 목록을 다시 만든다. 보던 장은 그대로 보이게 (쪽 번호가 밀리면 그 자리로 옮긴다)
|
||||
|
|
@ -259,6 +335,7 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_life.dispose();
|
||||
_timer?.cancel();
|
||||
if (_playing) unawaited(ReaderPage.keepScreenOn(false).catchError((_) {}));
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
|
|
@ -508,9 +585,11 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
final split = switch (s.readerSplit) {
|
||||
'on' => tr('두 쪽 나눠 보기: 켜기 (누르면 끄기)'),
|
||||
'off' => tr('두 쪽 나눠 보기: 끄기 (누르면 자동)'),
|
||||
_ => tr('두 쪽 나눠 보기: 자동 - 가로로 긴 그림만 (누르면 켜기)'),
|
||||
_ => tr('두 쪽 나눠 보기: 자동 - 두 쪽 스캔으로 보이는 그림만 (누르면 켜기)'),
|
||||
};
|
||||
return [
|
||||
// 223: 나눠 보고 있으면 바로 한 장으로 (자동이 사진을 잘라 보인다고 여기지 않게 맨 앞에)
|
||||
if (_cur.part >= 0) one(_Act(const Icon(Icons.crop_original), tr('두 쪽 나눔 끄기 (한 장으로 보기)'), _splitOff)),
|
||||
// 117: 계속 보기 · 간격 (▲ 늘리기 · ▼ 줄이기)
|
||||
one(_Act(Icon(_playing ? Icons.pause : Icons.play_arrow),
|
||||
_playing ? tr('계속 보기 멈추기') : trf('계속 보기 ({0}초마다 다음 장)', [secs]), () => _play(!_playing),
|
||||
|
|
|
|||
|
|
@ -218,8 +218,12 @@ void main() {
|
|||
await settle(tester, () => find.text('rsync').evaluate().isNotEmpty && find.textContaining('비교하는 중').evaluate().isEmpty,
|
||||
rounds: 300);
|
||||
expect(find.text('rsync'), findsWidgets);
|
||||
// 222: 원본이 대상 안 - 복사면 "복사됩니다 · 원본 폴더 그대로", 이동이면 "옮겨집니다 · 원본 파일은 지워짐"
|
||||
expect(find.textContaining('바로 아래에 복사됩니다'), findsOneWidget);
|
||||
await tester.tap(find.text('원본 파일 지우기 (--remove-source-files)'));
|
||||
await tester.pump();
|
||||
expect(find.textContaining('바로 아래로 옮겨집니다 (원본 파일은 지워지고'), findsOneWidget);
|
||||
expect(find.textContaining('바로 아래에 복사됩니다'), findsNothing);
|
||||
expect(find.text('원본이 대상 안에 있으면 지우기 (--delete) 를 함께 쓸 수 없습니다 (대상의 다른 파일이 모두 지워집니다).'), findsOneWidget);
|
||||
expect(tester.widget<FilledButton>(find.widgetWithText(FilledButton, '이동')).onPressed, isNull);
|
||||
await tester.tap(find.text('취소').last);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,14 @@ class _Sized extends ReaderSource {
|
|||
}
|
||||
}
|
||||
|
||||
/// 223: 폴더의 그림들처럼 (자동 나누기는 다른 그림과 견주어 판단)
|
||||
class _FolderSized extends ImageFilesSource {
|
||||
final List<ui.Image> images;
|
||||
_FolderSized(this.images) : super([for (var i = 0; i < images.length; i++) 'p$i.png'], tempDir: '.', title: 'photos');
|
||||
@override
|
||||
Future<ReaderImage> load(int i, {required int maxWidth}) async => ReaderImage(image: images[i].clone());
|
||||
}
|
||||
|
||||
void main() {
|
||||
late List<bool> screenOn;
|
||||
setUp(() {
|
||||
|
|
@ -90,7 +98,7 @@ void main() {
|
|||
expect(find.text('2-2 / 3장'), findsOneWidget);
|
||||
|
||||
// 버튼: 자동 → 켜기 (모든 장) → 끄기 → 자동
|
||||
await t.tap(find.byTooltip('두 쪽 나눠 보기: 자동 - 가로로 긴 그림만 (누르면 켜기)'));
|
||||
await t.tap(find.byTooltip('두 쪽 나눠 보기: 자동 - 두 쪽 스캔으로 보이는 그림만 (누르면 켜기)'));
|
||||
await t.pumpAndSettle();
|
||||
expect(c.settings.readerSplit, 'on');
|
||||
expect(find.text('2-2 / 3장'), findsOneWidget, reason: '보던 쪽 그대로');
|
||||
|
|
@ -105,6 +113,38 @@ void main() {
|
|||
expect(c.settings.readerSplit, 'auto');
|
||||
});
|
||||
|
||||
test('223: 자동 나누기 판단 - 묶음 (ZIP · CBZ · PDF) 의 가로 그림은 나눔, 폴더는 다른 그림 대부분이 세로일 때만', () {
|
||||
const wide = Size(480, 320), tall = Size(320, 480);
|
||||
expect(autoSplitsPage(wide, const [], bundle: true), isTrue);
|
||||
expect(autoSplitsPage(tall, const [], bundle: true), isFalse);
|
||||
// 사진 폴더: 가로 사진 한 장 · 가로 사진들 사이 → 나누지 않음
|
||||
expect(autoSplitsPage(wide, const [], bundle: false), isFalse);
|
||||
expect(autoSplitsPage(wide, const [wide, wide, tall], bundle: false), isFalse);
|
||||
// 만화 폴더 (낱장 세로들 사이의 펼침 한 장) → 나눔
|
||||
expect(autoSplitsPage(wide, const [tall, tall, tall, tall], bundle: false), isTrue);
|
||||
expect(autoSplitsPage(wide, const [tall], bundle: false), isFalse, reason: '비교할 그림이 너무 적으면 나누지 않음');
|
||||
});
|
||||
|
||||
testWidgets('223: 폴더의 평범한 가로 사진 (480×320) 은 자동이어도 반으로 자르지 않는다', (t) async {
|
||||
final c = controller();
|
||||
final images = <ui.Image>[];
|
||||
await t.runAsync(() async => images.add(await createTestImage(width: 480, height: 320)));
|
||||
await open(t, c, _FolderSized(images));
|
||||
expect(find.text('1 / 1'), findsOneWidget, reason: '"1-1 / 1장" 처럼 나뉘지 않음');
|
||||
expect(find.byTooltip('두 쪽 나눔 끄기 (한 장으로 보기)'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('223: 나눠 본 장에서는 위 막대의 [두 쪽 나눔 끄기] 로 바로 한 장으로', (t) async {
|
||||
final c = controller();
|
||||
final src = await source(t, [(200, 100)]);
|
||||
await open(t, c, src);
|
||||
expect(find.text('1-1 / 1장'), findsOneWidget);
|
||||
await t.tap(find.byTooltip('두 쪽 나눔 끄기 (한 장으로 보기)'));
|
||||
await t.pumpAndSettle();
|
||||
expect(c.settings.readerSplit, 'off');
|
||||
expect(find.text('1 / 1'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('119: 오른쪽 → 왼쪽으로 읽으면 오른쪽 반부터', (t) async {
|
||||
final c = controller()..settings.readerRtl = true;
|
||||
final src = await source(t, [(200, 100)]);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,45 @@ void main() {
|
|||
expect(AppSettings.fromJson({}).imageExts, defaultImageExtensions);
|
||||
});
|
||||
|
||||
testWidgets('224: 보는 동안 밖에서 지운 그림 - 앱으로 돌아오면 "파일이 없어졌습니다" 와 다음 그림, 다 없어지면 닫는다', (tester) async {
|
||||
tester.view.physicalSize = const Size(900, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
final files = [
|
||||
for (final (i, n) in ['a.png', 'b.png', 'c.png'].indexed)
|
||||
(File(p.join(tmp.path, n))..writeAsBytesSync(_png(i * 80, 0, 0))).path,
|
||||
];
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
final src = ImageFilesSource([...files], tempDir: tmp.path, title: 'imgs');
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Builder(
|
||||
builder: (ctx) => TextButton(
|
||||
onPressed: () => Navigator.of(ctx).push(MaterialPageRoute<void>(builder: (_) => ReaderPage(c: c, source: src, start: 1))),
|
||||
child: const Text('open')))));
|
||||
await tester.tap(find.text('open'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('2 / 3'), findsOneWidget);
|
||||
expect(find.text('b.png'), findsOneWidget);
|
||||
// 보던 b 를 밖에서 지움 → 돌아오면 c 로
|
||||
await tester.runAsync(() => File(files[1]).delete());
|
||||
await ((tester.state(find.byType(ReaderPage)) as dynamic).dropMissingFiles() as Future<void>);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('파일이 없어졌습니다'), findsOneWidget);
|
||||
expect(find.text('c.png'), findsOneWidget);
|
||||
expect(find.text('2 / 2'), findsOneWidget);
|
||||
// 나머지도 다 지우면 보기 화면을 닫는다
|
||||
await tester.runAsync(() async {
|
||||
await File(files[0]).delete();
|
||||
await File(files[2]).delete();
|
||||
});
|
||||
await ((tester.state(find.byType(ReaderPage)) as dynamic).dropMissingFiles() as Future<void>);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ReaderPage), findsNothing);
|
||||
expect(find.text('open'), findsOneWidget);
|
||||
ScaffoldMessenger.of(tester.element(find.text('open'))).clearSnackBars();
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('그림 보기: 오른쪽을 누르면 다음, 왼쪽을 누르면 이전, 가운데는 막대 숨기기 · 오른쪽→왼쪽 넘기기', (tester) async {
|
||||
tester.view.physicalSize = const Size(900, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
|
|
|
|||
Loading…
Reference in New Issue