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(