mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
更改安卓端的文件访问方式,优化导入逻辑 (#64)
* Refactor import function & Allow import local comics without copying them to local path. * android: use file_picker instead, support directory access for android 10 * Improve import logic * Fix sql query. * Add ability to remove invalid favorite items. * Perform sort before choosing cover * Revert changes of "use file_picker instead". * Try catch on "check update" * Added module 'flutter_saf' * gitignore * remove unsupported arch in build.gradle * Use flutter_saf to handle android's directory and files, improve import logic. * revert changes of 'requestLegacyExternalStorage' * fix cbz import * openDirectoryPlatform * Remove double check on source folder * use openFilePlatform * remove unused import * improve local comic's path handling * bump version * fix pubspec format * return null when comic folder is empty
This commit is contained in:
1
android/.gitignore
vendored
1
android/.gitignore
vendored
@@ -11,3 +11,4 @@ GeneratedPluginRegistrant.java
|
|||||||
key.properties
|
key.properties
|
||||||
**/*.keystore
|
**/*.keystore
|
||||||
**/*.jks
|
**/*.jks
|
||||||
|
/app/.cxx/
|
||||||
|
@@ -34,6 +34,8 @@ android {
|
|||||||
|
|
||||||
splits{
|
splits{
|
||||||
abi {
|
abi {
|
||||||
|
reset()
|
||||||
|
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||||
enable true
|
enable true
|
||||||
universalApk true
|
universalApk true
|
||||||
}
|
}
|
||||||
|
@@ -239,6 +239,9 @@
|
|||||||
"Chapter @ep": "第 @ep 章",
|
"Chapter @ep": "第 @ep 章",
|
||||||
"Page @page": "第 @page 页",
|
"Page @page": "第 @page 页",
|
||||||
"Also remove files on disk": "同时删除磁盘上的文件",
|
"Also remove files on disk": "同时删除磁盘上的文件",
|
||||||
|
"Copy to app local path": "将漫画复制到本地存储目录中",
|
||||||
|
"Delete all unavailable local favorite items": "删除所有无效的本地收藏",
|
||||||
|
"Deleted @a favorite items.": "已删除 @a 条无效收藏",
|
||||||
"New version available": "有新版本可用",
|
"New version available": "有新版本可用",
|
||||||
"A new version is available. Do you want to update now?" : "有新版本可用。您要现在更新吗?",
|
"A new version is available. Do you want to update now?" : "有新版本可用。您要现在更新吗?",
|
||||||
"No new version available": "没有新版本可用"
|
"No new version available": "没有新版本可用"
|
||||||
@@ -483,6 +486,9 @@
|
|||||||
"Chapter @ep": "第 @ep 章",
|
"Chapter @ep": "第 @ep 章",
|
||||||
"Page @page": "第 @page 頁",
|
"Page @page": "第 @page 頁",
|
||||||
"Also remove files on disk": "同時刪除磁盤上的文件",
|
"Also remove files on disk": "同時刪除磁盤上的文件",
|
||||||
|
"Copy to app local path": "將漫畫複製到本地儲存目錄中",
|
||||||
|
"Delete all unavailable local favorite items": "刪除所有無效的本地收藏",
|
||||||
|
"Deleted @a favorite items.": "已刪除 @a 條無效收藏",
|
||||||
"New version available": "有新版本可用",
|
"New version available": "有新版本可用",
|
||||||
"A new version is available. Do you want to update now?" : "有新版本可用。您要現在更新嗎?",
|
"A new version is available. Do you want to update now?" : "有新版本可用。您要現在更新嗎?",
|
||||||
"No new version available": "沒有新版本可用"
|
"No new version available": "沒有新版本可用"
|
||||||
|
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:sqlite3/sqlite3.dart';
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
import 'package:venera/foundation/appdata.dart';
|
import 'package:venera/foundation/appdata.dart';
|
||||||
import 'package:venera/foundation/image_provider/local_favorite_image.dart';
|
import 'package:venera/foundation/image_provider/local_favorite_image.dart';
|
||||||
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/foundation/log.dart';
|
import 'package:venera/foundation/log.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
@@ -496,6 +497,22 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> removeInvalid() async {
|
||||||
|
int count = 0;
|
||||||
|
await Future.microtask(() {
|
||||||
|
var all = allComics();
|
||||||
|
for(var c in all) {
|
||||||
|
var comicSource = c.type.comicSource;
|
||||||
|
if ((c.type == ComicType.local && LocalManager().find(c.id, c.type) == null)
|
||||||
|
|| (c.type != ComicType.local && comicSource == null)) {
|
||||||
|
deleteComicWithId(c.folder, c.id, c.type);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> clearAll() async {
|
Future<void> clearAll() async {
|
||||||
_db.dispose();
|
_db.dispose();
|
||||||
File("${App.dataPath}/local_favorite.db").deleteSync();
|
File("${App.dataPath}/local_favorite.db").deleteSync();
|
||||||
|
@@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:venera/network/images.dart';
|
import 'package:venera/network/images.dart';
|
||||||
|
import 'package:venera/utils/io.dart';
|
||||||
import 'base_image_provider.dart';
|
import 'base_image_provider.dart';
|
||||||
import 'cached_image.dart' as image_provider;
|
import 'cached_image.dart' as image_provider;
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ class CachedImageProvider
|
|||||||
@override
|
@override
|
||||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
||||||
if(url.startsWith("file://")) {
|
if(url.startsWith("file://")) {
|
||||||
var file = File(url.substring(7));
|
var file = openFilePlatform(url.substring(7));
|
||||||
return file.readAsBytes();
|
return file.readAsBytes();
|
||||||
}
|
}
|
||||||
await for (var progress in ImageDownloader.loadThumbnail(url, sourceKey, cid)) {
|
await for (var progress in ImageDownloader.loadThumbnail(url, sourceKey, cid)) {
|
||||||
|
@@ -71,12 +71,13 @@ class LocalComic with HistoryMixin implements Comic {
|
|||||||
downloadedChapters = List.from(jsonDecode(row[8] as String)),
|
downloadedChapters = List.from(jsonDecode(row[8] as String)),
|
||||||
createdAt = DateTime.fromMillisecondsSinceEpoch(row[9] as int);
|
createdAt = DateTime.fromMillisecondsSinceEpoch(row[9] as int);
|
||||||
|
|
||||||
File get coverFile => File(FilePath.join(
|
File get coverFile => openFilePlatform(FilePath.join(
|
||||||
LocalManager().path,
|
baseDir,
|
||||||
directory,
|
|
||||||
cover,
|
cover,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
String get baseDir => directory.contains("/") ? directory : FilePath.join(LocalManager().path, directory);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get description => "";
|
String get description => "";
|
||||||
|
|
||||||
@@ -341,12 +342,12 @@ class LocalManager with ChangeNotifier {
|
|||||||
throw "Invalid ep";
|
throw "Invalid ep";
|
||||||
}
|
}
|
||||||
var comic = find(id, type) ?? (throw "Comic Not Found");
|
var comic = find(id, type) ?? (throw "Comic Not Found");
|
||||||
var directory = Directory(FilePath.join(path, comic.directory));
|
var directory = openDirectoryPlatform(comic.baseDir);
|
||||||
if (comic.chapters != null) {
|
if (comic.chapters != null) {
|
||||||
var cid = ep is int
|
var cid = ep is int
|
||||||
? comic.chapters!.keys.elementAt(ep - 1)
|
? comic.chapters!.keys.elementAt(ep - 1)
|
||||||
: (ep as String);
|
: (ep as String);
|
||||||
directory = Directory(FilePath.join(directory.path, cid));
|
directory = openDirectoryPlatform(FilePath.join(directory.path, cid));
|
||||||
}
|
}
|
||||||
var files = <File>[];
|
var files = <File>[];
|
||||||
await for (var entity in directory.list()) {
|
await for (var entity in directory.list()) {
|
||||||
@@ -392,10 +393,10 @@ class LocalManager with ChangeNotifier {
|
|||||||
String id, ComicType type, String name) async {
|
String id, ComicType type, String name) async {
|
||||||
var comic = find(id, type);
|
var comic = find(id, type);
|
||||||
if (comic != null) {
|
if (comic != null) {
|
||||||
return Directory(FilePath.join(path, comic.directory));
|
return openDirectoryPlatform(FilePath.join(path, comic.directory));
|
||||||
}
|
}
|
||||||
var dir = findValidDirectoryName(path, name);
|
var dir = findValidDirectoryName(path, name);
|
||||||
return Directory(FilePath.join(path, dir)).create().then((value) => value);
|
return openDirectoryPlatform(FilePath.join(path, dir)).create().then((value) => value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void completeTask(DownloadTask task) {
|
void completeTask(DownloadTask task) {
|
||||||
@@ -454,7 +455,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
|
|
||||||
void deleteComic(LocalComic c, [bool removeFileOnDisk = true]) {
|
void deleteComic(LocalComic c, [bool removeFileOnDisk = true]) {
|
||||||
if(removeFileOnDisk) {
|
if(removeFileOnDisk) {
|
||||||
var dir = Directory(FilePath.join(path, c.directory));
|
var dir = openDirectoryPlatform(FilePath.join(path, c.directory));
|
||||||
dir.deleteIgnoreError(recursive: true);
|
dir.deleteIgnoreError(recursive: true);
|
||||||
}
|
}
|
||||||
//Deleting a local comic means that it's nolonger available, thus both favorite and history should be deleted.
|
//Deleting a local comic means that it's nolonger available, thus both favorite and history should be deleted.
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter_saf/flutter_saf.dart';
|
||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
import 'package:venera/foundation/cache_manager.dart';
|
import 'package:venera/foundation/cache_manager.dart';
|
||||||
import 'package:venera/foundation/comic_source/comic_source.dart';
|
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||||
@@ -12,6 +13,7 @@ import 'package:venera/utils/translations.dart';
|
|||||||
import 'foundation/appdata.dart';
|
import 'foundation/appdata.dart';
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
|
await SAFTaskWorker().init();
|
||||||
await AppTranslation.init();
|
await AppTranslation.init();
|
||||||
await appdata.init();
|
await appdata.init();
|
||||||
await App.init();
|
await App.init();
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sliver_tools/sliver_tools.dart';
|
import 'package:sliver_tools/sliver_tools.dart';
|
||||||
import 'package:venera/components/components.dart';
|
import 'package:venera/components/components.dart';
|
||||||
@@ -497,6 +496,10 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
|
|
||||||
String? selectedFolder;
|
String? selectedFolder;
|
||||||
|
|
||||||
|
bool copyToLocalFolder = true;
|
||||||
|
|
||||||
|
bool cancelled = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
loading = false;
|
loading = false;
|
||||||
@@ -530,22 +533,23 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
key: key,
|
key: key,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 600),
|
const SizedBox(width: 600),
|
||||||
...List.generate(importMethods.length, (index) {
|
...List.generate(importMethods.length, (index) {
|
||||||
return RadioListTile(
|
return RadioListTile(
|
||||||
title: Text(importMethods[index]),
|
title: Text(importMethods[index]),
|
||||||
value: index,
|
value: index,
|
||||||
groupValue: type,
|
groupValue: type,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
type = value as int;
|
type = value as int;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
if(type != 3)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text("Add to favorites".tl),
|
title: Text("Add to favorites".tl),
|
||||||
trailing: Select(
|
trailing: Select(
|
||||||
@@ -559,10 +563,19 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
).paddingHorizontal(8),
|
).paddingHorizontal(8),
|
||||||
const SizedBox(height: 8),
|
CheckboxListTile(
|
||||||
Text(info).paddingHorizontal(24),
|
enabled: true,
|
||||||
],
|
title: Text("Copy to app local path".tl),
|
||||||
),
|
value: copyToLocalFolder,
|
||||||
|
onChanged:(v) {
|
||||||
|
setState(() {
|
||||||
|
copyToLocalFolder = !copyToLocalFolder;
|
||||||
|
});
|
||||||
|
}).paddingHorizontal(8),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(info).paddingHorizontal(24),
|
||||||
|
],
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Button.text(
|
Button.text(
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -620,18 +633,20 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
|
|
||||||
void selectAndImport() async {
|
void selectAndImport() async {
|
||||||
height = key.currentContext!.size!.height;
|
height = key.currentContext!.size!.height;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
loading = true;
|
loading = true;
|
||||||
});
|
});
|
||||||
var importer = ImportComic(selectedFolder: selectedFolder);
|
var importer = ImportComic(
|
||||||
var result = false;
|
selectedFolder: selectedFolder,
|
||||||
if (type == 2) {
|
copyToLocal: copyToLocalFolder);
|
||||||
result = await importer.cbz();
|
var result = switch(type) {
|
||||||
} else if (type == 3) {
|
0 => await importer.directory(true),
|
||||||
result = await importer.ehViewer();
|
1 => await importer.directory(false),
|
||||||
} else {
|
2 => await importer.cbz(),
|
||||||
result = await importer.directory(type == 0);
|
3 => await importer.ehViewer(),
|
||||||
}
|
int() => true,
|
||||||
|
};
|
||||||
if(result) {
|
if(result) {
|
||||||
context.pop();
|
context.pop();
|
||||||
} else {
|
} else {
|
||||||
|
@@ -604,7 +604,7 @@ ImageProvider _createImageProvider(int page, BuildContext context) {
|
|||||||
var reader = context.reader;
|
var reader = context.reader;
|
||||||
var imageKey = reader.images![page - 1];
|
var imageKey = reader.images![page - 1];
|
||||||
if (imageKey.startsWith('file://')) {
|
if (imageKey.startsWith('file://')) {
|
||||||
return FileImage(File(imageKey.replaceFirst("file://", '')));
|
return FileImage(openFilePlatform(imageKey.replaceFirst("file://", '')));
|
||||||
} else {
|
} else {
|
||||||
return ReaderImageProvider(
|
return ReaderImageProvider(
|
||||||
imageKey,
|
imageKey,
|
||||||
|
@@ -469,7 +469,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
ImageProvider image;
|
ImageProvider image;
|
||||||
var imageKey = images[index];
|
var imageKey = images[index];
|
||||||
if (imageKey.startsWith('file://')) {
|
if (imageKey.startsWith('file://')) {
|
||||||
image = FileImage(File(imageKey.replaceFirst("file://", '')));
|
image = FileImage(openFilePlatform(imageKey.replaceFirst("file://", '')));
|
||||||
} else {
|
} else {
|
||||||
image = ReaderImageProvider(
|
image = ReaderImageProvider(
|
||||||
imageKey,
|
imageKey,
|
||||||
@@ -515,7 +515,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (imageKey.startsWith("file://")) {
|
if (imageKey.startsWith("file://")) {
|
||||||
return await File(imageKey.substring(7)).readAsBytes();
|
return await openFilePlatform(imageKey.substring(7)).readAsBytes();
|
||||||
} else {
|
} else {
|
||||||
return (await CacheManager().findCache(
|
return (await CacheManager().findCache(
|
||||||
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
|
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
|
||||||
|
@@ -86,29 +86,33 @@ Future<bool> checkUpdate() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkUpdateUi([bool showMessageIfNoUpdate = true]) async {
|
Future<void> checkUpdateUi([bool showMessageIfNoUpdate = true]) async {
|
||||||
var value = await checkUpdate();
|
try {
|
||||||
if (value) {
|
var value = await checkUpdate();
|
||||||
showDialog(
|
if (value) {
|
||||||
context: App.rootContext,
|
showDialog(
|
||||||
builder: (context) {
|
context: App.rootContext,
|
||||||
return ContentDialog(
|
builder: (context) {
|
||||||
title: "New version available".tl,
|
return ContentDialog(
|
||||||
content: Text(
|
title: "New version available".tl,
|
||||||
"A new version is available. Do you want to update now?".tl),
|
content: Text(
|
||||||
actions: [
|
"A new version is available. Do you want to update now?".tl),
|
||||||
Button.text(
|
actions: [
|
||||||
onPressed: () {
|
Button.text(
|
||||||
Navigator.pop(context);
|
onPressed: () {
|
||||||
launchUrlString(
|
Navigator.pop(context);
|
||||||
"https://github.com/venera-app/venera/releases");
|
launchUrlString(
|
||||||
},
|
"https://github.com/venera-app/venera/releases");
|
||||||
child: Text("Update".tl),
|
},
|
||||||
),
|
child: Text("Update".tl),
|
||||||
],
|
),
|
||||||
);
|
],
|
||||||
});
|
);
|
||||||
} else if (showMessageIfNoUpdate) {
|
});
|
||||||
App.rootContext.showMessage(message: "No new version available".tl);
|
} else if (showMessageIfNoUpdate) {
|
||||||
|
App.rootContext.showMessage(message: "No new version available".tl);
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Log.error("Check Update", e.toString(), s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,6 +38,16 @@ class _LocalFavoritesSettingsState extends State<LocalFavoritesSettings> {
|
|||||||
for (var e in LocalFavoritesManager().folderNames) e: e
|
for (var e in LocalFavoritesManager().folderNames) e: e
|
||||||
},
|
},
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
|
_CallbackSetting(
|
||||||
|
title: "Delete all unavailable local favorite items".tl,
|
||||||
|
callback: () async {
|
||||||
|
var controller = showLoadingDialog(context);
|
||||||
|
var count = await LocalFavoritesManager().removeInvalid();
|
||||||
|
controller.close();
|
||||||
|
context.showMessage(message: "Deleted @a favorite items".tlParams({'a': count}));
|
||||||
|
},
|
||||||
|
actionTitle: 'Delete'.tl,
|
||||||
|
).toSliver(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -8,50 +8,40 @@ import 'package:venera/foundation/favorites.dart';
|
|||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/foundation/log.dart';
|
import 'package:venera/foundation/log.dart';
|
||||||
import 'package:sqlite3/sqlite3.dart' as sql;
|
import 'package:sqlite3/sqlite3.dart' as sql;
|
||||||
|
import 'package:venera/utils/ext.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
import 'cbz.dart';
|
import 'cbz.dart';
|
||||||
import 'io.dart';
|
import 'io.dart';
|
||||||
|
|
||||||
class ImportComic {
|
class ImportComic {
|
||||||
final String? selectedFolder;
|
final String? selectedFolder;
|
||||||
|
final bool copyToLocal;
|
||||||
|
|
||||||
const ImportComic({this.selectedFolder});
|
const ImportComic({this.selectedFolder, this.copyToLocal = true});
|
||||||
|
|
||||||
Future<bool> cbz() async {
|
Future<bool> cbz() async {
|
||||||
var file = await selectFile(ext: ['cbz']);
|
var file = await selectFile(ext: ['cbz']);
|
||||||
|
Map<String?, List<LocalComic>> imported = {};
|
||||||
if(file == null) {
|
if(file == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var controller = showLoadingDialog(App.rootContext, allowCancel: false);
|
var controller = showLoadingDialog(App.rootContext, allowCancel: false);
|
||||||
var isSuccessful = false;
|
|
||||||
try {
|
try {
|
||||||
var comic = await CBZ.import(File(file.path));
|
var comic = await CBZ.import(File(file.path));
|
||||||
if (selectedFolder != null) {
|
imported[selectedFolder] = [comic];
|
||||||
LocalFavoritesManager().addComic(
|
|
||||||
selectedFolder!,
|
|
||||||
FavoriteItem(
|
|
||||||
id: comic.id,
|
|
||||||
name: comic.title,
|
|
||||||
coverPath: comic.cover,
|
|
||||||
author: comic.subtitle,
|
|
||||||
type: comic.comicType,
|
|
||||||
tags: comic.tags,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
isSuccessful = true;
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Import Comic", e.toString(), s);
|
Log.error("Import Comic", e.toString(), s);
|
||||||
App.rootContext.showMessage(message: e.toString());
|
App.rootContext.showMessage(message: e.toString());
|
||||||
}
|
}
|
||||||
controller.close();
|
controller.close();
|
||||||
return isSuccessful;
|
return registerComics(imported, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> ehViewer() async {
|
Future<bool> ehViewer() async {
|
||||||
var dbFile = await selectFile(ext: ['db']);
|
var dbFile = await selectFile(ext: ['db']);
|
||||||
final picker = DirectoryPicker();
|
final picker = DirectoryPicker();
|
||||||
final comicSrc = await picker.pickDirectory();
|
final comicSrc = await picker.pickDirectory();
|
||||||
|
Map<String?, List<LocalComic>> imported = {};
|
||||||
if (dbFile == null || comicSrc == null) {
|
if (dbFile == null || comicSrc == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -60,129 +50,91 @@ class ImportComic {
|
|||||||
var controller = showLoadingDialog(App.rootContext, onCancel: () {
|
var controller = showLoadingDialog(App.rootContext, onCancel: () {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
});
|
});
|
||||||
bool isSuccessful = false;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var cache = FilePath.join(App.cachePath, dbFile.name);
|
var db = sql.sqlite3.open(dbFile.path);
|
||||||
await dbFile.saveTo(cache);
|
|
||||||
var db = sql.sqlite3.open(cache);
|
|
||||||
|
|
||||||
Future<void> addTagComics(String destFolder, List<sql.Row> comics) async {
|
Future<List<LocalComic>> validateComics(List<sql.Row> comics) async {
|
||||||
|
List<LocalComic> imported = [];
|
||||||
for (var comic in comics) {
|
for (var comic in comics) {
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return;
|
return imported;
|
||||||
}
|
}
|
||||||
var comicDir = Directory(
|
var comicDir = openDirectoryPlatform(
|
||||||
FilePath.join(comicSrc.path, comic['DIRNAME'] as String));
|
FilePath.join(comicSrc.path, comic['DIRNAME'] as String));
|
||||||
if (!(await comicDir.exists())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String titleJP =
|
String titleJP =
|
||||||
comic['TITLE_JPN'] == null ? "" : comic['TITLE_JPN'] as String;
|
comic['TITLE_JPN'] == null ? "" : comic['TITLE_JPN'] as String;
|
||||||
String title = titleJP == "" ? comic['TITLE'] as String : titleJP;
|
String title = titleJP == "" ? comic['TITLE'] as String : titleJP;
|
||||||
if (LocalManager().findByName(title) != null) {
|
int timeStamp = comic['TIME'] as int;
|
||||||
Log.info("Import Comic", "Comic already exists: $title");
|
DateTime downloadTime = timeStamp != 0
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(timeStamp)
|
||||||
|
: DateTime.now();
|
||||||
|
var comicObj = await _checkSingleComic(comicDir,
|
||||||
|
title: title,
|
||||||
|
tags: [
|
||||||
|
//1 >> x
|
||||||
|
[
|
||||||
|
"MISC",
|
||||||
|
"DOUJINSHI",
|
||||||
|
"MANGA",
|
||||||
|
"ARTISTCG",
|
||||||
|
"GAMECG",
|
||||||
|
"IMAGE SET",
|
||||||
|
"COSPLAY",
|
||||||
|
"ASIAN PORN",
|
||||||
|
"NON-H",
|
||||||
|
"WESTERN",
|
||||||
|
][(log(comic['CATEGORY'] as int) / ln2).floor()]
|
||||||
|
],
|
||||||
|
createTime: downloadTime);
|
||||||
|
if (comicObj == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
imported.add(comicObj);
|
||||||
String coverURL = await comicDir.joinFile(".thumb").exists()
|
|
||||||
? comicDir.joinFile(".thumb").path
|
|
||||||
: (comic['THUMB'] as String)
|
|
||||||
.replaceAll('s.exhentai.org', 'ehgt.org');
|
|
||||||
int downloadedTimeStamp = comic['TIME'] as int;
|
|
||||||
DateTime downloadedTime = downloadedTimeStamp != 0
|
|
||||||
? DateTime.fromMillisecondsSinceEpoch(downloadedTimeStamp)
|
|
||||||
: DateTime.now();
|
|
||||||
var comicObj = LocalComic(
|
|
||||||
id: LocalManager().findValidId(ComicType.local),
|
|
||||||
title: title,
|
|
||||||
subtitle: '',
|
|
||||||
tags: [
|
|
||||||
//1 >> x
|
|
||||||
[
|
|
||||||
"MISC",
|
|
||||||
"DOUJINSHI",
|
|
||||||
"MANGA",
|
|
||||||
"ARTISTCG",
|
|
||||||
"GAMECG",
|
|
||||||
"IMAGE SET",
|
|
||||||
"COSPLAY",
|
|
||||||
"ASIAN PORN",
|
|
||||||
"NON-H",
|
|
||||||
"WESTERN",
|
|
||||||
][(log(comic['CATEGORY'] as int) / ln2).floor()]
|
|
||||||
],
|
|
||||||
directory: comicDir.path,
|
|
||||||
chapters: null,
|
|
||||||
cover: coverURL,
|
|
||||||
comicType: ComicType.local,
|
|
||||||
downloadedChapters: [],
|
|
||||||
createdAt: downloadedTime,
|
|
||||||
);
|
|
||||||
LocalManager().add(comicObj, comicObj.id);
|
|
||||||
LocalFavoritesManager().addComic(
|
|
||||||
destFolder,
|
|
||||||
FavoriteItem(
|
|
||||||
id: comicObj.id,
|
|
||||||
name: comicObj.title,
|
|
||||||
coverPath: comicObj.cover,
|
|
||||||
author: comicObj.subtitle,
|
|
||||||
type: comicObj.comicType,
|
|
||||||
tags: comicObj.tags,
|
|
||||||
favoriteTime: downloadedTime,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return imported;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
var tags = <String>[""];
|
||||||
var defaultFolderName = '(EhViewer)Default';
|
tags.addAll(db.select("""
|
||||||
if (!LocalFavoritesManager().existsFolder(defaultFolderName)) {
|
SELECT * FROM DOWNLOAD_LABELS LB
|
||||||
LocalFavoritesManager().createFolder(defaultFolderName);
|
ORDER BY LB.TIME DESC;
|
||||||
}
|
""").map((r) => r['LABEL'] as String).toList());
|
||||||
var comicList = db.select("""
|
|
||||||
SELECT *
|
|
||||||
FROM DOWNLOAD_DIRNAME DN
|
|
||||||
LEFT JOIN DOWNLOADS DL
|
|
||||||
ON DL.GID = DN.GID
|
|
||||||
WHERE DL.LABEL IS NULL AND DL.STATE = 3
|
|
||||||
ORDER BY DL.TIME DESC
|
|
||||||
""").toList();
|
|
||||||
await addTagComics(defaultFolderName, comicList);
|
|
||||||
}
|
|
||||||
|
|
||||||
var folders = db.select("""
|
for (var tag in tags) {
|
||||||
SELECT * FROM DOWNLOAD_LABELS;
|
|
||||||
""");
|
|
||||||
|
|
||||||
for (var folder in folders) {
|
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var label = folder["LABEL"] as String;
|
var folderName =
|
||||||
var folderName = '(EhViewer)$label';
|
tag == '' ? '(EhViewer)Default'.tl : '(EhViewer)$tag';
|
||||||
if (!LocalFavoritesManager().existsFolder(folderName)) {
|
|
||||||
LocalFavoritesManager().createFolder(folderName);
|
|
||||||
}
|
|
||||||
var comicList = db.select("""
|
var comicList = db.select("""
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM DOWNLOAD_DIRNAME DN
|
FROM DOWNLOAD_DIRNAME DN
|
||||||
LEFT JOIN DOWNLOADS DL
|
LEFT JOIN DOWNLOADS DL
|
||||||
ON DL.GID = DN.GID
|
ON DL.GID = DN.GID
|
||||||
WHERE DL.LABEL = ? AND DL.STATE = 3
|
WHERE DL.LABEL ${tag == '' ? 'IS NULL' : '= \'$tag\''} AND DL.STATE = 3
|
||||||
ORDER BY DL.TIME DESC
|
ORDER BY DL.TIME DESC
|
||||||
""", [label]).toList();
|
""").toList();
|
||||||
await addTagComics(folderName, comicList);
|
|
||||||
|
var validComics = await validateComics(comicList);
|
||||||
|
imported[folderName] = validComics;
|
||||||
|
if (validComics.isNotEmpty &&
|
||||||
|
!LocalFavoritesManager().existsFolder(folderName)) {
|
||||||
|
LocalFavoritesManager().createFolder(folderName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
db.dispose();
|
db.dispose();
|
||||||
|
|
||||||
|
//Android specific
|
||||||
|
var cache = FilePath.join(App.cachePath, dbFile.name);
|
||||||
await File(cache).deleteIgnoreError();
|
await File(cache).deleteIgnoreError();
|
||||||
isSuccessful = true;
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Import Comic", e.toString(), s);
|
Log.error("Import Comic", e.toString(), s);
|
||||||
App.rootContext.showMessage(message: e.toString());
|
App.rootContext.showMessage(message: e.toString());
|
||||||
}
|
}
|
||||||
controller.close();
|
controller.close();
|
||||||
return isSuccessful;
|
if(cancelled) return false;
|
||||||
|
return registerComics(imported, copyToLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> directory(bool single) async {
|
Future<bool> directory(bool single) async {
|
||||||
@@ -191,71 +143,43 @@ class ImportComic {
|
|||||||
if (path == null) {
|
if (path == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Map<Directory, LocalComic> comics = {};
|
Map<String?, List<LocalComic>> imported = {selectedFolder: []};
|
||||||
if (single) {
|
try {
|
||||||
var result = await _checkSingleComic(path);
|
if (single) {
|
||||||
if (result != null) {
|
var result = await _checkSingleComic(path);
|
||||||
comics[path] = result;
|
if (result != null) {
|
||||||
|
imported[selectedFolder]!.add(result);
|
||||||
|
} else {
|
||||||
|
App.rootContext.showMessage(message: "Invalid Comic".tl);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
App.rootContext.showMessage(message: "Invalid Comic".tl);
|
await for (var entry in path.list()) {
|
||||||
return false;
|
if (entry is Directory) {
|
||||||
}
|
var result = await _checkSingleComic(entry);
|
||||||
} else {
|
if (result != null) {
|
||||||
await for (var entry in path.list()) {
|
imported[selectedFolder]!.add(result);
|
||||||
if (entry is Directory) {
|
}
|
||||||
var result = await _checkSingleComic(entry);
|
|
||||||
if (result != null) {
|
|
||||||
comics[entry] = result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Log.error("Import Comic", e.toString(), s);
|
||||||
|
App.rootContext.showMessage(message: e.toString());
|
||||||
}
|
}
|
||||||
bool shouldCopy = true;
|
return registerComics(imported, copyToLocal);
|
||||||
for (var comic in comics.keys) {
|
|
||||||
if (comic.parent.path == LocalManager().path) {
|
|
||||||
shouldCopy = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldCopy && comics.isNotEmpty) {
|
|
||||||
try {
|
|
||||||
// copy the comics to the local directory
|
|
||||||
await compute<Map<String, dynamic>, void>(_copyDirectories, {
|
|
||||||
'toBeCopied': comics.keys.map((e) => e.path).toList(),
|
|
||||||
'destination': LocalManager().path,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
App.rootContext.showMessage(message: "Failed to import comics".tl);
|
|
||||||
Log.error("Import Comic", e.toString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var comic in comics.values) {
|
|
||||||
LocalManager().add(comic, LocalManager().findValidId(ComicType.local));
|
|
||||||
if (selectedFolder != null) {
|
|
||||||
LocalFavoritesManager().addComic(
|
|
||||||
selectedFolder!,
|
|
||||||
FavoriteItem(
|
|
||||||
id: comic.id,
|
|
||||||
name: comic.title,
|
|
||||||
coverPath: comic.cover,
|
|
||||||
author: comic.subtitle,
|
|
||||||
type: comic.comicType,
|
|
||||||
tags: comic.tags,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
App.rootContext.showMessage(
|
|
||||||
message: "Imported @a comics".tlParams({
|
|
||||||
'a': comics.length,
|
|
||||||
}));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<LocalComic?> _checkSingleComic(Directory directory) async {
|
//Automatically search for cover image and chapters
|
||||||
|
Future<LocalComic?> _checkSingleComic(Directory directory,
|
||||||
|
{String? id,
|
||||||
|
String? title,
|
||||||
|
String? subtitle,
|
||||||
|
List<String>? tags,
|
||||||
|
DateTime? createTime})
|
||||||
|
async {
|
||||||
if (!(await directory.exists())) return null;
|
if (!(await directory.exists())) return null;
|
||||||
var name = directory.name;
|
var name = title ?? directory.name;
|
||||||
if (LocalManager().findByName(name) != null) {
|
if (LocalManager().findByName(name) != null) {
|
||||||
Log.info("Import Comic", "Comic already exists: $name");
|
Log.info("Import Comic", "Comic already exists: $name");
|
||||||
return null;
|
return null;
|
||||||
@@ -263,7 +187,8 @@ class ImportComic {
|
|||||||
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
|
||||||
for (var entry in directory.listSync()) {
|
var fileList = <String>[];
|
||||||
|
await for (var entry in directory.list()) {
|
||||||
if (entry is Directory) {
|
if (entry is Directory) {
|
||||||
hasChapters = true;
|
hasChapters = true;
|
||||||
chapters.add(entry.name);
|
chapters.add(entry.name);
|
||||||
@@ -275,20 +200,24 @@ class ImportComic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (entry is File) {
|
} else if (entry is File) {
|
||||||
if (entry.name.startsWith('cover')) {
|
|
||||||
coverPath = entry.name;
|
|
||||||
}
|
|
||||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'jpe'];
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'jpe'];
|
||||||
if (!coverPath.startsWith('cover') &&
|
if (imageExtensions.contains(entry.extension)) {
|
||||||
imageExtensions.contains(entry.extension)) {
|
fileList.add(entry.name);
|
||||||
coverPath = entry.name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(fileList.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileList.sort();
|
||||||
|
coverPath = fileList.firstWhereOrNull((l) => l.startsWith('cover')) ?? fileList.first;
|
||||||
|
|
||||||
chapters.sort();
|
chapters.sort();
|
||||||
if (hasChapters && coverPath == '') {
|
if (hasChapters && coverPath == '') {
|
||||||
// use the first image in the first chapter as the cover
|
// use the first image in the first chapter as the cover
|
||||||
var firstChapter = Directory('${directory.path}/${chapters.first}');
|
var firstChapter = openDirectoryPlatform('${directory.path}/${chapters.first}');
|
||||||
await for (var entry in firstChapter.list()) {
|
await for (var entry in firstChapter.list()) {
|
||||||
if (entry is File) {
|
if (entry is File) {
|
||||||
coverPath = entry.name;
|
coverPath = entry.name;
|
||||||
@@ -301,25 +230,26 @@ class ImportComic {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return LocalComic(
|
return LocalComic(
|
||||||
id: '0',
|
id: id ?? '0',
|
||||||
title: name,
|
title: name,
|
||||||
subtitle: '',
|
subtitle: subtitle ?? '',
|
||||||
tags: [],
|
tags: tags ?? [],
|
||||||
directory: directory.name,
|
directory: directory.path,
|
||||||
chapters: hasChapters ? Map.fromIterables(chapters, chapters) : null,
|
chapters: hasChapters ? Map.fromIterables(chapters, chapters) : null,
|
||||||
cover: coverPath,
|
cover: coverPath,
|
||||||
comicType: ComicType.local,
|
comicType: ComicType.local,
|
||||||
downloadedChapters: chapters,
|
downloadedChapters: chapters,
|
||||||
createdAt: DateTime.now(),
|
createdAt: createTime ?? DateTime.now(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> _copyDirectories(Map<String, dynamic> data) async {
|
static Future<Map<String, String>> _copyDirectories(Map<String, dynamic> data) async {
|
||||||
var toBeCopied = data['toBeCopied'] as List<String>;
|
var toBeCopied = data['toBeCopied'] as List<String>;
|
||||||
var destination = data['destination'] as String;
|
var destination = data['destination'] as String;
|
||||||
|
Map<String, String> result = {};
|
||||||
for (var dir in toBeCopied) {
|
for (var dir in toBeCopied) {
|
||||||
var source = Directory(dir);
|
var source = openDirectoryPlatform(dir);
|
||||||
var dest = Directory("$destination/${source.name}");
|
var dest = openDirectoryPlatform("$destination/${source.name}");
|
||||||
if (dest.existsSync()) {
|
if (dest.existsSync()) {
|
||||||
// The destination directory already exists, and it is not managed by the app.
|
// The destination directory already exists, and it is not managed by the app.
|
||||||
// Rename the old directory to avoid conflicts.
|
// Rename the old directory to avoid conflicts.
|
||||||
@@ -330,6 +260,95 @@ class ImportComic {
|
|||||||
}
|
}
|
||||||
dest.createSync();
|
dest.createSync();
|
||||||
await copyDirectory(source, dest);
|
await copyDirectory(source, dest);
|
||||||
|
result[source.path] = dest.path;
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String?, List<LocalComic>>> _copyComicsToLocalDir(
|
||||||
|
Map<String?, List<LocalComic>> comics) async {
|
||||||
|
var destPath = LocalManager().path;
|
||||||
|
Map<String?, List<LocalComic>> result = {};
|
||||||
|
for (var favoriteFolder in comics.keys) {
|
||||||
|
result[favoriteFolder] = comics[favoriteFolder]!
|
||||||
|
.where((c) => c.directory.startsWith(destPath))
|
||||||
|
.toList();
|
||||||
|
comics[favoriteFolder]!
|
||||||
|
.removeWhere((c) => c.directory.startsWith(destPath));
|
||||||
|
|
||||||
|
if (comics[favoriteFolder]!.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// copy the comics to the local directory
|
||||||
|
var pathMap = await compute<Map<String, dynamic>, Map<String, String>>(
|
||||||
|
_copyDirectories, {
|
||||||
|
'toBeCopied': comics[favoriteFolder]!.map((e) => e.directory).toList(),
|
||||||
|
'destination': destPath,
|
||||||
|
});
|
||||||
|
//Construct a new object since LocalComic.directory is a final String
|
||||||
|
for (var c in comics[favoriteFolder]!) {
|
||||||
|
result[favoriteFolder]!.add(
|
||||||
|
LocalComic(
|
||||||
|
id: c.id,
|
||||||
|
title: c.title,
|
||||||
|
subtitle: c.subtitle,
|
||||||
|
tags: c.tags,
|
||||||
|
directory: pathMap[c.directory]!,
|
||||||
|
chapters: c.chapters,
|
||||||
|
cover: c.cover,
|
||||||
|
comicType: c.comicType,
|
||||||
|
downloadedChapters: c.downloadedChapters,
|
||||||
|
createdAt: c.createdAt
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
App.rootContext.showMessage(message: "Failed to copy comics".tl);
|
||||||
|
Log.error("Import Comic", e.toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> registerComics(Map<String?, List<LocalComic>> importedComics, bool copy) async {
|
||||||
|
try {
|
||||||
|
if (copy) {
|
||||||
|
importedComics = await _copyComicsToLocalDir(importedComics);
|
||||||
|
}
|
||||||
|
int importedCount = 0;
|
||||||
|
for (var folder in importedComics.keys) {
|
||||||
|
for (var comic in importedComics[folder]!) {
|
||||||
|
var id = LocalManager().findValidId(ComicType.local);
|
||||||
|
LocalManager().add(comic, id);
|
||||||
|
importedCount++;
|
||||||
|
if (folder != null) {
|
||||||
|
LocalFavoritesManager().addComic(
|
||||||
|
folder,
|
||||||
|
FavoriteItem(
|
||||||
|
id: id,
|
||||||
|
name: comic.title,
|
||||||
|
coverPath: comic.cover,
|
||||||
|
author: comic.subtitle,
|
||||||
|
type: comic.comicType,
|
||||||
|
tags: comic.tags,
|
||||||
|
favoriteTime: comic.createdAt
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
App.rootContext.showMessage(
|
||||||
|
message: "Imported @a comics".tlParams({
|
||||||
|
'a': importedCount,
|
||||||
|
}));
|
||||||
|
} catch(e) {
|
||||||
|
App.rootContext.showMessage(message: "Failed to register comics".tl);
|
||||||
|
Log.error("Import Comic", e.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import 'dart:isolate';
|
|||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||||
|
import 'package:flutter_saf/flutter_saf.dart';
|
||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
@@ -80,7 +81,7 @@ extension DirectoryExtension on Directory {
|
|||||||
int total = 0;
|
int total = 0;
|
||||||
for (var f in listSync(recursive: true)) {
|
for (var f in listSync(recursive: true)) {
|
||||||
if (FileSystemEntity.typeSync(f.path) == FileSystemEntityType.file) {
|
if (FileSystemEntity.typeSync(f.path) == FileSystemEntityType.file) {
|
||||||
total += await File(f.path).length();
|
total += await openFilePlatform(f.path).length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
@@ -92,7 +93,7 @@ extension DirectoryExtension on Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File joinFile(String name) {
|
File joinFile(String name) {
|
||||||
return File(FilePath.join(path, name));
|
return openFilePlatform(FilePath.join(path, name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ Future<void> copyDirectory(Directory source, Directory destination) async {
|
|||||||
if (content is File) {
|
if (content is File) {
|
||||||
content.copySync(newPath);
|
content.copySync(newPath);
|
||||||
} else if (content is Directory) {
|
} else if (content is Directory) {
|
||||||
Directory newDirectory = Directory(newPath);
|
Directory newDirectory = openDirectoryPlatform(newPath);
|
||||||
newDirectory.createSync();
|
newDirectory.createSync();
|
||||||
copyDirectory(content.absolute, newDirectory.absolute);
|
copyDirectory(content.absolute, newDirectory.absolute);
|
||||||
}
|
}
|
||||||
@@ -146,11 +147,11 @@ Future<void> copyDirectoryIsolate(
|
|||||||
|
|
||||||
String findValidDirectoryName(String path, String directory) {
|
String findValidDirectoryName(String path, String directory) {
|
||||||
var name = sanitizeFileName(directory);
|
var name = sanitizeFileName(directory);
|
||||||
var dir = Directory("$path/$name");
|
var dir = openDirectoryPlatform("$path/$name");
|
||||||
var i = 1;
|
var i = 1;
|
||||||
while (dir.existsSync() && dir.listSync().isNotEmpty) {
|
while (dir.existsSync() && dir.listSync().isNotEmpty) {
|
||||||
name = sanitizeFileName("$directory($i)");
|
name = sanitizeFileName("$directory($i)");
|
||||||
dir = Directory("$path/$name");
|
dir = openDirectoryPlatform("$path/$name");
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
@@ -180,14 +181,14 @@ class DirectoryPicker {
|
|||||||
if (App.isWindows || App.isLinux) {
|
if (App.isWindows || App.isLinux) {
|
||||||
directory = await file_selector.getDirectoryPath();
|
directory = await file_selector.getDirectoryPath();
|
||||||
} else if (App.isAndroid) {
|
} else if (App.isAndroid) {
|
||||||
directory = await _methodChannel.invokeMethod<String?>("getDirectoryPath");
|
directory = (await AndroidDirectory.pickDirectory())?.path;
|
||||||
} else {
|
} else {
|
||||||
// ios, macos
|
// ios, macos
|
||||||
directory = await _methodChannel.invokeMethod<String?>("getDirectoryPath");
|
directory = await _methodChannel.invokeMethod<String?>("getDirectoryPath");
|
||||||
}
|
}
|
||||||
if (directory == null) return null;
|
if (directory == null) return null;
|
||||||
_finalizer.attach(this, directory);
|
_finalizer.attach(this, directory);
|
||||||
return Directory(directory);
|
return openDirectoryPlatform(directory);
|
||||||
} finally {
|
} finally {
|
||||||
Future.delayed(const Duration(milliseconds: 100), () {
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
IO._isSelectingFiles = false;
|
IO._isSelectingFiles = false;
|
||||||
@@ -310,6 +311,30 @@ Future<void> saveFile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Directory openDirectoryPlatform(String path) {
|
||||||
|
if(App.isAndroid) {
|
||||||
|
var dir = AndroidDirectory.fromPathSync(path);
|
||||||
|
if(dir == null) {
|
||||||
|
return Directory(path);
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
} else {
|
||||||
|
return Directory(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File openFilePlatform(String path) {
|
||||||
|
if(App.isAndroid) {
|
||||||
|
var f = AndroidFile.fromPathSync(path);
|
||||||
|
if(f == null) {
|
||||||
|
return File(path);
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
} else {
|
||||||
|
return File(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Share {
|
class Share {
|
||||||
static void shareFile({
|
static void shareFile({
|
||||||
required Uint8List data,
|
required Uint8List data,
|
||||||
|
@@ -389,6 +389,15 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.1"
|
version: "2.5.1"
|
||||||
|
flutter_saf:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: HEAD
|
||||||
|
resolved-ref: "51a27e2ca0e05becfb8ee3a506294dc4460721a8"
|
||||||
|
url: "https://github.com/pkuislm/flutter_saf.git"
|
||||||
|
source: git
|
||||||
|
version: "0.0.1"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@@ -65,6 +65,10 @@ dependencies:
|
|||||||
ref: 285f87f15bccd2d5d5ff443761348c6ee47b98d1
|
ref: 285f87f15bccd2d5d5ff443761348c6ee47b98d1
|
||||||
battery_plus: ^6.2.0
|
battery_plus: ^6.2.0
|
||||||
local_auth: ^2.3.0
|
local_auth: ^2.3.0
|
||||||
|
flutter_saf:
|
||||||
|
git:
|
||||||
|
url: https://github.com/pkuislm/flutter_saf.git
|
||||||
|
ref: 829a566b738a26ea98e523807f49838e21308543
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user