diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
deleted file mode 100644
index a7b8502e6c59c6de3ae0519df7e74f87b9b64127..0000000000000000000000000000000000000000
--- a/.github/CODEOWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-* @bartekpacia
-* @salmaahhmed
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index c3c083bb55b72bcda5f137589aa176458597fc3d..0000000000000000000000000000000000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ""
-labels: "bug"
-assignees: ""
----
-
-
-
-**Describe the bug**
-
-
-
-**To Reproduce**
-
-
-
-Steps to reproduce the behavior:
-
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
-
-**Expected behavior**
-
-
-
-**Screenshots**
-
-
-
-**Desktop (please complete the following information):**
-
-- OS: [e.g. iOS]
-- Browser [e.g. chrome, safari]
-- Version [e.g. 22]
-
-**Device information:**
-
-
-
-- Device: [e.g. iPhone 13]
-- OS: [e.g. iOS 15.4]
-- plugin version [e.g. v1.8.0]
-
-**Additional context**
-
-
diff --git a/.github/workflows/prepare.yaml b/.github/workflows/prepare.yaml
deleted file mode 100644
index b477d38126019b18166e1b27c926fa29282587cc..0000000000000000000000000000000000000000
--- a/.github/workflows/prepare.yaml
+++ /dev/null
@@ -1,72 +0,0 @@
-name: prepare
-
-on:
- workflow_dispatch:
- pull_request:
- branches: [master]
-
-jobs:
- main:
- name: Flutter ${{ matrix.flutter-version }}
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: false
- matrix:
- flutter-version: ["3.19.x"]
-
- steps:
- - name: Clone repository
- uses: actions/checkout@v4
-
- - name: Set up Flutter
- uses: subosito/flutter-action@v2
- with:
- channel: stable
- flutter-version: ${{ matrix.flutter-version }}
-
- - name: Set up Java
- uses: actions/setup-java@v4
- with:
- distribution: temurin
- java-version: 21
-
- - name: flutter pub get
- run: flutter pub get
-
- - name: Build example app
- working-directory: example
- run: flutter build apk --debug
-
- - name: dart format
- run: dart format --set-exit-if-changed .
-
- - name: flutter analyze
- run: flutter analyze --no-fatal-infos
-
- - name: flutter test
- run: flutter test
-
- - name: ktlint check
- working-directory: example/android
- run: ./gradlew flutter_downloader:ktlintCheck
-
- - name: flutter pub get (example app)
- working-directory: ./example
- run: flutter pub get
-
- - name: flutter format (example app)
- working-directory: ./example
- run: dart format --set-exit-if-changed .
-
- - name: flutter analyze (example app)
- working-directory: ./example
- run: flutter analyze --no-fatal-infos
-
- - name: flutter test (example app)
- working-directory: ./example
- run: flutter test
-
- - name: Dry run pub publish
- # We don't want it to fail the CI, it's just to see how would `pub publish` behave.
- run: dart pub publish --dry-run || true
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
deleted file mode 100644
index 83682d51c9bfaa269841785cb485343e9c7e1d32..0000000000000000000000000000000000000000
--- a/.github/workflows/publish.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: publish
-
-on:
- push:
- tags: ["v*"]
-
-jobs:
- publish:
- name: Publish on pub.dev
- runs-on: ubuntu-latest
-
- permissions:
- id-token: write
- contents: write
-
- steps:
- - name: Clone repository
- uses: actions/checkout@v4
-
- # This step adds the auth token for pub.dev
- - name: Set up Dart
- uses: dart-lang/setup-dart@v1
- with:
- sdk: stable
-
- - name: Set up Flutter
- uses: subosito/flutter-action@v2
- with:
- channel: stable
-
- - name: Publish to pub.dev
- id: pub_release
- uses: leancodepl/mobile-tools/.github/actions/pub-release@pub-release-v1
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 64dde02e5b3e9f2fda0bf4825d12a71b9b92cdc6..0000000000000000000000000000000000000000
--- a/.gitignore
+++ /dev/null
@@ -1,95 +0,0 @@
-# Miscellaneous
-*.class
-*.lock
-*.log
-*.pyc
-*.swp
-.DS_Store
-.atom/
-.buildlog/
-.history
-.svn/
-
-# IntelliJ related
-*.iml
-*.ipr
-*.iws
-.idea/
-
-# Flutter repo-specific
-/bin/cache/
-/bin/mingit/
-/dev/benchmarks/mega_gallery/
-/dev/bots/.recipe_deps
-/dev/bots/android_tools/
-/dev/docs/doc/
-/dev/docs/flutter.docs.zip
-/dev/docs/lib/
-/dev/docs/pubspec.yaml
-/packages/flutter/coverage/
-version
-.flutter-plugins-dependencies
-
-# packages file containing multi-root paths
-.packages.generated
-
-# Flutter/Dart/Pub related
-**/doc/api/
-.dart_tool/
-.flutter-plugins
-.packages
-.pub-cache/
-.pub/
-build/
-flutter_*.png
-linked_*.ds
-unlinked.ds
-unlinked_spec.ds
-
-# Android related
-**/android/**/gradle-wrapper.jar
-**/android/.gradle
-**/android/captures/
-**/android/gradlew
-**/android/gradlew.bat
-**/android/local.properties
-**/android/**/GeneratedPluginRegistrant.java
-**/android/key.properties
-*.jks
-
-# iOS/XCode related
-**/ios/**/*.mode1v3
-**/ios/**/*.mode2v3
-**/ios/**/*.moved-aside
-**/ios/**/*.pbxuser
-**/ios/**/*.perspectivev3
-**/ios/**/*sync/
-**/ios/**/.sconsign.dblite
-**/ios/**/.tags*
-**/ios/**/.vagrant/
-**/ios/**/DerivedData/
-**/ios/**/Icon?
-**/ios/**/Pods/
-**/ios/**/.symlinks/
-**/ios/**/profile
-**/ios/**/xcuserdata
-**/ios/.generated/
-**/ios/Flutter/App.framework
-**/ios/Flutter/Flutter.framework
-**/ios/Flutter/Generated.xcconfig
-**/ios/Flutter/app.flx
-**/ios/Flutter/app.zip
-**/ios/Flutter/flutter_assets/
-**/ios/Flutter/flutter_export_environment.sh
-**/ios/ServiceDefinitions.json
-**/ios/Runner/GeneratedPluginRegistrant.*
-
-# Coverage
-coverage/
-
-# Exceptions to above rules.
-!**/ios/**/default.mode1v3
-!**/ios/**/default.mode2v3
-!**/ios/**/default.pbxuser
-!**/ios/**/default.perspectivev3
-!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 36eb658d41ddda1548872ec08293ec279e538db7..0000000000000000000000000000000000000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "example",
- "cwd": "example",
- "request": "launch",
- "type": "dart"
- },
- {
- "name": "example (profile)",
- "cwd": "example",
- "request": "launch",
- "type": "dart",
- "flutterMode": "profile"
- },
- {
- "name": "example (release)",
- "cwd": "example",
- "request": "launch",
- "type": "dart",
- "flutterMode": "release"
- }
- ]
-}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2151c4cc509cee88532f4865fabcec90f48eea6b..f064cf983c77bb5c50b0073e212a82cf8970c627 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,57 +1,3 @@
-## 1.11.8
-
-- Dependencies update (#952)
- - Gradle buildscripts on Android
- - Bump minimum Android SDK to 21
- - Bump minimum Flutter to 3.19
-
-## 1.11.7
-
-- Update dependencies (#946)
-- Update CI workflows (#945)
-- Bump `compileSdk` to 34 (#944)
-
-## 1.11.6
-
-- No release notes were provided
-
-## 1.11.5
-
-- Republish fixes for previous verison
-
-## 1.11.4
-
-- Fix for not working after upgrade to IOS 17 and Xcode 15 (#899)
-- fix for Issue with Spaces and Parentheses in File Names (#904)
-
-## 1.11.3
-
-- Fix for file name not being saved (#894)
-
-## 1.11.2
-
-- Security update for iOS (#887)
-- Support file store in any iOS directory (#829)
-
-## 1.11.1
-
-- Don't crash when `FlutterDownloader.registerCallback()` wasn't called (#879)
-
-## 1.11.0
-
-- Convert `DownloadTaskStatus` into an `enum` (#835)
-
-## 1.10.7
-
-- Override `operator ==` and `hashCode` for `DownloadTask` (#875)
-
-## 1.10.6
-
-- Fix `delete()` not working when file isn't saved to public storage (#871)
-- Update CI workflows on GitHub Actions (#872)
-- Bump native Android dependencies and Gradle (#873)
-- Bump minimum Flutter version to 3.10 (#873)
-
## 1.10.5
- Make the project compile when the app not doesn't have dependency on Kotlin
diff --git a/OAT.xml b/OAT.xml
new file mode 100644
index 0000000000000000000000000000000000000000..73e89e016ad10a69a0e5224960aab8240010f695
--- /dev/null
+++ b/OAT.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index fd0097a24f0d954e192bea27c1c5c9d2eabc0e15..c1362e1f0dc340980122954385c8caf650487981 100644
--- a/README.md
+++ b/README.md
@@ -19,12 +19,6 @@ manage download file location. It is still in triage and discussion in this
very appreciated to have contribution and feedback from Flutter developer to get
better design for the plugin._
-# Past Versions and SQL Injection Vulnerabilities
-
-In previous versions of this package, there were known vulnerabilities related to SQL injection. SQL injection is a type of security vulnerability that can allow malicious users to manipulate SQL queries executed by an application, potentially leading to unauthorized access or manipulation of the database.
-
-It is strongly recommended to upgrade to the latest version of this package to ensure that your application is not exposed to SQL injection vulnerabilities. The latest version contains the necessary security improvements and patches to mitigate such risks.
-
## iOS integration
### Required configuration:
diff --git a/android/.gitignore b/android/.gitignore
deleted file mode 100644
index 16feccee7979bb61ea60d44289f606b82b86595f..0000000000000000000000000000000000000000
--- a/android/.gitignore
+++ /dev/null
@@ -1,12 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/workspace.xml
-/.idea/misc.xml
-/.idea/markdown-navigator
-/.idea/encodings.xml
-/.idea/libraries
-/.idea/gradle.xml
-.DS_Store
-/build
-/captures
diff --git a/android/build.gradle b/android/build.gradle
index 78f9b70ba896f6fa831c7a72286bcbd4f4266054..dbcbc14b726bffbbcd4e342de64610a8c91389f4 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,7 +1,5 @@
-group = "vn.hunghd.flutterdownloader"
-version = "1.0-SNAPSHOT"
-
buildscript {
+ ext.kotlin_version = "1.7.22"
repositories {
google()
mavenCentral()
@@ -9,24 +7,29 @@ buildscript {
}
dependencies {
- classpath "com.android.tools.build:gradle:7.4.2"
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0"
- classpath "org.jlleitschuh.gradle:ktlint-gradle:11.5.0"
+ classpath "com.android.tools.build:gradle:7.2.2"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "org.jlleitschuh.gradle:ktlint-gradle:11.0.0"
}
}
-repositories {
- google()
- mavenCentral()
-}
-
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
apply plugin: "org.jlleitschuh.gradle.ktlint"
+group "vn.hunghd.flutterdownloader"
+version "1.0-SNAPSHOT"
+
+rootProject.allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
android {
- namespace = "vn.hunghd.flutterdownloader"
- compileSdk = 34
+ namespace "vn.hunghd.flutterdownloader"
+ compileSdk 32
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -34,23 +37,21 @@ android {
}
kotlinOptions {
- jvmTarget = JavaVersion.VERSION_1_8
- // allWarningsAsErrors = true // TODO(bartekpacia): Re-enable
+ jvmTarget = "1.8"
}
sourceSets {
main.java.srcDirs += "src/main/kotlin"
- test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
- minSdk = 21
- targetSdk = 34
+ minSdk 19
+ targetSdk 32
}
}
dependencies {
- compileOnly "androidx.annotation:annotation:1.6.0"
- implementation "androidx.core:core-ktx:1.13.1"
- implementation "androidx.work:work-runtime:2.9.0"
+ implementation "androidx.work:work-runtime:2.7.1"
+ implementation "androidx.annotation:annotation:1.5.0"
+ implementation "androidx.core:core-ktx:1.9.0"
}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..08f2b5f91bff656eaca532e9bc1510d5d3d2f912
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.enableJetifier=true
+android.useAndroidX=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..dcf0f19c5220b13c4eecfc34a6e786df5280a81c
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
diff --git a/android/src/main/kotlin/vn/hunghd/flutterdownloader/DownloadWorker.kt b/android/src/main/kotlin/vn/hunghd/flutterdownloader/DownloadWorker.kt
index 2d9bf628c93d74390c231cf9ad1c9ae1e291d526..94bd5c47419bb6f52fc1d262c3298074c45d3f3d 100644
--- a/android/src/main/kotlin/vn/hunghd/flutterdownloader/DownloadWorker.kt
+++ b/android/src/main/kotlin/vn/hunghd/flutterdownloader/DownloadWorker.kt
@@ -257,7 +257,7 @@ class DownloadWorker(context: Context, params: WorkerParameters) :
filename: String?,
headers: String,
isResume: Boolean,
- timeout: Int
+ timeout: Int,
) {
var actualFilename = filename
var url = fileURL
diff --git a/android/src/main/kotlin/vn/hunghd/flutterdownloader/FlutterDownloaderPlugin.kt b/android/src/main/kotlin/vn/hunghd/flutterdownloader/FlutterDownloaderPlugin.kt
index 6867bf6585006cf7c95d03340db9b12d676d7537..b6f527b2d053d7df9250c46e7e88cc5fc5759e20 100644
--- a/android/src/main/kotlin/vn/hunghd/flutterdownloader/FlutterDownloaderPlugin.kt
+++ b/android/src/main/kotlin/vn/hunghd/flutterdownloader/FlutterDownloaderPlugin.kt
@@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
-import android.util.Log
import androidx.core.app.NotificationManagerCompat
import androidx.work.BackoffPolicy
import androidx.work.Constraints
@@ -92,7 +91,7 @@ class FlutterDownloaderPlugin : MethodChannel.MethodCallHandler, FlutterPlugin {
requiresStorageNotLow: Boolean,
saveInPublicStorage: Boolean,
timeout: Int,
- allowCellular: Boolean
+ allowCellular: Boolean,
): WorkRequest {
return OneTimeWorkRequest.Builder(DownloadWorker::class.java)
.setConstraints(
@@ -391,14 +390,7 @@ class FlutterDownloaderPlugin : MethodChannel.MethodCallHandler, FlutterPlugin {
val saveFilePath = task.savedDir + File.separator + filename
val tempFile = File(saveFilePath)
if (tempFile.exists()) {
- try {
- deleteFileInMediaStore(tempFile)
- } catch (e: SecurityException) {
- Log.d(
- "FlutterDownloader",
- "Failed to delete file in media store, will fall back to normal delete()"
- )
- }
+ deleteFileInMediaStore(tempFile)
tempFile.delete()
}
}
diff --git a/android/src/main/kotlin/vn/hunghd/flutterdownloader/TaskDao.kt b/android/src/main/kotlin/vn/hunghd/flutterdownloader/TaskDao.kt
index 1fffd964e4232d56851767545b043a77bcf9e453..6bb7a8a90cc89eb40aac66bfe50ae8072aece1c4 100644
--- a/android/src/main/kotlin/vn/hunghd/flutterdownloader/TaskDao.kt
+++ b/android/src/main/kotlin/vn/hunghd/flutterdownloader/TaskDao.kt
@@ -21,7 +21,7 @@ class TaskDao(private val dbHelper: TaskDbHelper) {
TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION,
TaskEntry.COLUMN_NAME_TIME_CREATED,
TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE,
- TaskEntry.COLUMN_ALLOW_CELLULAR
+ TaskEntry.COLUMN_ALLOW_CELLULAR,
)
fun insertOrUpdateNewTask(
diff --git a/example/.gitignore b/example/.gitignore
deleted file mode 100644
index dee655cc42ea7c763579e3b22730f1c1b8ac38d4..0000000000000000000000000000000000000000
--- a/example/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-.DS_Store
-.dart_tool/
-
-.packages
-.pub/
-
-build/
-
-.flutter-plugins
diff --git a/example/.idea/libraries/Dart_SDK.xml b/example/.idea/libraries/Dart_SDK.xml
deleted file mode 100644
index 170212acf7a46c97492ee82b633f8abeafb424a3..0000000000000000000000000000000000000000
--- a/example/.idea/libraries/Dart_SDK.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/example/.idea/libraries/Flutter_for_Android.xml b/example/.idea/libraries/Flutter_for_Android.xml
deleted file mode 100644
index 27c77368754aa0c2dd3fbd1491be33c67c3aca0d..0000000000000000000000000000000000000000
--- a/example/.idea/libraries/Flutter_for_Android.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/example/.idea/modules.xml b/example/.idea/modules.xml
deleted file mode 100644
index eca6ca725b3f54753ea8f35d0b8cb910da662fbc..0000000000000000000000000000000000000000
--- a/example/.idea/modules.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/example/.idea/runConfigurations/main_dart.xml b/example/.idea/runConfigurations/main_dart.xml
deleted file mode 100644
index aab7b5cd8325696835286f195527e16ffb1f675a..0000000000000000000000000000000000000000
--- a/example/.idea/runConfigurations/main_dart.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/example/.idea/workspace.xml b/example/.idea/workspace.xml
deleted file mode 100644
index 003039871593c6076d67371bf161492f6123715f..0000000000000000000000000000000000000000
--- a/example/.idea/workspace.xml
+++ /dev/null
@@ -1,166 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1569986535984
-
-
- 1569986535984
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/example/.metadata b/example/.metadata
deleted file mode 100644
index b6d4e812e2e77023ee8dcb9921df753472726ab7..0000000000000000000000000000000000000000
--- a/example/.metadata
+++ /dev/null
@@ -1,8 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: 4cd5870ecef50fbf4fd7e681cf327897f0695abe
- channel: master
diff --git a/example/.vscode/launch.json b/example/.vscode/launch.json
deleted file mode 100644
index 62085bf955072c03d6c492e55ae6d3d0061bd1df..0000000000000000000000000000000000000000
--- a/example/.vscode/launch.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "example (debug)",
- "request": "launch",
- "type": "dart",
- "flutterMode": "debug"
- }
- ]
-}
diff --git a/example/android/.gitignore b/example/android/.gitignore
deleted file mode 100644
index bc2100d8f75e6efef413729756e448e910f7304d..0000000000000000000000000000000000000000
--- a/example/android/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-gradle-wrapper.jar
-/.gradle
-/captures/
-/gradlew
-/gradlew.bat
-/local.properties
-GeneratedPluginRegistrant.java
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index a4d5275e9b724d5772aa8b4a49b1a280fee872d8..7b41dae31bc4b89fe0bbc7dac83c8cf683c44a10 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -1,9 +1,3 @@
-plugins {
- id "com.android.application"
- id "org.jetbrains.kotlin.android"
- id "dev.flutter.flutter-gradle-plugin"
-}
-
def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
@@ -12,6 +6,11 @@ if (localPropertiesFile.exists()) {
}
}
+def flutterRoot = localProperties.getProperty("flutter.sdk")
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = "1"
@@ -22,26 +21,21 @@ if (flutterVersionName == null) {
flutterVersionName = "1.0"
}
+apply plugin: "com.android.application"
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
android {
- namespace = "vn.hunghd.example"
- compileSdk = 34
- ndkVersion = flutter.ndkVersion
+ compileSdk 33
+
+ namespace "vn.hunghd.example"
defaultConfig {
applicationId "vn.hunghd.example"
- minSdk = 21
- targetSdk = 34
- versionCode = flutterVersionCode.toInteger()
- versionName = flutterVersionName
- }
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
- }
-
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_1_8
+ minSdk 19
+ targetSdk 33
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
}
buildTypes {
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index d5e3a03355c3a4e21ea9ccae1e80d67569335df4..7796dc8210416d8287f89409c71a679126e19cbd 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
+ xmlns:tools="http://schemas.android.com/tools" package="vn.hunghd.flutter_downloader_example">
diff --git a/example/android/build.gradle b/example/android/build.gradle
index 2b1df4ece131109719cc65af59963a98f193f17e..8b3b6ce47e61a888ea88102e2d7298b7d22ecd8b 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -1,3 +1,23 @@
+buildscript {
+ ext.kotlin_version = "1.7.22"
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath "com.android.tools.build:gradle:7.2.2"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
@@ -7,5 +27,5 @@ subprojects {
}
tasks.register("clean", Delete) {
- delete(rootProject.buildDir)
+ delete rootProject.buildDir
}
diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372aef5e24af05341d49695ee84e5f9b594659..0000000000000000000000000000000000000000
Binary files a/example/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index d951fac2bf31f108828266e7e1a4a0c8a47af37f..dcf0f19c5220b13c4eecfc34a6e786df5280a81c 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
diff --git a/example/android/gradlew b/example/android/gradlew
deleted file mode 100755
index 9d82f78915133e1c35a6ea51252590fb38efac2f..0000000000000000000000000000000000000000
--- a/example/android/gradlew
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=$((i+1))
- done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat
deleted file mode 100644
index 8a0b282aa6885fb573c106b3551f7275c5f17e8e..0000000000000000000000000000000000000000
--- a/example/android/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
index 3c2396230b9484ee0b2901b201720b9f083e321d..f29f78b31d75a2eefa9c2bf4616aa2added05d47 100644
--- a/example/android/settings.gradle
+++ b/example/android/settings.gradle
@@ -1,25 +1,15 @@
-pluginManagement {
- def flutterSdkPath = {
- def properties = new Properties()
- file("local.properties").withInputStream { properties.load(it) }
- def flutterSdkPath = properties.getProperty("flutter.sdk")
- assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
- return flutterSdkPath
- }()
+include ":app"
- includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
+def plugins = new Properties()
+def pluginsFile = new File(flutterProjectRoot.toFile(), ".flutter-plugins")
+if (pluginsFile.exists()) {
+ pluginsFile.withReader("UTF-8") { reader -> plugins.load(reader) }
}
-plugins {
- id("dev.flutter.flutter-plugin-loader")
- id("com.android.application") version ("8.3.1") apply false
- id("org.jetbrains.kotlin.android") version ("1.9.23") apply false
+plugins.each { name, path ->
+ def pluginDirectory = flutterProjectRoot.resolve(path).resolve("android").toFile()
+ include ":$name"
+ project(":$name").projectDir = pluginDirectory
}
-
-include(":app")
diff --git a/example/ios/.gitignore b/example/ios/.gitignore
deleted file mode 100644
index b48ca83718d612d69aa44b2481422c6041fa3c10..0000000000000000000000000000000000000000
--- a/example/ios/.gitignore
+++ /dev/null
@@ -1,72 +0,0 @@
-.idea/
-.vagrant/
-.sconsign.dblite
-.svn/
-
-.DS_Store
-*.swp
-profile
-
-DerivedData/
-build/
-GeneratedPluginRegistrant.h
-GeneratedPluginRegistrant.m
-
-.generated/
-
-*.pbxuser
-*.mode1v3
-*.mode2v3
-*.perspectivev3
-
-!default.pbxuser
-!default.mode1v3
-!default.mode2v3
-!default.perspectivev3
-
-xcuserdata
-
-*.moved-aside
-
-*.pyc
-*sync/
-Icon?
-.tags*
-
-/Flutter/app.flx
-/Flutter/app.zip
-/Flutter/flutter_assets/
-/Flutter/App.framework
-/Flutter/Flutter.framework
-/Flutter/Generated.xcconfig
-/ServiceDefinitions.json
-
-Pods/
-.symlinks/
-
-# iOS/XCode related
-**/ios/**/*.mode1v3
-**/ios/**/*.mode2v3
-**/ios/**/*.moved-aside
-**/ios/**/*.pbxuser
-**/ios/**/*.perspectivev3
-**/ios/**/*sync/
-**/ios/**/.sconsign.dblite
-**/ios/**/.tags*
-**/ios/**/.vagrant/
-**/ios/**/DerivedData/
-**/ios/**/Icon?
-**/ios/**/Pods/
-**/ios/**/.symlinks/
-**/ios/**/profile
-**/ios/**/xcuserdata
-**/ios/.generated/
-**/ios/Flutter/App.framework
-**/ios/Flutter/Flutter.framework
-**/ios/Flutter/Generated.xcconfig
-**/ios/Flutter/app.flx
-**/ios/Flutter/app.zip
-**/ios/Flutter/flutter_assets/
-**/ios/Flutter/flutter_export_environment.sh
-**/ios/ServiceDefinitions.json
-**/ios/Runner/GeneratedPluginRegistrant.*
diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id
deleted file mode 100644
index 40acc3c9309dd3f5cd2cf9e9f80509949d47a1d9..0000000000000000000000000000000000000000
--- a/example/ios/Flutter/.last_build_id
+++ /dev/null
@@ -1 +0,0 @@
-0602751b23487fe11743492e7dbc3fe4
\ No newline at end of file
diff --git a/example/lib/data.dart b/example/lib/data.dart
index 45f266f7b5fc26fbe6d62f9aa3e10e829d763452..8d4a06a255c918b53a37471c807e8a0c59a045b5 100644
--- a/example/lib/data.dart
+++ b/example/lib/data.dart
@@ -1,37 +1,19 @@
class DownloadItems {
- static const documents = [
- DownloadItem(
- name: 'Android Programming Cookbook',
- url:
- 'http://enos.itcollege.ee/~jpoial/allalaadimised/reading/Android-Programming-Cookbook.pdf',
- ),
- DownloadItem(
- name: 'iOS Programming Guide',
- url:
- 'http://englishonlineclub.com/pdf/iOS%20Programming%20-%20The%20Big%20Nerd%20Ranch%20Guide%20(6th%20Edition)%20[EnglishOnlineClub.com].pdf',
- ),
- ];
-
static const images = [
DownloadItem(
name: 'Arches National Park',
url:
- 'https://upload.wikimedia.org/wikipedia/commons/6/60/The_Organ_at_Arches_National_Park_Utah_Corrected.jpg',
+ 'https://images.pexels.com/photos/26690031/pexels-photo-26690031.jpeg',
),
DownloadItem(
name: 'Canyonlands National Park',
url:
- 'https://upload.wikimedia.org/wikipedia/commons/7/78/Canyonlands_National_Park%E2%80%A6Needles_area_%286294480744%29.jpg',
+ 'https://images.pexels.com/photos/23495788/pexels-photo-23495788.jpeg',
),
DownloadItem(
name: 'Death Valley National Park',
url:
- 'https://upload.wikimedia.org/wikipedia/commons/b/b2/Sand_Dunes_in_Death_Valley_National_Park.jpg',
- ),
- DownloadItem(
- name: 'Gates of the Arctic National Park and Preserve',
- url:
- 'https://upload.wikimedia.org/wikipedia/commons/e/e4/GatesofArctic.jpg',
+ 'https://images.pexels.com/photos/27675493/pexels-photo-27675493.jpeg',
),
];
@@ -39,20 +21,12 @@ class DownloadItems {
DownloadItem(
name: 'Big Buck Bunny',
url:
- 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
+ 'https://videos.pexels.com/video-files/17880140/17880140-uhd_1440_2560_60fps.mp4',
),
DownloadItem(
name: 'Elephant Dream',
url:
- 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
- ),
- ];
-
- static const apks = [
- DownloadItem(
- name: 'Spitfire',
- url:
- 'https://github.com/bartekpacia/spitfire/releases/download/v1.2.0/spitfire.apk',
+ 'https://videos.pexels.com/video-files/20500768/20500768-uhd_1440_2560_30fps.mp4',
),
];
}
diff --git a/example/lib/download_list_item.dart b/example/lib/download_list_item.dart
index c0ae7cc081ae9b450521a111785b71d2bf0de8e0..a7ac1093490a2183c63282a3fd9a5b93b147d06d 100644
--- a/example/lib/download_list_item.dart
+++ b/example/lib/download_list_item.dart
@@ -12,9 +12,9 @@ class DownloadListItem extends StatelessWidget {
});
final ItemHolder? data;
- final void Function(TaskInfo?)? onTap;
- final void Function(TaskInfo)? onActionTap;
- final void Function(TaskInfo)? onCancel;
+ final Function(TaskInfo?)? onTap;
+ final Function(TaskInfo)? onActionTap;
+ final Function(TaskInfo)? onCancel;
Widget? _buildTrailing(TaskInfo task) {
if (task.status == DownloadTaskStatus.undefined) {
@@ -66,7 +66,7 @@ class DownloadListItem extends StatelessWidget {
constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
icon: const Icon(Icons.delete),
tooltip: 'Delete',
- ),
+ )
],
);
} else if (task.status == DownloadTaskStatus.canceled) {
@@ -81,7 +81,7 @@ class DownloadListItem extends StatelessWidget {
constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
icon: const Icon(Icons.cancel),
tooltip: 'Cancel',
- ),
+ )
],
);
} else if (task.status == DownloadTaskStatus.failed) {
@@ -95,7 +95,7 @@ class DownloadListItem extends StatelessWidget {
constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
icon: const Icon(Icons.refresh, color: Colors.green),
tooltip: 'Refresh',
- ),
+ )
],
);
} else if (task.status == DownloadTaskStatus.enqueued) {
@@ -147,7 +147,7 @@ class DownloadListItem extends StatelessWidget {
child: LinearProgressIndicator(
value: data!.task!.progress! / 100,
),
- ),
+ )
],
),
),
diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart
index 01716b4c0c2db35257f04f5c217517601e373951..4123820cbc324aec69b29075f25ff2596dd118d8 100644
--- a/example/lib/home_page.dart
+++ b/example/lib/home_page.dart
@@ -2,6 +2,7 @@ import 'dart:io';
import 'dart:isolate';
import 'dart:ui';
+import 'package:android_path_provider/android_path_provider.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
@@ -63,7 +64,7 @@ class _MyHomePageState extends State {
}
_port.listen((dynamic data) {
final taskId = (data as List)[0] as String;
- final status = DownloadTaskStatus.fromInt(data[1] as int);
+ final status = DownloadTaskStatus(data[1] as int);
final progress = data[2] as int;
print(
@@ -137,11 +138,9 @@ class _MyHomePageState extends State {
return DownloadListItem(
data: item,
onTap: (task) async {
- final scaffoldMessenger = ScaffoldMessenger.of(context);
-
final success = await _openDownloadedFile(task);
if (!success) {
- scaffoldMessenger.showSnackBar(
+ ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Cannot open this file'),
),
@@ -194,7 +193,7 @@ class _MyHomePageState extends State {
fontSize: 20,
),
),
- ),
+ )
],
),
);
@@ -273,6 +272,10 @@ class _MyHomePageState extends State {
return result == PermissionStatus.granted;
}
+ if (Platform.operatingSystem == 'ohos') {
+ return true;
+ }
+
throw StateError('unknown platform');
}
@@ -288,18 +291,6 @@ class _MyHomePageState extends State {
_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)),
@@ -322,17 +313,6 @@ class _MyHomePageState extends State {
count++;
}
- _tasks!.addAll(
- DownloadItems.apks
- .map((video) => TaskInfo(name: video.name, link: video.url)),
- );
-
- _items.add(ItemHolder(name: 'APKs'));
- 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) {
@@ -356,17 +336,30 @@ class _MyHomePageState extends State {
Future _prepareSaveDir() async {
_localPath = (await _getSavedDir())!;
- final savedDir = Directory(_localPath);
- if (!savedDir.existsSync()) {
- await savedDir.create();
- }
+ // final savedDir = Directory(_localPath);
+ // if (!savedDir.existsSync()) {
+ // await savedDir.create();
+ // }
}
Future _getSavedDir() async {
String? externalStorageDirPath;
- externalStorageDirPath =
- (await getApplicationDocumentsDirectory()).absolute.path;
+ if (Platform.isAndroid) {
+ try {
+ externalStorageDirPath = await AndroidPathProvider.downloadsPath;
+ } catch (err, st) {
+ print('failed to get downloads path: $err, $st');
+
+ final directory = await getExternalStorageDirectory();
+ externalStorageDirPath = directory?.path;
+ }
+ } else if (Platform.isIOS) {
+ externalStorageDirPath =
+ (await getApplicationDocumentsDirectory()).absolute.path;
+ } else if (Platform.operatingSystem == 'ohos') {
+ externalStorageDirPath = 'Image';
+ }
return externalStorageDirPath;
}
@@ -393,7 +386,7 @@ class _MyHomePageState extends State {
),
),
],
- ),
+ )
],
),
body: Builder(
diff --git a/example/ohos/AppScope/app.json5 b/example/ohos/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..b067e3e0b7ccf74ddd3c95351e811a2bf376a689
--- /dev/null
+++ b/example/ohos/AppScope/app.json5
@@ -0,0 +1,10 @@
+{
+ "app": {
+ "bundleName": "com.example.flutter_downloader",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name"
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/AppScope/resources/base/element/string.json b/example/ohos/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..810f4a362c1d177309eec4f2efe5cac2f4558c28
--- /dev/null
+++ b/example/ohos/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "example"
+ }
+ ]
+}
diff --git a/example/ohos/AppScope/resources/base/media/app_icon.png b/example/ohos/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/example/ohos/AppScope/resources/base/media/app_icon.png differ
diff --git a/example/ohos/build-profile.json5 b/example/ohos/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..1d12140d202702d7c73d64f1b291fe5c45a660ce
--- /dev/null
+++ b/example/ohos/build-profile.json5
@@ -0,0 +1,27 @@
+{
+ "app": {
+ "signingConfigs": [],
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ "compatibleSdkVersion": "5.0.0(12)",
+ "runtimeOS": "HarmonyOS"
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/build-profile.json5 b/example/ohos/entry/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..633d360fbc91a3186a23b66ab71b27e5618944cb
--- /dev/null
+++ b/example/ohos/entry/build-profile.json5
@@ -0,0 +1,29 @@
+/*
+* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+{
+ "apiType": 'stageMode',
+ "buildOption": {
+ },
+ "targets": [
+ {
+ "name": "default",
+ "runtimeOS": "HarmonyOS"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/hvigorfile.ts b/example/ohos/entry/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5bda56eeac3f79703639db986e2faaa433b0e48c
--- /dev/null
+++ b/example/ohos/entry/hvigorfile.ts
@@ -0,0 +1,17 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+export { hapTasks } from '@ohos/hvigor-ohos-plugin';
diff --git a/example/ohos/entry/oh-package.json5 b/example/ohos/entry/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..27fda28ce5f3357b42849b72f85f1344db9048ed
--- /dev/null
+++ b/example/ohos/entry/oh-package.json5
@@ -0,0 +1,11 @@
+{
+ "name": "entry",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {
+ "flutter_downloader": "file:../har/flutter_downloader.har"
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..b5888ed9002dd328f9abd3141ee3e63e88b40d0f
--- /dev/null
+++ b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets
@@ -0,0 +1,24 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
+import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';
+
+export default class EntryAbility extends FlutterAbility {
+ configureFlutterEngine(flutterEngine: FlutterEngine) {
+ super.configureFlutterEngine(flutterEngine)
+ GeneratedPluginRegistrant.registerWith(flutterEngine)
+ }
+}
diff --git a/example/ohos/entry/src/main/ets/pages/Index.ets b/example/ohos/entry/src/main/ets/pages/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..5d9647743f4651f3dab7aa3076ed0707b207b436
--- /dev/null
+++ b/example/ohos/entry/src/main/ets/pages/Index.ets
@@ -0,0 +1,38 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import common from '@ohos.app.ability.common';
+import { FlutterPage } from '@ohos/flutter_ohos'
+
+let storage = LocalStorage.getShared()
+const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS'
+
+@Entry(storage)
+@Component
+struct Index {
+ private context = getContext(this) as common.UIAbilityContext
+ @LocalStorageLink('viewId') viewId: string = "";
+
+ build() {
+ Column() {
+ FlutterPage({ viewId: this.viewId })
+ }
+ }
+
+ onBackPress(): boolean {
+ this.context.eventHub.emit(EVENT_BACK_PRESS)
+ return true
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets
new file mode 100644
index 0000000000000000000000000000000000000000..a06962346d7bb436de40ca4fdfb7316b77dfb2b7
--- /dev/null
+++ b/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { FlutterEngine, Log } from '@ohos/flutter_ohos';
+import FlutterDownloaderPlugin from 'flutter_downloader';
+
+/**
+ * Generated file. Do not edit.
+ * This file is generated by the Flutter tool based on the
+ * plugins that support the Ohos platform.
+ */
+
+const TAG = "GeneratedPluginRegistrant";
+
+export class GeneratedPluginRegistrant {
+
+ static registerWith(flutterEngine: FlutterEngine) {
+ try {
+ flutterEngine.getPlugins()?.add(new FlutterDownloaderPlugin());
+ } catch (e) {
+ Log.e(
+ TAG,
+ "Tried to register plugins with FlutterEngine ("
+ + flutterEngine
+ + ") failed.");
+ Log.e(TAG, "Received exception while registering", e);
+ }
+ }
+}
diff --git a/example/ohos/entry/src/main/module.json5 b/example/ohos/entry/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..abc5a9f7eee29394fdd7bde8b2998133b3abb2a4
--- /dev/null
+++ b/example/ohos/entry/src/main/module.json5
@@ -0,0 +1,53 @@
+/*
+* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "EntryAbility",
+ "deviceTypes": [
+ "phone"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "abilities": [
+ {
+ "name": "EntryAbility",
+ "srcEntry": "./ets/entryability/EntryAbility.ets",
+ "description": "$string:EntryAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:EntryAbility_label",
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {"name" : "ohos.permission.INTERNET"}
+ ]
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/resources/base/element/color.json b/example/ohos/entry/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/resources/base/element/string.json b/example/ohos/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..67e0f4ff4ac762d1714f6e215c6636a4ad3d620e
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "example"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/resources/base/media/icon.png b/example/ohos/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/example/ohos/entry/src/main/resources/base/media/icon.png differ
diff --git a/example/ohos/entry/src/main/resources/base/profile/main_pages.json b/example/ohos/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "pages/Index"
+ ]
+}
diff --git a/example/ohos/entry/src/main/resources/en_US/element/string.json b/example/ohos/entry/src/main/resources/en_US/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..67e0f4ff4ac762d1714f6e215c6636a4ad3d620e
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/en_US/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "example"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/resources/zh_CN/element/string.json b/example/ohos/entry/src/main/resources/zh_CN/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..601e2b5a1c273aa04920b126e3ab715a4450e58f
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/zh_CN/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "模块描述"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "example"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..bdb2d4b3479e7c4b3d4ffb539abc734470c57f32
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets
@@ -0,0 +1,50 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import hilog from '@ohos.hilog';
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'
+
+export default function abilityTest() {
+ describe('ActsAbilityTest', function () {
+ // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+ beforeAll(function () {
+ // Presets an action, which is performed only once before all test cases of the test suite start.
+ // This API supports only one parameter: preset action function.
+ })
+ beforeEach(function () {
+ // Presets an action, which is performed before each unit test case starts.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: preset action function.
+ })
+ afterEach(function () {
+ // Presets a clear action, which is performed after each unit test case ends.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: clear action function.
+ })
+ afterAll(function () {
+ // Presets a clear action, which is performed after all test cases of the test suite end.
+ // This API supports only one parameter: clear action function.
+ })
+ it('assertContain',0, function () {
+ // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+ hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
+ let a = 'abc'
+ let b = 'b'
+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+ expect(a).assertContain(b)
+ expect(a).assertEqual(a)
+ })
+ })
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/ets/test/List.test.ets b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..5ed99383f79bb67de7472f356de5b0421fa26e71
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets
@@ -0,0 +1,20 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import abilityTest from './Ability.test'
+
+export default function testsuite() {
+ abilityTest()
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..465211ee2e66f8b16d8c28881acc0f534df700e5
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets
@@ -0,0 +1,63 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import UIAbility from '@ohos.app.ability.UIAbility';
+import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
+import hilog from '@ohos.hilog';
+import { Hypium } from '@ohos/hypium';
+import testsuite from '../test/List.test';
+import window from '@ohos.window';
+
+export default class TestAbility extends UIAbility {
+ onCreate(want, launchParam) {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate');
+ hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
+ hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? '');
+ var abilityDelegator: any
+ abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
+ var abilityDelegatorArguments: any
+ abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
+ hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!');
+ Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite)
+ }
+
+ onDestroy() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy');
+ }
+
+ onWindowStageCreate(windowStage: window.WindowStage) {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate');
+ windowStage.loadContent('testability/pages/Index', (err, data) => {
+ if (err.code) {
+ hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
+ return;
+ }
+ hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s',
+ JSON.stringify(data) ?? '');
+ });
+ }
+
+ onWindowStageDestroy() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy');
+ }
+
+ onForeground() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground');
+ }
+
+ onBackground() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground');
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..bdf1e5f905bcacd6a73af7709c41d4a79d41b170
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets
@@ -0,0 +1,49 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import hilog from '@ohos.hilog';
+
+@Entry
+@Component
+struct Index {
+ aboutToAppear() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear');
+ }
+ @State message: string = 'Hello World'
+ build() {
+ Row() {
+ Column() {
+ Text(this.message)
+ .fontSize(50)
+ .fontWeight(FontWeight.Bold)
+ Button() {
+ Text('next page')
+ .fontSize(20)
+ .fontWeight(FontWeight.Bold)
+ }.type(ButtonType.Capsule)
+ .margin({
+ top: 20
+ })
+ .backgroundColor('#0D9FFB')
+ .width('35%')
+ .height('5%')
+ .onClick(()=>{
+ })
+ }
+ .width('100%')
+ }
+ .height('100%')
+ }
+ }
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58d9c312f08e7c9ac01e4d6f2d0a33ddc6188ed9
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts
@@ -0,0 +1,64 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import hilog from '@ohos.hilog';
+import TestRunner from '@ohos.application.testRunner';
+import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
+
+var abilityDelegator = undefined
+var abilityDelegatorArguments = undefined
+
+async function onAbilityCreateCallback() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback');
+}
+
+async function addAbilityMonitorCallback(err: any) {
+ hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? '');
+}
+
+export default class OpenHarmonyTestRunner implements TestRunner {
+ constructor() {
+ }
+
+ onPrepare() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare ');
+ }
+
+ async onRun() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run');
+ abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
+ abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
+ var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility'
+ let lMonitor = {
+ abilityName: testAbilityName,
+ onAbilityCreate: onAbilityCreateCallback,
+ };
+ abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
+ var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName
+ var debug = abilityDelegatorArguments.parameters['-D']
+ if (debug == 'true')
+ {
+ cmd += ' -D'
+ }
+ hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd);
+ abilityDelegator.executeShellCommand(cmd,
+ (err: any, d: any) => {
+ hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? '');
+ hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? '');
+ hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? '');
+ })
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end');
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/module.json5 b/example/ohos/entry/src/ohosTest/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..fab77ce2e0c61e3ad010bab5b27ccbd15f9a8c96
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/module.json5
@@ -0,0 +1,51 @@
+/*
+* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+{
+ "module": {
+ "name": "entry_test",
+ "type": "feature",
+ "description": "$string:module_test_desc",
+ "mainElement": "TestAbility",
+ "deviceTypes": [
+ "phone"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:test_pages",
+ "abilities": [
+ {
+ "name": "TestAbility",
+ "srcEntry": "./ets/testability/TestAbility.ets",
+ "description": "$string:TestAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:TestAbility_label",
+ "exported": true,
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:start_window_background",
+ "skills": [
+ {
+ "actions": [
+ "action.system.home"
+ ],
+ "entities": [
+ "entity.system.home"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/example/ohos/entry/src/ohosTest/resources/base/element/color.json b/example/ohos/entry/src/ohosTest/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/resources/base/element/string.json b/example/ohos/entry/src/ohosTest/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..65d8fa5a7cf54aa3943dcd0214f58d1771bc1f6c
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_test_desc",
+ "value": "test ability description"
+ },
+ {
+ "name": "TestAbility_desc",
+ "value": "the test ability"
+ },
+ {
+ "name": "TestAbility_label",
+ "value": "test label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/resources/base/media/icon.png b/example/ohos/entry/src/ohosTest/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/example/ohos/entry/src/ohosTest/resources/base/media/icon.png differ
diff --git a/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..b7e7343cacb32ce982a45e76daad86e435e054fe
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "testability/pages/Index"
+ ]
+}
diff --git a/example/ohos/hvigor/hvigor-config.json5 b/example/ohos/hvigor/hvigor-config.json5
new file mode 100644
index 0000000000000000000000000000000000000000..541ba35711b75986f9295410ee38fdb8f2572878
--- /dev/null
+++ b/example/ohos/hvigor/hvigor-config.json5
@@ -0,0 +1,20 @@
+/*
+* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+{
+ "modelVersion": "5.0.0",
+ "dependencies": {
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/hvigorfile.ts b/example/ohos/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..38626e385a5b47dd3cba0e1e83c614f091b7cc9e
--- /dev/null
+++ b/example/ohos/hvigorfile.ts
@@ -0,0 +1,21 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { appTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
\ No newline at end of file
diff --git a/example/ohos/oh-package.json5 b/example/ohos/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..eceba12a95b34e122eb70ba92d9dd7afc1f328ef
--- /dev/null
+++ b/example/ohos/oh-package.json5
@@ -0,0 +1,20 @@
+{
+ "modelVersion": "5.0.0",
+ "name": "example",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {
+ "@ohos/flutter_ohos": "file:./har/flutter.har"
+ },
+ "devDependencies": {
+ "@ohos/hypium": "1.0.6"
+ },
+ "overrides": {
+ "@ohos/flutter_ohos": "file:./har/flutter.har",
+ "flutter_downloader": "file:./har/flutter_downloader.har",
+ "@ohos/flutter_module": "file:./entry"
+ }
+}
\ No newline at end of file
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index a81cae347eb2a3146e2402393c11ee2b2f01f451..4df8493912644180cbf1324c66061e91a3d94988 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -4,22 +4,23 @@ version: 1.0.0+1
publish_to: none
environment:
- sdk: ">=3.3.0 <4.0.0"
- flutter: ">=3.19.0"
+ sdk: '>=2.17.0 <4.0.0'
+ flutter: '>=3.0.0'
dependencies:
- device_info_plus: ^10.1.0
+ android_path_provider: ^0.3.0
+ device_info_plus: ^8.0.0
flutter:
sdk: flutter
flutter_downloader:
path: ../
- path_provider: ^2.1.3
- permission_handler: ^11.3.1
+ path_provider: ^2.0.11
+ permission_handler: ^10.0.0
dev_dependencies:
flutter_test:
sdk: flutter
- leancode_lint: ^12.1.0
+ leancode_lint: ^2.0.0+1
flutter:
uses-material-design: true
diff --git a/ios/.gitignore b/ios/.gitignore
deleted file mode 100644
index 710ec6cf1c71f1e8bbddf86c51313b2790667162..0000000000000000000000000000000000000000
--- a/ios/.gitignore
+++ /dev/null
@@ -1,36 +0,0 @@
-.idea/
-.vagrant/
-.sconsign.dblite
-.svn/
-
-.DS_Store
-*.swp
-profile
-
-DerivedData/
-build/
-GeneratedPluginRegistrant.h
-GeneratedPluginRegistrant.m
-
-.generated/
-
-*.pbxuser
-*.mode1v3
-*.mode2v3
-*.perspectivev3
-
-!default.pbxuser
-!default.mode1v3
-!default.mode2v3
-!default.perspectivev3
-
-xcuserdata
-
-*.moved-aside
-
-*.pyc
-*sync/
-Icon?
-.tags*
-
-/Flutter/Generated.xcconfig
diff --git a/ios/Assets/download_tasks.sql b/ios/Assets/download_tasks.sql
index 88cee1a11eef67f912e262a5503f53d295051743..1c20b143d0657efc443ad95423156ebb565c6bf2 100644
Binary files a/ios/Assets/download_tasks.sql and b/ios/Assets/download_tasks.sql differ
diff --git a/ios/Classes/FlutterDownloaderDBManager.h b/ios/Classes/DBManager.h
similarity index 59%
rename from ios/Classes/FlutterDownloaderDBManager.h
rename to ios/Classes/DBManager.h
index 82e069314baa3915da255f4a05b1fc14157ff5d0..616f63d221b3d27f9746df360e1942a49b3c577f 100644
--- a/ios/Classes/FlutterDownloaderDBManager.h
+++ b/ios/Classes/DBManager.h
@@ -1,5 +1,5 @@
//
-// FlutterDownloaderDBManager.h
+// DBManager.h
// Runner
//
// Author: GABRIEL THEODOROPOULOS.
@@ -7,7 +7,7 @@
#import
-@interface FlutterDownloaderDBManager : NSObject
+@interface DBManager : NSObject
@property (nonatomic, strong) NSMutableArray *arrColumnNames;
@@ -19,9 +19,8 @@
-(instancetype)initWithDatabaseFilePath:(NSString *)dbFilePath;
--(NSArray *)loadDataFromDB:(NSString *)query withParameters:(NSArray *)parameters;
+-(NSArray *)loadDataFromDB:(NSString *)query;
-
--(void)executeQuery:(NSString *)query withParameters:(NSArray *)parameters;
+-(void)executeQuery:(NSString *)query;
@end
diff --git a/ios/Classes/FlutterDownloaderDBManager.m b/ios/Classes/DBManager.m
similarity index 69%
rename from ios/Classes/FlutterDownloaderDBManager.m
rename to ios/Classes/DBManager.m
index 1162d08fcd0bcdeccad980c51e501f948133649f..3932d2396a3b8ffbd2370a1ea5511a601c7add6f 100644
--- a/ios/Classes/FlutterDownloaderDBManager.m
+++ b/ios/Classes/DBManager.m
@@ -1,14 +1,14 @@
//
-// FlutterDownloaderDBManager.m
+// DBManager.m
// Runner
//
// Author: GABRIEL THEODOROPOULOS.
//
-#import "FlutterDownloaderDBManager.h"
+#import "DBManager.h"
#import
-@interface FlutterDownloaderDBManager()
+@interface DBManager()
@property (nonatomic, strong) NSString *appDirectory;
@property (nonatomic, strong) NSString *databaseFilePath;
@@ -16,11 +16,11 @@
@property (nonatomic, strong) NSMutableArray *arrResults;
-(void)copyDatabaseIntoAppDirectory;
--(void)runQuery:(const char *)query withParameters:(NSArray *)parameters isQueryExecutable:(BOOL)queryExecutable;
+-(void)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable;
@end
-@implementation FlutterDownloaderDBManager
+@implementation DBManager
@synthesize debug;
@@ -83,9 +83,7 @@
}
}
-
-- (void)runQuery:(const char *)query withParameters:(NSArray *)parameters isQueryExecutable:(BOOL)queryExecutable {
-
+-(void)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{
if (debug) {
NSLog(@"execute query: %s", query);
}
@@ -110,16 +108,16 @@
}
self.arrColumnNames = [[NSMutableArray alloc] init];
+
// Open the database.
int openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database);
- if (openDatabaseResult != SQLITE_OK) {
- if (debug) {
+ if(openDatabaseResult != SQLITE_OK) {
+ if(debug) {
NSLog(@"error opening the database with error no.: %d", openDatabaseResult);
}
return;
}
-
- if (openDatabaseResult == SQLITE_OK) {
+ if(openDatabaseResult == SQLITE_OK) {
if (debug) {
NSLog(@"open DB successfully");
}
@@ -127,114 +125,95 @@
// Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite statement.
sqlite3_stmt *compiledStatement;
- // Prepare the SQL query.
+ // Load all data from database to memory.
int prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL);
- if (prepareStatementResult == SQLITE_OK) {
- // Bind parameters if provided.
- if (parameters != nil && parameters.count > 0) {
- for (int i = 0; i < parameters.count; i++) {
- id parameter = parameters[i];
- int bindResult = SQLITE_OK;
-
- if ([parameter isKindOfClass:[NSString class]]) {
- const char *paramStr = [parameter UTF8String];
- bindResult = sqlite3_bind_text(compiledStatement, i + 1, paramStr, -1, SQLITE_TRANSIENT);
- } else if ([parameter isKindOfClass:[NSNumber class]]) {
- if (strcmp([parameter objCType], @encode(int)) == 0) {
- int intValue = [parameter intValue];
- bindResult = sqlite3_bind_int(compiledStatement, i + 1, intValue);
- } else if (strcmp([parameter objCType], @encode(long long)) == 0) {
- long long longValue = [parameter longLongValue];
- bindResult = sqlite3_bind_int64(compiledStatement, i + 1, longValue);
- }
- }
- if (bindResult != SQLITE_OK) {
- NSLog(@"Error binding parameter at index %d: %s", i, sqlite3_errmsg(sqlite3Database));
- }
- }
- }
-
- // Execute the query.
- if (queryExecutable) {
- int executeQueryResults = sqlite3_step(compiledStatement);
- if (executeQueryResults == SQLITE_DONE) {
- // Keep the affected rows.
- self.affectedRows = sqlite3_changes(sqlite3Database);
-
- // Keep the last inserted row ID.
- self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database);
- } else {
- // If could not execute the query, show the error message on the debugger.
- if (debug) {
- NSLog(@"DB Error: %s", sqlite3_errmsg(sqlite3Database));
- }
- }
- } else {
- // This is the case of loading data from the database.
-
+ if(prepareStatementResult == SQLITE_OK) {
+ // Check if the query is non-executable.
+ if (!queryExecutable){
+ // In this case data must be loaded from the database.
+
// Declare an array to keep the data for each fetched row.
NSMutableArray *arrDataRow;
-
+
// Loop through the results and add them to the results array row by row.
- while (sqlite3_step(compiledStatement) == SQLITE_ROW) {
+ while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Initialize the mutable array that will contain the data of a fetched row.
arrDataRow = [[NSMutableArray alloc] init];
-
+
// Get the total number of columns.
int totalColumns = sqlite3_column_count(compiledStatement);
-
+
// Go through all columns and fetch each column data.
- for (int i = 0; i < totalColumns; i++) {
+ for (int i=0; i 0) {
[self.arrResults addObject:arrDataRow];
}
}
}
-
- // Release the compiled statement from memory.
- sqlite3_finalize(compiledStatement);
- } else {
+ else {
+ // This is the case of an executable query (insert, update, ...).
+
+ // Execute the query.
+ int executeQueryResults = sqlite3_step(compiledStatement);
+ if (executeQueryResults == SQLITE_DONE) {
+ // Keep the affected rows.
+ self.affectedRows = sqlite3_changes(sqlite3Database);
+
+ // Keep the last inserted row ID.
+ self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database);
+ }
+ else {
+ // If could not execute the query show the error message on the debugger.
+ if (debug) {
+ NSLog(@"DB Error: %s", sqlite3_errmsg(sqlite3Database));
+ }
+ }
+ }
+ }
+ else {
// In the database cannot be opened then show the error message on the debugger.
if (debug) {
NSLog(@"%s", sqlite3_errmsg(sqlite3Database));
}
}
-
- // Close the database.
- sqlite3_close(sqlite3Database);
+
+ // Release the compiled statement from memory.
+ sqlite3_finalize(compiledStatement);
}
+
+ // Close the database.
+ sqlite3_close(sqlite3Database);
}
--(NSArray *)loadDataFromDB:(NSString *)query withParameters:(NSArray *)parameters{
+-(NSArray *)loadDataFromDB:(NSString *)query{
// Run the query and indicate that is not executable.
// The query string is converted to a char* object.
- [self runQuery:[query UTF8String] withParameters:parameters isQueryExecutable:NO];
+ [self runQuery:[query UTF8String] isQueryExecutable:NO];
+
// Returned the loaded results.
return (NSArray *)self.arrResults;
}
-
-
-- (void)executeQuery:(NSString *)query withParameters:(NSArray *)parameters {
- // Run the query with parameters.
- [self runQuery:[query UTF8String] withParameters:parameters isQueryExecutable:YES];
+-(void)executeQuery:(NSString *)query{
+ // Run the query and indicate that is executable.
+ [self runQuery:[query UTF8String] isQueryExecutable:YES];
}
@end
diff --git a/ios/Classes/FlutterDownloaderPlugin.m b/ios/Classes/FlutterDownloaderPlugin.m
index ccb3a66aeb28c67c21a0760547df4c60b800537e..e3385b3731cb92d4720168ec88748f2367bf8ca4 100644
--- a/ios/Classes/FlutterDownloaderPlugin.m
+++ b/ios/Classes/FlutterDownloaderPlugin.m
@@ -1,5 +1,5 @@
#import "FlutterDownloaderPlugin.h"
-#import "FlutterDownloaderDBManager.h"
+#import "DBManager.h"
#define STATUS_UNDEFINED 0
#define STATUS_ENQUEUED 1
@@ -11,7 +11,6 @@
#define KEY_URL @"url"
#define KEY_SAVED_DIR @"saved_dir"
-
#define KEY_FILE_NAME @"file_name"
#define KEY_PROGRESS @"progress"
#define KEY_ID @"id"
@@ -35,7 +34,7 @@
FlutterMethodChannel *_mainChannel;
FlutterMethodChannel *_callbackChannel;
NSObject *_registrar;
- FlutterDownloaderDBManager *_dbManager;
+ DBManager *_dbManager;
NSString *_allFilesDownloadedMsg;
NSMutableArray *_eventQueue;
}
@@ -58,6 +57,7 @@ static int64_t _callbackHandle = 0;
static int _step = 10;
static NSMutableDictionary *_runningTaskById = nil;
+
@synthesize databaseQueue;
- (instancetype)init:(NSObject *)registrar;
@@ -93,8 +93,7 @@ static NSMutableDictionary *_runningTaskById =
NSLog(@"database path: %@", dbPath);
}
databaseQueue = dispatch_queue_create("vn.hunghd.flutter_downloader", 0);
-
- _dbManager = [[FlutterDownloaderDBManager alloc] initWithDatabaseFilePath:dbPath];
+ _dbManager = [[DBManager alloc] initWithDatabaseFilePath:dbPath];
if (_runningTaskById == nil) {
_runningTaskById = [[NSMutableDictionary alloc] init];
@@ -292,9 +291,7 @@ static NSMutableDictionary *_runningTaskById =
{
NSArray *args = @[@(_callbackHandle), taskId, status, progress];
if (initialized && _callbackHandle != 0) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [self-> _callbackChannel invokeMethod:@"" arguments:args];
- });
+ [_callbackChannel invokeMethod:@"" arguments:args];
} else {
[_eventQueue addObject:args];
}
@@ -308,7 +305,6 @@ static NSMutableDictionary *_runningTaskById =
});
}
-
- (BOOL)openDocumentWithURL:(NSURL*)url {
if (debug) {
NSLog(@"try to open file in url: %@", url);
@@ -340,69 +336,39 @@ static NSMutableDictionary *_runningTaskById =
}
- (NSURL*)fileUrlOf:(NSString*)taskId taskInfo:(NSDictionary*)taskInfo downloadTask:(NSURLSessionDownloadTask*)downloadTask {
- NSString *filename = taskInfo[KEY_FILE_NAME];
- NSString *suggestedFilename = downloadTask.response.suggestedFilename;
- if (debug) {
- NSLog(@"SuggestedFileName: %@", suggestedFilename);
- }
- // Check if filename is nil or empty
- if (filename == nil || ![filename isKindOfClass:[NSString class]] || [filename isEqualToString:@""]) {
- // If suggestedFilename is empty, use the last path component of the URL as the filename
- filename = [self sanitizeFilename:suggestedFilename];
- }
- // Update the taskInfo with the sanitized filename
- NSMutableDictionary *mutableTaskInfo = [taskInfo mutableCopy];
- mutableTaskInfo[KEY_FILE_NAME] = filename;
-
- // Update the taskInfo
- if ([_runningTaskById objectForKey:taskId]) {
- _runningTaskById[taskId][KEY_FILE_NAME] = filename;
- }
-
- // update DB
- __weak typeof(self) weakSelf = self;
- [self executeInDatabaseQueueForTask:^{
- [weakSelf updateTask:taskId filename:filename];
- }];
-
- return [self fileUrlFromDict:mutableTaskInfo];
-}
-
-- (NSString *)sanitizeFilename:(nullable NSString *)filename {
- // Define a list of allowed characters for filenames
- NSCharacterSet *allowedCharacters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.() "];
+ NSString *filename = taskInfo[KEY_FILE_NAME];
+ NSString *suggestedFilename = downloadTask.response.suggestedFilename;
+ if (debug) {
+ NSLog(@"SuggestedFileName: %@", suggestedFilename);
+ }
- if (filename == nil || [filename isEqual:[NSNull null]] || [filename isEqualToString:@""]) {
- NSString *defaultFilename = @"default_filename";
- return defaultFilename;
- }
- // Create a mutable string to build the sanitized filename
- NSMutableString *sanitizedFilename = [NSMutableString string];
-
- // Iterate over each character in the original filename
- for (NSUInteger i = 0; i < filename.length; i++) {
- unichar character = [filename characterAtIndex:i];
-
- // Check if the character is in the allowed set
- if ([allowedCharacters characterIsMember:character]) {
- // Append the allowed character to the sanitized filename
- [sanitizedFilename appendFormat:@"%C", character];
+ // check filename, if it is empty then we try to extract it from http response or url path
+ if (filename == (NSString*) [NSNull null] || [NULL_VALUE isEqualToString: filename]) {
+ if (suggestedFilename) {
+ filename = suggestedFilename;
} else {
- // Replace forbidden characters with an underscore
- [sanitizedFilename appendString:@"_"];
+ filename = downloadTask.currentRequest.URL.lastPathComponent;
}
+
+ NSMutableDictionary *mutableTask = [taskInfo mutableCopy];
+ [mutableTask setObject:filename forKey:KEY_FILE_NAME];
+
+ // update taskInfo
+ if ([_runningTaskById objectForKey:taskId]) {
+ _runningTaskById[taskId][KEY_FILE_NAME] = filename;
+ }
+
+ // update DB
+ __typeof__(self) __weak weakSelf = self;
+ [self executeInDatabaseQueueForTask:^{
+ [weakSelf updateTask:taskId filename:filename];
+ }];
+
+ return [self fileUrlFromDict:mutableTask];
}
-
- // Ensure the sanitized filename is not empty
- if ([sanitizedFilename isEqualToString:@""]) {
- // Provide a default filename if the sanitized one is empty
- NSString *defaultFilename = @"default_filename";
- sanitizedFilename = [[NSMutableString alloc] initWithString:defaultFilename];
- }
-
- return sanitizedFilename;
-}
+ return [self fileUrlFromDict:taskInfo];
+}
- (NSString*)absoluteSavedDirPath:(NSString*)savedDir {
return [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:savedDir];
@@ -412,7 +378,7 @@ static NSMutableDictionary *_runningTaskById =
if (debug) {
NSLog(@"Absolute savedDir path: %@", absolutePath);
}
-
+
if (absolutePath) {
NSString* documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
if ([absolutePath isEqualToString:documentDirPath]) {
@@ -425,7 +391,7 @@ static NSMutableDictionary *_runningTaskById =
return shortenSavedDirPath != nil ? shortenSavedDirPath : @"";
}
}
-
+
return absolutePath;
}
@@ -436,7 +402,6 @@ static NSMutableDictionary *_runningTaskById =
# pragma mark - Database Accessing
-
- (NSString*) escape:(NSString*) origin revert:(BOOL)revert
{
if ( origin == (NSString *)[NSNull null] )
@@ -448,26 +413,11 @@ static NSMutableDictionary *_runningTaskById =
: [origin stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];
}
-
-- (void)addNewTask:(NSString *)taskId
- url:(NSString *)url
- status:(int)status
- progress:(int)progress
- filename:(NSString *)filename
- savedDir:(NSString *)savedDir
- headers:(NSString *)headers
- resumable:(BOOL)resumable
- showNotification:(BOOL)showNotification
- openFileFromNotification:(BOOL)openFileFromNotification {
-
- headers = [self escape:headers revert:NO];
-
- NSString *query = @"INSERT INTO task (task_id, url, status, progress, file_name, saved_dir, headers, resumable, show_notification, open_file_from_notification, time_created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
- NSString *sanitizedFileName = [self sanitizeFilename:filename];
- NSArray *values = @[taskId, url, @(status), @(progress), sanitizedFileName, savedDir, headers, @(resumable ? 1:0), @(showNotification ? 1 : 0), @(openFileFromNotification ? 1: 0), @([self currentTimeInMilliseconds])];
-
- [_dbManager executeQuery:query withParameters:values];
-
+- (void) addNewTask: (NSString*) taskId url: (NSString*) url status: (int) status progress: (int) progress filename: (NSString*) filename savedDir: (NSString*) savedDir headers: (NSString*) headers resumable: (BOOL) resumable showNotification: (BOOL) showNotification openFileFromNotification: (BOOL) openFileFromNotification
+{
+ headers = [self escape:headers revert:false];
+ NSString *query = [NSString stringWithFormat:@"INSERT INTO task (task_id,url,status,progress,file_name,saved_dir,headers,resumable,show_notification,open_file_from_notification,time_created) VALUES (\"%@\",\"%@\",%d,%d,\"%@\",\"%@\",\"%@\",%d,%d,%d,%lld)", taskId, url, status, progress, filename, savedDir, headers, resumable ? 1 : 0, showNotification ? 1 : 0, openFileFromNotification ? 1 : 0, [self currentTimeInMilliseconds]];
+ [_dbManager executeQuery:query];
if (debug) {
if (_dbManager.affectedRows != 0) {
NSLog(@"Query was executed successfully. Affected rows = %d", _dbManager.affectedRows);
@@ -479,13 +429,8 @@ static NSMutableDictionary *_runningTaskById =
- (void) updateTask: (NSString*) taskId status: (int) status progress: (int) progress
{
-
- NSString *query = @"UPDATE task SET status = ?, progress = ? WHERE task_id = ?";
-
- NSArray *values = @[@(status), @(progress), taskId];
-
- [_dbManager executeQuery:query withParameters:values];
-
+ NSString *query = [NSString stringWithFormat:@"UPDATE task SET status=%d, progress=%d WHERE task_id=\"%@\"", status, progress, taskId];
+ [_dbManager executeQuery:query];
if (debug) {
if (_dbManager.affectedRows != 0) {
NSLog(@"Query was executed successfully. Affected rows = %d", _dbManager.affectedRows);
@@ -495,16 +440,9 @@ static NSMutableDictionary *_runningTaskById =
}
}
-
-
-- (void)updateTask:(NSString *)taskId filename:(NSString *)filename {
- NSString *query = @"UPDATE task SET file_name = ? WHERE task_id = ?";
-
- // Create an array to hold the parameter values
- NSArray *values = @[filename, taskId];
-
- [_dbManager executeQuery:query withParameters:values];
-
+- (void) updateTask: (NSString*) taskId filename: (NSString*) filename {
+ NSString *query = [NSString stringWithFormat:@"UPDATE task SET file_name=\"%@\" WHERE task_id=\"%@\"", filename, taskId];
+ [_dbManager executeQuery:query];
if (debug) {
if (_dbManager.affectedRows != 0) {
NSLog(@"Query was executed successfully. Affected rows = %d", _dbManager.affectedRows);
@@ -514,18 +452,9 @@ static NSMutableDictionary *_runningTaskById =
}
}
-
-- (void)updateTask:(NSString *)taskId
- status:(int)status
- progress:(int)progress
- resumable:(BOOL)resumable {
-
- NSString *query = @"UPDATE task SET status = ?, progress = ?, resumable = ? WHERE task_id = ?";
-
- NSArray *values = @[@(status), @(progress), @(resumable ? 1 : 0), taskId];
-
- [_dbManager executeQuery:query withParameters:values];
-
+- (void) updateTask: (NSString*) taskId status: (int) status progress: (int) progress resumable: (BOOL) resumable {
+ NSString *query = [NSString stringWithFormat:@"UPDATE task SET status=%d, progress=%d, resumable=%d WHERE task_id=\"%@\"", status, progress, resumable ? 1 : 0, taskId];
+ [_dbManager executeQuery:query];
if (debug) {
if (_dbManager.affectedRows != 0) {
NSLog(@"Query was executed successfully. Affected rows = %d", _dbManager.affectedRows);
@@ -535,17 +464,9 @@ static NSMutableDictionary *_runningTaskById =
}
}
-- (void)updateTask:(NSString *)currentTaskId
- newTaskId:(NSString *)newTaskId
- status:(int)status
- resumable:(BOOL)resumable {
-
- NSString *query = @"UPDATE task SET task_id = ?, status = ?, resumable = ?, time_created = ? WHERE task_id = ?";
-
- NSArray *values = @[newTaskId, @(status), @(resumable ? 1 : 0), @([self currentTimeInMilliseconds]), currentTaskId];
-
- [_dbManager executeQuery:query withParameters:values];
-
+- (void) updateTask: (NSString*) currentTaskId newTaskId: (NSString*) newTaskId status: (int) status resumable: (BOOL) resumable {
+ NSString *query = [NSString stringWithFormat:@"UPDATE task SET task_id=\"%@\", status=%d, resumable=%d, time_created=%lld WHERE task_id=\"%@\"", newTaskId, status, resumable ? 1 : 0, [self currentTimeInMilliseconds], currentTaskId];
+ [_dbManager executeQuery:query];
if (debug) {
if (_dbManager.affectedRows != 0) {
NSLog(@"Query was executed successfully. Affected rows = %d", _dbManager.affectedRows);
@@ -555,15 +476,11 @@ static NSMutableDictionary *_runningTaskById =
}
}
-- (void)updateTask:(NSString *)taskId resumable:(BOOL)resumable {
- NSString *query = @"UPDATE task SET resumable = ? WHERE task_id = ?";
-
- NSArray *values = @[@(resumable ? 1 : 0), taskId];
-
- [_dbManager executeQuery:query withParameters:values];
-
+- (void) updateTask: (NSString*) taskId resumable: (BOOL) resumable
+{
+ NSString *query = [NSString stringWithFormat:@"UPDATE task SET resumable=%d WHERE task_id=\"%@\"", resumable ? 1 : 0, taskId];
+ [_dbManager executeQuery:query];
if (debug) {
- NSLog(@"Update \n%@\n\n%@",taskId,query);
if (_dbManager.affectedRows != 0) {
NSLog(@"Query was executed successfully. Affected rows = %d", _dbManager.affectedRows);
} else {
@@ -572,34 +489,28 @@ static NSMutableDictionary *_runningTaskById =
}
}
-- (void)deleteTask:(NSString *)taskId {
- NSString *query = @"DELETE FROM task WHERE task_id = ?";
-
- NSArray *values = @[taskId];
-
- [_dbManager executeQuery:query withParameters:values];
-
+- (void) deleteTask: (NSString*) taskId {
+ NSString *query = [NSString stringWithFormat:@"DELETE FROM task WHERE task_id=\"%@\"", taskId];
+ [_dbManager executeQuery:query];
if (debug) {
- NSLog(@"Delete \n%@\n\n%@",taskId,query);
if (_dbManager.affectedRows != 0) {
NSLog(@"Query was executed successfully. Affected rows = %d", _dbManager.affectedRows);
} else {
NSLog(@"Could not execute the query.");
}
-
}
}
-- (NSArray*)loadAllTasks{
+- (NSArray*)loadAllTasks
+{
NSString *query = @"SELECT * FROM task";
- NSArray *records = [[NSArray alloc] initWithArray:[_dbManager loadDataFromDB:query withParameters:@[]]];
+ NSArray *records = [[NSArray alloc] initWithArray:[_dbManager loadDataFromDB:query]];
if (debug) {
NSLog(@"Load tasks successfully");
}
NSMutableArray *results = [NSMutableArray new];
for(NSArray *record in records) {
NSDictionary *task = [self taskDictFromRecordArray:record];
- NSLog(@"Task found in load all tasks \n%@", task);
if (debug) {
NSLog(@"%@", task);
}
@@ -610,7 +521,7 @@ static NSMutableDictionary *_runningTaskById =
- (NSArray*)loadTasksWithRawQuery: (NSString*)query
{
- NSArray *records = [[NSArray alloc] initWithArray:[_dbManager loadDataFromDB:query withParameters:@[]]];
+ NSArray *records = [[NSArray alloc] initWithArray:[_dbManager loadDataFromDB:query]];
if (debug) {
NSLog(@"Load tasks successfully");
}
@@ -621,21 +532,21 @@ static NSMutableDictionary *_runningTaskById =
return results;
}
-- (NSDictionary *)loadTaskWithId:(NSString *)taskId {
- // Check the task in memory-cache first
+- (NSDictionary*)loadTaskWithId:(NSString*)taskId
+{
+ // check task in memory-cache first
if ([_runningTaskById objectForKey:taskId]) {
return [_runningTaskById objectForKey:taskId];
} else {
- NSString *query = @"SELECT * FROM task WHERE task_id = ? ORDER BY id DESC LIMIT 1";
- NSArray *parameters = @[taskId];
- NSArray *records = [[NSArray alloc] initWithArray:[_dbManager loadDataFromDB:query withParameters:parameters]];
+ NSString *query = [NSString stringWithFormat:@"SELECT * FROM task WHERE task_id = \"%@\" ORDER BY id DESC LIMIT 1", taskId];
+ NSArray *records = [[NSArray alloc] initWithArray:[_dbManager loadDataFromDB:query]];
if (debug) {
NSLog(@"Load task successfully");
}
if (records != nil && [records count] > 0) {
NSArray *record = [records firstObject];
NSDictionary *task = [self taskDictFromRecordArray:record];
- // Checking if the task is valid
+ // checking if task is valid
if (task.count == 0) {
return nil;
}
@@ -659,7 +570,7 @@ static NSMutableDictionary *_runningTaskById =
NSString *filename = [record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"file_name"]];
NSString *savedDir = [self absoluteSavedDirPath:[record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"saved_dir"]]];
NSString *headers = @"";
- // in certain cases, headers column might not be available and will cause NSRangeException
+ // in certain cases, headers column might not be available and will cause NSRangeException
@try {
NSString *rawHeaders = [record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"headers"]];
headers = [self escape:rawHeaders revert:true];
@@ -722,11 +633,11 @@ static NSMutableDictionary *_runningTaskById =
NSString *headers = call.arguments[KEY_HEADERS];
NSNumber *showNotification = call.arguments[KEY_SHOW_NOTIFICATION];
NSNumber *openFileFromNotification = call.arguments[KEY_OPEN_FILE_FROM_NOTIFICATION];
-
+
NSURLSessionDownloadTask *task = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers];
-
+
NSString *taskId = [self identifierForTask:task];
-
+
[_runningTaskById setObject: [NSMutableDictionary dictionaryWithObjectsAndKeys:
urlString, KEY_URL,
fileName, KEY_FILE_NAME,
@@ -738,7 +649,7 @@ static NSMutableDictionary *_runningTaskById =
@(STATUS_ENQUEUED), KEY_STATUS,
@(0), KEY_PROGRESS, nil]
forKey:taskId];
-
+
__typeof__(self) __weak weakSelf = self;
[self executeInDatabaseQueueForTask:^{
@@ -1044,11 +955,6 @@ static NSMutableDictionary *_runningTaskById =
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
- // Ensure the destination directory exists
- NSURL *destinationDirectory = [destinationURL URLByDeletingLastPathComponent];
- [fileManager createDirectoryAtURL:destinationDirectory withIntermediateDirectories:YES attributes:nil error:nil];
-
- // Remove the existing file if it exists
if ([fileManager fileExistsAtPath:[destinationURL path]]) {
[fileManager removeItemAtURL:destinationURL error:nil];
}
@@ -1073,6 +979,7 @@ static NSMutableDictionary *_runningTaskById =
}];
}
}
+
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
diff --git a/lib/src/callback_dispatcher.dart b/lib/src/callback_dispatcher.dart
index deb9b81a2e04a76db2140f7fe72dbf5abb218bee..674b4b60a34e482a16953cce587fbba54f01ce5c 100644
--- a/lib/src/callback_dispatcher.dart
+++ b/lib/src/callback_dispatcher.dart
@@ -1,3 +1,4 @@
+import 'dart:io';
import 'dart:ui';
import 'package:flutter/services.dart';
@@ -15,18 +16,18 @@ void callbackDispatcher() {
..setMethodCallHandler((call) async {
final args = call.arguments as List;
final handle = CallbackHandle.fromRawHandle(args[0] as int);
- final id = args[1] as String;
- final status = args[2] as int;
- final progress = args[3] as int;
-
- final callback = PluginUtilities.getCallbackFromHandle(handle) as void
- Function(String id, int status, int progress)?;
+ final callback = PluginUtilities.getCallbackFromHandle(handle);
if (callback == null) {
- // The callback wasn't registered. Ignore.
- return;
+ // ignore: avoid_print
+ print('fatal error: could not find callback');
+ exit(1);
}
+ final id = args[1] as String;
+ final status = args[2] as int;
+ final progress = args[3] as int;
+
callback(id, status, progress);
})
..invokeMethod('didInitializeDispatcher');
diff --git a/lib/src/downloader.dart b/lib/src/downloader.dart
index adbe45044ec621993724a84c0aea403915e47423..ecb1effb77e170d16ce35c5bb491858cb9b3d7f7 100644
--- a/lib/src/downloader.dart
+++ b/lib/src/downloader.dart
@@ -1,5 +1,3 @@
-// ignore_for_file: avoid_dynamic_calls
-
import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -104,7 +102,7 @@ class FlutterDownloader {
int timeout = 15000,
}) async {
assert(_initialized, 'plugin flutter_downloader is not initialized');
- assert(Directory(savedDir).existsSync(), 'savedDir does not exist');
+ // assert(Directory(savedDir).existsSync(), 'savedDir does not exist');
try {
final taskId = await _channel.invokeMethod('enqueue', {
@@ -151,9 +149,11 @@ class FlutterDownloader {
return result.map(
(dynamic item) {
+ // item as Map; // throws
+
return DownloadTask(
taskId: item['task_id'] as String,
- status: DownloadTaskStatus.fromInt(item['status'] as int),
+ status: DownloadTaskStatus(item['status'] as int),
progress: item['progress'] as int,
url: item['url'] as String,
filename: item['file_name'] as String?,
@@ -209,9 +209,11 @@ class FlutterDownloader {
return result.map(
(dynamic item) {
+ // item as Map; // throws
+
return DownloadTask(
taskId: item['task_id'] as String,
- status: DownloadTaskStatus.fromInt(item['status'] as int),
+ status: DownloadTaskStatus(item['status'] as int),
progress: item['progress'] as int,
url: item['url'] as String,
filename: item['file_name'] as String?,
diff --git a/lib/src/models.dart b/lib/src/models.dart
index 95f985a3577eafa2f20849a4b9f529ab8b9df4a4..5801e31b782b01dd34e44fa8c574fbcf8874653e 100644
--- a/lib/src/models.dart
+++ b/lib/src/models.dart
@@ -1,48 +1,49 @@
/// Defines a set of possible states which a [DownloadTask] can be in.
@pragma('vm:entry-point')
-enum DownloadTaskStatus {
+class DownloadTaskStatus {
+ /// Creates a new [DownloadTaskStatus].
+ const DownloadTaskStatus(int value) : _value = value;
+
+ final int _value;
+
+ /// The underlying index of this status.
+ int get value => _value;
+
/// Status of the task is either unknown or corrupted.
- undefined,
+ static const undefined = DownloadTaskStatus(0);
/// The task is scheduled, but is not running yet.
- enqueued,
+ static const enqueued = DownloadTaskStatus(1);
/// The task is in progress.
- running,
+ static const running = DownloadTaskStatus(2);
/// The task has completed successfully.
- complete,
+ static const complete = DownloadTaskStatus(3);
/// The task has failed.
- failed,
+ static const failed = DownloadTaskStatus(4);
/// The task was canceled and cannot be resumed.
- canceled,
-
- /// The task was paused and can be resumed
- paused;
-
- /// Creates a new [DownloadTaskStatus] from an [int].
- factory DownloadTaskStatus.fromInt(int value) {
- switch (value) {
- case 0:
- return DownloadTaskStatus.undefined;
- case 1:
- return DownloadTaskStatus.enqueued;
- case 2:
- return DownloadTaskStatus.running;
- case 3:
- return DownloadTaskStatus.complete;
- case 4:
- return DownloadTaskStatus.failed;
- case 5:
- return DownloadTaskStatus.canceled;
- case 6:
- return DownloadTaskStatus.paused;
- default:
- throw ArgumentError('Invalid value: $value');
+ static const canceled = DownloadTaskStatus(5);
+
+ /// The task was paused and can be resumed.
+ static const paused = DownloadTaskStatus(6);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) {
+ return true;
}
+
+ return other is DownloadTaskStatus && other._value == _value;
}
+
+ @override
+ int get hashCode => _value.hashCode;
+
+ @override
+ String toString() => 'DownloadTaskStatus($_value)';
}
/// Encapsulates all information of a single download task.
@@ -88,35 +89,4 @@ class DownloadTask {
@override
String toString() =>
'DownloadTask(taskId: $taskId, status: $status, progress: $progress, url: $url, filename: $filename, savedDir: $savedDir, timeCreated: $timeCreated, allowCellular: $allowCellular)';
-
- @override
- bool operator ==(Object other) {
- if (identical(this, other)) {
- return true;
- }
-
- return other is DownloadTask &&
- other.taskId == taskId &&
- other.status == status &&
- other.progress == progress &&
- other.url == url &&
- other.filename == filename &&
- other.savedDir == savedDir &&
- other.timeCreated == timeCreated &&
- other.allowCellular == allowCellular;
- }
-
- @override
- int get hashCode {
- return Object.hash(
- taskId,
- status,
- progress,
- url,
- filename,
- savedDir,
- timeCreated,
- allowCellular,
- );
- }
}
diff --git a/ohos/.gitignore b/ohos/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5
--- /dev/null
+++ b/ohos/.gitignore
@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test
\ No newline at end of file
diff --git a/ohos/BuildProfile.ets b/ohos/BuildProfile.ets
new file mode 100644
index 0000000000000000000000000000000000000000..fde84590b4cebb8b75f4d038e944f120c643fe2c
--- /dev/null
+++ b/ohos/BuildProfile.ets
@@ -0,0 +1,32 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/**
+ * Use these variables when you tailor your ArkTS code. They must be of the const type.
+ */
+export const HAR_VERSION = '1.0.0';
+export const BUILD_MODE_NAME = 'debug';
+export const DEBUG = true;
+export const TARGET_NAME = 'default';
+
+/**
+ * BuildProfile Class is used only for compatibility purposes.
+ */
+export default class BuildProfile {
+ static readonly HAR_VERSION = HAR_VERSION;
+ static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
+ static readonly DEBUG = DEBUG;
+ static readonly TARGET_NAME = TARGET_NAME;
+}
\ No newline at end of file
diff --git a/ohos/Index.ets b/ohos/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..5aaa61da8ee2c937722b9db8b244301d7cde4220
--- /dev/null
+++ b/ohos/Index.ets
@@ -0,0 +1,17 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { FlutterDownloaderPlugin } from './src/main/ets/components/FlutterDownloaderPlugin';
+export default FlutterDownloaderPlugin;
\ No newline at end of file
diff --git a/ohos/build-profile.json5 b/ohos/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..bfd753ac3c70d1afd9ba0a7955a7938d12375a62
--- /dev/null
+++ b/ohos/build-profile.json5
@@ -0,0 +1,31 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": false,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ },
+ "consumerFiles": [
+ "./consumer-rules.txt"
+ ]
+ }
+ },
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ios/Assets/.gitkeep b/ohos/consumer-rules.txt
similarity index 100%
rename from ios/Assets/.gitkeep
rename to ohos/consumer-rules.txt
diff --git a/ohos/hvigorfile.ts b/ohos/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d3c13e1193d9899a3e95a527fcd50034a649877c
--- /dev/null
+++ b/ohos/hvigorfile.ts
@@ -0,0 +1,16 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+export { harTasks } from '@ohos/hvigor-ohos-plugin';
diff --git a/ohos/obfuscation-rules.txt b/ohos/obfuscation-rules.txt
new file mode 100644
index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b
--- /dev/null
+++ b/ohos/obfuscation-rules.txt
@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation
\ No newline at end of file
diff --git a/ohos/oh-package.json5 b/ohos/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..9e6f3a270ab467e0a1d5efd594aa945577246aae
--- /dev/null
+++ b/ohos/oh-package.json5
@@ -0,0 +1,11 @@
+{
+ "name": "flutter_downloader",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "Index.ets",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ohos/flutter_ohos": "file:../har/flutter.har"
+ }
+}
diff --git a/ohos/src/main/ets/components/DownloadStatus.ets b/ohos/src/main/ets/components/DownloadStatus.ets
new file mode 100644
index 0000000000000000000000000000000000000000..1d56ac604e304543cf66b7fdf7ef3a451f020fd0
--- /dev/null
+++ b/ohos/src/main/ets/components/DownloadStatus.ets
@@ -0,0 +1,24 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+export enum DownloadStatus {
+ UNDEFINED,
+ ENQUEUED,
+ RUNNING,
+ COMPLETE,
+ FAILED,
+ CANCELED,
+ PAUSED
+}
\ No newline at end of file
diff --git a/ohos/src/main/ets/components/DownloadTask.ets b/ohos/src/main/ets/components/DownloadTask.ets
new file mode 100644
index 0000000000000000000000000000000000000000..93e0d51724676092b13bec0de1719620c34e1f9a
--- /dev/null
+++ b/ohos/src/main/ets/components/DownloadTask.ets
@@ -0,0 +1,33 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { DownloadStatus } from './DownloadStatus';
+
+export interface DownloadTask {
+ primaryId: number;
+ taskId: string;
+ status: DownloadStatus;
+ progress: number;
+ url: string;
+ filename: string;
+ savedDir: string;
+ headers: string;
+ mimeType: string;
+ resumable: boolean;
+ showNotification: boolean;
+ openFileFromNotification: boolean;
+ timeCreated: number;
+ allowCellular: boolean;
+}
\ No newline at end of file
diff --git a/ohos/src/main/ets/components/FileUtils.ets b/ohos/src/main/ets/components/FileUtils.ets
new file mode 100644
index 0000000000000000000000000000000000000000..56ec32bf3299d6b71db52cc2714821c288e4d756
--- /dev/null
+++ b/ohos/src/main/ets/components/FileUtils.ets
@@ -0,0 +1,109 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+const mimeTypeMap: Map = new Map([
+ ["aac", "audio/aac"],
+ ["abw", "application/x-abiword"],
+ ["apng", "image/apng"],
+ ["arc", "application/x-freearc"],
+ ["avif", "image/avif"],
+ ["avi", "video/x-msvideo"],
+ ["azw", "application/vnd.amazon.ebook"],
+ ["bin", "application/octet-stream"],
+ ["bmp", "image/bmp"],
+ ["bz", "application/x-bzip"],
+ ["bz2", "application/x-bzip2"],
+ ["cda", "application/x-cdf"],
+ ["csh", "application/x-csh"],
+ ["css", "text/css"],
+ ["csv", "text/csv"],
+ ["doc", "application/msword"],
+ ["docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
+ ["eot", "application/vnd.ms-fontobject"],
+ ["epub", "application/epub+zip"],
+ ["gz", "application/gzip"],
+ ["gif", "image/gif"],
+ ["htm", "text/html"],
+ ["html", "text/html"],
+ ["ico", "image/vnd.microsoft.icon"],
+ ["ics", "text/calendar"],
+ ["jar", "application/java-archive"],
+ ["jpeg", "image/jpeg"],
+ ["jpg", "image/jpeg"],
+ ["js", "text/javascript"],
+ ["json", "application/json"],
+ ["jsonld", "application/ld+json"],
+ ["mid", "audio/midi"],
+ ["midi", "audio/x-midi"],
+ ["mjs", "text/javascript"],
+ ["mp3", "audio/mpeg"],
+ ["mp4", "video/mp4"],
+ ["mpeg", "video/mpeg"],
+ ["mpkg", "application/vnd.apple.installer+xml"],
+ ["odp", "application/vnd.oasis.opendocument.presentation"],
+ ["ods", "application/vnd.oasis.opendocument.spreadsheet"],
+ ["odt", "application/vnd.oasis.opendocument.text"],
+ ["oga", "audio/ogg"],
+ ["ogv", "video/ogg"],
+ ["ogx", "application/ogg"],
+ ["opus", "audio/opus"],
+ ["otf", "font/otf"],
+ ["png", "image/png"],
+ ["pdf", "application/pdf"],
+ ["php", "application/x-httpd-php"],
+ ["ppt", "application/vnd.ms-powerpoint"],
+ ["pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"],
+ ["rar", "application/vnd.rar"],
+ ["rtf", "application/rtf"],
+ ["sh", "application/x-sh"],
+ ["svg", "image/svg+xml"],
+ ["tar", "application/x-tar"],
+ ["tif", "image/tiff"],
+ ["tiff", "image/tiff"],
+ ["ts", "video/mp2t"],
+ ["ttf", "font/ttf"],
+ ["txt", "text/plain"],
+ ["vsd", "application/vnd.visio"],
+ ["wav", "audio/wav"],
+ ["weba", "audio/webm"],
+ ["webm", "video/webm"],
+ ["webp", "image/webp"],
+ ["woff", "font/woff"],
+ ["woff2", "font/woff2"],
+ ["xhtml", "application/xhtml+xml"],
+ ["xls", "application/vnd.ms-excel"],
+ ["xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
+ ["xml", "application/xml"],
+ ["xul", "application/vnd.mozilla.xul+xml"],
+ ["zip", "application/zip"],
+ ["3gp", "video/3gpp"],
+ ["3g2", "video/3gpp2"],
+ ["7z", "application/x-7z-compressed"],
+]);
+
+export function getMimeType(filename: string): string {
+ let newFilename = filename;
+ let index = newFilename.lastIndexOf('/');
+ if (index !== -1) {
+ newFilename = newFilename.substring(index + 1, newFilename.length);
+ }
+ index = newFilename.lastIndexOf('.');
+ if (index == -1) {
+ return "";
+ }
+ let fileExtension = newFilename.substring(index + 1, newFilename.length);
+ let mimeType = mimeTypeMap.get(fileExtension);
+ return mimeType == undefined ? "" : mimeType;
+}
\ No newline at end of file
diff --git a/ohos/src/main/ets/components/FlutterDownloaderPlugin.ets b/ohos/src/main/ets/components/FlutterDownloaderPlugin.ets
new file mode 100644
index 0000000000000000000000000000000000000000..19af95eaaf9e7dd77b4f18f737f1916226134e21
--- /dev/null
+++ b/ohos/src/main/ets/components/FlutterDownloaderPlugin.ets
@@ -0,0 +1,552 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import {
+ AbilityAware,
+ AbilityPluginBinding,
+ Any,
+ FlutterPlugin,
+ FlutterPluginBinding,
+ MethodCall,
+ MethodCallHandler
+} from '@ohos/flutter_ohos';
+import MethodChannel, { MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel';
+import preferences from '@ohos.data.preferences';
+import fs from '@ohos.file.fs';
+import { TaskDao } from './TaskDao';
+import { TaskDbHelper } from './TaskDbHelper';
+import { DownloadStatus } from './DownloadStatus';
+import { ValuesBucket } from '@kit.ArkData';
+import { TaskEntry } from './TaskEntry';
+import { fileUri, picker } from '@kit.CoreFileKit';
+import { common, Want, wantConstant } from '@kit.AbilityKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { request } from '@kit.BasicServicesKit';
+import Logger from './Logger';
+
+const invalidTaskId = "invalid_task_id";
+const invalidStatus = "invalid_status";
+const invalidData = "invalid_data";
+const TAG = 'FlutterDownloaderPlugin';
+const CHANNEL = "vn.hunghd/downloader";
+
+export class FlutterDownloaderPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
+ public static readonly SHARED_PREFERENCES_KEY = 'vn.hunghd.downloader.pref';
+ public static readonly CALLBACK_DISPATCHER_HANDLE_KEY = "callback_dispatcher_handle_key";
+ private flutterChannel: MethodChannel | null = null;
+ private flutterPluginBinding: FlutterPluginBinding | null = null;
+ private taskDao: TaskDao | null = null;
+ private context: common.UIAbilityContext | null = null;
+ private callbackHandle: number = 0;
+ private debugMode: number = 0;
+ private tasks: Set = new Set();
+
+ onAttachedToEngine(binding: FlutterPluginBinding): void {
+ this.flutterPluginBinding = binding;
+ }
+
+ onDetachedFromEngine(binding: FlutterPluginBinding): void {
+ this.onDetachedFromAbility();
+ this.context = null
+ this.flutterPluginBinding = null;
+ }
+
+ onAttachedToAbility(binding: AbilityPluginBinding): void {
+ if (this.flutterChannel != null) {
+ return;
+ }
+ this.context = binding.getAbility().context;
+ this.flutterChannel =
+ new MethodChannel(this.flutterPluginBinding!.getBinaryMessenger(), CHANNEL);
+ this.flutterChannel.setMethodCallHandler(this);
+ this.taskDao = new TaskDao(TaskDbHelper.getInstance(this.context))
+ }
+
+ onDetachedFromAbility(): void {
+ if (this.flutterChannel != null) {
+ this.flutterChannel.setMethodCallHandler(null);
+ this.flutterChannel = null;
+ }
+ }
+
+ getUniqueClassName(): string {
+ return TAG;
+ }
+
+ onMethodCall(call: MethodCall, result: MethodResult): void {
+ Logger.info(TAG, `method: ${call.method}`);
+ switch (call.method) {
+ case 'initialize':
+ this.initialize(call, result);
+ break;
+ case 'registerCallback':
+ this.registerCallback(call, result);
+ break;
+ case 'enqueue':
+ this.enqueue(call, result);
+ break;
+ case 'loadTasks':
+ this.loadTasks(call, result);
+ break;
+ case 'loadTasksWithRawQuery':
+ this.loadTasksWithRawQuery(call, result);
+ break;
+ case 'cancel':
+ this.cancel(call, result);
+ break;
+ case 'cancelAll':
+ this.cancelAll(call, result);
+ break;
+ case 'pause':
+ this.pause(call, result);
+ break;
+ case 'resume':
+ this.resume(call, result);
+ break;
+ case 'retry':
+ this.retry(call, result);
+ break;
+ case 'open':
+ this.open(call, result);
+ break;
+ case 'remove':
+ this.remove(call, result);
+ break;
+ default:
+ result.notImplemented();
+ break;
+ }
+ }
+
+ private remove(call: MethodCall, result: MethodResult) {
+ const taskId = this.requireArgument(call, "task_id");
+ const shouldDeleteContent = this.requireArgument(call, "should_delete_content");
+ const task = this.taskDao!.loadTask(taskId);
+ if (task != null) {
+ if (task.status == DownloadStatus.ENQUEUED || task.status == DownloadStatus.RUNNING) {
+ request.agent.remove(taskId, (err: BusinessError) => {
+ if (err) {
+ Logger.error(TAG, `Failed to removing a download task, Code: ${err.code}, message: ${err.message}`);
+ return;
+ }
+ Logger.info(TAG, `Succeeded in removing a download task.`);
+ });
+ }
+ if (shouldDeleteContent) {
+ const saveFilePath = task.savedDir + '/' + task.filename;
+ if (fs.accessSync(saveFilePath)) {
+ fs.unlink(saveFilePath).then(() => {
+ Logger.info(TAG, "remove file succeed");
+ }).catch((err: BusinessError) => {
+ Logger.error(TAG, "remove file failed with error message: " + err.message + ", error code: " + err.code);
+ });
+ }
+ }
+ this.taskDao!.deleteTask(taskId)
+ result.success(null);
+ } else {
+ result.error(invalidTaskId, "not found task corresponding to given task id", null);
+ }
+ }
+
+ private open(call: MethodCall, result: MethodResult) {
+ const taskId = this.requireArgument(call, "task_id");
+ const task = this.taskDao!.loadTask(taskId);
+ if (task == null) {
+ result.error(invalidTaskId, "not found task with id $taskId", null);
+ return;
+ }
+
+ if (task.status != DownloadStatus.COMPLETE) {
+ result.error(invalidStatus, "only completed tasks can be opened", null);
+ return;
+ }
+ this.openFile(`${task.savedDir}/${task.filename}`, task.mimeType).then(openResult => {
+ result.success(openResult);
+ }).catch((e: BusinessError) => {
+ result.success(false);
+ });
+ }
+
+ private async retry(call: MethodCall, result: MethodResult): Promise {
+ const taskId = this.requireArgument(call, "task_id");
+ const task = this.taskDao!.loadTask(taskId);
+ if (task != null) {
+ if (task.status == DownloadStatus.FAILED || task.status == DownloadStatus.CANCELED) {
+ const newTaskId =
+ await this.downloadFile(task.url, task.headers, task.filename!, task.savedDir, task.allowCellular,
+ task.showNotification, task.openFileFromNotification);
+ result.success(newTaskId);
+ this.sendUpdateProgress(newTaskId, DownloadStatus.ENQUEUED, task.progress);
+ const values: ValuesBucket = {};
+ values[TaskEntry.COLUMN_NAME_TASK_ID] = newTaskId;
+ values[TaskEntry.COLUMN_NAME_STATUS] = DownloadStatus.ENQUEUED;
+ values[TaskEntry.COLUMN_NAME_PROGRESS] = task.progress;
+ values[TaskEntry.COLUMN_NAME_RESUMABLE] = 0;
+ this.taskDao!.updateTask(taskId, values);
+ } else {
+ result.error(invalidStatus, "only failed and canceled task can be retried", null);
+ }
+ } else {
+ result.error(invalidTaskId, "not found task corresponding to given task id", null);
+ }
+ }
+
+ private resume(call: MethodCall, result: MethodResult) {
+ const taskId = this.requireArgument(call, "task_id");
+ const task = this.taskDao!.loadTask(taskId);
+ if (task != null) {
+ if (task.status == DownloadStatus.PAUSED) {
+ request.agent.getTask(this.context!, taskId).then((downloadTask: request.agent.Task) => {
+ downloadTask.resume((err: BusinessError) => {
+ if (err) {
+ Logger.error(TAG, `Failed to resume the download task, Code: ${err.code}, message: ${err.message}`);
+ result.error(
+ invalidData,
+ `Failed to resume the download task, Code: ${err.code}, message: ${err.message}`,
+ null
+ )
+ return;
+ }
+ Logger.info(TAG, `Succeeded in resuming a download task. `);
+ result.success(taskId);
+ })
+ })
+ } else {
+ Logger.error(TAG, "only paused task can be resumed");
+ result.error(invalidStatus, "only paused task can be resumed", null);
+ }
+ } else {
+ Logger.error(TAG, "not found task corresponding to given task id");
+ result.error(invalidTaskId, "not found task corresponding to given task id", null);
+ }
+ }
+
+ private pause(call: MethodCall, result: MethodResult) {
+ const taskId = this.requireArgument(call, "task_id");
+ request.agent.getTask(this.context!, taskId).then((task: request.agent.Task) => {
+ task.pause((err: BusinessError) => {
+ if (err) {
+ Logger.error(TAG, `Failed to pause the download task, Code: ${err.code}, message: ${err.message}`);
+ return;
+ }
+ Logger.info(TAG, `Succeeded in pausing a download task. `);
+ })
+ })
+ result.success(null);
+ }
+
+ private cancel(call: MethodCall, result: MethodResult) {
+ const taskId = this.requireArgument(call, "task_id");
+ this.cancelTask(taskId);
+ result.success(null);
+ }
+
+ private cancelAll(call: MethodCall, result: MethodResult) {
+ for (const taskId of this.tasks) {
+ this.cancelTask(taskId);
+ }
+ result.success(null);
+ }
+
+ private loadTasksWithRawQuery(call: MethodCall, result: MethodResult) {
+ const query = this.requireArgument(call, "query");
+ const tasks = this.taskDao!.loadTasksWithRawQuery(query);
+ const array: Array> = new Array();
+ for (const task of tasks) {
+ const item: Record = {
+ "task_id": task.taskId,
+ "status": task.status,
+ "progress": task.progress,
+ "url": task.url,
+ "file_name": task.filename,
+ "saved_dir": task.savedDir,
+ "time_created": task.timeCreated,
+ "allow_cellular": task.allowCellular
+ }
+ array.push(item);
+ }
+ result.success(array);
+ }
+
+ private async loadTasks(call: MethodCall, result: MethodResult) {
+ const tasks = await this.taskDao!.loadAllTasks();
+ const array: Array> = new Array();
+ for (const task of tasks) {
+ const item: Record = {
+ "task_id": task.taskId,
+ "status": task.status,
+ "progress": task.progress,
+ "url": task.url,
+ "file_name": task.filename,
+ "saved_dir": task.savedDir,
+ "time_created": task.timeCreated,
+ "allow_cellular": task.allowCellular
+ }
+ array.push(item);
+ this.tasks.add(task.taskId);
+ }
+ result.success(array);
+ }
+
+ private requireArgument(call: MethodCall, key: string): T {
+ const value: Any = call.argument(key);
+ if (value === null || value === undefined) {
+ throw new Error(`Required key '${key}' was null`);
+ }
+ return value as T; // 使用类型断言返回值
+ }
+
+ private async enqueue(call: MethodCall, result: MethodResult): Promise {
+ const url = this.requireArgument(call, 'url');
+ const savedDir = this.requireArgument(call, 'saved_dir');
+ const filename: string = call.argument('file_name') ?? url.substring(url.lastIndexOf("/") + 1, url.length);
+ const headers = this.requireArgument(call, 'headers');
+ const showNotification = this.requireArgument(call, 'show_notification');
+ const openFileFromNotification = this.requireArgument(call, 'open_file_from_notification');
+ const allowCellular = this.requireArgument(call, 'allow_cellular');
+ const taskId =
+ await this.downloadFile(url, headers, filename, savedDir, allowCellular, showNotification,
+ openFileFromNotification);
+ result.success(taskId);
+ this.sendUpdateProgress(taskId, DownloadStatus.ENQUEUED, 0);
+ this.taskDao!.insertOrUpdateNewTask(
+ taskId,
+ url,
+ DownloadStatus.ENQUEUED,
+ 0,
+ filename,
+ savedDir,
+ headers,
+ showNotification,
+ openFileFromNotification,
+ allowCellular
+ );
+ return Promise.resolve();
+ }
+
+ private registerCallback(call:
+ MethodCall, result:
+ MethodResult
+ ): void {
+ const args: number[] = call.args as number[];
+ this.callbackHandle = args[0];
+ result.success(null);
+ }
+
+ private initialize(call:
+ MethodCall, result:
+ MethodResult
+ ): void {
+ const args: number[] = call.args as number[];
+ this.callbackHandle = args[0];
+ this.debugMode = args[1];
+ preferences.getPreferencesSync(this.context, { name: FlutterDownloaderPlugin.SHARED_PREFERENCES_KEY })
+ .putSync(FlutterDownloaderPlugin.CALLBACK_DISPATCHER_HANDLE_KEY, this.callbackHandle);
+ result.success(null)
+ }
+
+ private sendUpdateProgress(id: string, status: DownloadStatus, progress: number): void {
+ const args: Map = new Map();
+ args["task_id"] = id
+ args["status"] = status
+ args["progress"] = progress
+ this.flutterChannel!.invokeMethod("updateProgress", args)
+ }
+
+ private async openFile(savedFile: string, mimeType: string): Promise {
+ let uri = fileUri.getUriFromPath(savedFile);
+ const want: Want = {
+ action: 'ohos.want.action.viewData',
+ flags: wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION,
+ uri: uri,
+ type: mimeType
+ }
+ try {
+ await this.context!.startAbility(want);
+ return Promise.resolve(true);
+ } catch (err) {
+ // 处理入参错误异常
+ let code = (err as BusinessError).code;
+ let message = (err as BusinessError).message;
+ Logger.error(TAG, `startAbility failed, code is ${code}, message is ${message}`);
+ return Promise.resolve(false);
+ }
+ }
+
+ private cancelTask(taskId: string): void {
+ request.agent.getTask(this.context!, taskId).then((task: request.agent.Task) => {
+ task.stop((err: BusinessError) => {
+ if (err) {
+ Logger.error(TAG, `Failed to stop the download task, Code: ${err.code}, message: ${err.message}`);
+ return;
+ }
+ Logger.info(TAG, `Succeeded in stopping a download task. `);
+ const values: ValuesBucket = {};
+ values[TaskEntry.COLUMN_NAME_STATUS] = DownloadStatus.CANCELED;
+ values[TaskEntry.COLUMN_NAME_PROGRESS] = -1
+ this.taskDao!.updateTask(taskId, values);
+ request.agent.remove(taskId, (err: BusinessError) => {
+ if (err) {
+ Logger.error(TAG, `Failed to removing a download task, Code: ${err.code}, message: ${err.message}`);
+ return;
+ }
+ Logger.info(TAG, `Succeeded in removing a download task. taskId: ${taskId}`);
+ });
+ });
+ });
+ }
+
+ private callback(taskId: string, filename: string, status: DownloadStatus,
+ progress: request.agent.Progress, isResume?: number): void {
+ const task = this.taskDao!.loadTask(taskId);
+ if (task != null) {
+ let lastProgress = 0;
+ if (status == DownloadStatus.COMPLETE) {
+ lastProgress = 100;
+ } else if (status == DownloadStatus.FAILED) {
+ lastProgress = -1;
+ } else {
+ lastProgress = Math.trunc(progress.processed * 100 / progress.sizes[0]);
+ }
+ const values: ValuesBucket = {};
+ values[TaskEntry.COLUMN_NAME_FILE_NAME] = filename;
+ values[TaskEntry.COLUMN_NAME_STATUS] = status;
+ values[TaskEntry.COLUMN_NAME_PROGRESS] = lastProgress;
+ if (!isResume) {
+ values[TaskEntry.COLUMN_NAME_RESUMABLE] = isResume!;
+ }
+ this.taskDao!.updateTask(taskId, values);
+ }
+ }
+
+ private renameSavedFile(filename: string): string {
+ // 匹配文件名结尾的 "(数字)"
+ const regex = /\((\d+)\)$/;
+
+ // 找到最后一个点的位置
+ const lastDotIndex = filename.lastIndexOf('.');
+
+ const baseName = lastDotIndex === -1 ? filename : filename.substring(0, lastDotIndex); // 文件名
+ const extension = lastDotIndex === -1 ? '' : filename.substring(lastDotIndex); // 包含点的扩展名
+
+ // 如果基本名称的结尾匹配到 "(数字)",则替换
+ if (regex.test(baseName)) {
+ return baseName.replace(regex, (match, number: string) => {
+ const newNumber = parseInt(number, 10) + 1; // 数字加 1
+ return `(${newNumber})`; // 返回新的 "(新数字)"
+ }) + extension; // 返回新的文件名
+ } else {
+ // 如果没有匹配到 "(数字)",则在扩展名之前添加 "(1)"
+ return `${baseName}(1)${extension}`;
+ }
+ }
+
+ private async downloadFile(
+ url: string,
+ headers: string,
+ filename: string,
+ savedDir: string,
+ allowCellular: boolean,
+ showNotification: boolean,
+ openFileFromNotification: boolean
+ ): Promise {
+ const tempDir = this.context!.getApplicationContext().tempDir;
+ const tempFile = `${tempDir}/${filename}`;
+ const documentViewPicker = new picker.DocumentViewPicker(this.context!);
+ const documentSaveOptions = new picker.DocumentSaveOptions();
+ documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD;
+ const documentSaveResult = await documentViewPicker.save(documentSaveOptions)
+ const downloadDirUri = documentSaveResult[0];
+ const downloadDir = new fileUri.FileUri(downloadDirUri).path;
+ const saveFileDir = `${downloadDir}/${savedDir}`;
+ if (!fs.accessSync(saveFileDir)) fs.mkdirSync(`${saveFileDir}`, true);
+ let savedFile = `${saveFileDir}/${filename}`;
+ while (fs.accessSync(savedFile)) {
+ filename = this.renameSavedFile(filename);
+ savedFile = `${saveFileDir}/${filename}`;
+ }
+ const requestConfig: request.agent.Config = {
+ action: request.agent.Action.DOWNLOAD,
+ url: url,
+ saveas: tempFile,
+ metered: allowCellular,
+ overwrite: true,
+ gauge: showNotification
+ };
+
+ const downloadTask = await request.agent.create(this.context!, requestConfig);
+ const taskId = downloadTask.tid;
+ downloadTask.start((err: BusinessError) => {
+ if (err) {
+ Logger.error(TAG, `Failed to start the download task, Code: ${err.code}, message: ${err.message}`);
+ return;
+ }
+ Logger.info(TAG, `Succeeded in starting a download task.`);
+ this.tasks.add(taskId);
+ });
+
+ downloadTask.on('progress', (progress) => {
+ Logger.info(TAG, `downloader running`);
+ this.callback(taskId, filename, DownloadStatus.RUNNING, progress);
+ });
+
+ downloadTask.on('pause', (progress) => {
+ Logger.info(TAG, `downloader paused`);
+ this.callback(taskId, filename, DownloadStatus.PAUSED, progress, 1);
+ });
+
+ downloadTask.on('resume', (progress) => {
+ Logger.info(TAG, `downloader resumed`);
+ this.callback(taskId, filename, DownloadStatus.RUNNING, progress, 0);
+ });
+
+ downloadTask.on('failed', (progress) => {
+ Logger.info(TAG, `downloader failed`);
+ this.callback(taskId, filename, DownloadStatus.FAILED, progress);
+ });
+
+ downloadTask.on('remove', () => {
+ this.tasks.delete(taskId);
+ });
+
+ downloadTask.on('completed', () => {
+ Logger.info(TAG, `Request download completed`);
+ fs.moveFileSync(tempFile, savedFile, 0);
+ const task = this.taskDao!.loadTask(taskId);
+ if (task != null) {
+ if (openFileFromNotification) {
+ const mimeType = task.mimeType;
+ this.openFile(savedFile, mimeType);
+ }
+ const values: ValuesBucket = {};
+ values[TaskEntry.COLUMN_NAME_SAVED_DIR] = saveFileDir;
+ values[TaskEntry.COLUMN_NAME_STATUS] = DownloadStatus.COMPLETE;
+ values[TaskEntry.COLUMN_NAME_PROGRESS] = 100;
+ this.taskDao!.updateTask(taskId, values);
+ }
+
+ request.agent.remove(taskId, (err: BusinessError) => {
+ if (err) {
+ Logger.error(TAG, `Failed to removing a download task, Code: ${err.code}, message: ${err.message}`);
+ return;
+ }
+ Logger.info(TAG, `Succeeded in removing a download task.`);
+ })
+ })
+
+ return Promise.resolve(taskId);
+ }
+}
\ No newline at end of file
diff --git a/ohos/src/main/ets/components/Logger.ts b/ohos/src/main/ets/components/Logger.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a5d2b4d0e45b1de036fd731b324b7eba5ee99dd0
--- /dev/null
+++ b/ohos/src/main/ets/components/Logger.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import hilog from '@ohos.hilog';
+
+class Logger {
+ private domain: number;
+ private prefix: string;
+ private format: string = "%{public}s, %{public}s";
+ constructor(prefix: string) {
+ this.prefix = prefix;
+ this.domain = 0xFF00;
+ }
+ debug(...args: string[]) {
+ hilog.debug(this.domain, this.prefix, this.format, args);
+ }
+ info(...args: string[]) {
+ hilog.info(this.domain, this.prefix, this.format, args);
+ }
+ warn(...args: string[]) {
+ hilog.warn(this.domain, this.prefix, this.format, args);
+ }
+ error(...args: string[]) {
+ hilog.error(this.domain, this.prefix, this.format, args);
+ }
+}
+export default new Logger('[Flutter_Downloader]');
\ No newline at end of file
diff --git a/ohos/src/main/ets/components/TaskDao.ets b/ohos/src/main/ets/components/TaskDao.ets
new file mode 100644
index 0000000000000000000000000000000000000000..3696d6c2bc6b7c1b03a4ed30c18cceef894374e5
--- /dev/null
+++ b/ohos/src/main/ets/components/TaskDao.ets
@@ -0,0 +1,235 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { DownloadStatus } from './DownloadStatus';
+import { TaskDbHelper } from './TaskDbHelper';
+import { TaskEntry } from './TaskEntry';
+import { relationalStore, ValuesBucket } from '@kit.ArkData';
+import { BusinessError } from '@kit.BasicServicesKit';
+import Logger from './Logger';
+import { DownloadTask } from './DownloadTask';
+import { getMimeType } from './FileUtils';
+
+const TAG = 'TaskDao';
+
+export class TaskDao {
+ private dbHelper: TaskDbHelper | null = null;
+ private projection: string[] = [
+ TaskEntry.COLUMN_NAME_ID,
+ TaskEntry.COLUMN_NAME_TASK_ID,
+ TaskEntry.COLUMN_NAME_PROGRESS,
+ TaskEntry.COLUMN_NAME_STATUS,
+ TaskEntry.COLUMN_NAME_URL,
+ TaskEntry.COLUMN_NAME_FILE_NAME,
+ TaskEntry.COLUMN_NAME_SAVED_DIR,
+ TaskEntry.COLUMN_NAME_HEADERS,
+ TaskEntry.COLUMN_NAME_MIME_TYPE,
+ TaskEntry.COLUMN_NAME_RESUMABLE,
+ TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION,
+ TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION,
+ TaskEntry.COLUMN_NAME_TIME_CREATED,
+ TaskEntry.COLUMN_ALLOW_CELLULAR,
+ ];
+
+ constructor(dbHelper: TaskDbHelper) {
+ this.dbHelper = dbHelper; // 将 dbHelper 作为实例变量保存
+ }
+
+ async insertOrUpdateNewTask(
+ taskId: string,
+ url: string,
+ status: DownloadStatus,
+ progress: number,
+ filename: string,
+ savedDir: string,
+ headers: string,
+ showNotification: boolean,
+ openFileFromNotification: boolean,
+ allowCellular: boolean,
+ ): Promise {
+ let db = this.dbHelper!.getRdbStore();
+ if (db == null) {
+ return;
+ }
+ let values: ValuesBucket = {};
+ values[TaskEntry.COLUMN_NAME_TASK_ID] = taskId;
+ values[TaskEntry.COLUMN_NAME_URL] = url;
+ values[TaskEntry.COLUMN_NAME_STATUS] = status;
+ values[TaskEntry.COLUMN_NAME_PROGRESS] = progress;
+ values[TaskEntry.COLUMN_NAME_FILE_NAME] = filename;
+ values[TaskEntry.COLUMN_NAME_SAVED_DIR] = savedDir;
+ values[TaskEntry.COLUMN_NAME_HEADERS] = headers;
+ values[TaskEntry.COLUMN_NAME_MIME_TYPE] = getMimeType(filename);
+ values[TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION] = showNotification ? 1 : 0;
+ values[TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION] = openFileFromNotification ? 1 : 0;
+ values[TaskEntry.COLUMN_NAME_RESUMABLE] = 0;
+ values[TaskEntry.COLUMN_NAME_TIME_CREATED] = Date.now();
+ values[TaskEntry.COLUMN_ALLOW_CELLULAR] = allowCellular ? 1 : 0;
+ db.beginTransaction();
+ try {
+ let rows = await db.insert(TaskEntry.TABLE_NAME, values,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE);
+ Logger.info(TAG, `insertOrUpdateNewTask row count: ${rows}`);
+ db.commit();
+ } catch (e) {
+ let err: BusinessError = e as BusinessError
+ Logger.error(TAG, `insertOrUpdateNewTask failed, err.code: ${err.code}, err.message: ${err.message}`);
+ return Promise.reject(e);
+ }
+ return Promise.resolve();
+ }
+
+ async loadAllTasks(): Promise> {
+ let db = this.dbHelper!.getRdbStore();
+ let result = new Array();
+ if (db != null) {
+ try {
+ let predicates = new relationalStore.RdbPredicates(TaskEntry.TABLE_NAME);
+ let resultSet = await db.query(predicates, this.projection);
+ Logger.info(TAG, `ResultSet row count: ${resultSet.rowCount}`);
+ while (resultSet.goToNextRow()) {
+ result.push(this.parseResultSet(resultSet));
+ }
+ // 释放数据集的内存
+ resultSet.close();
+ } catch (e) {
+ let err: BusinessError = e as BusinessError
+ Logger.error(TAG, `loadAllTasks failed, err.code: ${err.code}, err.message: ${err.message}`);
+ Promise.reject(e);
+ }
+ }
+ Logger.info(TAG, `ResultSet: ${JSON.stringify(result)}`);
+ return Promise.resolve(result);
+ }
+
+ loadTasksWithRawQuery(query: string): Array {
+ let db = this.dbHelper!.getRdbStore();
+ let result = new Array();
+ if (db != null) {
+ try {
+ let resultSet = db.querySqlSync(query);
+ Logger.info(TAG, `ResultSet row count: ${resultSet.rowCount}`);
+ while (resultSet.goToNextRow()) {
+ result.push(this.parseResultSet(resultSet));
+ }
+ // 释放数据集的内存
+ resultSet.close();
+ } catch (e) {
+ let err: BusinessError = e as BusinessError;
+ Logger.error(TAG, `loadTasksWithRawQuery failed, err.code: ${err.code}, err.message: ${err.message}`);
+ }
+ }
+ return result;
+ }
+
+ loadTask(taskId: string): DownloadTask | null {
+ let db = this.dbHelper!.getRdbStore();
+ let result: DownloadTask | null = null;
+ if (db != null) {
+ try {
+ let predicates = new relationalStore.RdbPredicates(TaskEntry.TABLE_NAME);
+ predicates.equalTo(TaskEntry.COLUMN_NAME_TASK_ID, taskId);
+ predicates.orderByDesc(TaskEntry.COLUMN_NAME_ID);
+ predicates.limitAs(1);
+ let resultSet = db.querySync(predicates);
+ while (resultSet.goToNextRow()) {
+ result = this.parseResultSet(resultSet);
+ }
+ // 释放数据集的内存
+ resultSet.close();
+ } catch (e) {
+ let err: BusinessError = e as BusinessError;
+ Logger.error(TAG, `loadTasksWithRawQuery failed, err.code: ${err.code}, err.message: ${err.message}`);
+ }
+ }
+ return result;
+ }
+
+ async updateTask(taskId: string, values: ValuesBucket): Promise {
+ let db = this.dbHelper!.getRdbStore();
+ if (db == null) {
+ return;
+ }
+ db.beginTransaction();
+ try {
+ let predicates = new relationalStore.RdbPredicates(TaskEntry.TABLE_NAME);
+ predicates.equalTo(TaskEntry.COLUMN_NAME_TASK_ID, taskId);
+ let rows = await db.update(values, predicates);
+ Logger.info(TAG, `Updated row count: ${rows}`);
+ db.commit();
+ } catch (e) {
+ let err: BusinessError = e as BusinessError
+ Logger.error(TAG, `updateTask err, err.code: ${err.code}, err.message: ${err.message}`);
+ return Promise.reject(`updateTask failed`);
+ }
+ return Promise.resolve();
+ }
+
+ async deleteTask(taskId: string): Promise {
+ let db = this.dbHelper!.getRdbStore();
+ if (db == null) {
+ return;
+ }
+ db.beginTransaction();
+ try {
+ let predicates = new relationalStore.RdbPredicates(TaskEntry.TABLE_NAME);
+ predicates.equalTo(TaskEntry.COLUMN_NAME_TASK_ID, taskId);
+ let rows = await db.delete(predicates);
+ Logger.info(TAG, `Deleted row count: ${rows}`);
+ db.commit();
+ } catch (e) {
+ let err: BusinessError = e as BusinessError
+ Logger.error(TAG, `deleteTask err, err.code: ${err.code}, err.message: ${err.message}`);
+ return Promise.reject(`deleteTask failed`);
+ }
+ return Promise.resolve();
+ }
+
+ private parseResultSet(resultSet: relationalStore.ResultSet): DownloadTask {
+ const primaryId = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_ID));
+ const task_id = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_TASK_ID));
+ const status = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_STATUS));
+ const progress = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_PROGRESS));
+ const url = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_URL));
+ const filename = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_FILE_NAME));
+ const savedDir = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_SAVED_DIR));
+ const headers = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_HEADERS));
+ const mimeType = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_MIME_TYPE));
+ const resumable = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_RESUMABLE));
+ const showNotification = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION));
+ const clickToOpenDownloadedFile =
+ resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION));
+ const timeCreated = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_TIME_CREATED));
+ const allowCellular = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_ALLOW_CELLULAR));
+
+ let task: DownloadTask = {
+ primaryId: primaryId,
+ taskId: task_id,
+ status: status,
+ progress: progress,
+ url: url,
+ filename: filename,
+ savedDir: savedDir,
+ headers: headers,
+ mimeType: mimeType,
+ resumable: resumable == 1,
+ showNotification: showNotification == 1,
+ openFileFromNotification: clickToOpenDownloadedFile == 1,
+ timeCreated: timeCreated,
+ allowCellular: allowCellular == 1
+ }
+ return task;
+ }
+}
\ No newline at end of file
diff --git a/ohos/src/main/ets/components/TaskDbHelper.ets b/ohos/src/main/ets/components/TaskDbHelper.ets
new file mode 100644
index 0000000000000000000000000000000000000000..0dc6d37ecb08dd307af927e6b411aa298c6ae3fb
--- /dev/null
+++ b/ohos/src/main/ets/components/TaskDbHelper.ets
@@ -0,0 +1,69 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import relationalStore from '@ohos.data.relationalStore';
+import { Context } from '@kit.AbilityKit';
+import { TaskEntry } from './TaskEntry';
+
+export class TaskDbHelper {
+ private static instance: TaskDbHelper;
+ private static readonly DATABASE_VERSION = 4;
+ private static readonly DATABASE_NAME = "download_tasks.db";
+ private static readonly SQL_CREATE_ENTRIES =
+ "CREATE TABLE IF NOT EXISTS " + TaskEntry.TABLE_NAME + " (" +
+ TaskEntry.COLUMN_NAME_ID + " INTEGER PRIMARY KEY," +
+ TaskEntry.COLUMN_NAME_TASK_ID + " VARCHAR(256), " +
+ TaskEntry.COLUMN_NAME_URL + " TEXT, " +
+ TaskEntry.COLUMN_NAME_STATUS + " INTEGER DEFAULT 0, " +
+ TaskEntry.COLUMN_NAME_PROGRESS + " INTEGER DEFAULT 0, " +
+ TaskEntry.COLUMN_NAME_FILE_NAME + " TEXT, " +
+ TaskEntry.COLUMN_NAME_SAVED_DIR + " TEXT, " +
+ TaskEntry.COLUMN_NAME_HEADERS + " TEXT, " +
+ TaskEntry.COLUMN_NAME_MIME_TYPE + " VARCHAR(128), " +
+ TaskEntry.COLUMN_NAME_RESUMABLE + " TINYINT DEFAULT 0, " +
+ TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION + " TINYINT DEFAULT 0, " +
+ TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION + " TINYINT DEFAULT 0, " +
+ TaskEntry.COLUMN_NAME_TIME_CREATED + " INTEGER DEFAULT 0, " +
+ TaskEntry.COLUMN_ALLOW_CELLULAR + " TINYINT DEFAULT 1" +
+ ")";
+ private static readonly SQL_DELETE_ENTRIES = `DROP TABLE IF EXISTS ${TaskEntry.TABLE_NAME}`;
+ private rdbStore: relationalStore.RdbStore | null = null;
+
+ constructor(context: Context) {
+ if (TaskDbHelper.instance) {
+ return TaskDbHelper.instance; // 单例模式
+ }
+ relationalStore.getRdbStore(context, {
+ name: TaskDbHelper.DATABASE_NAME,
+ securityLevel: relationalStore.SecurityLevel.S1
+ }).then(rdbStore => {
+ this.rdbStore = rdbStore;
+ this.rdbStore.version = TaskDbHelper.DATABASE_VERSION;
+ this.rdbStore.executeSql(TaskDbHelper.SQL_CREATE_ENTRIES);
+ TaskDbHelper.instance = this; // 设置单例
+ })
+ }
+
+ static getInstance(context?: Context): TaskDbHelper {
+ if (!TaskDbHelper.instance) {
+ TaskDbHelper.instance = new TaskDbHelper(context!);
+ }
+ return TaskDbHelper.instance;
+ }
+
+ getRdbStore(): relationalStore.RdbStore | null {
+ return this.rdbStore;
+ }
+}
\ No newline at end of file
diff --git a/ohos/src/main/ets/components/TaskEntry.ets b/ohos/src/main/ets/components/TaskEntry.ets
new file mode 100644
index 0000000000000000000000000000000000000000..56aa130cbf15b78027921c72351cb2e4752bd739
--- /dev/null
+++ b/ohos/src/main/ets/components/TaskEntry.ets
@@ -0,0 +1,32 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+export class TaskEntry {
+ public static readonly TABLE_NAME = "task";
+ public static readonly COLUMN_NAME_ID = "_id";
+ public static readonly COLUMN_NAME_TASK_ID = "task_id";
+ public static readonly COLUMN_NAME_STATUS = "status";
+ public static readonly COLUMN_NAME_PROGRESS = "progress";
+ public static readonly COLUMN_NAME_URL = "url";
+ public static readonly COLUMN_NAME_SAVED_DIR = "saved_dir";
+ public static readonly COLUMN_NAME_FILE_NAME = "file_name";
+ public static readonly COLUMN_NAME_MIME_TYPE = "mime_type";
+ public static readonly COLUMN_NAME_RESUMABLE = "resumable";
+ public static readonly COLUMN_NAME_HEADERS = "headers";
+ public static readonly COLUMN_NAME_SHOW_NOTIFICATION = "show_notification";
+ public static readonly COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION = "open_file_from_notification";
+ public static readonly COLUMN_NAME_TIME_CREATED = "time_created";
+ public static readonly COLUMN_ALLOW_CELLULAR = "allow_cellular";
+}
\ No newline at end of file
diff --git a/ohos/src/main/module.json5 b/ohos/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..d8ff7a7effa1344e6d1d57cd99a3f5e0ff1f33ca
--- /dev/null
+++ b/ohos/src/main/module.json5
@@ -0,0 +1,11 @@
+{
+ "module": {
+ "name": "flutter_downloader",
+ "type": "har",
+ "deviceTypes": [
+ "default",
+ "tablet",
+ "2in1"
+ ]
+ }
+}
diff --git a/ohos/src/test/List.test.ets b/ohos/src/test/List.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..e3f1904373eb8182a07290c5b9a1287d1a9dd219
--- /dev/null
+++ b/ohos/src/test/List.test.ets
@@ -0,0 +1,20 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import localUnitTest from './LocalUnit.test';
+
+export default function testsuite() {
+ localUnitTest();
+}
\ No newline at end of file
diff --git a/ohos/src/test/LocalUnit.test.ets b/ohos/src/test/LocalUnit.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..13b2128b8428b9368a257451baf665e38434bbac
--- /dev/null
+++ b/ohos/src/test/LocalUnit.test.ets
@@ -0,0 +1,48 @@
+/*
+* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD.
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function localUnitTest() {
+ describe('localUnitTest', () => {
+ // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+ beforeAll(() => {
+ // Presets an action, which is performed only once before all test cases of the test suite start.
+ // This API supports only one parameter: preset action function.
+ });
+ beforeEach(() => {
+ // Presets an action, which is performed before each unit test case starts.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: preset action function.
+ });
+ afterEach(() => {
+ // Presets a clear action, which is performed after each unit test case ends.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: clear action function.
+ });
+ afterAll(() => {
+ // Presets a clear action, which is performed after all test cases of the test suite end.
+ // This API supports only one parameter: clear action function.
+ });
+ it('assertContain', 0, () => {
+ // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+ let a = 'abc';
+ let b = 'b';
+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+ expect(a).assertContain(b);
+ expect(a).assertEqual(a);
+ });
+ });
+}
\ No newline at end of file
diff --git a/pubspec.yaml b/pubspec.yaml
index efba96638ca542a9b6298b75c44438ee0c96efca..e7cf9cc1f3d5a7d1a608f32189264ffc2f8a11cf 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,9 +1,9 @@
name: flutter_downloader
description: Powerful plugin making it easy to download files.
-version: 1.11.7
+version: 1.10.5
repository: https://github.com/fluttercommunity/flutter_downloader
issue_tracker: https://github.com/fluttercommunity/flutter_downloader/issues
-maintainer: Salma (@salmaahhmed)
+maintainer: Bartek Pacia (@bartekpacia)
flutter:
plugin:
@@ -13,10 +13,12 @@ flutter:
pluginClass: FlutterDownloaderPlugin
ios:
pluginClass: FlutterDownloaderPlugin
+ ohos:
+ pluginClass: FlutterDownloaderPlugin
environment:
- sdk: ">=3.3.0 <4.0.0"
- flutter: ">=3.19.0"
+ sdk: '>=2.17.0 <4.0.0'
+ flutter: '>=3.0.0'
dependencies:
flutter:
@@ -25,4 +27,4 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
- leancode_lint: ^12.1.0
+ leancode_lint: ^2.0.0+1