132 lines
5.2 KiB
Dart
132 lines
5.2 KiB
Dart
import 'package:archive/archive.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../app/app_controller.dart';
|
|
import '../core/file_ops.dart' show formatSize;
|
|
import '../core/reader_sources.dart';
|
|
import '../core/vfs.dart';
|
|
import '../l10n/tr.dart';
|
|
import 'archive_ui.dart';
|
|
import 'reader_page.dart';
|
|
import 'theme.dart';
|
|
|
|
/// ZIP · CBZ 목록: 안의 파일을 보고, 골라서 (또는 모두) 다른 곳으로 풀고, 그림이 있으면 만화처럼 본다.
|
|
/// [otherDir] 파일 탐색기의 다른 창 폴더 (풀 곳으로 먼저 보여 줌). 풀었으면 그 폴더를 돌려준다.
|
|
Future<String?> openZip(BuildContext context, AppController c, String path, {String? otherDir}) =>
|
|
Navigator.of(context).push<String>(MaterialPageRoute(builder: (_) => ZipPage(c: c, path: path, otherDir: otherDir)));
|
|
|
|
/// ZIP 안 그림을 만화처럼 보기 (그림이 없으면 false)
|
|
Future<bool> openZipComic(BuildContext context, AppController c, String path) async {
|
|
final temp = await c.services.storage.tempDirectory();
|
|
final src = await ZipImagesSource.open(path, tempDir: temp, imageExts: c.settings.reader.imageExts);
|
|
if (src.length == 0) {
|
|
await src.dispose();
|
|
return false;
|
|
}
|
|
if (!context.mounted) return true;
|
|
await openReader(context, c, src);
|
|
return true;
|
|
}
|
|
|
|
class ZipPage extends StatefulWidget {
|
|
final AppController c;
|
|
final String path;
|
|
final String? otherDir;
|
|
const ZipPage({super.key, required this.c, required this.path, this.otherDir});
|
|
|
|
@override
|
|
State<ZipPage> createState() => _ZipPageState();
|
|
}
|
|
|
|
class _ZipPageState extends State<ZipPage> {
|
|
AppController get c => widget.c;
|
|
List<ArchiveFile>? _entries;
|
|
Object? _error;
|
|
final _marked = <String>{};
|
|
(int, int)? _progress;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_load();
|
|
}
|
|
|
|
Future<void> _load() async {
|
|
try {
|
|
final temp = await c.services.storage.tempDirectory();
|
|
final list = await zipEntries(widget.path, tempDir: temp);
|
|
if (mounted) setState(() => _entries = list.where((f) => f.isFile).toList());
|
|
} catch (e) {
|
|
if (mounted) setState(() => _error = e);
|
|
}
|
|
}
|
|
|
|
bool get _hasImages => _entries?.any((f) => c.settings.reader.imageExts.contains(extOf(f.name))) ?? false;
|
|
|
|
/// 86: 풀기는 압축 풀기 흐름 (목록 검사 - 밖으로 나가는 경로 · 링크 · 크기 · 비밀번호, 같은 이름 · 진행 · 취소) 으로
|
|
Future<void> _extract({required bool all}) async {
|
|
final dest = await extractArchiveFlow(context, c, widget.path, otherDir: widget.otherDir, names: all ? const [] : _marked.toList());
|
|
if (dest != null && mounted) Navigator.pop(context, dest);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final entries = _entries;
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(vBasename(widget.path), overflow: TextOverflow.ellipsis),
|
|
actions: [
|
|
if (_hasImages)
|
|
IconButton(
|
|
tooltip: tr('만화 보기'),
|
|
icon: const Icon(Icons.auto_stories_outlined),
|
|
onPressed: () => openZipComic(context, c, widget.path),
|
|
),
|
|
IconButton(
|
|
tooltip: tr('고른 것 풀기'),
|
|
icon: const Icon(Icons.unarchive_outlined),
|
|
onPressed: _marked.isEmpty || _progress != null ? null : () => _extract(all: false),
|
|
),
|
|
IconButton(
|
|
tooltip: tr('모두 풀기'),
|
|
icon: const Icon(Icons.folder_zip_outlined),
|
|
onPressed: entries == null || entries.isEmpty || _progress != null ? null : () => _extract(all: true),
|
|
),
|
|
],
|
|
),
|
|
body: Column(children: [
|
|
if (_progress != null)
|
|
LinearProgressIndicator(value: _progress!.$2 == 0 ? null : _progress!.$1 / _progress!.$2),
|
|
Expanded(
|
|
child: _error != null
|
|
? Center(child: Text(trf('열 수 없습니다: {0}', [_error])))
|
|
: entries == null
|
|
? const Center(child: CircularProgressIndicator())
|
|
: entries.isEmpty
|
|
? Center(child: Text(tr('비어 있는 압축 파일입니다.')))
|
|
: ListView.builder(
|
|
itemCount: entries.length,
|
|
itemBuilder: (_, i) {
|
|
final f = entries[i];
|
|
final on = _marked.contains(f.name);
|
|
return CheckboxListTile(
|
|
dense: true,
|
|
value: on,
|
|
onChanged: (v) => setState(() => v == true ? _marked.add(f.name) : _marked.remove(f.name)),
|
|
secondary: Icon(
|
|
c.settings.reader.imageExts.contains(extOf(f.name))
|
|
? Icons.image_outlined
|
|
: Icons.insert_drive_file_outlined,
|
|
color: JjColors.textDim,
|
|
),
|
|
title: Text(f.name, maxLines: 2, overflow: TextOverflow.ellipsis),
|
|
subtitle: Text(formatSize(f.size)),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
]),
|
|
);
|
|
}
|
|
}
|