mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
Feat/saf (#81)
* [Android] Use SAF to change local path * Use IOOverrides to replace openDirectoryPlatform and openFilePlatform * fix io
This commit is contained in:
@@ -25,7 +25,6 @@ import 'package:venera/network/cloudflare.dart';
|
|||||||
import 'package:venera/pages/comic_page.dart';
|
import 'package:venera/pages/comic_page.dart';
|
||||||
import 'package:venera/pages/favorites/favorites_page.dart';
|
import 'package:venera/pages/favorites/favorites_page.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
import 'package:venera/utils/io.dart';
|
|
||||||
import 'package:venera/utils/tags_translation.dart';
|
import 'package:venera/utils/tags_translation.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async' show Future, StreamController, scheduleMicrotask;
|
import 'dart:async' show Future, StreamController, scheduleMicrotask;
|
||||||
import 'dart:collection';
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ui' as ui show Codec;
|
import 'dart:ui' as ui show Codec;
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
@@ -108,7 +107,7 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final _cache = LinkedHashMap<String, Uint8List>();
|
static final _cache = <String, Uint8List>{};
|
||||||
|
|
||||||
static var _cacheSize = 0;
|
static var _cacheSize = 0;
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async' show Future, StreamController;
|
import 'dart:async' show Future, StreamController;
|
||||||
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';
|
||||||
@@ -25,7 +24,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 = openFilePlatform(url.substring(7));
|
var file = File(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,7 +71,7 @@ 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 => openFilePlatform(FilePath.join(
|
File get coverFile => File(FilePath.join(
|
||||||
baseDir,
|
baseDir,
|
||||||
cover,
|
cover,
|
||||||
));
|
));
|
||||||
@@ -151,6 +151,8 @@ class LocalManager with ChangeNotifier {
|
|||||||
/// path to the directory where all the comics are stored
|
/// path to the directory where all the comics are stored
|
||||||
late String path;
|
late String path;
|
||||||
|
|
||||||
|
Directory get directory => Directory(path);
|
||||||
|
|
||||||
// return error message if failed
|
// return error message if failed
|
||||||
Future<String?> setNewPath(String newPath) async {
|
Future<String?> setNewPath(String newPath) async {
|
||||||
var newDir = Directory(newPath);
|
var newDir = Directory(newPath);
|
||||||
@@ -162,7 +164,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await copyDirectoryIsolate(
|
await copyDirectoryIsolate(
|
||||||
Directory(path),
|
directory,
|
||||||
newDir,
|
newDir,
|
||||||
);
|
);
|
||||||
await File(FilePath.join(App.dataPath, 'local_path')).writeAsString(newPath);
|
await File(FilePath.join(App.dataPath, 'local_path')).writeAsString(newPath);
|
||||||
@@ -170,7 +172,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
Log.error("IO", e, s);
|
Log.error("IO", e, s);
|
||||||
return e.toString();
|
return e.toString();
|
||||||
}
|
}
|
||||||
await Directory(path).deleteIgnoreError(recursive:true);
|
await directory.deleteContents(recursive: true);
|
||||||
path = newPath;
|
path = newPath;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -217,15 +219,15 @@ class LocalManager with ChangeNotifier {
|
|||||||
''');
|
''');
|
||||||
if (File(FilePath.join(App.dataPath, 'local_path')).existsSync()) {
|
if (File(FilePath.join(App.dataPath, 'local_path')).existsSync()) {
|
||||||
path = File(FilePath.join(App.dataPath, 'local_path')).readAsStringSync();
|
path = File(FilePath.join(App.dataPath, 'local_path')).readAsStringSync();
|
||||||
if (!Directory(path).existsSync()) {
|
if (!directory.existsSync()) {
|
||||||
path = await findDefaultPath();
|
path = await findDefaultPath();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
path = await findDefaultPath();
|
path = await findDefaultPath();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!Directory(path).existsSync()) {
|
if (!directory.existsSync()) {
|
||||||
await Directory(path).create();
|
await directory.create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e, s) {
|
catch(e, s) {
|
||||||
@@ -354,12 +356,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 = openDirectoryPlatform(comic.baseDir);
|
var directory = Directory(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 = openDirectoryPlatform(FilePath.join(directory.path, cid));
|
directory = Directory(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()) {
|
||||||
@@ -406,10 +408,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 openDirectoryPlatform(FilePath.join(path, comic.directory));
|
return Directory(FilePath.join(path, comic.directory));
|
||||||
}
|
}
|
||||||
var dir = findValidDirectoryName(path, name);
|
var dir = findValidDirectoryName(path, name);
|
||||||
return openDirectoryPlatform(FilePath.join(path, dir)).create().then((value) => value);
|
return Directory(FilePath.join(path, dir)).create().then((value) => value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void completeTask(DownloadTask task) {
|
void completeTask(DownloadTask task) {
|
||||||
@@ -468,7 +470,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 = openDirectoryPlatform(FilePath.join(path, c.directory));
|
var dir = Directory(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.
|
||||||
|
@@ -20,40 +20,42 @@ void main(List<String> args) {
|
|||||||
if (runWebViewTitleBarWidget(args)) {
|
if (runWebViewTitleBarWidget(args)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
runZonedGuarded(() async {
|
overrideIO(() {
|
||||||
await Rhttp.init();
|
runZonedGuarded(() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
await Rhttp.init();
|
||||||
await init();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
if (App.isAndroid) {
|
await init();
|
||||||
handleLinks();
|
if (App.isAndroid) {
|
||||||
}
|
handleLinks();
|
||||||
FlutterError.onError = (details) {
|
}
|
||||||
Log.error(
|
FlutterError.onError = (details) {
|
||||||
"Unhandled Exception", "${details.exception}\n${details.stack}");
|
Log.error(
|
||||||
};
|
"Unhandled Exception", "${details.exception}\n${details.stack}");
|
||||||
runApp(const MyApp());
|
};
|
||||||
if (App.isDesktop) {
|
runApp(const MyApp());
|
||||||
await windowManager.ensureInitialized();
|
if (App.isDesktop) {
|
||||||
windowManager.waitUntilReadyToShow().then((_) async {
|
await windowManager.ensureInitialized();
|
||||||
await windowManager.setTitleBarStyle(
|
windowManager.waitUntilReadyToShow().then((_) async {
|
||||||
TitleBarStyle.hidden,
|
await windowManager.setTitleBarStyle(
|
||||||
windowButtonVisibility: App.isMacOS,
|
TitleBarStyle.hidden,
|
||||||
);
|
windowButtonVisibility: App.isMacOS,
|
||||||
if (App.isLinux) {
|
);
|
||||||
await windowManager.setBackgroundColor(Colors.transparent);
|
if (App.isLinux) {
|
||||||
}
|
await windowManager.setBackgroundColor(Colors.transparent);
|
||||||
await windowManager.setMinimumSize(const Size(500, 600));
|
}
|
||||||
if (!App.isLinux) {
|
await windowManager.setMinimumSize(const Size(500, 600));
|
||||||
// https://github.com/leanflutter/window_manager/issues/460
|
if (!App.isLinux) {
|
||||||
var placement = await WindowPlacement.loadFromFile();
|
// https://github.com/leanflutter/window_manager/issues/460
|
||||||
await placement.applyToWindow();
|
var placement = await WindowPlacement.loadFromFile();
|
||||||
await windowManager.show();
|
await placement.applyToWindow();
|
||||||
WindowPlacement.loop();
|
await windowManager.show();
|
||||||
}
|
WindowPlacement.loop();
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
}, (error, stack) {
|
}
|
||||||
Log.error("Unhandled Exception", "$error\n$stack");
|
}, (error, stack) {
|
||||||
|
Log.error("Unhandled Exception", "$error\n$stack");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -235,20 +235,21 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
var dir = await LocalManager().findValidDirectory(
|
try {
|
||||||
comicId,
|
var dir = await LocalManager().findValidDirectory(
|
||||||
comicType,
|
comicId,
|
||||||
comic!.title,
|
comicType,
|
||||||
);
|
comic!.title,
|
||||||
if (!(await dir.exists())) {
|
);
|
||||||
try {
|
if (!(await dir.exists())) {
|
||||||
await dir.create();
|
await dir.create();
|
||||||
} catch (e) {
|
|
||||||
_setError("Error: $e");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
path = dir.path;
|
||||||
|
} catch (e, s) {
|
||||||
|
Log.error("Download", e.toString(), s);
|
||||||
|
_setError("Error: $e");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
path = dir.path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await LocalManager().saveCurrentDownloadingTasks();
|
await LocalManager().saveCurrentDownloadingTasks();
|
||||||
@@ -266,11 +267,13 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
throw "Failed to download cover";
|
throw "Failed to download cover";
|
||||||
}
|
}
|
||||||
var fileType = detectFileType(data);
|
var fileType = detectFileType(data);
|
||||||
var file = File(FilePath.join(path!, "cover${fileType.ext}"));
|
var file =
|
||||||
|
File(FilePath.join(path!, "cover${fileType.ext}"));
|
||||||
file.writeAsBytesSync(data);
|
file.writeAsBytesSync(data);
|
||||||
return "file://${file.path}";
|
return "file://${file.path}";
|
||||||
});
|
});
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
|
Log.error("Download", res.errorMessage!);
|
||||||
_setError("Error: ${res.errorMessage}");
|
_setError("Error: ${res.errorMessage}");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -294,6 +297,7 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
|
Log.error("Download", res.errorMessage!);
|
||||||
_setError("Error: ${res.errorMessage}");
|
_setError("Error: ${res.errorMessage}");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -323,6 +327,7 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
|
Log.error("Download", res.errorMessage!);
|
||||||
_setError("Error: ${res.errorMessage}");
|
_setError("Error: ${res.errorMessage}");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -347,6 +352,7 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (task.error != null) {
|
if (task.error != null) {
|
||||||
|
Log.error("Download", task.error.toString());
|
||||||
_setError("Error: ${task.error}");
|
_setError("Error: ${task.error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -375,7 +381,6 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
_message = message;
|
_message = message;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
stopRecorder();
|
stopRecorder();
|
||||||
Log.error("Download", message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -448,7 +453,8 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
directory: Directory(path!).name,
|
directory: Directory(path!).name,
|
||||||
chapters: comic!.chapters,
|
chapters: comic!.chapters,
|
||||||
cover: File(_cover!.split("file://").last).uri.pathSegments.last,
|
cover:
|
||||||
|
File(_cover!.split("file://").last).name,
|
||||||
comicType: ComicType(source.key.hashCode),
|
comicType: ComicType(source.key.hashCode),
|
||||||
downloadedChapters: chapters ?? [],
|
downloadedChapters: chapters ?? [],
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
@@ -721,13 +727,12 @@ class ArchiveDownloadTask extends DownloadTask {
|
|||||||
_currentBytes = status.downloadedBytes;
|
_currentBytes = status.downloadedBytes;
|
||||||
_expectedBytes = status.totalBytes;
|
_expectedBytes = status.totalBytes;
|
||||||
_message =
|
_message =
|
||||||
"${bytesToReadableString(_currentBytes)}/${bytesToReadableString(_expectedBytes)}";
|
"${bytesToReadableString(_currentBytes)}/${bytesToReadableString(_expectedBytes)}";
|
||||||
_speed = status.bytesPerSecond;
|
_speed = status.bytesPerSecond;
|
||||||
isDownloaded = status.isFinished;
|
isDownloaded = status.isFinished;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
} catch (e) {
|
||||||
catch(e) {
|
|
||||||
_setError("Error: $e");
|
_setError("Error: $e");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
|
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
|
||||||
import 'package:venera/components/components.dart';
|
import 'package:venera/components/components.dart';
|
||||||
@@ -11,9 +10,7 @@ import 'package:venera/foundation/comic_source/comic_source.dart';
|
|||||||
import 'package:venera/foundation/comic_type.dart';
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
import 'package:venera/foundation/consts.dart';
|
import 'package:venera/foundation/consts.dart';
|
||||||
import 'package:venera/foundation/favorites.dart';
|
import 'package:venera/foundation/favorites.dart';
|
||||||
import 'package:venera/foundation/local.dart';
|
|
||||||
import 'package:venera/foundation/res.dart';
|
import 'package:venera/foundation/res.dart';
|
||||||
import 'package:venera/network/download.dart';
|
|
||||||
import 'package:venera/pages/comic_page.dart';
|
import 'package:venera/pages/comic_page.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';
|
||||||
|
@@ -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(openFilePlatform(imageKey.replaceFirst("file://", '')));
|
return FileImage(File(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(openFilePlatform(imageKey.replaceFirst("file://", '')));
|
image = FileImage(File(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 openFilePlatform(imageKey.substring(7)).readAsBytes();
|
return await File(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}"))!
|
||||||
|
@@ -34,25 +34,8 @@ class _AppSettingsState extends State<AppSettings> {
|
|||||||
callback: () async {
|
callback: () async {
|
||||||
String? result;
|
String? result;
|
||||||
if (App.isAndroid) {
|
if (App.isAndroid) {
|
||||||
var channel = const MethodChannel("venera/storage");
|
var picker = DirectoryPicker();
|
||||||
var permission = await channel.invokeMethod('');
|
result = (await picker.pickDirectory())?.path;
|
||||||
if (permission != true) {
|
|
||||||
context.showMessage(message: "Permission denied".tl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var path = await selectDirectory();
|
|
||||||
if (path != null) {
|
|
||||||
// check if the path is writable
|
|
||||||
var testFile = File(FilePath.join(path, "test"));
|
|
||||||
try {
|
|
||||||
await testFile.writeAsBytes([1]);
|
|
||||||
await testFile.delete();
|
|
||||||
} catch (e) {
|
|
||||||
context.showMessage(message: "Permission denied".tl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
result = path;
|
|
||||||
}
|
|
||||||
} else if (App.isIOS) {
|
} else if (App.isIOS) {
|
||||||
result = await selectDirectoryIOS();
|
result = await selectDirectoryIOS();
|
||||||
} else {
|
} else {
|
||||||
|
@@ -187,7 +187,7 @@ abstract class CBZ {
|
|||||||
}
|
}
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (var image in allImages) {
|
for (var image in allImages) {
|
||||||
var src = openFilePlatform(image);
|
var src = File(image);
|
||||||
var width = allImages.length.toString().length;
|
var width = allImages.length.toString().length;
|
||||||
var dstName =
|
var dstName =
|
||||||
'${i.toString().padLeft(width, '0')}.${image.split('.').last}';
|
'${i.toString().padLeft(width, '0')}.${image.split('.').last}';
|
||||||
|
@@ -60,7 +60,7 @@ class ImportComic {
|
|||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return imported;
|
return imported;
|
||||||
}
|
}
|
||||||
var comicDir = openDirectoryPlatform(
|
var comicDir = Directory(
|
||||||
FilePath.join(comicSrc.path, comic['DIRNAME'] as String));
|
FilePath.join(comicSrc.path, comic['DIRNAME'] as String));
|
||||||
String titleJP =
|
String titleJP =
|
||||||
comic['TITLE_JPN'] == null ? "" : comic['TITLE_JPN'] as String;
|
comic['TITLE_JPN'] == null ? "" : comic['TITLE_JPN'] as String;
|
||||||
@@ -217,7 +217,7 @@ class ImportComic {
|
|||||||
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 = openDirectoryPlatform('${directory.path}/${chapters.first}');
|
var firstChapter = Directory('${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;
|
||||||
@@ -248,8 +248,8 @@ class ImportComic {
|
|||||||
var destination = data['destination'] as String;
|
var destination = data['destination'] as String;
|
||||||
Map<String, String> result = {};
|
Map<String, String> result = {};
|
||||||
for (var dir in toBeCopied) {
|
for (var dir in toBeCopied) {
|
||||||
var source = openDirectoryPlatform(dir);
|
var source = Directory(dir);
|
||||||
var dest = openDirectoryPlatform("$destination/${source.name}");
|
var dest = Directory("$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.
|
||||||
|
@@ -81,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 openFilePlatform(f.path).length();
|
total += await File(f.path).length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
@@ -93,7 +93,21 @@ extension DirectoryExtension on Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File joinFile(String name) {
|
File joinFile(String name) {
|
||||||
return openFilePlatform(FilePath.join(path, name));
|
return File(FilePath.join(path, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteContentsSync({recursive = true}) {
|
||||||
|
if (!existsSync()) return;
|
||||||
|
for (var f in listSync()) {
|
||||||
|
f.deleteIfExistsSync(recursive: recursive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteContents({recursive = true}) async {
|
||||||
|
if (!existsSync()) return;
|
||||||
|
for (var f in listSync()) {
|
||||||
|
await f.deleteIfExists(recursive: recursive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,14 +138,15 @@ String sanitizeFileName(String fileName) {
|
|||||||
Future<void> copyDirectory(Directory source, Directory destination) async {
|
Future<void> copyDirectory(Directory source, Directory destination) async {
|
||||||
List<FileSystemEntity> contents = source.listSync();
|
List<FileSystemEntity> contents = source.listSync();
|
||||||
for (FileSystemEntity content in contents) {
|
for (FileSystemEntity content in contents) {
|
||||||
String newPath = destination.path +
|
String newPath = FilePath.join(destination.path, content.name);
|
||||||
Platform.pathSeparator +
|
|
||||||
content.path.split(Platform.pathSeparator).last;
|
|
||||||
|
|
||||||
if (content is File) {
|
if (content is File) {
|
||||||
content.copySync(newPath);
|
var resultFile = File(newPath);
|
||||||
|
resultFile.createSync();
|
||||||
|
var data = content.readAsBytesSync();
|
||||||
|
resultFile.writeAsBytesSync(data);
|
||||||
} else if (content is Directory) {
|
} else if (content is Directory) {
|
||||||
Directory newDirectory = openDirectoryPlatform(newPath);
|
Directory newDirectory = Directory(newPath);
|
||||||
newDirectory.createSync();
|
newDirectory.createSync();
|
||||||
copyDirectory(content.absolute, newDirectory.absolute);
|
copyDirectory(content.absolute, newDirectory.absolute);
|
||||||
}
|
}
|
||||||
@@ -140,18 +155,18 @@ Future<void> copyDirectory(Directory source, Directory destination) async {
|
|||||||
|
|
||||||
Future<void> copyDirectoryIsolate(
|
Future<void> copyDirectoryIsolate(
|
||||||
Directory source, Directory destination) async {
|
Directory source, Directory destination) async {
|
||||||
await Isolate.run(() {
|
await Isolate.run(() async {
|
||||||
copyDirectory(source, destination);
|
await copyDirectory(source, destination);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String findValidDirectoryName(String path, String directory) {
|
String findValidDirectoryName(String path, String directory) {
|
||||||
var name = sanitizeFileName(directory);
|
var name = sanitizeFileName(directory);
|
||||||
var dir = openDirectoryPlatform("$path/$name");
|
var dir = Directory("$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 = openDirectoryPlatform("$path/$name");
|
dir = Directory("$path/$name");
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
@@ -184,11 +199,12 @@ class DirectoryPicker {
|
|||||||
directory = (await AndroidDirectory.pickDirectory())?.path;
|
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 openDirectoryPlatform(directory);
|
return Directory(directory);
|
||||||
} finally {
|
} finally {
|
||||||
Future.delayed(const Duration(milliseconds: 100), () {
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
IO._isSelectingFiles = false;
|
IO._isSelectingFiles = false;
|
||||||
@@ -311,31 +327,42 @@ Future<void> saveFile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory openDirectoryPlatform(String path) {
|
class _IOOverrides extends IOOverrides {
|
||||||
if(App.isAndroid) {
|
@override
|
||||||
var dir = AndroidDirectory.fromPathSync(path);
|
Directory createDirectory(String path) {
|
||||||
if(dir == null) {
|
if (App.isAndroid) {
|
||||||
return Directory(path);
|
var dir = AndroidDirectory.fromPathSync(path);
|
||||||
|
if (dir == null) {
|
||||||
|
return super.createDirectory(path);
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
} else {
|
||||||
|
return super.createDirectory(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
File createFile(String path) {
|
||||||
|
if (path.startsWith("file://")) {
|
||||||
|
path = path.substring(7);
|
||||||
|
}
|
||||||
|
if (App.isAndroid) {
|
||||||
|
var f = AndroidFile.fromPathSync(path);
|
||||||
|
if (f == null) {
|
||||||
|
return super.createFile(path);
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
} else {
|
||||||
|
return super.createFile(path);
|
||||||
}
|
}
|
||||||
return dir;
|
|
||||||
} else {
|
|
||||||
return Directory(path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File openFilePlatform(String path) {
|
void overrideIO(void Function() f) {
|
||||||
if(path.startsWith("file://")) {
|
IOOverrides.runWithIOOverrides(
|
||||||
path = path.substring(7);
|
f,
|
||||||
}
|
_IOOverrides(),
|
||||||
if(App.isAndroid) {
|
);
|
||||||
var f = AndroidFile.fromPathSync(path);
|
|
||||||
if(f == null) {
|
|
||||||
return File(path);
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
} else {
|
|
||||||
return File(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Share {
|
class Share {
|
||||||
@@ -396,4 +423,4 @@ class FileSelectResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String get name => File(path).name;
|
String get name => File(path).name;
|
||||||
}
|
}
|
||||||
|
@@ -393,8 +393,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "829a566b738a26ea98e523807f49838e21308543"
|
ref: dd5242918da0ea9a0a50b0f87ade7a2def65453d
|
||||||
resolved-ref: "829a566b738a26ea98e523807f49838e21308543"
|
resolved-ref: dd5242918da0ea9a0a50b0f87ade7a2def65453d
|
||||||
url: "https://github.com/pkuislm/flutter_saf.git"
|
url: "https://github.com/pkuislm/flutter_saf.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
Reference in New Issue
Block a user