docs: added new feature graphics

fix: updated folder handling on ios (#155)
This commit is contained in:
Daniel
2026-06-16 10:19:37 +02:00
parent 9b6219ceca
commit 10791d33aa
26 changed files with 452 additions and 320 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 118 KiB

+10 -133
View File
@@ -1,46 +1,4 @@
PODS:
- AEXML (4.6.0)
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- EpubViewerKit (0.1.3):
- AEXML (= 4.6.0)
- FontBlaster (= 5.1.1)
- MenuItemKit (= 4.0.1)
- RealmSwift (= 5.5.1)
- SSZipArchive (= 2.2.3)
- ZFDragableModalTransition (= 0.6)
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview_ios (0.0.1):
- Flutter
@@ -51,123 +9,42 @@ PODS:
- OrderedSet (~> 6.0.3)
- fluttertoast (0.0.2):
- Flutter
- FontBlaster (5.1.1)
- image_picker_ios (0.0.1):
- Flutter
- MenuItemKit (4.0.1)
- open_file_ios (0.0.1):
- isar_community_flutter_libs (1.0.0):
- Flutter
- OrderedSet (6.0.3)
- package_info_plus (0.4.5):
- screen_brightness_ios (0.1.0):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- Realm (5.5.1):
- Realm/Headers (= 5.5.1)
- Realm/Headers (5.5.1)
- RealmSwift (5.5.1):
- Realm (= 5.5.1)
- SDWebImage (5.21.5):
- SDWebImage/Core (= 5.21.5)
- SDWebImage/Core (5.21.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- SSZipArchive (2.2.3)
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1):
- Flutter
- vocsy_epub_viewer (2.0.0):
- EpubViewerKit (~> 0.1.3)
- Flutter
- ZFDragableModalTransition (0.6)
DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- open_file_ios (from `.symlinks/plugins/open_file_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- vocsy_epub_viewer (from `.symlinks/plugins/vocsy_epub_viewer/ios`)
- isar_community_flutter_libs (from `.symlinks/plugins/isar_community_flutter_libs/ios`)
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
SPEC REPOS:
trunk:
- AEXML
- DKImagePickerController
- DKPhotoGallery
- EpubViewerKit
- FontBlaster
- MenuItemKit
- OrderedSet
- Realm
- RealmSwift
- SDWebImage
- SSZipArchive
- SwiftyGif
- ZFDragableModalTransition
EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
open_file_ios:
:path: ".symlinks/plugins/open_file_ios/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
vocsy_epub_viewer:
:path: ".symlinks/plugins/vocsy_epub_viewer/ios"
isar_community_flutter_libs:
:path: ".symlinks/plugins/isar_community_flutter_libs/ios"
screen_brightness_ios:
:path: ".symlinks/plugins/screen_brightness_ios/ios"
SPEC CHECKSUMS:
AEXML: 2fbb73d8724797ed2cde888731e176fa4d44dd83
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
EpubViewerKit: 5f6c8ff9161bba18d6742c59b3c575c19f0623a9
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
FontBlaster: 8897ebdbb4b6ace3ab78c92b665d88c954f19e4a
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
MenuItemKit: 1459920e984d823e1bcc858b7a964f94e6bfa0f6
open_file_ios: 5ff7526df64e4394b4fe207636b67a95e83078bb
isar_community_flutter_libs: bede843185a61a05ff364a05c9b23209523f7e0d
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
Realm: c2ffe0667f9c98c9ba65f608ba85684d39826145
RealmSwift: 5a35c1c35c3e1925e6fcd092c881d5cc4a826bff
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
vocsy_epub_viewer: f6b70ce4776ac1871caee8e6bc7a108d847cad79
ZFDragableModalTransition: 0d294eaaba6edfcb9839595de765f9ca06a4b524
screen_brightness_ios: 28c5fbdb40634de44f86025d84470158ad4df48c
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
+22
View File
@@ -16,6 +16,7 @@
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
FCBD8B4FECDED30E88FAD71B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F1D761D07B3CD346428E2F /* Pods_RunnerTests.framework */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -65,6 +66,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -80,6 +82,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
955B95705D44B98F16C785A6 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -98,6 +101,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -188,6 +192,9 @@
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
@@ -213,6 +220,9 @@
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
@@ -724,6 +734,18 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,59 @@
{
"pins" : [
{
"identity" : "dkcamera",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKCamera",
"state" : {
"branch" : "master",
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
}
},
{
"identity" : "dkimagepickercontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKImagePickerController",
"state" : {
"branch" : "4.3.9",
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
}
},
{
"identity" : "dkphotogallery",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
"state" : {
"branch" : "master",
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
}
},
{
"identity" : "sdwebimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImage",
"state" : {
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
"version" : "5.21.7"
}
},
{
"identity" : "swiftygif",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kirualex/SwiftyGif.git",
"state" : {
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
"version" : "5.4.5"
}
},
{
"identity" : "tocropviewcontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/TimOliver/TOCropViewController",
"state" : {
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
"version" : "2.8.0"
}
}
],
"version" : 2
}
@@ -5,6 +5,24 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -0,0 +1,59 @@
{
"pins" : [
{
"identity" : "dkcamera",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKCamera",
"state" : {
"branch" : "master",
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
}
},
{
"identity" : "dkimagepickercontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKImagePickerController",
"state" : {
"branch" : "4.3.9",
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
}
},
{
"identity" : "dkphotogallery",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
"state" : {
"branch" : "master",
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
}
},
{
"identity" : "sdwebimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImage",
"state" : {
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
"version" : "5.21.7"
}
},
{
"identity" : "swiftygif",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kirualex/SwiftyGif.git",
"state" : {
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
"version" : "5.4.5"
}
},
{
"identity" : "tocropviewcontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/TimOliver/TOCropViewController",
"state" : {
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
"version" : "2.8.0"
}
}
],
"version" : 2
}
@@ -282,15 +282,26 @@ class BookDetailsBloc extends Bloc<BookDetailsEvent, BookDetailsState> {
final schema = event.schema;
final filePath = await repository.downloadBook(
state.bookDetails!,
event.directory,
schema,
format: event.format,
progressCallback: (progress) {
emit(state.copyWith(downloadProgress: progress));
},
);
final String filePath;
if (event.directory == null) {
filePath = await repository.downloadBookToDevice(
state.bookDetails!,
format: event.format,
progressCallback: (progress) {
emit(state.copyWith(downloadProgress: progress));
},
);
} else {
filePath = await repository.downloadBook(
state.bookDetails!,
event.directory!,
schema,
format: event.format,
progressCallback: (progress) {
emit(state.copyWith(downloadProgress: progress));
},
);
}
if (state.bookViewModel != null) {
await downloadManager.registerDownload(
@@ -67,7 +67,7 @@ class DownloadBook extends BookDetailsEvent {
final String author;
final String series;
final int seriesIndex;
final DocumentFile directory;
final DocumentFile? directory;
final DownloadSchema schema;
const DownloadBook({
@@ -77,7 +77,7 @@ class DownloadBook extends BookDetailsEvent {
required this.author,
required this.series,
required this.seriesIndex,
required this.directory,
this.directory,
required this.schema,
});
@@ -112,13 +112,10 @@ class SendBookByEmail extends BookDetailsEvent {
}
class OpenBookInReader extends BookDetailsEvent {
final DocumentFile selectedDirectory;
final DocumentFile? selectedDirectory;
final DownloadSchema schema;
const OpenBookInReader({
required this.selectedDirectory,
required this.schema,
});
const OpenBookInReader({this.selectedDirectory, required this.schema});
@override
List<Object?> get props => [selectedDirectory, schema];
@@ -8,6 +8,7 @@ import 'package:http/http.dart' as http;
import 'package:logger/logger.dart';
import 'package:open_file/open_file.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -573,7 +574,7 @@ class BookDetailsRemoteDatasource {
Future<bool> openInReader(
BookDetailsModel book,
DocumentFile selectedDirectory,
DocumentFile? selectedDirectory,
DownloadSchema schema, {
Function(int)? progressCallback,
}) async {
@@ -585,29 +586,39 @@ class BookDetailsRemoteDatasource {
format = book.formats.first.toLowerCase();
}
final filePath = await downloadBookToPath(
book: book,
selectedDirectory: selectedDirectory,
schema: schema,
format: format,
progressCallback: progressCallback,
);
String localPath;
if (selectedDirectory == null) {
localPath = await downloadBookToDevice(
book,
format: format,
progressCallback: progressCallback,
);
} else {
final filePath = await downloadBookToPath(
book: book,
selectedDirectory: selectedDirectory,
schema: schema,
format: format,
progressCallback: progressCallback,
);
DocumentFile? file =
filePath.isNotEmpty ? await DocumentFile.fromUri(filePath) : null;
final file =
filePath.isNotEmpty ? await DocumentFile.fromUri(filePath) : null;
if (file == null || !file.isFile) {
logger.e('Downloaded file is not a valid file: $filePath');
return false;
if (file == null || !file.isFile) {
logger.e('Downloaded file is not a valid file: $filePath');
return false;
}
final cachedFile = await file.cache();
if (cachedFile == null) {
logger.e('Could not cache file for opening');
return false;
}
localPath = cachedFile.path;
}
final cachedFile = await file.cache();
if (cachedFile == null) {
logger.e('Could not cache file for opening');
return false;
}
final result = await OpenFile.open(cachedFile.path);
final result = await OpenFile.open(localPath);
if (result.type != ResultType.done) {
logger.e('Error while opening the file: ${result.message}');
@@ -729,6 +740,29 @@ class BookDetailsRemoteDatasource {
return Uint8List.fromList(bytes);
}
Future<String> downloadBookToDevice(
BookDetailsModel book, {
String format = 'epub',
Function(int)? progressCallback,
}) async {
logger.i('Downloading "${book.title}" to app sandbox, format: $format');
final bytes = await streamBookBytes(
book,
format: format,
progressCallback: progressCallback,
);
final dir = await getApplicationDocumentsDirectory();
final safeTitle =
book.title.replaceAll(RegExp(r'[\\/:*?"<>|.]'), '').trim();
final fileName = '${safeTitle.isEmpty ? 'book' : safeTitle}.$format';
final file = File('${dir.path}/$fileName');
await file.writeAsBytes(bytes, flush: true);
logger.i('Saved book to ${file.path}');
return file.path;
}
Future<Uint8List?> readLocalEpubBytes(String path) async {
try {
String name;
@@ -52,7 +52,7 @@ class BookDetailsRepository {
Future<bool> openInReader(
BookDetailsModel book,
DocumentFile selectedDirectory,
DocumentFile? selectedDirectory,
DownloadSchema schema, {
Function(int)? progressCallback,
}) async {
@@ -190,6 +190,16 @@ class BookDetailsRepository {
progressCallback: progressCallback,
);
Future<String> downloadBookToDevice(
BookDetailsModel book, {
String format = 'epub',
Function(int)? progressCallback,
}) => datasource.downloadBookToDevice(
book,
format: format,
progressCallback: progressCallback,
);
Future<Uint8List?> readLocalEpubBytes(String path) =>
datasource.readLocalEpubBytes(path);
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:convert';
@@ -1108,33 +1109,35 @@ class _BookDetailsPageState extends State<BookDetailsPage> {
: () async {
DocumentFile? selectedDirectory;
if (settingsState.defaultDownloadPath.isEmpty) {
selectedDirectory = await DocMan.pick.directory();
if (selectedDirectory == null) {
// ignore: use_build_context_synchronously
Navigator.pop(context);
if (Platform.isAndroid) {
if (settingsState.defaultDownloadPath.isEmpty) {
selectedDirectory = await DocMan.pick.directory();
if (selectedDirectory == null) {
// ignore: use_build_context_synchronously
Navigator.pop(context);
// ignore: use_build_context_synchronously
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
return;
}
} else {
final uri = settingsState.defaultDownloadPath;
selectedDirectory =
uri.isNotEmpty
? await DocumentFile.fromUri(uri)
: null;
if (selectedDirectory == null ||
!selectedDirectory.isDirectory) {
// ignore: use_build_context_synchronously
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
return;
// ignore: use_build_context_synchronously
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
return;
}
} else {
final uri = settingsState.defaultDownloadPath;
selectedDirectory =
uri.isNotEmpty
? await DocumentFile.fromUri(uri)
: null;
if (selectedDirectory == null ||
!selectedDirectory.isDirectory) {
// ignore: use_build_context_synchronously
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
return;
}
}
}
@@ -1,6 +1,9 @@
import 'dart:io';
import 'package:docman/docman.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:open_file/open_file.dart';
import 'package:calibre_web_companion/features/book_details/bloc/book_details_bloc.dart';
import 'package:calibre_web_companion/features/book_details/bloc/book_details_event.dart';
@@ -126,40 +129,61 @@ class DownloadToDeviceWidget extends StatelessWidget {
) async {
final settingsState = context.read<SettingsBloc>().state;
DocumentFile? selectedDirectory;
if (settingsState.defaultDownloadPath.isEmpty) {
_showDownloadStatusSheet(
// ignore: use_build_context_synchronously
context,
localizations,
DownloadState.selectingDestination,
null,
0,
() {
if (Platform.isAndroid) {
if (settingsState.defaultDownloadPath.isEmpty) {
_showDownloadStatusSheet(
// ignore: use_build_context_synchronously
context,
localizations,
DownloadState.selectingDestination,
null,
0,
() {
Navigator.pop(context);
},
);
selectedDirectory = await DocMan.pick.directory();
if (selectedDirectory == null) {
// ignore: use_build_context_synchronously
Navigator.pop(context);
},
);
selectedDirectory = await DocMan.pick.directory();
if (selectedDirectory == null) {
// ignore: use_build_context_synchronously
Navigator.pop(context);
// ignore: use_build_context_synchronously
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
return;
}
} else {
final uri = settingsState.defaultDownloadPath;
selectedDirectory =
uri.isNotEmpty ? await DocumentFile.fromUri(uri) : null;
if (selectedDirectory == null || !selectedDirectory.isDirectory) {
// ignore: use_build_context_synchronously
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
return;
}
// ignore: use_build_context_synchronously
context.showSnackBar(localizations.noFolderWasSelected, isError: true);
return;
_showDownloadStatusSheet(
// ignore: use_build_context_synchronously
context,
localizations,
DownloadState.downloading,
null,
0,
() {
context.read<BookDetailsBloc>().add(CancelDownload());
Navigator.pop(context);
},
);
}
} else {
final uri = settingsState.defaultDownloadPath;
selectedDirectory =
uri.isNotEmpty ? await DocumentFile.fromUri(uri) : null;
if (selectedDirectory == null || !selectedDirectory.isDirectory) {
// ignore: use_build_context_synchronously
context.showSnackBar(localizations.noFolderWasSelected, isError: true);
return;
}
_showDownloadStatusSheet(
// ignore: use_build_context_synchronously
context,
localizations,
DownloadState.downloading,
@@ -214,8 +238,11 @@ class DownloadToDeviceWidget extends StatelessWidget {
current.downloadErrorMessage;
},
listener: (context, state) {
if (state.downloadState == DownloadState.success ||
state.downloadState == DownloadState.failed) {}
if (state.downloadState == DownloadState.success &&
!Platform.isAndroid &&
state.downloadFilePath != null) {
OpenFile.open(state.downloadFilePath!);
}
},
buildWhen: (previous, current) {
return previous.downloadState != current.downloadState ||
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:docman/docman.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -84,42 +86,44 @@ class SendToEreaderWidget extends StatelessWidget {
AppLocalizations localizations,
SettingsState settingsState,
) async {
if (settingsState.storeReadNowAndSendToEReaderOnDevice &&
settingsState.defaultDownloadPath.isEmpty) {
if (context.mounted) {
context.showSnackBar(
localizations.pleaseSetDefaultDownloadFolderFirst,
isError: true,
);
}
return;
}
DocumentFile? selectedDirectory;
if (settingsState.defaultDownloadPath.isEmpty) {
selectedDirectory = await DocMan.pick.directory();
if (selectedDirectory == null) {
if (Platform.isAndroid) {
if (settingsState.storeReadNowAndSendToEReaderOnDevice &&
settingsState.defaultDownloadPath.isEmpty) {
if (context.mounted) {
context.showSnackBar(
localizations.noFolderWasSelected,
localizations.pleaseSetDefaultDownloadFolderFirst,
isError: true,
);
}
return;
}
} else {
final uri = settingsState.defaultDownloadPath;
selectedDirectory =
uri.isNotEmpty ? await DocumentFile.fromUri(uri) : null;
if (selectedDirectory == null || !selectedDirectory.isDirectory) {
if (context.mounted) {
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
if (settingsState.defaultDownloadPath.isEmpty) {
selectedDirectory = await DocMan.pick.directory();
if (selectedDirectory == null) {
if (context.mounted) {
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
}
return;
}
} else {
final uri = settingsState.defaultDownloadPath;
selectedDirectory =
uri.isNotEmpty ? await DocumentFile.fromUri(uri) : null;
if (selectedDirectory == null || !selectedDirectory.isDirectory) {
if (context.mounted) {
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
}
return;
}
return;
}
}
@@ -472,7 +476,13 @@ class SendToEreaderWidget extends StatelessWidget {
final settingsState = context.read<SettingsBloc>().state;
DocumentFile? selectedDirectory;
if (settingsState.storeReadNowAndSendToEReaderOnDevice) {
// "Store on device first" needs a SAF folder, which iOS doesn't have — there
// the bytes are streamed straight to the upload instead.
final storeOnDevice =
Platform.isAndroid &&
settingsState.storeReadNowAndSendToEReaderOnDevice;
if (storeOnDevice) {
final uri = settingsState.defaultDownloadPath;
if (uri.isEmpty) {
context.showSnackBar(
@@ -507,8 +517,7 @@ class SendToEreaderWidget extends StatelessWidget {
isKindle: isKindle,
title: book.title,
send2ereaderUrl: context.read<SettingsBloc>().state.send2ereaderUrl,
downloadToDeviceFirst:
settingsState.storeReadNowAndSendToEReaderOnDevice,
downloadToDeviceFirst: storeOnDevice,
selectedDirectory: selectedDirectory,
schema: settingsState.downloadSchema,
),
@@ -846,81 +846,85 @@ class _SettingsPageState extends State<SettingsPage> {
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.download_for_offline_rounded,
size: 24,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
localizations.storeReadNowAndSendToEReaderOnDevice,
),
SizedBox(height: 4),
Text(
localizations
.storeReadNowAndSendToEReaderOnDeviceDescription,
style: TextStyle(fontSize: 12),
),
],
if (Platform.isAndroid) ...[
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.download_for_offline_rounded,
size: 24,
color: Theme.of(context).colorScheme.primary,
),
),
Switch(
value: state.storeReadNowAndSendToEReaderOnDevice,
onChanged: (value) async {
if (!value) {
context.read<SettingsBloc>().add(
const SetStoreReadNowAndSendToEReaderOnDevice(false),
);
return;
}
if (state.defaultDownloadPath.isEmpty) {
String? selectedPath;
if (Platform.isAndroid) {
final selectedDirectory =
await DocMan.pick.directory();
selectedPath = selectedDirectory?.uri;
} else {
selectedPath = await FilePicker.getDirectoryPath();
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
localizations.storeReadNowAndSendToEReaderOnDevice,
),
SizedBox(height: 4),
Text(
localizations
.storeReadNowAndSendToEReaderOnDeviceDescription,
style: TextStyle(fontSize: 12),
),
],
),
),
Switch(
value: state.storeReadNowAndSendToEReaderOnDevice,
onChanged: (value) async {
if (!value) {
context.read<SettingsBloc>().add(
const SetStoreReadNowAndSendToEReaderOnDevice(
false,
),
);
return;
}
if (selectedPath == null) {
if (state.defaultDownloadPath.isEmpty) {
String? selectedPath;
if (Platform.isAndroid) {
final selectedDirectory =
await DocMan.pick.directory();
selectedPath = selectedDirectory?.uri;
} else {
selectedPath = await FilePicker.getDirectoryPath();
}
if (selectedPath == null) {
if (context.mounted) {
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
);
}
return;
}
if (context.mounted) {
context.showSnackBar(
localizations.noFolderWasSelected,
isError: true,
context.read<SettingsBloc>().add(
SetDownloadFolder(selectedPath),
);
}
return;
}
if (context.mounted) {
context.read<SettingsBloc>().add(
SetDownloadFolder(selectedPath),
const SetStoreReadNowAndSendToEReaderOnDevice(true),
);
}
}
if (context.mounted) {
context.read<SettingsBloc>().add(
const SetStoreReadNowAndSendToEReaderOnDevice(true),
);
}
},
activeThumbColor: Theme.of(context).colorScheme.primary,
),
],
),
},
activeThumbColor: Theme.of(context).colorScheme.primary,
),
],
),
],
],
],
),
@@ -21,8 +21,10 @@ class DownloadOptionsWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: [
_buildSelectingDownloadFolder(context),
_buildSelectingDownloadSchema(context),
if (Platform.isAndroid) ...[
_buildSelectingDownloadFolder(context),
_buildSelectingDownloadSchema(context),
],
],
);
}