mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
cbz import & export
This commit is contained in:
@@ -132,7 +132,17 @@
|
|||||||
"Failed to import": "导入失败",
|
"Failed to import": "导入失败",
|
||||||
"Cache Limit": "缓存限制",
|
"Cache Limit": "缓存限制",
|
||||||
"Set Cache Limit": "设置缓存限制",
|
"Set Cache Limit": "设置缓存限制",
|
||||||
"Size in MB": "大小(MB)"
|
"Size in MB": "大小(MB)",
|
||||||
|
"Select a directory which contains the comic directories." : "选择一个包含漫画文件夹的目录",
|
||||||
|
"Help": "帮助",
|
||||||
|
"A directory is considered as a comic only if it matches one of the following conditions:" : "只有当目录满足以下条件之一时,才被视为漫画:",
|
||||||
|
"1. The directory only contains image files." : "1. 目录只包含图片文件。",
|
||||||
|
"2. The directory contains directories which contain image files. Each directory is considered as a chapter." : "2. 目录包含多个包含图片文件的目录。每个目录被视为一个章节。",
|
||||||
|
"If the directory contains a file named 'cover.*', it will be used as the cover image. Otherwise the first image will be used." : "如果目录包含一个名为'cover.*'的文件,它将被用作封面图片。否则将使用第一张图片。",
|
||||||
|
"The directory name will be used as the comic title. And the name of chapter directories will be used as the chapter titles." : "目录名称将被用作漫画标题。章节目录的名称将被用作章节标题。",
|
||||||
|
"Export as cbz": "导出为cbz",
|
||||||
|
"Select a cbz file." : "选择一个cbz文件",
|
||||||
|
"A cbz file" : "一个cbz文件"
|
||||||
},
|
},
|
||||||
"zh_TW": {
|
"zh_TW": {
|
||||||
"Home": "首頁",
|
"Home": "首頁",
|
||||||
@@ -267,6 +277,16 @@
|
|||||||
"Failed to import": "匯入失敗",
|
"Failed to import": "匯入失敗",
|
||||||
"Cache Limit": "緩存限制",
|
"Cache Limit": "緩存限制",
|
||||||
"Set Cache Limit": "設置緩存限制",
|
"Set Cache Limit": "設置緩存限制",
|
||||||
"Size in MB": "大小(MB)"
|
"Size in MB": "大小(MB)",
|
||||||
|
"Select a directory which contains the comic directories." : "選擇一個包含漫畫文件夾的目錄",
|
||||||
|
"Help": "幫助",
|
||||||
|
"A directory is considered as a comic only if it matches one of the following conditions:" : "只有當目錄滿足以下條件之一時,才被視為漫畫:",
|
||||||
|
"1. The directory only contains image files." : "1. 目錄只包含圖片文件。",
|
||||||
|
"2. The directory contains directories which contain image files. Each directory is considered as a chapter." : "2. 目錄包含多個包含圖片文件的目錄。每個目錄被視為一個章節。",
|
||||||
|
"If the directory contains a file named 'cover.*', it will be used as the cover image. Otherwise the first image will be used." : "如果目錄包含一個名為'cover.*'的文件,它將被用作封面圖片。否則將使用第一張圖片。",
|
||||||
|
"The directory name will be used as the comic title. And the name of chapter directories will be used as the chapter titles." : "目錄名稱將被用作漫畫標題。章節目錄的名稱將被用作章節標題。",
|
||||||
|
"Export as cbz": "匯出為cbz",
|
||||||
|
"Select a cbz file." : "選擇一個cbz文件",
|
||||||
|
"A cbz file" : "一個cbz文件"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -221,7 +221,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
if (res.isEmpty) {
|
if (res.isEmpty) {
|
||||||
return '1';
|
return '1';
|
||||||
}
|
}
|
||||||
return ((res.first[0] as int) + 1).toString();
|
return (int.parse((res.first[0])) + 1).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> add(LocalComic comic, [String? id]) async {
|
Future<void> add(LocalComic comic, [String? id]) async {
|
||||||
@@ -424,7 +424,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
|
|
||||||
void deleteComic(LocalComic c) {
|
void deleteComic(LocalComic c) {
|
||||||
var dir = Directory(FilePath.join(path, c.directory));
|
var dir = Directory(FilePath.join(path, c.directory));
|
||||||
dir.deleteSync(recursive: true);
|
dir.deleteIgnoreError(recursive: true);
|
||||||
remove(c.id, c.comicType);
|
remove(c.id, c.comicType);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import 'package:venera/pages/comic_source_page.dart';
|
|||||||
import 'package:venera/pages/downloading_page.dart';
|
import 'package:venera/pages/downloading_page.dart';
|
||||||
import 'package:venera/pages/history_page.dart';
|
import 'package:venera/pages/history_page.dart';
|
||||||
import 'package:venera/pages/search_page.dart';
|
import 'package:venera/pages/search_page.dart';
|
||||||
|
import 'package:venera/utils/cbz.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
import 'package:venera/utils/io.dart';
|
import 'package:venera/utils/io.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
@@ -391,9 +392,11 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String info = type == 0
|
String info = [
|
||||||
? "Select a directory which contains the comic files.".tl
|
"Select a directory which contains the comic files.".tl,
|
||||||
: "Select a directory which contains the comic directories.".tl;
|
"Select a directory which contains the comic directories.".tl,
|
||||||
|
"Select a cbz file.".tl,
|
||||||
|
][type];
|
||||||
|
|
||||||
return ContentDialog(
|
return ContentDialog(
|
||||||
dismissible: !loading,
|
dismissible: !loading,
|
||||||
@@ -431,6 +434,16 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: Text("A cbz file".tl),
|
||||||
|
value: 2,
|
||||||
|
groupValue: type,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
type = value as int;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(info).paddingHorizontal(24),
|
Text(info).paddingHorizontal(24),
|
||||||
],
|
],
|
||||||
@@ -490,6 +503,21 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void selectAndImport() async {
|
void selectAndImport() async {
|
||||||
|
if (type == 2) {
|
||||||
|
var xFile = await selectFile(ext: ['cbz']);
|
||||||
|
var controller = showLoadingDialog(context, allowCancel: false);
|
||||||
|
try {
|
||||||
|
var cache = FilePath.join(App.cachePath, xFile?.name ?? 'temp.cbz');
|
||||||
|
await xFile!.saveTo(cache);
|
||||||
|
await CBZ.import(File(cache));
|
||||||
|
await File(cache).delete();
|
||||||
|
} catch (e, s) {
|
||||||
|
Log.error("Import Comic", e.toString(), s);
|
||||||
|
context.showMessage(message: e.toString());
|
||||||
|
}
|
||||||
|
controller.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
height = key.currentContext!.size!.height;
|
height = key.currentContext!.size!.height;
|
||||||
setState(() {
|
setState(() {
|
||||||
loading = true;
|
loading = true;
|
||||||
@@ -583,16 +611,16 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
Future<LocalComic?> checkSingleComic(Directory directory) async {
|
Future<LocalComic?> checkSingleComic(Directory directory) async {
|
||||||
if (!(await directory.exists())) return null;
|
if (!(await directory.exists())) return null;
|
||||||
var name = directory.name;
|
var name = directory.name;
|
||||||
|
if (LocalManager().findByName(name) != null) {
|
||||||
|
Log.info("Import Comic", "Comic already exists: $name");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
bool hasChapters = false;
|
bool hasChapters = false;
|
||||||
var chapters = <String>[];
|
var chapters = <String>[];
|
||||||
var coverPath = ''; // relative path to the cover image
|
var coverPath = ''; // relative path to the cover image
|
||||||
await for (var entry in directory.list()) {
|
await for (var entry in directory.list()) {
|
||||||
if (entry is Directory) {
|
if (entry is Directory) {
|
||||||
hasChapters = true;
|
hasChapters = true;
|
||||||
if (LocalManager().findByName(entry.name) != null) {
|
|
||||||
Log.info("Import Comic", "Comic already exists: $name");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
chapters.add(entry.name);
|
chapters.add(entry.name);
|
||||||
await for (var file in entry.list()) {
|
await for (var file in entry.list()) {
|
||||||
if (file is Directory) {
|
if (file is Directory) {
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:venera/components/components.dart';
|
import 'package:venera/components/components.dart';
|
||||||
|
import 'package:venera/foundation/app.dart';
|
||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/pages/downloading_page.dart';
|
import 'package:venera/pages/downloading_page.dart';
|
||||||
|
import 'package:venera/utils/cbz.dart';
|
||||||
|
import 'package:venera/utils/io.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
class LocalComicsPage extends StatefulWidget {
|
class LocalComicsPage extends StatefulWidget {
|
||||||
@@ -60,12 +63,29 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
menuBuilder: (c) {
|
menuBuilder: (c) {
|
||||||
return [
|
return [
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon: Icons.delete,
|
icon: Icons.delete,
|
||||||
text: "Delete".tl,
|
text: "Delete".tl,
|
||||||
onClick: () {
|
onClick: () {
|
||||||
LocalManager().deleteComic(c as LocalComic);
|
LocalManager().deleteComic(c as LocalComic);
|
||||||
}
|
}),
|
||||||
),
|
MenuEntry(
|
||||||
|
icon: Icons.delete,
|
||||||
|
text: "Export as cbz".tl,
|
||||||
|
onClick: () async {
|
||||||
|
var controller = showLoadingDialog(
|
||||||
|
context,
|
||||||
|
allowCancel: false,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
var file = await CBZ.export(c as LocalComic);
|
||||||
|
await saveFile(filename: file.name, file: file);
|
||||||
|
await file.delete();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
context.showMessage(message: e.toString());
|
||||||
|
}
|
||||||
|
controller.close();
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
217
lib/utils/cbz.dart
Normal file
217
lib/utils/cbz.dart
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:venera/foundation/app.dart';
|
||||||
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
|
import 'package:venera/foundation/local.dart';
|
||||||
|
import 'package:venera/utils/ext.dart';
|
||||||
|
import 'package:venera/utils/io.dart';
|
||||||
|
import 'package:zip_flutter/zip_flutter.dart';
|
||||||
|
|
||||||
|
class ComicMetaData {
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
final String author;
|
||||||
|
|
||||||
|
final List<String> tags;
|
||||||
|
|
||||||
|
final List<ComicChapter>? chapters;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'title': title,
|
||||||
|
'author': author,
|
||||||
|
'tags': tags,
|
||||||
|
'chapters': chapters?.map((e) => e.toJson()).toList()
|
||||||
|
};
|
||||||
|
|
||||||
|
ComicMetaData.fromJson(Map<String, dynamic> json)
|
||||||
|
: title = json['title'],
|
||||||
|
author = json['author'],
|
||||||
|
tags = List<String>.from(json['tags']),
|
||||||
|
chapters = json['chapters'] == null
|
||||||
|
? null
|
||||||
|
: List<ComicChapter>.from(
|
||||||
|
json['chapters'].map((e) => ComicChapter.fromJson(e)));
|
||||||
|
|
||||||
|
ComicMetaData({
|
||||||
|
required this.title,
|
||||||
|
required this.author,
|
||||||
|
required this.tags,
|
||||||
|
this.chapters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComicChapter {
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
final int start;
|
||||||
|
|
||||||
|
final int end;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'title': title, 'start': start, 'end': end};
|
||||||
|
|
||||||
|
ComicChapter.fromJson(Map<String, dynamic> json)
|
||||||
|
: title = json['title'],
|
||||||
|
start = json['start'],
|
||||||
|
end = json['end'];
|
||||||
|
|
||||||
|
ComicChapter({required this.title, required this.start, required this.end});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CBZ {
|
||||||
|
static Future<void> import(File file) async {
|
||||||
|
var cache = Directory(FilePath.join(App.cachePath, 'cbz_import'));
|
||||||
|
if (cache.existsSync()) cache.deleteSync(recursive: true);
|
||||||
|
cache.createSync();
|
||||||
|
await Isolate.run(() => ZipFile.openAndExtract(file.path, cache.path));
|
||||||
|
var metaDataFile = File(FilePath.join(cache.path, 'metadata.json'));
|
||||||
|
ComicMetaData? metaData;
|
||||||
|
if (metaDataFile.existsSync()) {
|
||||||
|
try {
|
||||||
|
metaData =
|
||||||
|
ComicMetaData.fromJson(jsonDecode(metaDataFile.readAsStringSync()));
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
metaData ??= ComicMetaData(
|
||||||
|
title: file.name.replaceLast('.cbz', ''),
|
||||||
|
author: "",
|
||||||
|
tags: [],
|
||||||
|
);
|
||||||
|
var old = LocalManager().findByName(metaData.title);
|
||||||
|
if (old != null) {
|
||||||
|
throw Exception('Comic with name ${metaData.title} already exists');
|
||||||
|
}
|
||||||
|
var files = cache.listSync().whereType<File>().toList();
|
||||||
|
files.removeWhere((e) {
|
||||||
|
var ext = e.path.split('.').last;
|
||||||
|
return !['jpg', 'jpeg', 'png', 'webp', 'gif', 'jpe'].contains(ext);
|
||||||
|
});
|
||||||
|
files.sort((a, b) => a.path.compareTo(b.path));
|
||||||
|
var coverFile = files.firstWhereOrNull(
|
||||||
|
(element) =>
|
||||||
|
element.path.endsWith('cover.${element.path.split('.').last}'),
|
||||||
|
);
|
||||||
|
if (coverFile != null) {
|
||||||
|
files.remove(coverFile);
|
||||||
|
} else {
|
||||||
|
coverFile = files.first;
|
||||||
|
}
|
||||||
|
Map<String, String>? cpMap;
|
||||||
|
var dest = Directory(
|
||||||
|
FilePath.join(LocalManager().path, sanitizeFileName(metaData.title)),
|
||||||
|
);
|
||||||
|
dest.createSync();
|
||||||
|
coverFile.copy(
|
||||||
|
FilePath.join(dest.path, 'cover.${coverFile.path.split('.').last}'));
|
||||||
|
if (metaData.chapters == null) {
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
var src = files[i];
|
||||||
|
var dst = File(
|
||||||
|
FilePath.join(dest.path, '${i + 1}.${src.path.split('.').last}'));
|
||||||
|
src.copy(dst.path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dest.createSync();
|
||||||
|
var chapters = <String, List<File>>{};
|
||||||
|
for (var chapter in metaData.chapters!) {
|
||||||
|
chapters[chapter.title] = files.sublist(chapter.start - 1, chapter.end);
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
cpMap = <String, String>{};
|
||||||
|
for (var chapter in chapters.entries) {
|
||||||
|
cpMap[i.toString()] = chapter.key;
|
||||||
|
var chapterDir = Directory(FilePath.join(dest.path, i.toString()));
|
||||||
|
chapterDir.createSync();
|
||||||
|
for (var i = 0; i < chapter.value.length; i++) {
|
||||||
|
var src = chapter.value[i];
|
||||||
|
var dst = File(FilePath.join(
|
||||||
|
chapterDir.path, '${i + 1}.${src.path.split('.').last}'));
|
||||||
|
src.copy(dst.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LocalManager().add(
|
||||||
|
LocalComic(
|
||||||
|
id: LocalManager().findValidId(ComicType.local),
|
||||||
|
title: metaData.title,
|
||||||
|
subtitle: metaData.author,
|
||||||
|
tags: metaData.tags,
|
||||||
|
comicType: ComicType.local,
|
||||||
|
directory: dest.name,
|
||||||
|
chapters: cpMap,
|
||||||
|
downloadedChapters: cpMap?.keys.toList() ?? [],
|
||||||
|
cover: 'cover.${coverFile.path.split('.').last}',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<File> export(LocalComic comic) async {
|
||||||
|
var cache = Directory(FilePath.join(App.cachePath, 'cbz_export'));
|
||||||
|
if (cache.existsSync()) cache.deleteSync(recursive: true);
|
||||||
|
cache.createSync();
|
||||||
|
List<ComicChapter>? chapters;
|
||||||
|
if (comic.chapters == null) {
|
||||||
|
var images = await LocalManager().getImages(comic.id, comic.comicType, 1);
|
||||||
|
int i = 1;
|
||||||
|
for (var image in images) {
|
||||||
|
var src = File(image.replaceFirst('file://', ''));
|
||||||
|
var width = images.length.toString().length;
|
||||||
|
var dstName =
|
||||||
|
'${i.toString().padLeft(width, '0')}.${image.split('.').last}';
|
||||||
|
var dst = File(FilePath.join(cache.path, dstName));
|
||||||
|
await src.copy(dst.path);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chapters = [];
|
||||||
|
var allImages = <String>[];
|
||||||
|
for (var c in comic.downloadedChapters) {
|
||||||
|
var chapterName = comic.chapters![c];
|
||||||
|
var images = await LocalManager().getImages(
|
||||||
|
comic.id,
|
||||||
|
comic.comicType,
|
||||||
|
c,
|
||||||
|
);
|
||||||
|
allImages.addAll(images);
|
||||||
|
var chapter = ComicChapter(
|
||||||
|
title: chapterName!,
|
||||||
|
start: chapters.length + 1,
|
||||||
|
end: chapters.length + images.length,
|
||||||
|
);
|
||||||
|
chapters.add(chapter);
|
||||||
|
}
|
||||||
|
int i = 1;
|
||||||
|
for (var image in allImages) {
|
||||||
|
var src = File(image.replaceFirst('file://', ''));
|
||||||
|
var width = allImages.length.toString().length;
|
||||||
|
var dstName =
|
||||||
|
'${i.toString().padLeft(width, '0')}.${image.split('.').last}';
|
||||||
|
var dst = File(FilePath.join(cache.path, dstName));
|
||||||
|
await src.copy(dst.path);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var cover = comic.coverFile;
|
||||||
|
await cover
|
||||||
|
.copy(FilePath.join(cache.path, 'cover.${cover.path.split('.').last}'));
|
||||||
|
await File(FilePath.join(cache.path, 'metadata.json')).writeAsString(
|
||||||
|
jsonEncode(
|
||||||
|
ComicMetaData(
|
||||||
|
title: comic.title,
|
||||||
|
author: comic.subtitle,
|
||||||
|
tags: comic.tags,
|
||||||
|
chapters: chapters,
|
||||||
|
).toJson(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
var cbz = File(FilePath.join(App.cachePath, '${comic.title}.cbz'));
|
||||||
|
await _compress(cache.path, cbz.path);
|
||||||
|
cache.deleteSync(recursive: true);
|
||||||
|
return cbz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _compress(String src, String dst) async {
|
||||||
|
await Isolate.run(() => ZipFile.compressFolder(src, dst));
|
||||||
|
}
|
||||||
|
}
|
@@ -73,6 +73,9 @@ extension DirectoryExtension on Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String sanitizeFileName(String fileName) {
|
String sanitizeFileName(String fileName) {
|
||||||
|
if(fileName.endsWith('.')) {
|
||||||
|
fileName = fileName.substring(0, fileName.length - 1);
|
||||||
|
}
|
||||||
const maxLength = 255;
|
const maxLength = 255;
|
||||||
final invalidChars = RegExp(r'[<>:"/\\|?*]');
|
final invalidChars = RegExp(r'[<>:"/\\|?*]');
|
||||||
final sanitizedFileName = fileName.replaceAll(invalidChars, ' ');
|
final sanitizedFileName = fileName.replaceAll(invalidChars, ' ');
|
||||||
|
@@ -14,6 +14,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
zip_flutter
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
@@ -839,6 +839,15 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
|
zip_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: HEAD
|
||||||
|
resolved-ref: d5721f1fd8179ee4a5db59f932ae7c89d94e12a0
|
||||||
|
url: "https://github.com/wgh136/zip_flutter"
|
||||||
|
source: git
|
||||||
|
version: "0.0.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.4"
|
flutter: ">=3.24.4"
|
||||||
|
@@ -51,6 +51,9 @@ dependencies:
|
|||||||
sliver_tools: ^0.2.12
|
sliver_tools: ^0.2.12
|
||||||
flutter_file_dialog: ^3.0.2
|
flutter_file_dialog: ^3.0.2
|
||||||
file_selector: ^1.0.3
|
file_selector: ^1.0.3
|
||||||
|
zip_flutter:
|
||||||
|
git:
|
||||||
|
url: https://github.com/wgh136/zip_flutter
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@@ -16,6 +16,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
zip_flutter
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
Reference in New Issue
Block a user