diff --git a/README.md b/README.md
index aa68139a..39b26b39 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,8 @@ The following steps require to open your `ios` project in Xcode.
1. Enable background mode.
-
+
2. Add `sqlite` library.
@@ -110,7 +111,7 @@ private func registerPlugins(registry: FlutterPluginRegistry) {
- **Support HTTP request:** if you want to download file with HTTP request, you
need to disable Apple Transport Security (ATS) feature. There're two options:
-1. Disable ATS for a specific domain only: (add following codes to your
+1. Disable ATS for a specific domain only: (add the following code to your
`Info.plist` file)
```xml
@@ -134,7 +135,7 @@ private func registerPlugins(registry: FlutterPluginRegistry) {
```
-2. Completely disable ATS: (add following codes to your `Info.plist` file)
+2. Completely disable ATS. Add the following to your `Info.plist` file)
```xml
NSAppTransportSecurity
@@ -146,7 +147,7 @@ private func registerPlugins(registry: FlutterPluginRegistry) {
- **Configure maximum number of concurrent tasks:** the plugin allows 3 download
tasks running at a moment by default (if you enqueue more than 3 tasks,
there're only 3 tasks running, other tasks are put in pending state). You can
- change this number by adding following codes to your `Info.plist` file.
+ change this number by adding the following code to your `Info.plist` file.
```xml
@@ -172,12 +173,14 @@ private func registerPlugins(registry: FlutterPluginRegistry) {
## Android integration
-There's no additional configurations required on Android.
+You don't have to do anything extra to make the plugin work on Android.
-In order to handle click action on notification to open the downloaded file on
-Android, you need to add some additional configurations.
+There are although a few optional settings you might want to configure.
-Add the following to `AndroidManifest.xml`:
+### Open downloaded file from notification
+
+To make tapping on notification open the downloaded file on Android, add the
+following code to `AndroidManifest.xml`:
```xml
```
-**Note:**
+**Notes**
- You have to save your downloaded files in external storage (where the other
applications have permission to read your files)
-- The downloaded files are only able to be opened if your device has at least an
- application that can read these file types (mp3, pdf, etc)
+- The downloaded files are only able to be opened if your device has at least
+ one application that can read these file types (mp3, pdf, etc.)
-### Optional configuration:
+### Configure maximum number of concurrent download tasks
-- **Configure maximum number of concurrent tasks:** the plugin depends on
- `WorkManager` library and `WorkManager` depends on the number of available
- processor to configure the maximum number of tasks running at a moment. You
- can setup a fixed number for this configuration by adding following codes to
- your `AndroidManifest.xml`:
+The plugin depends on `WorkManager` library and `WorkManager` depends on the
+number of available processor to configure the maximum number of tasks running
+at a moment. You can setup a fixed number for this configuration by adding the
+following code to your `AndroidManifest.xml`:
```xml
@@ -233,9 +235,10 @@ Add the following to `AndroidManifest.xml`:
```
-- **Localize notification messages:** you can localize notification messages of
- download progress by localizing following messages. (you can find the detail
- of string localization in Android in this [link][4])
+### Localize strings in notifications
+
+You can localize texts in download progress notifications by localizing
+following messages.
```xml
Download started
@@ -246,9 +249,13 @@ Add the following to `AndroidManifest.xml`:
Download paused
```
-- **PackageInstaller:** in order to open APK files, your application needs
- `REQUEST_INSTALL_PACKAGES` permission. Add the following code in your
- `AndroidManifest.xml`:
+You can learn more about localization on Android [here][4]).
+
+### Install .apk files
+
+To open and install `.apk` files, your application needs
+`REQUEST_INSTALL_PACKAGES` permission. Add the following in your
+`AndroidManifest.xml`:
```xml
@@ -437,12 +444,16 @@ plugin is missing some feature.
Pull request are also very welcome!
-[fluttercommunity_badge]: https://fluttercommunity.dev/_github/header/flutter_downloader
+[fluttercommunity_badge]:
+ https://fluttercommunity.dev/_github/header/flutter_downloader
[fluttercommunity_link]: https://github.com/fluttercommunity/community
[pub_badge]: https://img.shields.io/pub/v/flutter_downloader.svg
[pub_link]: https://pub.dartlang.org/packages/flutter_downloader
-[work_manager]: https://developer.android.com/topic/libraries/architecture/workmanager
-[url_session_download_task]: https://developer.apple.com/documentation/foundation/nsurlsessiondownloadtask?language=objc
-[android_9_cleartext_traffic]: https://medium.com/@son.rommer/fix-cleartext-traffic-error-in-android-9-pie-2f4e9e2235e6
+[work_manager]:
+ https://developer.android.com/topic/libraries/architecture/workmanager
+[url_session_download_task]:
+ https://developer.apple.com/documentation/foundation/nsurlsessiondownloadtask?language=objc
+[android_9_cleartext_traffic]:
+ https://medium.com/@son.rommer/fix-cleartext-traffic-error-in-android-9-pie-2f4e9e2235e6
[3]: https://medium.com/@guerrix/info-plist-localization-ad5daaea732a
[4]: https://developer.android.com/training/basics/supporting-devices/languages
diff --git a/android/src/main/java/vn/hunghd/flutterdownloader/DownloadWorker.java b/android/src/main/java/vn/hunghd/flutterdownloader/DownloadWorker.java
index 350337dd..69e08064 100644
--- a/android/src/main/java/vn/hunghd/flutterdownloader/DownloadWorker.java
+++ b/android/src/main/java/vn/hunghd/flutterdownloader/DownloadWorker.java
@@ -90,7 +90,7 @@ public class DownloadWorker extends Worker implements MethodChannel.MethodCallHa
private static final int STEP_UPDATE = 5;
private static final AtomicBoolean isolateStarted = new AtomicBoolean(false);
- private static final ArrayDeque isolateQueue = new ArrayDeque<>();
+ private static final ArrayDeque> isolateQueue = new ArrayDeque<>();
private static FlutterEngine backgroundFlutterEngine;
private final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)");
@@ -114,12 +114,7 @@ public DownloadWorker(@NonNull final Context context,
@NonNull WorkerParameters params) {
super(context, params);
- new Handler(context.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- startBackgroundIsolate(context);
- }
- });
+ new Handler(context.getMainLooper()).post(() -> startBackgroundIsolate(context));
}
private void startBackgroundIsolate(Context context) {
@@ -150,7 +145,7 @@ private void startBackgroundIsolate(Context context) {
}
@Override
- public void onMethodCall(MethodCall call, MethodChannel.Result result) {
+ public void onMethodCall(MethodCall call, @NonNull MethodChannel.Result result) {
if (call.method.equals("didInitializeDispatcher")) {
synchronized (isolateStarted) {
while (!isolateQueue.isEmpty()) {
@@ -819,12 +814,7 @@ public interface CallbackUri {
void invoke(Uri uri);
}
- final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
-
- public boolean verify(String hostname, SSLSession session) {
- return true;
- }
- };
+ final static HostnameVerifier DO_NOT_VERIFY = (hostname, session) -> true;
/**
* Trust every server - dont check for any certificate
@@ -838,11 +828,11 @@ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
- public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
Log.i(TAG, "checkClientTrusted");
}
- public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
Log.i(TAG, "checkServerTrusted");
}
} };
diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml
new file mode 100644
index 00000000..d7fff949
--- /dev/null
+++ b/example/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: package:leancode_lint/analysis_options.yaml
+
+linter:
+ rules:
+ avoid_print: false
diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
deleted file mode 100644
index f880684a..00000000
--- a/example/android/app/src/debug/AndroidManifest.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index aee61d32..8e34c548 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,10 +1,5 @@
-
+ xmlns:tools="http://schemas.android.com/tools" package="vn.hunghd.flutter_downloader_example">
diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
deleted file mode 100644
index f880684a..00000000
--- a/example/android/app/src/profile/AndroidManifest.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
diff --git a/example/example.iml b/example/example.iml
deleted file mode 100644
index e5c83719..00000000
--- a/example/example.iml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/example/flutter_downloader_example.iml b/example/flutter_downloader_example.iml
deleted file mode 100644
index c92516a5..00000000
--- a/example/flutter_downloader_example.iml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/example/flutter_downloader_example_android.iml b/example/flutter_downloader_example_android.iml
deleted file mode 100644
index b050030a..00000000
--- a/example/flutter_downloader_example_android.iml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 8466c036..c010a10f 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -10,8 +10,6 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
42905ABFD5C2732268868D9F /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5796D507EECFA2AC0239D9A9 /* libPods-Runner.a */; };
- 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
- 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -187,10 +185,9 @@
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
- English,
en,
Base,
);
@@ -210,9 +207,7 @@
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
- 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
- 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
@@ -328,6 +323,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -337,14 +333,17 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -369,7 +368,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -381,6 +380,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -390,14 +390,17 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -416,7 +419,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -439,10 +442,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@@ -469,10 +469,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a69542ed..50a8cfc9 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
-
-
-
-
+
+
-
-
onActionTap!(task),
+ constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
+ icon: const Icon(Icons.file_download),
+ );
+ } else if (task.status == DownloadTaskStatus.running) {
+ return Row(
+ children: [
+ Text('${task.progress}%'),
+ IconButton(
+ onPressed: () => onActionTap!(task),
+ constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
+ icon: const Icon(Icons.pause, color: Colors.red),
+ ),
+ ],
+ );
+ } else if (task.status == DownloadTaskStatus.paused) {
+ return Row(
+ children: [
+ Text('${task.progress}%'),
+ IconButton(
+ onPressed: () => onActionTap!(task),
+ constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
+ icon: const Icon(Icons.play_arrow, color: Colors.green),
+ ),
+ ],
+ );
+ } else if (task.status == DownloadTaskStatus.complete) {
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ const Text('Ready', style: TextStyle(color: Colors.green)),
+ IconButton(
+ onPressed: () => onActionTap!(task),
+ constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
+ icon: const Icon(Icons.delete),
+ )
+ ],
+ );
+ } else if (task.status == DownloadTaskStatus.canceled) {
+ return const Text('Canceled', style: TextStyle(color: Colors.red));
+ } else if (task.status == DownloadTaskStatus.failed) {
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ const Text('Failed', style: TextStyle(color: Colors.red)),
+ IconButton(
+ onPressed: () => onActionTap!(task),
+ constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
+ icon: const Icon(Icons.refresh, color: Colors.green),
+ )
+ ],
+ );
+ } else if (task.status == DownloadTaskStatus.enqueued) {
+ return const Text('Pending', style: TextStyle(color: Colors.orange));
+ } else {
+ return null;
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ onTap: data!.task!.status == DownloadTaskStatus.complete
+ ? () {
+ onTap!(data!.task);
+ }
+ : null,
+ child: Container(
+ padding: const EdgeInsets.only(left: 16, right: 8),
+ child: InkWell(
+ child: Stack(
+ children: [
+ SizedBox(
+ width: double.infinity,
+ height: 64,
+ child: Row(
+ children: [
+ Expanded(
+ child: Text(
+ data!.name!,
+ maxLines: 1,
+ softWrap: true,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.only(left: 8),
+ child: _buildTrailing(data!.task!),
+ ),
+ ],
+ ),
+ ),
+ if (data!.task!.status == DownloadTaskStatus.running ||
+ data!.task!.status == DownloadTaskStatus.paused)
+ Positioned(
+ left: 0,
+ right: 0,
+ bottom: 0,
+ child: LinearProgressIndicator(
+ value: data!.task!.progress! / 100,
+ ),
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart
new file mode 100644
index 00000000..8ba45034
--- /dev/null
+++ b/example/lib/home_page.dart
@@ -0,0 +1,393 @@
+import 'dart:io';
+import 'dart:isolate';
+import 'dart:ui';
+
+import 'package:android_path_provider/android_path_provider.dart';
+import 'package:device_info/device_info.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_downloader/flutter_downloader.dart';
+import 'package:flutter_downloader_example/data.dart';
+import 'package:flutter_downloader_example/download_list_item.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+class MyHomePage extends StatefulWidget with WidgetsBindingObserver {
+ const MyHomePage({super.key, required this.title, required this.platform});
+
+ final TargetPlatform? platform;
+
+ final String title;
+
+ @override
+ _MyHomePageState createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State {
+ List? _tasks;
+ late List _items;
+ late bool _isLoading;
+ late bool _permissionReady;
+ late String _localPath;
+ final ReceivePort _port = ReceivePort();
+
+ @override
+ void initState() {
+ super.initState();
+
+ _bindBackgroundIsolate();
+
+ FlutterDownloader.registerCallback(downloadCallback);
+
+ _isLoading = true;
+ _permissionReady = false;
+
+ _prepare();
+ }
+
+ @override
+ void dispose() {
+ _unbindBackgroundIsolate();
+ super.dispose();
+ }
+
+ void _bindBackgroundIsolate() {
+ final isSuccess = IsolateNameServer.registerPortWithName(
+ _port.sendPort,
+ 'downloader_send_port',
+ );
+ if (!isSuccess) {
+ _unbindBackgroundIsolate();
+ _bindBackgroundIsolate();
+ return;
+ }
+ _port.listen((dynamic data) {
+ final taskId = (data as List)[0] as String;
+ final status = data[1] as DownloadTaskStatus;
+ final progress = data[2] as int;
+
+ print(
+ 'Callback on UI isolate: '
+ 'task ($taskId) is in status ($status) and process ($progress)',
+ );
+
+ if (_tasks != null && _tasks!.isNotEmpty) {
+ final task = _tasks!.firstWhere((task) => task.taskId == taskId);
+ setState(() {
+ task
+ ..status = status
+ ..progress = progress;
+ });
+ }
+ });
+ }
+
+ void _unbindBackgroundIsolate() {
+ IsolateNameServer.removePortNameMapping('downloader_send_port');
+ }
+
+ static void downloadCallback(
+ String id,
+ DownloadTaskStatus status,
+ int progress,
+ ) {
+ print(
+ 'Callback on background isolate: '
+ 'task ($id) is in status ($status) and process ($progress)',
+ );
+
+ IsolateNameServer.lookupPortByName('downloader_send_port')
+ ?.send([id, status, progress]);
+ }
+
+ Widget _buildDownloadList() => ListView(
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ children: [
+ for (final item in _items)
+ item.task == null
+ ? _buildListSectionHeading(item.name!)
+ : DownloadListItem(
+ data: item,
+ onTap: (task) {
+ _openDownloadedFile(task).then((success) {
+ if (!success) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Cannot open this file'),
+ ),
+ );
+ }
+ });
+ },
+ onActionTap: (task) {
+ if (task.status == DownloadTaskStatus.undefined) {
+ _requestDownload(task);
+ } else if (task.status == DownloadTaskStatus.running) {
+ _pauseDownload(task);
+ } else if (task.status == DownloadTaskStatus.paused) {
+ _resumeDownload(task);
+ } else if (task.status == DownloadTaskStatus.complete) {
+ _delete(task);
+ } else if (task.status == DownloadTaskStatus.failed) {
+ _retryDownload(task);
+ }
+ },
+ ),
+ ],
+ );
+
+ Widget _buildListSectionHeading(String title) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ child: Text(
+ title,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ color: Colors.blue,
+ fontSize: 18,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildNoPermissionWarning() {
+ return Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 24),
+ child: Text(
+ 'Grant storage permission to continue',
+ textAlign: TextAlign.center,
+ style: TextStyle(color: Colors.blueGrey, fontSize: 18),
+ ),
+ ),
+ const SizedBox(height: 32),
+ TextButton(
+ onPressed: _retryRequestPermission,
+ child: const Text(
+ 'Retry',
+ style: TextStyle(
+ color: Colors.blue,
+ fontWeight: FontWeight.bold,
+ fontSize: 20,
+ ),
+ ),
+ )
+ ],
+ ),
+ );
+ }
+
+ Future _retryRequestPermission() async {
+ final hasGranted = await _checkPermission();
+
+ if (hasGranted) {
+ await _prepareSaveDir();
+ }
+
+ setState(() {
+ _permissionReady = hasGranted;
+ });
+ }
+
+ Future _requestDownload(TaskInfo task) async {
+ task.taskId = await FlutterDownloader.enqueue(
+ url: task.link!,
+ headers: {'auth': 'test_for_sql_encoding'},
+ savedDir: _localPath,
+ saveInPublicStorage: true,
+ );
+ }
+
+ // Not used in the example.
+ // void _cancelDownload(_TaskInfo task) async {
+ // await FlutterDownloader.cancel(taskId: task.taskId!);
+ // }
+
+ Future _pauseDownload(TaskInfo task) async {
+ await FlutterDownloader.pause(taskId: task.taskId!);
+ }
+
+ Future _resumeDownload(TaskInfo task) async {
+ final newTaskId = await FlutterDownloader.resume(taskId: task.taskId!);
+ task.taskId = newTaskId;
+ }
+
+ Future _retryDownload(TaskInfo task) async {
+ final newTaskId = await FlutterDownloader.retry(taskId: task.taskId!);
+ task.taskId = newTaskId;
+ }
+
+ Future _openDownloadedFile(TaskInfo? task) {
+ if (task != null) {
+ return FlutterDownloader.open(taskId: task.taskId!);
+ } else {
+ return Future.value(false);
+ }
+ }
+
+ Future _delete(TaskInfo task) async {
+ await FlutterDownloader.remove(
+ taskId: task.taskId!,
+ shouldDeleteContent: true,
+ );
+ await _prepare();
+ setState(() {});
+ }
+
+ Future _checkPermission() async {
+ if (Platform.isIOS) {
+ return true;
+ }
+
+ final deviceInfo = DeviceInfoPlugin();
+ final androidInfo = await deviceInfo.androidInfo;
+ if (widget.platform == TargetPlatform.android &&
+ androidInfo.version.sdkInt <= 28) {
+ final status = await Permission.storage.status;
+ if (status != PermissionStatus.granted) {
+ final result = await Permission.storage.request();
+ if (result == PermissionStatus.granted) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ return false;
+ }
+
+ Future _prepare() async {
+ final tasks = await FlutterDownloader.loadTasks();
+
+ if (tasks == null) {
+ print('No tasks were retrieved from the database.');
+ return;
+ }
+
+ var count = 0;
+ _tasks = [];
+ _items = [];
+
+ _tasks!.addAll(
+ DownloadItems.documents.map(
+ (document) => TaskInfo(name: document.name, link: document.url),
+ ),
+ );
+
+ _items.add(ItemHolder(name: 'Documents'));
+ for (var i = count; i < _tasks!.length; i++) {
+ _items.add(ItemHolder(name: _tasks![i].name, task: _tasks![i]));
+ count++;
+ }
+
+ _tasks!.addAll(
+ DownloadItems.images
+ .map((image) => TaskInfo(name: image.name, link: image.url)),
+ );
+
+ _items.add(ItemHolder(name: 'Images'));
+ for (var i = count; i < _tasks!.length; i++) {
+ _items.add(ItemHolder(name: _tasks![i].name, task: _tasks![i]));
+ count++;
+ }
+
+ _tasks!.addAll(
+ DownloadItems.videos
+ .map((video) => TaskInfo(name: video.name, link: video.url)),
+ );
+
+ _items.add(ItemHolder(name: 'Videos'));
+ for (var i = count; i < _tasks!.length; i++) {
+ _items.add(ItemHolder(name: _tasks![i].name, task: _tasks![i]));
+ count++;
+ }
+
+ for (final task in tasks) {
+ for (final info in _tasks!) {
+ if (info.link == task.url) {
+ info
+ ..taskId = task.taskId
+ ..status = task.status
+ ..progress = task.progress;
+ }
+ }
+ }
+
+ _permissionReady = await _checkPermission();
+
+ if (_permissionReady) {
+ await _prepareSaveDir();
+ }
+
+ setState(() {
+ _isLoading = false;
+ });
+ }
+
+ Future _prepareSaveDir() async {
+ _localPath = (await _findLocalPath())!;
+ final savedDir = Directory(_localPath);
+ final hasExisted = savedDir.existsSync();
+ if (!hasExisted) {
+ await savedDir.create();
+ }
+ }
+
+ Future _findLocalPath() async {
+ String? externalStorageDirPath;
+ if (Platform.isAndroid) {
+ try {
+ externalStorageDirPath = await AndroidPathProvider.downloadsPath;
+ } catch (e) {
+ final directory = await getExternalStorageDirectory();
+ externalStorageDirPath = directory?.path;
+ }
+ } else if (Platform.isIOS) {
+ externalStorageDirPath =
+ (await getApplicationDocumentsDirectory()).absolute.path;
+ }
+ return externalStorageDirPath;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.title),
+ ),
+ body: Builder(
+ builder: (context) {
+ if (_isLoading) {
+ return const Center(child: CircularProgressIndicator());
+ }
+
+ return _permissionReady
+ ? _buildDownloadList()
+ : _buildNoPermissionWarning();
+ },
+ ),
+ );
+ }
+}
+
+class ItemHolder {
+ ItemHolder({this.name, this.task});
+
+ final String? name;
+ final TaskInfo? task;
+}
+
+class TaskInfo {
+ TaskInfo({this.name, this.link});
+
+ final String? name;
+ final String? link;
+
+ String? taskId;
+ int? progress = 0;
+ DownloadTaskStatus? status = DownloadTaskStatus.undefined;
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 6b9504d9..73ab65a3 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,590 +1,30 @@
-import 'dart:async';
-import 'dart:io';
-import 'dart:isolate';
-import 'dart:ui';
-
-import 'package:android_path_provider/android_path_provider.dart';
-import 'package:device_info/device_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
-import 'package:path_provider/path_provider.dart';
-import 'package:permission_handler/permission_handler.dart';
-
-const debug = true;
+import 'package:flutter_downloader_example/home_page.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
- await FlutterDownloader.initialize(debug: debug, ignoreSsl: true);
+ await FlutterDownloader.initialize(debug: true, ignoreSsl: true);
- runApp(new MyApp());
+ runApp(const MyApp());
}
class MyApp extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- final platform = Theme.of(context).platform;
-
- return new MaterialApp(
- title: 'Flutter Demo',
- theme: new ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: new MyHomePage(
- title: 'Downloader',
- platform: platform,
- ),
- );
- }
-}
-
-class MyHomePage extends StatefulWidget with WidgetsBindingObserver {
- final TargetPlatform? platform;
+ const MyApp({super.key});
- MyHomePage({Key? key, this.title, this.platform}) : super(key: key);
-
- final String? title;
-
- @override
- _MyHomePageState createState() => new _MyHomePageState();
-}
-
-class _MyHomePageState extends State {
- final _documents = [
- {
- 'name': 'Learning Android Studio',
- 'link':
- 'http://barbra-coco.dyndns.org/student/learning_android_studio.pdf'
- },
- {
- 'name': 'Android Programming Cookbook',
- 'link':
- 'http://enos.itcollege.ee/~jpoial/allalaadimised/reading/Android-Programming-Cookbook.pdf'
- },
- {
- 'name': 'iOS Programming Guide',
- 'link':
- 'http://darwinlogic.com/uploads/education/iOS_Programming_Guide.pdf'
- },
- {
- 'name': 'Objective-C Programming (Pre-Course Workbook',
- 'link':
- 'https://www.bignerdranch.com/documents/objective-c-prereading-assignment.pdf'
- },
- ];
-
- final _images = [
- {
- 'name': 'Arches National Park',
- 'link':
- 'https://upload.wikimedia.org/wikipedia/commons/6/60/The_Organ_at_Arches_National_Park_Utah_Corrected.jpg'
- },
- {
- 'name': 'Canyonlands National Park',
- 'link':
- 'https://upload.wikimedia.org/wikipedia/commons/7/78/Canyonlands_National_Park%E2%80%A6Needles_area_%286294480744%29.jpg'
- },
- {
- 'name': 'Death Valley National Park',
- 'link':
- 'https://upload.wikimedia.org/wikipedia/commons/b/b2/Sand_Dunes_in_Death_Valley_National_Park.jpg'
- },
- {
- 'name': 'Gates of the Arctic National Park and Preserve',
- 'link':
- 'https://upload.wikimedia.org/wikipedia/commons/e/e4/GatesofArctic.jpg'
- }
- ];
-
- final _videos = [
- {
- 'name': 'Big Buck Bunny',
- 'link':
- 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
- },
- {
- 'name': 'Elephant Dream',
- 'link':
- 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4'
- }
- ];
-
- List<_TaskInfo>? _tasks;
- late List<_ItemHolder> _items;
- late bool _isLoading;
- late bool _permissionReady;
- late String _localPath;
- ReceivePort _port = ReceivePort();
-
- @override
- void initState() {
- super.initState();
-
- _bindBackgroundIsolate();
-
- FlutterDownloader.registerCallback(downloadCallback);
-
- _isLoading = true;
- _permissionReady = false;
-
- _prepare();
- }
-
- @override
- void dispose() {
- _unbindBackgroundIsolate();
- super.dispose();
- }
-
- void _bindBackgroundIsolate() {
- bool isSuccess = IsolateNameServer.registerPortWithName(
- _port.sendPort, 'downloader_send_port');
- if (!isSuccess) {
- _unbindBackgroundIsolate();
- _bindBackgroundIsolate();
- return;
- }
- _port.listen((dynamic data) {
- if (debug) {
- print('UI Isolate Callback: $data');
- }
- String? id = data[0];
- DownloadTaskStatus? status = data[1];
- int? progress = data[2];
-
- if (_tasks != null && _tasks!.isNotEmpty) {
- final task = _tasks!.firstWhere((task) => task.taskId == id);
- setState(() {
- task.status = status;
- task.progress = progress;
- });
- }
- });
- }
-
- void _unbindBackgroundIsolate() {
- IsolateNameServer.removePortNameMapping('downloader_send_port');
- }
-
- static void downloadCallback(
- String id, DownloadTaskStatus status, int progress) {
- if (debug) {
- print(
- 'Background Isolate Callback: task ($id) is in status ($status) and process ($progress)');
- }
- final SendPort send =
- IsolateNameServer.lookupPortByName('downloader_send_port')!;
- send.send([id, status, progress]);
- }
+ static const _title = 'flutter_downloader demo';
@override
Widget build(BuildContext context) {
- return new Scaffold(
- appBar: new AppBar(
- title: new Text(widget.title!),
- ),
- body: Builder(
- builder: (context) => _isLoading
- ? new Center(
- child: new CircularProgressIndicator(),
- )
- : _permissionReady
- ? _buildDownloadList()
- : _buildNoPermissionWarning()),
- );
- }
-
- Widget _buildDownloadList() => Container(
- child: ListView(
- padding: const EdgeInsets.symmetric(vertical: 16.0),
- children: _items
- .map((item) => item.task == null
- ? _buildListSection(item.name!)
- : DownloadItem(
- data: item,
- onItemClick: (task) {
- _openDownloadedFile(task).then((success) {
- if (!success) {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
- content: Text('Cannot open this file')));
- }
- });
- },
- onActionClick: (task) {
- if (task.status == DownloadTaskStatus.undefined) {
- _requestDownload(task);
- } else if (task.status == DownloadTaskStatus.running) {
- _pauseDownload(task);
- } else if (task.status == DownloadTaskStatus.paused) {
- _resumeDownload(task);
- } else if (task.status == DownloadTaskStatus.complete) {
- _delete(task);
- } else if (task.status == DownloadTaskStatus.failed) {
- _retryDownload(task);
- }
- },
- ))
- .toList(),
- ),
- );
-
- Widget _buildListSection(String title) => Container(
- padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
- child: Text(
- title,
- style: TextStyle(
- fontWeight: FontWeight.bold, color: Colors.blue, fontSize: 18.0),
- ),
- );
-
- Widget _buildNoPermissionWarning() => Container(
- child: Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 24.0),
- child: Text(
- 'Please grant accessing storage permission to continue -_-',
- textAlign: TextAlign.center,
- style: TextStyle(color: Colors.blueGrey, fontSize: 18.0),
- ),
- ),
- SizedBox(
- height: 32.0,
- ),
- TextButton(
- onPressed: () {
- _retryRequestPermission();
- },
- child: Text(
- 'Retry',
- style: TextStyle(
- color: Colors.blue,
- fontWeight: FontWeight.bold,
- fontSize: 20.0),
- ))
- ],
- ),
- ),
- );
-
- Future _retryRequestPermission() async {
- final hasGranted = await _checkPermission();
-
- if (hasGranted) {
- await _prepareSaveDir();
- }
-
- setState(() {
- _permissionReady = hasGranted;
- });
- }
-
- void _requestDownload(_TaskInfo task) async {
- task.taskId = await FlutterDownloader.enqueue(
- url: task.link!,
- headers: {"auth": "test_for_sql_encoding"},
- savedDir: _localPath,
- showNotification: true,
- openFileFromNotification: true,
- saveInPublicStorage: true,
- );
- }
-
- // Not used in the example.
- // void _cancelDownload(_TaskInfo task) async {
- // await FlutterDownloader.cancel(taskId: task.taskId!);
- // }
-
- void _pauseDownload(_TaskInfo task) async {
- await FlutterDownloader.pause(taskId: task.taskId!);
- }
-
- void _resumeDownload(_TaskInfo task) async {
- String? newTaskId = await FlutterDownloader.resume(taskId: task.taskId!);
- task.taskId = newTaskId;
- }
-
- void _retryDownload(_TaskInfo task) async {
- String? newTaskId = await FlutterDownloader.retry(taskId: task.taskId!);
- task.taskId = newTaskId;
- }
-
- Future _openDownloadedFile(_TaskInfo? task) {
- if (task != null) {
- return FlutterDownloader.open(taskId: task.taskId!);
- } else {
- return Future.value(false);
- }
- }
-
- void _delete(_TaskInfo task) async {
- await FlutterDownloader.remove(
- taskId: task.taskId!, shouldDeleteContent: true);
- await _prepare();
- setState(() {});
- }
-
- Future _checkPermission() async {
- if (Platform.isIOS) return true;
-
- DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
- AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
- if (widget.platform == TargetPlatform.android &&
- androidInfo.version.sdkInt <= 28) {
- final status = await Permission.storage.status;
- if (status != PermissionStatus.granted) {
- final result = await Permission.storage.request();
- if (result == PermissionStatus.granted) {
- return true;
- }
- } else {
- return true;
- }
- } else {
- return true;
- }
- return false;
- }
-
- Future _prepare() async {
- final tasks = await FlutterDownloader.loadTasks();
-
- int count = 0;
- _tasks = [];
- _items = [];
-
- _tasks!.addAll(_documents.map((document) =>
- _TaskInfo(name: document['name'], link: document['link'])));
-
- _items.add(_ItemHolder(name: 'Documents'));
- for (int i = count; i < _tasks!.length; i++) {
- _items.add(_ItemHolder(name: _tasks![i].name, task: _tasks![i]));
- count++;
- }
-
- _tasks!.addAll(_images
- .map((image) => _TaskInfo(name: image['name'], link: image['link'])));
-
- _items.add(_ItemHolder(name: 'Images'));
- for (int i = count; i < _tasks!.length; i++) {
- _items.add(_ItemHolder(name: _tasks![i].name, task: _tasks![i]));
- count++;
- }
-
- _tasks!.addAll(_videos
- .map((video) => _TaskInfo(name: video['name'], link: video['link'])));
-
- _items.add(_ItemHolder(name: 'Videos'));
- for (int i = count; i < _tasks!.length; i++) {
- _items.add(_ItemHolder(name: _tasks![i].name, task: _tasks![i]));
- count++;
- }
-
- tasks!.forEach((task) {
- for (_TaskInfo info in _tasks!) {
- if (info.link == task.url) {
- info.taskId = task.taskId;
- info.status = task.status;
- info.progress = task.progress;
- }
- }
- });
-
- _permissionReady = await _checkPermission();
-
- if (_permissionReady) {
- await _prepareSaveDir();
- }
-
- setState(() {
- _isLoading = false;
- });
- }
-
- Future _prepareSaveDir() async {
- _localPath = (await _findLocalPath())!;
- final savedDir = Directory(_localPath);
- bool hasExisted = await savedDir.exists();
- if (!hasExisted) {
- savedDir.create();
- }
- }
-
- Future _findLocalPath() async {
- var externalStorageDirPath;
- if (Platform.isAndroid) {
- try {
- externalStorageDirPath = await AndroidPathProvider.downloadsPath;
- } catch (e) {
- final directory = await getExternalStorageDirectory();
- externalStorageDirPath = directory?.path;
- }
- } else if (Platform.isIOS) {
- externalStorageDirPath =
- (await getApplicationDocumentsDirectory()).absolute.path;
- }
- return externalStorageDirPath;
- }
-}
-
-class DownloadItem extends StatelessWidget {
- final _ItemHolder? data;
- final Function(_TaskInfo?)? onItemClick;
- final Function(_TaskInfo)? onActionClick;
-
- DownloadItem({this.data, this.onItemClick, this.onActionClick});
+ final platform = Theme.of(context).platform;
- @override
- Widget build(BuildContext context) {
- return Container(
- padding: const EdgeInsets.only(left: 16.0, right: 8.0),
- child: InkWell(
- onTap: data!.task!.status == DownloadTaskStatus.complete
- ? () {
- onItemClick!(data!.task);
- }
- : null,
- child: Stack(
- children: [
- Container(
- width: double.infinity,
- height: 64.0,
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Expanded(
- child: Text(
- data!.name!,
- maxLines: 1,
- softWrap: true,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- Padding(
- padding: const EdgeInsets.only(left: 8.0),
- child: _buildActionForTask(data!.task!),
- ),
- ],
- ),
- ),
- data!.task!.status == DownloadTaskStatus.running ||
- data!.task!.status == DownloadTaskStatus.paused
- ? Positioned(
- left: 0.0,
- right: 0.0,
- bottom: 0.0,
- child: LinearProgressIndicator(
- value: data!.task!.progress! / 100,
- ),
- )
- : Container()
- ].toList(),
- ),
+ return MaterialApp(
+ title: _title,
+ theme: ThemeData(primarySwatch: Colors.blue),
+ home: MyHomePage(
+ title: _title,
+ platform: platform,
),
);
}
-
- Widget? _buildActionForTask(_TaskInfo task) {
- if (task.status == DownloadTaskStatus.undefined) {
- return RawMaterialButton(
- onPressed: () {
- onActionClick!(task);
- },
- child: Icon(Icons.file_download),
- shape: CircleBorder(),
- constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
- );
- } else if (task.status == DownloadTaskStatus.running) {
- return RawMaterialButton(
- onPressed: () {
- onActionClick!(task);
- },
- child: Icon(
- Icons.pause,
- color: Colors.red,
- ),
- shape: CircleBorder(),
- constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
- );
- } else if (task.status == DownloadTaskStatus.paused) {
- return RawMaterialButton(
- onPressed: () {
- onActionClick!(task);
- },
- child: Icon(
- Icons.play_arrow,
- color: Colors.green,
- ),
- shape: CircleBorder(),
- constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
- );
- } else if (task.status == DownloadTaskStatus.complete) {
- return Row(
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Text(
- 'Ready',
- style: TextStyle(color: Colors.green),
- ),
- RawMaterialButton(
- onPressed: () {
- onActionClick!(task);
- },
- child: Icon(
- Icons.delete_forever,
- color: Colors.red,
- ),
- shape: CircleBorder(),
- constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
- )
- ],
- );
- } else if (task.status == DownloadTaskStatus.canceled) {
- return Text('Canceled', style: TextStyle(color: Colors.red));
- } else if (task.status == DownloadTaskStatus.failed) {
- return Row(
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Text('Failed', style: TextStyle(color: Colors.red)),
- RawMaterialButton(
- onPressed: () {
- onActionClick!(task);
- },
- child: Icon(
- Icons.refresh,
- color: Colors.green,
- ),
- shape: CircleBorder(),
- constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
- )
- ],
- );
- } else if (task.status == DownloadTaskStatus.enqueued) {
- return Text('Pending', style: TextStyle(color: Colors.orange));
- } else {
- return null;
- }
- }
-}
-
-class _TaskInfo {
- final String? name;
- final String? link;
-
- String? taskId;
- int? progress = 0;
- DownloadTaskStatus? status = DownloadTaskStatus.undefined;
-
- _TaskInfo({this.name, this.link});
-}
-
-class _ItemHolder {
- final String? name;
- final _TaskInfo? task;
-
- _ItemHolder({this.name, this.task});
}
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 3f252e0c..c6d44174 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,76 +1,28 @@
name: flutter_downloader_example
description: Demonstrates how to use the flutter_downloader plugin.
-
-# The following defines the version and build number for your application.
-# A version number is three numbers separated by dots, like 1.2.43
-# followed by an optional build number separated by a +.
-# Both the version and the builder number may be overridden in flutter
-# build by specifying --build-name and --build-number, respectively.
-# Read more about versioning at semver.org.
version: 1.0.0+1
publish_to: none
environment:
- sdk: '>=2.12.0 <3.0.0'
- flutter: '>=1.20.0'
+ sdk: ">=2.17.0 <3.0.0"
+ flutter: ">=3.0.0 <4.0.0"
dependencies:
+ android_path_provider: ^0.3.0
+ cupertino_icons: ^1.0.4
+ device_info: ^2.0.3
flutter:
sdk: flutter
- path_provider: ^2.0.2
- permission_handler: ^8.0.0+2
flutter_downloader:
path: ../
-
- android_path_provider: ^0.3.0
- device_info: ^2.0.2
-
- # The following adds the Cupertino Icons font to your application.
- # Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^1.0.2
+ path_provider: ^2.0.10
+ permission_handler: ^9.2.0
dev_dependencies:
flutter_test:
sdk: flutter
-# For information on the generic Dart part of this file, see the
-# following page: https://www.dartlang.org/tools/pub/pubspec
+ leancode_lint: ^1.2.1
-# The following section is specific to Flutter.
flutter:
-
- # The following line ensures that the Material Icons font is
- # included with your application, so that you can use the icons in
- # the material Icons class.
uses-material-design: true
-
- # To add assets to your application, add an assets section, like this:
- # assets:
- # - images/a_dot_burr.jpeg
- # - images/a_dot_ham.jpeg
-
- # An image asset can refer to one or more resolution-specific "variants", see
- # https://flutter.io/assets-and-images/#resolution-aware.
-
- # For details regarding adding assets from package dependencies, see
- # https://flutter.io/assets-and-images/#from-packages
-
- # To add custom fonts to your application, add a fonts section here,
- # in this "flutter" section. Each entry in this list should have a
- # "family" key with the font family name, and a "fonts" key with a
- # list giving the asset and other descriptors for the font. For
- # example:
- # fonts:
- # - family: Schyler
- # fonts:
- # - asset: fonts/Schyler-Regular.ttf
- # - asset: fonts/Schyler-Italic.ttf
- # style: italic
- # - family: Trajan Pro
- # fonts:
- # - asset: fonts/TrajanPro.ttf
- # - asset: fonts/TrajanPro_Bold.ttf
- # weight: 700
- #
- # For details regarding fonts from package dependencies,
- # see https://flutter.io/custom-fonts/#from-packages
diff --git a/ios/flutter_downloader.podspec b/ios/flutter_downloader.podspec
index 67372ef1..3cc0957b 100644
--- a/ios/flutter_downloader.podspec
+++ b/ios/flutter_downloader.podspec
@@ -1,23 +1,18 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
-Pod::Spec.new do |s|
- s.name = 'flutter_downloader'
- s.version = '0.0.1'
- s.summary = 'A plugin to create and manage bankground download tasks'
- s.description = <<-DESC
-A plugin to create and manage bankground download tasks
- DESC
- s.homepage = 'https://github.com/hnvn/flutter_downloader'
- s.license = { :file => '../LICENSE' }
- s.author = { 'HungHD' => 'hunghd.yb@gmail.com' }
- s.source = { :path => '.' }
- s.source_files = 'Classes/**/*'
- s.public_header_files = 'Classes/**/*.h'
- s.dependency 'Flutter'
- s.ios.library = 'sqlite3'
- s.ios.resource_bundle = { 'FlutterDownloaderDatabase' => 'Assets/download_tasks.sql' }
-
- s.ios.deployment_target = '8.0'
+Pod::Spec.new do |spec|
+ spec.name = 'flutter_downloader'
+ spec.version = '0.0.1'
+ spec.summary = 'A plugin to create and manage bankground download tasks'
+ spec.homepage = 'https://github.com/hnvn/flutter_downloader'
+ spec.license = { :file => '../LICENSE' }
+ spec.author = { 'HungHD' => 'hunghd.yb@gmail.com' }
+ spec.source = { :path => '.' }
+ spec.source_files = 'Classes/**/*'
+ spec.public_header_files = 'Classes/**/*.h'
+ spec.dependency 'Flutter'
+ spec.ios.library = 'sqlite3'
+ spec.ios.resource_bundle = { 'FlutterDownloaderDatabase' => 'Assets/download_tasks.sql' }
+ spec.ios.deployment_target = '9.0'
end
-
diff --git a/lib/src/downloader.dart b/lib/src/downloader.dart
index 8ecd09ca..a72d2eae 100644
--- a/lib/src/downloader.dart
+++ b/lib/src/downloader.dart
@@ -24,13 +24,13 @@ class FlutterDownloader {
/// Initializes the plugin. This must be called before any other method.
///
- /// Pass true for [debug] if you want to see debug logs in the console.
+ /// If [debug] is true, then verbose logging is printed to the console.
///
/// To ignore SSL-related errors on Android, set [ignoreSsl] to true. This may
/// be useful when connecting to a test server which is not using SSL, but
/// should be never used in production.
static Future initialize({
- bool debug = true,
+ bool debug = false,
bool ignoreSsl = false,
}) async {
assert(