Feat/saf (#81)

* [Android] Use SAF to change local path

* Use IOOverrides to replace openDirectoryPlatform and openFilePlatform

* fix io
This commit is contained in:
nyne
2024-11-29 21:33:28 +08:00
parent 06094fc5fc
commit 72507d907a
14 changed files with 147 additions and 134 deletions

View File

@@ -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';

View File

@@ -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;

View File

@@ -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)) {

View File

@@ -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.

View File

@@ -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");
});
}); });
} }

View File

@@ -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;
} }

View File

@@ -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';

View File

@@ -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,

View File

@@ -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}"))!

View File

@@ -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 {

View File

@@ -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}';

View File

@@ -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.

View File

@@ -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;
} }

View File

@@ -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"