fix(deps): refresh and document native dependencies
This commit is contained in:
@@ -58,8 +58,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/getsentry/sentry-cocoa",
|
"location" : "https://github.com/getsentry/sentry-cocoa",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "16cd512711375fa73f25ae5e373f596bdf4251ae",
|
"revision" : "dad229c665bfd043c5d80ac7aa77717cbd19a1c3",
|
||||||
"version" : "8.58.0"
|
"version" : "8.58.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,28 @@
|
|||||||
## 2.0.0 (plezy vendored patch)
|
## 3.1.0 (Plezy vendored patch)
|
||||||
|
|
||||||
Vendored from pub.dev saf_util 2.0.0 with a Result-lifecycle fix:
|
Vendored from `flutter-cavalry/saf_util` at
|
||||||
pending picker replies are take-and-clear (no reply to an already-answered
|
`e300a03ea34b49414b42f309e02531ece57cd0d1`. The local Result-lifecycle patch
|
||||||
Result → no "Reply already submitted" crash), unrelated activity request
|
take-and-clears picker replies, ignores unrelated request codes, reattaches the
|
||||||
codes no longer consume the pending picker, and failed picker launches
|
listener across configuration changes, and clears state after launch failures.
|
||||||
clear the stale pending state.
|
When updating, reapply those behaviors to every picker request code and run the
|
||||||
|
Android plugin tests.
|
||||||
|
|
||||||
|
## 3.1.0
|
||||||
|
|
||||||
|
- Set `length` to -1 when the length is unknown, matching Android `DocumentFile.length()`.
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
- **Breaking**: Migrate to AGP 9.
|
||||||
|
- **Breaking**: Minimum Flutter version is 3.44.0.
|
||||||
|
|
||||||
|
## 2.2.0
|
||||||
|
|
||||||
|
- Add `pickMedia`.
|
||||||
|
|
||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
- Add the `throws` parameter to `stat`.
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
include: package:flutter_lints/flutter.yaml
|
include: package:mgenware_dart_lints/flutter_lints.yaml
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/libraries
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.cxx
|
||||||
@@ -2,14 +2,14 @@ group = "com.fluttercavalry.saf_util"
|
|||||||
version = "1.0-SNAPSHOT"
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
val kotlinVersion = "2.2.20"
|
val kotlinVersion = "2.3.20"
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.11.1")
|
classpath("com.android.tools.build:gradle:9.0.1")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,6 @@ allprojects {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -36,10 +35,6 @@ android {
|
|||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
getByName("main") {
|
getByName("main") {
|
||||||
java.srcDirs("src/main/kotlin")
|
java.srcDirs("src/main/kotlin")
|
||||||
@@ -71,6 +66,12 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.documentfile:documentfile:1.1.0")
|
implementation("androidx.documentfile:documentfile:1.1.0")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
rootProject.name = 'saf_util'
|
rootProject.name = "saf_util"
|
||||||
|
|||||||
+293
-179
@@ -12,8 +12,9 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import android.provider.MediaStore
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||||
@@ -22,19 +23,21 @@ import io.flutter.plugin.common.MethodChannel
|
|||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
import io.flutter.plugin.common.MethodChannel.Result
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
import io.flutter.plugin.common.PluginRegistry
|
import io.flutter.plugin.common.PluginRegistry
|
||||||
|
import java.io.File
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
/** SafUtilPlugin */
|
/** SafUtilPlugin */
|
||||||
class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
class SafUtilPlugin :
|
||||||
/// The MethodChannel that will the communication between Flutter and native Android
|
FlutterPlugin,
|
||||||
///
|
MethodCallHandler,
|
||||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
ActivityAware {
|
||||||
/// when the Flutter Engine is detached from the Activity
|
// / The MethodChannel that will the communication between Flutter and native Android
|
||||||
private lateinit var channel : MethodChannel
|
// /
|
||||||
|
// / This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||||
|
// / when the Flutter Engine is detached from the Activity
|
||||||
|
private lateinit var channel: MethodChannel
|
||||||
|
|
||||||
private lateinit var context: Context
|
private lateinit var context: Context
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
@@ -44,20 +47,19 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
private var pendingArguments: PendingArguments? = null
|
private var pendingArguments: PendingArguments? = null
|
||||||
private val requestCodeOpenDocumentTree = 1001
|
private val requestCodeOpenDocumentTree = 1001
|
||||||
private val requestCodeOpenFiles = 1002
|
private val requestCodeOpenFiles = 1002
|
||||||
|
private val requestCodePickMedia = 1003
|
||||||
private val activityResultListener = PluginRegistry.ActivityResultListener { requestCode, resultCode, data ->
|
private val activityResultListener = PluginRegistry.ActivityResultListener { requestCode, resultCode, data ->
|
||||||
onActivityResult(requestCode, resultCode, data)
|
onActivityResult(requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
|
private val fdMap = mutableMapOf<Int, ParcelFileDescriptor>()
|
||||||
|
|
||||||
/// Atomically takes ownership of the pending picker state. Every reply to a
|
/** Takes ownership before replying so a Result can never be answered twice. */
|
||||||
/// pending Result must go through this so no already-answered Result is ever
|
|
||||||
/// left behind to be answered again ("Reply already submitted" crashes).
|
|
||||||
private fun takePendingResult(): Result? {
|
private fun takePendingResult(): Result? {
|
||||||
val result = pendingResult
|
val result = pendingResult
|
||||||
pendingResult = null
|
pendingResult = null
|
||||||
pendingArguments = null
|
pendingArguments = null
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
private val fdMap = mutableMapOf<Int, ParcelFileDescriptor>()
|
|
||||||
|
|
||||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "saf_util")
|
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "saf_util")
|
||||||
@@ -94,7 +96,10 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
activity = null
|
activity = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
override fun onMethodCall(
|
||||||
|
call: MethodCall,
|
||||||
|
result: Result
|
||||||
|
) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"list" -> {
|
"list" -> {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
@@ -105,25 +110,27 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
val dir = documentFileFromUri(uri, true) ?: throw Exception("Failed to get DocumentFile from $uri")
|
val dir = documentFileFromUri(uri, true) ?: throw Exception("Failed to get DocumentFile from $uri")
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
val mUri = dir.uri
|
val mUri = dir.uri
|
||||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
|
val childrenUri =
|
||||||
mUri,
|
DocumentsContract.buildChildDocumentsUriUsingTree(
|
||||||
DocumentsContract.getDocumentId(mUri)
|
mUri,
|
||||||
)
|
DocumentsContract.getDocumentId(mUri)
|
||||||
|
)
|
||||||
val results = mutableListOf<Map<String, Any?>>()
|
val results = mutableListOf<Map<String, Any?>>()
|
||||||
|
|
||||||
cursor = resolver.query(
|
cursor =
|
||||||
childrenUri,
|
resolver.query(
|
||||||
arrayOf(
|
childrenUri,
|
||||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
arrayOf(
|
||||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||||
DocumentsContract.Document.COLUMN_SIZE,
|
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||||
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
DocumentsContract.Document.COLUMN_SIZE,
|
||||||
DocumentsContract.Document.COLUMN_LAST_MODIFIED
|
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||||
),
|
DocumentsContract.Document.COLUMN_LAST_MODIFIED
|
||||||
null,
|
),
|
||||||
null,
|
null,
|
||||||
null
|
null,
|
||||||
)
|
null
|
||||||
|
)
|
||||||
|
|
||||||
while (cursor?.moveToNext() == true) {
|
while (cursor?.moveToNext() == true) {
|
||||||
val documentId = cursor.getString(0)
|
val documentId = cursor.getString(0)
|
||||||
@@ -137,13 +144,14 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
val isDirectory = DocumentsContract.Document.MIME_TYPE_DIR == mimeType
|
val isDirectory = DocumentsContract.Document.MIME_TYPE_DIR == mimeType
|
||||||
|
|
||||||
// Create a dictionary (map) for each file with its details
|
// Create a dictionary (map) for each file with its details
|
||||||
val fileInfo = fileObjMap(
|
val fileInfo =
|
||||||
documentUri,
|
fileObjMap(
|
||||||
isDirectory,
|
documentUri,
|
||||||
fileName,
|
isDirectory,
|
||||||
fileSize,
|
fileName,
|
||||||
lastModified,
|
fileSize,
|
||||||
)
|
lastModified
|
||||||
|
)
|
||||||
results.add(fileInfo)
|
results.add(fileInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,9 +199,22 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
try {
|
try {
|
||||||
val uri = call.argument<String>("uri") as String
|
val uri = call.argument<String>("uri") as String
|
||||||
val isDir = call.argument<Boolean>("isDir")
|
val isDir = call.argument<Boolean>("isDir")
|
||||||
|
val throws = call.argument<Boolean>("throws") ?: false
|
||||||
|
|
||||||
val df = documentFileFromUri(uri, isDir)
|
val df = documentFileFromUri(uri, isDir)
|
||||||
if (df == null || !df.exists()) {
|
if (df == null) {
|
||||||
|
if (throws) {
|
||||||
|
throw Exception("Failed to get DocumentFile from $uri")
|
||||||
|
}
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
if (!df.exists()) {
|
||||||
|
if (throws) {
|
||||||
|
throw Exception("DocumentFile at $uri does not exist")
|
||||||
|
}
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
@@ -258,7 +279,8 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
val uri = call.argument<String>("uri") as String
|
val uri = call.argument<String>("uri") as String
|
||||||
|
|
||||||
val df = documentFileFromUri(uri, false) ?: throw Exception("Failed to get DocumentFile from $uri")
|
val df = documentFileFromUri(uri, false) ?: throw Exception("Failed to get DocumentFile from $uri")
|
||||||
val fd = context.contentResolver.openFileDescriptor(df.uri, "r") ?: throw Exception("Failed to open file descriptor")
|
val fd =
|
||||||
|
context.contentResolver.openFileDescriptor(df.uri, "r") ?: throw Exception("Failed to open file descriptor")
|
||||||
val fdInt = fd.fd
|
val fdInt = fd.fd
|
||||||
fdMap[fdInt] = fd
|
fdMap[fdInt] = fd
|
||||||
|
|
||||||
@@ -308,11 +330,12 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
// There are cases where the created directory has a different name due to concurrent operations.
|
// There are cases where the created directory has a different name due to concurrent operations.
|
||||||
// In this case, we need to find the directory with the correct name again.
|
// In this case, we need to find the directory with the correct name again.
|
||||||
val findRes2 = findDirectChild(curDocument.uri, curName)
|
val findRes2 = findDirectChild(curDocument.uri, curName)
|
||||||
nextDocument = if (findRes2 != null) {
|
nextDocument =
|
||||||
documentFileFromUriObj(findRes2.uri, findRes2.isDir)
|
if (findRes2 != null) {
|
||||||
} else {
|
documentFileFromUriObj(findRes2.uri, findRes2.isDir)
|
||||||
null
|
} else {
|
||||||
}
|
null
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
nextDocument = createRes
|
nextDocument = createRes
|
||||||
}
|
}
|
||||||
@@ -355,7 +378,9 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
curDocument = documentFileFromUriObj(findRes.uri, findRes.isDir) ?: throw Exception("Failed to get DocumentFile at $curName")
|
curDocument =
|
||||||
|
documentFileFromUriObj(findRes.uri, findRes.isDir)
|
||||||
|
?: throw Exception("Failed to get DocumentFile at $curName")
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
@@ -386,10 +411,12 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
result.success(documentFileToMap(df))
|
result.success(documentFileToMap(df))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val newUri = renameFileDocumentFile(df, newName)
|
val newUri =
|
||||||
?: throw Exception("Failed to rename to $newName")
|
renameFileDocumentFile(df, newName)
|
||||||
val newDF = documentFileFromUriObj(newUri, false)
|
?: throw Exception("Failed to rename to $newName")
|
||||||
?: throw Exception("Failed to get DocumentFile from $newUri")
|
val newDF =
|
||||||
|
documentFileFromUriObj(newUri, false)
|
||||||
|
?: throw Exception("Failed to get DocumentFile from $newUri")
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
result.success(documentFileToMap(newDF))
|
result.success(documentFileToMap(newDF))
|
||||||
}
|
}
|
||||||
@@ -414,12 +441,13 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
val parentUriObj = parentUri.toUri()
|
val parentUriObj = parentUri.toUri()
|
||||||
val newParentUriObj = newParentUri.toUri()
|
val newParentUriObj = newParentUri.toUri()
|
||||||
|
|
||||||
val resUri = DocumentsContract.moveDocument(
|
val resUri =
|
||||||
context.contentResolver,
|
DocumentsContract.moveDocument(
|
||||||
uriObj,
|
context.contentResolver,
|
||||||
parentUriObj,
|
uriObj,
|
||||||
newParentUriObj
|
parentUriObj,
|
||||||
) ?: throw Exception("Failed to move document")
|
newParentUriObj
|
||||||
|
) ?: throw Exception("Failed to move document")
|
||||||
|
|
||||||
val resultDF = documentFileFromUriObj(resUri, isDir) ?: throw Exception("Failed to get DocumentFile from $resUri")
|
val resultDF = documentFileFromUriObj(resUri, isDir) ?: throw Exception("Failed to get DocumentFile from $resUri")
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
@@ -443,11 +471,12 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
val uriObj = uri.toUri()
|
val uriObj = uri.toUri()
|
||||||
val newParentUriObj = newParentUri.toUri()
|
val newParentUriObj = newParentUri.toUri()
|
||||||
|
|
||||||
val resUri = DocumentsContract.copyDocument(
|
val resUri =
|
||||||
context.contentResolver,
|
DocumentsContract.copyDocument(
|
||||||
uriObj,
|
context.contentResolver,
|
||||||
newParentUriObj
|
uriObj,
|
||||||
) ?: throw Exception("Failed to move document")
|
newParentUriObj
|
||||||
|
) ?: throw Exception("Failed to move document")
|
||||||
|
|
||||||
val resultDF = documentFileFromUriObj(resUri, isDir) ?: throw Exception("Failed to get DocumentFile from $resUri")
|
val resultDF = documentFileFromUriObj(resUri, isDir) ?: throw Exception("Failed to get DocumentFile from $resUri")
|
||||||
|
|
||||||
@@ -492,14 +521,15 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
intent.addFlags(
|
intent.addFlags(
|
||||||
if (writePermission) Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
if (writePermission) {
|
||||||
else Intent.FLAG_GRANT_READ_URI_PERMISSION
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
} else {
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
activity?.startActivityForResult(intent, requestCodeOpenDocumentTree)
|
activity?.startActivityForResult(intent, requestCodeOpenDocumentTree)
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
// Launch failed after pendingResult was set: drop the pending state
|
|
||||||
// so a later activity result can't reply to this Result again.
|
|
||||||
takePendingResult()
|
takePendingResult()
|
||||||
result.error("PluginError", err.message, null)
|
result.error("PluginError", err.message, null)
|
||||||
}
|
}
|
||||||
@@ -508,7 +538,7 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
"pickFiles" -> {
|
"pickFiles" -> {
|
||||||
try {
|
try {
|
||||||
val initialUri = call.argument<String>("initialUri")
|
val initialUri = call.argument<String>("initialUri")
|
||||||
val multiple = call.argument<Boolean>("multiple") ?: false
|
val multiple = call.argument<Boolean>("multiple") ?: true
|
||||||
val mimeTypes = call.argument<ArrayList<String>>("mimeTypes") ?: arrayListOf()
|
val mimeTypes = call.argument<ArrayList<String>>("mimeTypes") ?: arrayListOf()
|
||||||
|
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
@@ -544,8 +574,53 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
|
|
||||||
activity?.startActivityForResult(intent, requestCodeOpenFiles)
|
activity?.startActivityForResult(intent, requestCodeOpenFiles)
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
// Launch failed after pendingResult was set: drop the pending state
|
takePendingResult()
|
||||||
// so a later activity result can't reply to this Result again.
|
result.error("PluginError", err.message, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"pickMedia" -> {
|
||||||
|
try {
|
||||||
|
val multiple = call.argument<Boolean>("multiple") ?: false
|
||||||
|
val mode = call.argument<String>("mode") ?: "all"
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
result.error("NOT_SUPPORTED", "Photo Picker is only supported on Android 13 (API 33) and above", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (activity == null) {
|
||||||
|
result.error("NO_ACTIVITY", "Activity is null", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pendingResult != null) {
|
||||||
|
result.error("ALREADY_PICKING", "Another picker process is already in progress", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalizedMode = mode.lowercase()
|
||||||
|
if (normalizedMode != "photo" && normalizedMode != "video" && normalizedMode != "all") {
|
||||||
|
result.error("INVALID_ARGUMENT", "mode must be one of: photo, video, all", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingResult = result
|
||||||
|
pendingArguments = PendingMediaArguments(multiple)
|
||||||
|
val intent = Intent(MediaStore.ACTION_PICK_IMAGES)
|
||||||
|
intent.type =
|
||||||
|
when (normalizedMode) {
|
||||||
|
"photo" -> "image/*"
|
||||||
|
"video" -> "video/*"
|
||||||
|
else -> "*/*"
|
||||||
|
}
|
||||||
|
if (multiple) {
|
||||||
|
intent.putExtra(
|
||||||
|
MediaStore.EXTRA_PICK_IMAGES_MAX,
|
||||||
|
MediaStore.getPickImagesMaxLimit()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
activity?.startActivityForResult(intent, requestCodePickMedia)
|
||||||
|
} catch (err: Exception) {
|
||||||
takePendingResult()
|
takePendingResult()
|
||||||
result.error("PluginError", err.message, null)
|
result.error("PluginError", err.message, null)
|
||||||
}
|
}
|
||||||
@@ -558,12 +633,13 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
val checkRead = call.argument<Boolean>("checkRead") ?: true
|
val checkRead = call.argument<Boolean>("checkRead") ?: true
|
||||||
val checkWrite = call.argument<Boolean>("checkWrite") ?: false
|
val checkWrite = call.argument<Boolean>("checkWrite") ?: false
|
||||||
|
|
||||||
val persisted = hasPersistedUriPermission(
|
val persisted =
|
||||||
context,
|
hasPersistedUriPermission(
|
||||||
uri.toUri(),
|
context,
|
||||||
checkRead,
|
uri.toUri(),
|
||||||
checkWrite
|
checkRead,
|
||||||
)
|
checkWrite
|
||||||
|
)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
result.success(persisted)
|
result.success(persisted)
|
||||||
}
|
}
|
||||||
@@ -584,10 +660,15 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
|
|
||||||
context.contentResolver.releasePersistableUriPermission(
|
context.contentResolver.releasePersistableUriPermission(
|
||||||
uri.toUri(),
|
uri.toUri(),
|
||||||
if (read && write) Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
if (read && write) {
|
||||||
else if (read) Intent.FLAG_GRANT_READ_URI_PERMISSION
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
else if (write) Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
} else if (read) {
|
||||||
else 0
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
} else if (write) {
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
)
|
)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
result.success(null)
|
result.success(null)
|
||||||
@@ -626,26 +707,28 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
if (mime.startsWith("video/")) {
|
if (mime.startsWith("video/")) {
|
||||||
val mmr = MediaMetadataRetriever()
|
val mmr = MediaMetadataRetriever()
|
||||||
mmr.setDataSource(context, uri)
|
mmr.setDataSource(context, uri)
|
||||||
bitmap = if (Build.VERSION.SDK_INT >= 27) {
|
bitmap =
|
||||||
mmr.getScaledFrameAtTime(-1, OPTION_CLOSEST_SYNC, width, height)
|
if (Build.VERSION.SDK_INT >= 27) {
|
||||||
} else {
|
mmr.getScaledFrameAtTime(-1, OPTION_CLOSEST_SYNC, width, height)
|
||||||
mmr.frameAtTime
|
} else {
|
||||||
}
|
mmr.frameAtTime
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use DocumentsContract for other files.
|
// Use DocumentsContract for other files.
|
||||||
bitmap = DocumentsContract.getDocumentThumbnail(
|
bitmap =
|
||||||
context.contentResolver,
|
DocumentsContract.getDocumentThumbnail(
|
||||||
uri,
|
context.contentResolver,
|
||||||
Point(width, height),
|
uri,
|
||||||
null
|
Point(width, height),
|
||||||
)
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
File(dest).writeBitmap(
|
File(dest).writeBitmap(
|
||||||
bitmap,
|
bitmap,
|
||||||
if (isPng) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG,
|
if (isPng) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG,
|
||||||
quality,
|
quality
|
||||||
)
|
)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
result.success(true)
|
result.success(true)
|
||||||
@@ -663,66 +746,69 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the result of the folder/file pickers. Returns whether the
|
// Handle folder/file/media picker results; unrelated request codes are not ours.
|
||||||
// request code was ours — unrelated request codes must not touch (let
|
private fun onActivityResult(
|
||||||
// alone answer) the pending picker state.
|
requestCode: Int,
|
||||||
private fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
resultCode: Int,
|
||||||
if (requestCode != requestCodeOpenDocumentTree && requestCode != requestCodeOpenFiles) {
|
data: Intent?
|
||||||
|
): Boolean {
|
||||||
|
if (
|
||||||
|
requestCode != requestCodeOpenDocumentTree &&
|
||||||
|
requestCode != requestCodeOpenFiles &&
|
||||||
|
requestCode != requestCodePickMedia
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Take ownership before replying: a duplicate delivery finds no pending
|
|
||||||
// state instead of a second reply on an already-answered Result.
|
|
||||||
val args = pendingArguments
|
val args = pendingArguments
|
||||||
val result = takePendingResult() ?: return true
|
val result = takePendingResult() ?: return true
|
||||||
try {
|
try {
|
||||||
if (requestCode == requestCodeOpenDocumentTree) {
|
if (requestCode == requestCodeOpenDocumentTree) {
|
||||||
// Handle the result of the folder picker.
|
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
val uri: Uri? = data.data
|
val uri: Uri? = data.data
|
||||||
if (uri != null && args is PendingDirArguments) {
|
if (uri != null && args is PendingDirArguments && args.persistablePermission) {
|
||||||
if (args.persistablePermission) {
|
context.contentResolver.takePersistableUriPermission(
|
||||||
context.contentResolver.takePersistableUriPermission(
|
uri,
|
||||||
uri,
|
if (args.writePermission) {
|
||||||
if (args.writePermission) Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
else Intent.FLAG_GRANT_READ_URI_PERMISSION
|
} else {
|
||||||
)
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val df = documentFileFromUri(uri.toString(), true)
|
val df = documentFileFromUri(uri.toString(), true)
|
||||||
result.success(
|
result.success(if (df != null) documentFileToMap(df) else null)
|
||||||
if (df != null) documentFileToMap(df)
|
|
||||||
else null
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle the result of file picker.
|
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
val uris: List<Uri> = if (data.clipData != null) {
|
val allowMultiple =
|
||||||
val clipData = data.clipData
|
if (requestCode == requestCodePickMedia) {
|
||||||
val uris = mutableListOf<Uri>()
|
(args as? PendingMediaArguments)?.multiple ?: false
|
||||||
for (i in 0 until clipData!!.itemCount) {
|
} else {
|
||||||
uris.add(clipData.getItemAt(i).uri)
|
true
|
||||||
|
}
|
||||||
|
val uris: List<Uri> =
|
||||||
|
if (allowMultiple && data.clipData != null) {
|
||||||
|
val clipData = data.clipData!!
|
||||||
|
List(clipData.itemCount) { index -> clipData.getItemAt(index).uri }
|
||||||
|
} else {
|
||||||
|
listOf(data.data!!)
|
||||||
}
|
}
|
||||||
uris
|
|
||||||
} else {
|
|
||||||
listOf(data.data!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
val documentFileMaps: MutableList<Map<String, Any?>> = mutableListOf()
|
val documentFileMaps: MutableList<Map<String, Any?>> = mutableListOf()
|
||||||
for (uri in uris) {
|
for (uri in uris) {
|
||||||
val df = documentFileFromUri(uri.toString(), false)
|
val df = documentFileFromUri(uri.toString(), false)
|
||||||
if (df != null) {
|
if (df != null) documentFileMaps.add(documentFileToMap(df))
|
||||||
documentFileMaps.add(documentFileToMap(df))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
result.success(documentFileMaps)
|
||||||
result.success(documentFileMaps) // Return the URIs to Flutter
|
|
||||||
} else {
|
} else {
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
@@ -731,8 +817,7 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
try {
|
try {
|
||||||
result.error("PluginError", err.message, null)
|
result.error("PluginError", err.message, null)
|
||||||
} catch (_: IllegalStateException) {
|
} catch (_: IllegalStateException) {
|
||||||
// Reply already submitted — never crash the host activity over a
|
// A duplicate native delivery must not crash the host Activity.
|
||||||
// picker teardown race.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -742,19 +827,26 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
channel.setMethodCallHandler(null)
|
channel.setMethodCallHandler(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun documentFileFromUri(uri: String, isDir: Boolean?): DocumentFile? {
|
private fun documentFileFromUri(
|
||||||
|
uri: String,
|
||||||
|
isDir: Boolean?
|
||||||
|
): DocumentFile? {
|
||||||
val uriObj = uri.toUri()
|
val uriObj = uri.toUri()
|
||||||
val isDirRes =
|
val isDirRes =
|
||||||
isDir ?: DocumentsContract.isTreeUri(uriObj)
|
isDir ?: DocumentsContract.isTreeUri(uriObj)
|
||||||
return documentFileFromUriObj(uriObj, isDirRes)
|
return documentFileFromUriObj(uriObj, isDirRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun documentFileFromUriObj(uriObj: Uri, isDir: Boolean): DocumentFile? {
|
private fun documentFileFromUriObj(
|
||||||
val res = if (isDir) {
|
uriObj: Uri,
|
||||||
DocumentFile.fromTreeUri(context, uriObj)
|
isDir: Boolean
|
||||||
} else {
|
): DocumentFile? {
|
||||||
DocumentFile.fromSingleUri(context, uriObj)
|
val res =
|
||||||
}
|
if (isDir) {
|
||||||
|
DocumentFile.fromTreeUri(context, uriObj)
|
||||||
|
} else {
|
||||||
|
DocumentFile.fromSingleUri(context, uriObj)
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,31 +867,39 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun areSameDocumentLocation(treeUri: Uri, docUri: Uri): Boolean {
|
private fun areSameDocumentLocation(
|
||||||
|
treeUri: Uri,
|
||||||
|
docUri: Uri
|
||||||
|
): Boolean {
|
||||||
val treeDocId = DocumentsContract.getTreeDocumentId(treeUri)
|
val treeDocId = DocumentsContract.getTreeDocumentId(treeUri)
|
||||||
val docId = DocumentsContract.getDocumentId(docUri)
|
val docId = DocumentsContract.getDocumentId(docUri)
|
||||||
return treeDocId == docId
|
return treeDocId == docId
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findDirectChild(parentUri: Uri, name: String): UriInfo? {
|
private fun findDirectChild(
|
||||||
|
parentUri: Uri,
|
||||||
|
name: String
|
||||||
|
): UriInfo? {
|
||||||
var cursor: Cursor? = null
|
var cursor: Cursor? = null
|
||||||
try {
|
try {
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
|
val childrenUri =
|
||||||
parentUri,
|
DocumentsContract.buildChildDocumentsUriUsingTree(
|
||||||
DocumentsContract.getDocumentId(parentUri)
|
parentUri,
|
||||||
)
|
DocumentsContract.getDocumentId(parentUri)
|
||||||
cursor = resolver.query(
|
)
|
||||||
childrenUri,
|
cursor =
|
||||||
arrayOf(
|
resolver.query(
|
||||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
childrenUri,
|
||||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
arrayOf(
|
||||||
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||||
),
|
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||||
null,
|
DocumentsContract.Document.COLUMN_MIME_TYPE
|
||||||
null,
|
),
|
||||||
null
|
null,
|
||||||
)
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
while (cursor?.moveToNext() == true) {
|
while (cursor?.moveToNext() == true) {
|
||||||
val documentId = cursor.getString(0)
|
val documentId = cursor.getString(0)
|
||||||
@@ -821,45 +921,51 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renameFileDocumentFile(df: DocumentFile, newName: String): Uri? {
|
private fun renameFileDocumentFile(
|
||||||
|
df: DocumentFile,
|
||||||
|
newName: String
|
||||||
|
): Uri? {
|
||||||
// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:documentfile/documentfile/src/main/java/androidx/documentfile/provider/TreeDocumentFile.java
|
// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:documentfile/documentfile/src/main/java/androidx/documentfile/provider/TreeDocumentFile.java
|
||||||
try {
|
try {
|
||||||
val result = DocumentsContract.renameDocument(
|
val result =
|
||||||
context.contentResolver, df.uri, newName
|
DocumentsContract.renameDocument(
|
||||||
)
|
context.contentResolver,
|
||||||
|
df.uri,
|
||||||
|
newName
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun documentFileToMap(file: DocumentFile): Map<String, Any?> {
|
private fun documentFileToMap(file: DocumentFile): Map<String, Any?> = fileObjMap(
|
||||||
return fileObjMap(
|
file.uri,
|
||||||
file.uri,
|
file.isDirectory,
|
||||||
file.isDirectory,
|
file.name ?: "",
|
||||||
file.name ?: "",
|
file.length(),
|
||||||
file.length(),
|
file.lastModified()
|
||||||
file.lastModified(),
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fileObjMap(
|
private fun fileObjMap(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
isDir: Boolean,
|
isDir: Boolean,
|
||||||
name: String,
|
name: String,
|
||||||
length: Long,
|
length: Long,
|
||||||
lastMod: Long,
|
lastMod: Long
|
||||||
): Map<String, Any?> {
|
): Map<String, Any?> = mapOf(
|
||||||
return mapOf(
|
"uri" to uri.toString(),
|
||||||
"uri" to uri.toString(),
|
"isDir" to isDir,
|
||||||
"isDir" to isDir,
|
"name" to name,
|
||||||
"name" to name,
|
"length" to length,
|
||||||
"length" to length,
|
"lastModified" to lastMod
|
||||||
"lastModified" to lastMod,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) {
|
private fun File.writeBitmap(
|
||||||
|
bitmap: Bitmap,
|
||||||
|
format: Bitmap.CompressFormat,
|
||||||
|
quality: Int
|
||||||
|
) {
|
||||||
outputStream().use { out ->
|
outputStream().use { out ->
|
||||||
bitmap.compress(format, quality, out)
|
bitmap.compress(format, quality, out)
|
||||||
out.flush()
|
out.flush()
|
||||||
@@ -867,11 +973,19 @@ class SafUtilPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class UriInfo(val uri: Uri, val name: String, val isDir: Boolean)
|
internal data class UriInfo(
|
||||||
|
val uri: Uri,
|
||||||
|
val name: String,
|
||||||
|
val isDir: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
internal open class PendingArguments
|
internal open class PendingArguments
|
||||||
|
|
||||||
internal class PendingDirArguments(
|
internal class PendingDirArguments(
|
||||||
val writePermission: Boolean,
|
val writePermission: Boolean,
|
||||||
val persistablePermission: Boolean,
|
val persistablePermission: Boolean
|
||||||
): PendingArguments()
|
) : PendingArguments()
|
||||||
|
|
||||||
|
internal class PendingMediaArguments(
|
||||||
|
val multiple: Boolean
|
||||||
|
) : PendingArguments()
|
||||||
|
|||||||
+96
-70
@@ -6,84 +6,110 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
|||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.PluginRegistry
|
import io.flutter.plugin.common.PluginRegistry
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
import org.mockito.ArgumentCaptor
|
import org.mockito.ArgumentCaptor
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.verifyNoInteractions
|
||||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
internal class SafUtilPluginTest {
|
internal class SafUtilPluginTest {
|
||||||
@Test
|
@Test
|
||||||
fun onMethodCall_unknownMethod_returnsNotImplemented() {
|
fun onMethodCall_unknownMethod_returnsNotImplemented() {
|
||||||
val plugin = SafUtilPlugin()
|
val plugin = SafUtilPlugin()
|
||||||
val result = mock(MethodChannel.Result::class.java)
|
val result = mock(MethodChannel.Result::class.java)
|
||||||
|
|
||||||
plugin.onMethodCall(MethodCall("unknown", null), result)
|
plugin.onMethodCall(MethodCall("unknown", null), result)
|
||||||
|
|
||||||
verify(result).notImplemented()
|
verify(result).notImplemented()
|
||||||
verifyNoMoreInteractions(result)
|
verifyNoMoreInteractions(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun pickDirectory_withoutActivity_returnsNoActivityError() {
|
fun pickDirectory_withoutActivity_returnsNoActivityError() {
|
||||||
val plugin = SafUtilPlugin()
|
val plugin = SafUtilPlugin()
|
||||||
val result = mock(MethodChannel.Result::class.java)
|
val result = mock(MethodChannel.Result::class.java)
|
||||||
|
|
||||||
plugin.onMethodCall(MethodCall("pickDirectory", null), result)
|
plugin.onMethodCall(MethodCall("pickDirectory", null), result)
|
||||||
|
|
||||||
verify(result).error("NO_ACTIVITY", "Activity is null", null)
|
verify(result).error("NO_ACTIVITY", "Activity is null", null)
|
||||||
verifyNoMoreInteractions(result)
|
verifyNoMoreInteractions(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@Test
|
@Test
|
||||||
fun pickDirectory_afterConfigChange_reattachesListenerAndClearsPendingResult() {
|
fun unrelatedActivityResultDoesNotConsumeOrAnswerPendingPicker() {
|
||||||
val plugin = SafUtilPlugin()
|
val plugin = SafUtilPlugin()
|
||||||
val firstActivity = RecordingActivity()
|
val activity = RecordingActivity()
|
||||||
val firstBinding = mock(ActivityPluginBinding::class.java)
|
val binding = mock(ActivityPluginBinding::class.java)
|
||||||
`when`(firstBinding.activity).thenReturn(firstActivity)
|
`when`(binding.activity).thenReturn(activity)
|
||||||
|
plugin.onAttachedToActivity(binding)
|
||||||
plugin.onAttachedToActivity(firstBinding)
|
|
||||||
|
val listenerCaptor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener::class.java)
|
||||||
val firstListenerCaptor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener::class.java)
|
verify(binding).addActivityResultListener(listenerCaptor.capture())
|
||||||
verify(firstBinding).addActivityResultListener(firstListenerCaptor.capture())
|
val result = mock(MethodChannel.Result::class.java)
|
||||||
|
plugin.onMethodCall(MethodCall("pickDirectory", null), result)
|
||||||
val firstResult = mock(MethodChannel.Result::class.java)
|
|
||||||
plugin.onMethodCall(MethodCall("pickDirectory", null), firstResult)
|
assertFalse(listenerCaptor.value.onActivityResult(9999, Activity.RESULT_CANCELED, null))
|
||||||
assertEquals(listOf(1001), firstActivity.startedRequestCodes)
|
verifyNoInteractions(result)
|
||||||
|
assertTrue(listenerCaptor.value.onActivityResult(1001, Activity.RESULT_CANCELED, null))
|
||||||
val secondResult = mock(MethodChannel.Result::class.java)
|
verify(result).success(null)
|
||||||
plugin.onMethodCall(MethodCall("pickDirectory", null), secondResult)
|
|
||||||
verify(secondResult).error("ALREADY_PICKING", "Another picker process is already in progress", null)
|
assertTrue(listenerCaptor.value.onActivityResult(1001, Activity.RESULT_CANCELED, null))
|
||||||
|
verifyNoMoreInteractions(result)
|
||||||
plugin.onDetachedFromActivityForConfigChanges()
|
}
|
||||||
verify(firstBinding).removeActivityResultListener(firstListenerCaptor.value)
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val secondActivity = RecordingActivity()
|
@Test
|
||||||
val secondBinding = mock(ActivityPluginBinding::class.java)
|
fun pickDirectory_afterConfigChange_reattachesListenerAndClearsPendingResult() {
|
||||||
`when`(secondBinding.activity).thenReturn(secondActivity)
|
val plugin = SafUtilPlugin()
|
||||||
|
val firstActivity = RecordingActivity()
|
||||||
plugin.onReattachedToActivityForConfigChanges(secondBinding)
|
val firstBinding = mock(ActivityPluginBinding::class.java)
|
||||||
|
`when`(firstBinding.activity).thenReturn(firstActivity)
|
||||||
val secondListenerCaptor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener::class.java)
|
|
||||||
verify(secondBinding).addActivityResultListener(secondListenerCaptor.capture())
|
plugin.onAttachedToActivity(firstBinding)
|
||||||
|
|
||||||
secondListenerCaptor.value.onActivityResult(1001, Activity.RESULT_CANCELED, null)
|
val firstListenerCaptor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener::class.java)
|
||||||
verify(firstResult).success(null)
|
verify(firstBinding).addActivityResultListener(firstListenerCaptor.capture())
|
||||||
|
|
||||||
val thirdResult = mock(MethodChannel.Result::class.java)
|
val firstResult = mock(MethodChannel.Result::class.java)
|
||||||
plugin.onMethodCall(MethodCall("pickDirectory", null), thirdResult)
|
plugin.onMethodCall(MethodCall("pickDirectory", null), firstResult)
|
||||||
assertEquals(listOf(1001), secondActivity.startedRequestCodes)
|
assertEquals(listOf(1001), firstActivity.startedRequestCodes)
|
||||||
}
|
|
||||||
|
val secondResult = mock(MethodChannel.Result::class.java)
|
||||||
private class RecordingActivity : Activity() {
|
plugin.onMethodCall(MethodCall("pickDirectory", null), secondResult)
|
||||||
val startedRequestCodes = mutableListOf<Int>()
|
verify(secondResult).error("ALREADY_PICKING", "Another picker process is already in progress", null)
|
||||||
|
|
||||||
@Deprecated("Deprecated in Android")
|
plugin.onDetachedFromActivityForConfigChanges()
|
||||||
override fun startActivityForResult(intent: Intent?, requestCode: Int) {
|
verify(firstBinding).removeActivityResultListener(firstListenerCaptor.value)
|
||||||
startedRequestCodes.add(requestCode)
|
|
||||||
}
|
val secondActivity = RecordingActivity()
|
||||||
|
val secondBinding = mock(ActivityPluginBinding::class.java)
|
||||||
|
`when`(secondBinding.activity).thenReturn(secondActivity)
|
||||||
|
|
||||||
|
plugin.onReattachedToActivityForConfigChanges(secondBinding)
|
||||||
|
|
||||||
|
val secondListenerCaptor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener::class.java)
|
||||||
|
verify(secondBinding).addActivityResultListener(secondListenerCaptor.capture())
|
||||||
|
|
||||||
|
secondListenerCaptor.value.onActivityResult(1001, Activity.RESULT_CANCELED, null)
|
||||||
|
verify(firstResult).success(null)
|
||||||
|
|
||||||
|
val thirdResult = mock(MethodChannel.Result::class.java)
|
||||||
|
plugin.onMethodCall(MethodCall("pickDirectory", null), thirdResult)
|
||||||
|
assertEquals(listOf(1001), secondActivity.startedRequestCodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RecordingActivity : Activity() {
|
||||||
|
val startedRequestCodes = mutableListOf<Int>()
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Android")
|
||||||
|
override fun startActivityForResult(intent: Intent?, requestCode: Int) {
|
||||||
|
startedRequestCodes.add(requestCode)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ class SafUtil {
|
|||||||
/// [initialUri] is the initial URI to show in the dialog.
|
/// [initialUri] is the initial URI to show in the dialog.
|
||||||
/// [writePermission] is true if the folder should have write permission.
|
/// [writePermission] is true if the folder should have write permission.
|
||||||
/// [persistablePermission] is true if the permission should be persistable.
|
/// [persistablePermission] is true if the permission should be persistable.
|
||||||
Future<SafDocumentFile?> pickDirectory(
|
Future<SafDocumentFile?> pickDirectory({
|
||||||
{String? initialUri,
|
String? initialUri,
|
||||||
bool? writePermission,
|
bool? writePermission,
|
||||||
bool? persistablePermission}) {
|
bool? persistablePermission,
|
||||||
|
}) {
|
||||||
return SafUtilPlatform.instance.pickDirectory(
|
return SafUtilPlatform.instance.pickDirectory(
|
||||||
initialUri: initialUri,
|
initialUri: initialUri,
|
||||||
writePermission: writePermission,
|
writePermission: writePermission,
|
||||||
persistablePermission: persistablePermission);
|
persistablePermission: persistablePermission,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a file picker dialog and returns the selected file [SafDocumentFile].
|
/// Shows a file picker dialog and returns the selected file [SafDocumentFile].
|
||||||
@@ -39,7 +41,7 @@ class SafUtil {
|
|||||||
Future<List<SafDocumentFile>?> pickFiles({
|
Future<List<SafDocumentFile>?> pickFiles({
|
||||||
String? initialUri,
|
String? initialUri,
|
||||||
List<String>? mimeTypes,
|
List<String>? mimeTypes,
|
||||||
multiple = true,
|
bool multiple = true,
|
||||||
}) {
|
}) {
|
||||||
return SafUtilPlatform.instance.pickFiles(
|
return SafUtilPlatform.instance.pickFiles(
|
||||||
initialUri: initialUri,
|
initialUri: initialUri,
|
||||||
@@ -48,6 +50,20 @@ class SafUtil {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows a media picker dialog and returns selected media as [SafDocumentFile].
|
||||||
|
///
|
||||||
|
/// [multiple] is true if multiple media files can be selected.
|
||||||
|
/// [mode] controls what can be picked and must be one of:
|
||||||
|
/// - 'photo'
|
||||||
|
/// - 'video'
|
||||||
|
/// - 'all'
|
||||||
|
Future<List<SafDocumentFile>?> pickMedia({
|
||||||
|
bool multiple = true,
|
||||||
|
String mode = 'all',
|
||||||
|
}) {
|
||||||
|
return SafUtilPlatform.instance.pickMedia(multiple: multiple, mode: mode);
|
||||||
|
}
|
||||||
|
|
||||||
/// Lists the contents of the specified directory URI.
|
/// Lists the contents of the specified directory URI.
|
||||||
/// Returns a list of [SafDocumentFile] objects.
|
/// Returns a list of [SafDocumentFile] objects.
|
||||||
///
|
///
|
||||||
@@ -72,8 +88,9 @@ class SafUtil {
|
|||||||
/// [uri] is the URI of the file or directory.
|
/// [uri] is the URI of the file or directory.
|
||||||
/// [isDir] is true if the URI is a directory. [null] means
|
/// [isDir] is true if the URI is a directory. [null] means
|
||||||
/// auto-detect.
|
/// auto-detect.
|
||||||
Future<SafDocumentFile?> stat(String uri, bool? isDir) {
|
/// [throws] when true, throws an exception if the URI does not exist or is inaccessible.
|
||||||
return SafUtilPlatform.instance.stat(uri, isDir);
|
Future<SafDocumentFile?> stat(String uri, bool? isDir, {bool? throws}) {
|
||||||
|
return SafUtilPlatform.instance.stat(uri, isDir, throws: throws);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the specified file or directory exists.
|
/// Checks if the specified file or directory exists.
|
||||||
@@ -126,7 +143,11 @@ class SafUtil {
|
|||||||
/// [parentUri] is the URI of the current parent directory.
|
/// [parentUri] is the URI of the current parent directory.
|
||||||
/// [newParentUri] is the URI of the new parent directory.
|
/// [newParentUri] is the URI of the new parent directory.
|
||||||
Future<SafDocumentFile> moveTo(
|
Future<SafDocumentFile> moveTo(
|
||||||
String uri, bool isDir, String parentUri, String newParentUri) {
|
String uri,
|
||||||
|
bool isDir,
|
||||||
|
String parentUri,
|
||||||
|
String newParentUri,
|
||||||
|
) {
|
||||||
return SafUtilPlatform.instance.moveTo(uri, isDir, parentUri, newParentUri);
|
return SafUtilPlatform.instance.moveTo(uri, isDir, parentUri, newParentUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,14 +199,16 @@ class SafUtil {
|
|||||||
/// [writePermission] is true if the folder should have write permission.
|
/// [writePermission] is true if the folder should have write permission.
|
||||||
/// [persistablePermission] is true if the permission should be persistable.
|
/// [persistablePermission] is true if the permission should be persistable.
|
||||||
@Deprecated('Use [pickDirectory] instead, which returns a [SafDocumentFile].')
|
@Deprecated('Use [pickDirectory] instead, which returns a [SafDocumentFile].')
|
||||||
Future<String?> openDirectory(
|
Future<String?> openDirectory({
|
||||||
{String? initialUri,
|
String? initialUri,
|
||||||
bool? writePermission,
|
bool? writePermission,
|
||||||
bool? persistablePermission}) {
|
bool? persistablePermission,
|
||||||
|
}) {
|
||||||
return SafUtilPlatform.instance.openDirectory(
|
return SafUtilPlatform.instance.openDirectory(
|
||||||
initialUri: initialUri,
|
initialUri: initialUri,
|
||||||
writePermission: writePermission,
|
writePermission: writePermission,
|
||||||
persistablePermission: persistablePermission);
|
persistablePermission: persistablePermission,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a file picker dialog and returns the selected file URI.
|
/// Shows a file picker dialog and returns the selected file URI.
|
||||||
@@ -194,10 +217,7 @@ class SafUtil {
|
|||||||
/// [initialUri] is the initial URI to show in the dialog.
|
/// [initialUri] is the initial URI to show in the dialog.
|
||||||
/// [mimeTypes] is a list of MIME types to filter the files.
|
/// [mimeTypes] is a list of MIME types to filter the files.
|
||||||
@Deprecated('Use [pickFile] instead, which returns a [SafDocumentFile].')
|
@Deprecated('Use [pickFile] instead, which returns a [SafDocumentFile].')
|
||||||
Future<String?> openFile({
|
Future<String?> openFile({String? initialUri, List<String>? mimeTypes}) {
|
||||||
String? initialUri,
|
|
||||||
List<String>? mimeTypes,
|
|
||||||
}) {
|
|
||||||
return SafUtilPlatform.instance.openFile(
|
return SafUtilPlatform.instance.openFile(
|
||||||
initialUri: initialUri,
|
initialUri: initialUri,
|
||||||
mimeTypes: mimeTypes,
|
mimeTypes: mimeTypes,
|
||||||
@@ -212,7 +232,7 @@ class SafUtil {
|
|||||||
Future<List<String>?> openFiles({
|
Future<List<String>?> openFiles({
|
||||||
String? initialUri,
|
String? initialUri,
|
||||||
List<String>? mimeTypes,
|
List<String>? mimeTypes,
|
||||||
multiple = true,
|
bool multiple = true,
|
||||||
}) {
|
}) {
|
||||||
return SafUtilPlatform.instance.openFiles(
|
return SafUtilPlatform.instance.openFiles(
|
||||||
initialUri: initialUri,
|
initialUri: initialUri,
|
||||||
@@ -240,17 +260,26 @@ class SafUtil {
|
|||||||
bool checkRead = true,
|
bool checkRead = true,
|
||||||
bool checkWrite = false,
|
bool checkWrite = false,
|
||||||
}) {
|
}) {
|
||||||
return SafUtilPlatform.instance.hasPersistedPermission(uri,
|
return SafUtilPlatform.instance.hasPersistedPermission(
|
||||||
checkRead: checkRead, checkWrite: checkWrite);
|
uri,
|
||||||
|
checkRead: checkRead,
|
||||||
|
checkWrite: checkWrite,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Releases the persisted permission of the specified URI.
|
/// Releases the persisted permission of the specified URI.
|
||||||
/// Use [read] and [write] to specify the type of permission to release.
|
/// Use [read] and [write] to specify the type of permission to release.
|
||||||
/// [read] defaults to true.
|
/// [read] defaults to true.
|
||||||
/// [write] defaults to false.
|
/// [write] defaults to false.
|
||||||
Future<void> releasePersistedPermission(String uri,
|
Future<void> releasePersistedPermission(
|
||||||
{bool read = true, bool write = false}) async {
|
String uri, {
|
||||||
return SafUtilPlatform.instance
|
bool read = true,
|
||||||
.releasePersistedPermission(uri, read: read, write: write);
|
bool write = false,
|
||||||
|
}) async {
|
||||||
|
return SafUtilPlatform.instance.releasePersistedPermission(
|
||||||
|
uri,
|
||||||
|
read: read,
|
||||||
|
write: write,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,17 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
final methodChannel = const MethodChannel('saf_util');
|
final methodChannel = const MethodChannel('saf_util');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SafDocumentFile?> pickDirectory(
|
Future<SafDocumentFile?> pickDirectory({
|
||||||
{String? initialUri,
|
String? initialUri,
|
||||||
bool? writePermission,
|
bool? writePermission,
|
||||||
bool? persistablePermission}) async {
|
bool? persistablePermission,
|
||||||
final map =
|
}) async {
|
||||||
await methodChannel.invokeMapMethod<String, dynamic>('pickDirectory', {
|
final map = await methodChannel
|
||||||
'initialUri': initialUri,
|
.invokeMapMethod<String, dynamic>('pickDirectory', {
|
||||||
'writePermission': writePermission,
|
'initialUri': initialUri,
|
||||||
'persistablePermission': persistablePermission,
|
'writePermission': writePermission,
|
||||||
});
|
'persistablePermission': persistablePermission,
|
||||||
|
});
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -27,10 +28,11 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> openDirectory(
|
Future<String?> openDirectory({
|
||||||
{String? initialUri,
|
String? initialUri,
|
||||||
bool? writePermission,
|
bool? writePermission,
|
||||||
bool? persistablePermission}) async {
|
bool? persistablePermission,
|
||||||
|
}) async {
|
||||||
final res = await pickDirectory(
|
final res = await pickDirectory(
|
||||||
initialUri: initialUri,
|
initialUri: initialUri,
|
||||||
writePermission: writePermission,
|
writePermission: writePermission,
|
||||||
@@ -57,10 +59,7 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
String? initialUri,
|
String? initialUri,
|
||||||
List<String>? mimeTypes,
|
List<String>? mimeTypes,
|
||||||
}) async {
|
}) async {
|
||||||
final res = await pickFile(
|
final res = await pickFile(initialUri: initialUri, mimeTypes: mimeTypes);
|
||||||
initialUri: initialUri,
|
|
||||||
mimeTypes: mimeTypes,
|
|
||||||
);
|
|
||||||
return res?.uri;
|
return res?.uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,14 +67,24 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
Future<List<SafDocumentFile>?> pickFiles({
|
Future<List<SafDocumentFile>?> pickFiles({
|
||||||
String? initialUri,
|
String? initialUri,
|
||||||
List<String>? mimeTypes,
|
List<String>? mimeTypes,
|
||||||
multiple = true,
|
bool multiple = true,
|
||||||
}) async {
|
}) async {
|
||||||
final maps = await methodChannel
|
final maps = await methodChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
||||||
.invokeListMethod<Map<dynamic, dynamic>>('pickFiles', {
|
'pickFiles',
|
||||||
'initialUri': initialUri,
|
{'initialUri': initialUri, 'mimeTypes': mimeTypes, 'multiple': multiple},
|
||||||
'mimeTypes': mimeTypes,
|
);
|
||||||
'multiple': multiple,
|
return maps?.map((map) => SafDocumentFile.fromMap(map)).toList();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<SafDocumentFile>?> pickMedia({
|
||||||
|
bool multiple = true,
|
||||||
|
String mode = 'all',
|
||||||
|
}) async {
|
||||||
|
final maps = await methodChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
||||||
|
'pickMedia',
|
||||||
|
{'multiple': multiple, 'mode': mode},
|
||||||
|
);
|
||||||
return maps?.map((map) => SafDocumentFile.fromMap(map)).toList();
|
return maps?.map((map) => SafDocumentFile.fromMap(map)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +92,7 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
Future<List<String>?> openFiles({
|
Future<List<String>?> openFiles({
|
||||||
String? initialUri,
|
String? initialUri,
|
||||||
List<String>? mimeTypes,
|
List<String>? mimeTypes,
|
||||||
multiple = true,
|
bool multiple = true,
|
||||||
}) async {
|
}) async {
|
||||||
final res = await pickFiles(
|
final res = await pickFiles(
|
||||||
initialUri: initialUri,
|
initialUri: initialUri,
|
||||||
@@ -95,8 +104,10 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<SafDocumentFile>> list(String uri) async {
|
Future<List<SafDocumentFile>> list(String uri) async {
|
||||||
final maps = await methodChannel
|
final maps = await methodChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
||||||
.invokeListMethod<Map<dynamic, dynamic>>('list', {'uri': uri});
|
'list',
|
||||||
|
{'uri': uri},
|
||||||
|
);
|
||||||
return (maps ?? []).map((map) => SafDocumentFile.fromMap(map)).toList();
|
return (maps ?? []).map((map) => SafDocumentFile.fromMap(map)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,11 +124,12 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SafDocumentFile?> stat(String uri, bool? isDir) async {
|
Future<SafDocumentFile?> stat(String uri, bool? isDir, {bool? throws}) async {
|
||||||
final map = await methodChannel.invokeMapMethod<String, dynamic>(
|
final map = await methodChannel.invokeMapMethod<String, dynamic>('stat', {
|
||||||
'stat',
|
'uri': uri,
|
||||||
{'uri': uri, 'isDir': isDir},
|
'isDir': isDir,
|
||||||
);
|
'throws': throws,
|
||||||
|
});
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -126,10 +138,10 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> exists(String uri, bool isDir) async {
|
Future<bool> exists(String uri, bool isDir) async {
|
||||||
final res = await methodChannel.invokeMethod<bool>(
|
final res = await methodChannel.invokeMethod<bool>('exists', {
|
||||||
'exists',
|
'uri': uri,
|
||||||
{'uri': uri, 'isDir': isDir},
|
'isDir': isDir,
|
||||||
);
|
});
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
throw Exception('Failed to check if file exists: $uri');
|
throw Exception('Failed to check if file exists: $uri');
|
||||||
}
|
}
|
||||||
@@ -138,10 +150,10 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> delete(String uri, bool isDir) async {
|
Future<void> delete(String uri, bool isDir) async {
|
||||||
final res = await methodChannel.invokeMethod<bool>(
|
final res = await methodChannel.invokeMethod<bool>('delete', {
|
||||||
'delete',
|
'uri': uri,
|
||||||
{'uri': uri, 'isDir': isDir},
|
'isDir': isDir,
|
||||||
);
|
});
|
||||||
if (res != true) {
|
if (res != true) {
|
||||||
throw Exception('Failed to delete file: $uri');
|
throw Exception('Failed to delete file: $uri');
|
||||||
}
|
}
|
||||||
@@ -149,10 +161,10 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SafDocumentFile> mkdirp(String uri, List<String> names) async {
|
Future<SafDocumentFile> mkdirp(String uri, List<String> names) async {
|
||||||
final map = await methodChannel.invokeMapMethod<String, dynamic>(
|
final map = await methodChannel.invokeMapMethod<String, dynamic>('mkdirp', {
|
||||||
'mkdirp',
|
'uri': uri,
|
||||||
{'uri': uri, 'names': names},
|
'names': names,
|
||||||
);
|
});
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
throw Exception('Failed to create directory: $uri');
|
throw Exception('Failed to create directory: $uri');
|
||||||
}
|
}
|
||||||
@@ -161,10 +173,10 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SafDocumentFile?> child(String uri, List<String> names) async {
|
Future<SafDocumentFile?> child(String uri, List<String> names) async {
|
||||||
final map = await methodChannel.invokeMapMethod<String, dynamic>(
|
final map = await methodChannel.invokeMapMethod<String, dynamic>('child', {
|
||||||
'child',
|
'uri': uri,
|
||||||
{'uri': uri, 'names': names},
|
'names': names,
|
||||||
);
|
});
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -173,10 +185,11 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SafDocumentFile> rename(String uri, bool isDir, String newName) async {
|
Future<SafDocumentFile> rename(String uri, bool isDir, String newName) async {
|
||||||
final map = await methodChannel.invokeMapMethod<String, dynamic>(
|
final map = await methodChannel.invokeMapMethod<String, dynamic>('rename', {
|
||||||
'rename',
|
'uri': uri,
|
||||||
{'uri': uri, 'isDir': isDir, 'newName': newName},
|
'isDir': isDir,
|
||||||
);
|
'newName': newName,
|
||||||
|
});
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
throw Exception('Failed to rename: $uri');
|
throw Exception('Failed to rename: $uri');
|
||||||
}
|
}
|
||||||
@@ -185,16 +198,17 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SafDocumentFile> moveTo(
|
Future<SafDocumentFile> moveTo(
|
||||||
String uri, bool isDir, String parentUri, String newParentUri) async {
|
String uri,
|
||||||
final map = await methodChannel.invokeMapMethod<String, dynamic>(
|
bool isDir,
|
||||||
'moveTo',
|
String parentUri,
|
||||||
{
|
String newParentUri,
|
||||||
'uri': uri,
|
) async {
|
||||||
'isDir': isDir,
|
final map = await methodChannel.invokeMapMethod<String, dynamic>('moveTo', {
|
||||||
'parentUri': parentUri,
|
'uri': uri,
|
||||||
'newParentUri': newParentUri
|
'isDir': isDir,
|
||||||
},
|
'parentUri': parentUri,
|
||||||
);
|
'newParentUri': newParentUri,
|
||||||
|
});
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
throw Exception('Failed to move: $uri');
|
throw Exception('Failed to move: $uri');
|
||||||
}
|
}
|
||||||
@@ -203,11 +217,15 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SafDocumentFile> copyTo(
|
Future<SafDocumentFile> copyTo(
|
||||||
String uri, bool isDir, String newParentUri) async {
|
String uri,
|
||||||
final map = await methodChannel.invokeMapMethod<String, dynamic>(
|
bool isDir,
|
||||||
'copyTo',
|
String newParentUri,
|
||||||
{'uri': uri, 'isDir': isDir, 'newParentUri': newParentUri},
|
) async {
|
||||||
);
|
final map = await methodChannel.invokeMapMethod<String, dynamic>('copyTo', {
|
||||||
|
'uri': uri,
|
||||||
|
'isDir': isDir,
|
||||||
|
'newParentUri': newParentUri,
|
||||||
|
});
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
throw Exception('Failed to copy: $uri');
|
throw Exception('Failed to copy: $uri');
|
||||||
}
|
}
|
||||||
@@ -223,26 +241,22 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
String? format,
|
String? format,
|
||||||
int? quality,
|
int? quality,
|
||||||
}) async {
|
}) async {
|
||||||
final res = await methodChannel.invokeMethod<bool>(
|
final res = await methodChannel.invokeMethod<bool>('saveThumbnailToFile', {
|
||||||
'saveThumbnailToFile',
|
'uri': uri.toString(),
|
||||||
{
|
'width': width,
|
||||||
'uri': uri.toString(),
|
'height': height,
|
||||||
'width': width,
|
'destPath': destPath,
|
||||||
'height': height,
|
'format': format,
|
||||||
'destPath': destPath,
|
'quality': quality,
|
||||||
'format': format,
|
});
|
||||||
'quality': quality,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return res ?? false;
|
return res ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> getFileDescriptor(String uri) async {
|
Future<int> getFileDescriptor(String uri) async {
|
||||||
final res = await methodChannel.invokeMethod<int>(
|
final res = await methodChannel.invokeMethod<int>('getFileDescriptor', {
|
||||||
'getFileDescriptor',
|
'uri': uri,
|
||||||
{'uri': uri},
|
});
|
||||||
);
|
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
throw Exception('Failed to get file descriptor: $uri');
|
throw Exception('Failed to get file descriptor: $uri');
|
||||||
}
|
}
|
||||||
@@ -251,10 +265,7 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> closeFileDescriptor(int fd) {
|
Future<void> closeFileDescriptor(int fd) {
|
||||||
return methodChannel.invokeMethod<void>(
|
return methodChannel.invokeMethod<void>('closeFileDescriptor', {'fd': fd});
|
||||||
'closeFileDescriptor',
|
|
||||||
{'fd': fd},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -265,11 +276,7 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
}) async {
|
}) async {
|
||||||
final res = await methodChannel.invokeMethod<bool>(
|
final res = await methodChannel.invokeMethod<bool>(
|
||||||
'hasPersistedPermission',
|
'hasPersistedPermission',
|
||||||
{
|
{'uri': uri, 'checkRead': checkRead, 'checkWrite': checkWrite},
|
||||||
'uri': uri,
|
|
||||||
'checkRead': checkRead,
|
|
||||||
'checkWrite': checkWrite,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
throw Exception('Failed to check persisted permission: $uri');
|
throw Exception('Failed to check persisted permission: $uri');
|
||||||
@@ -278,15 +285,15 @@ class MethodChannelSafUtil extends SafUtilPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> releasePersistedPermission(String uri,
|
Future<void> releasePersistedPermission(
|
||||||
{bool read = true, bool write = false}) async {
|
String uri, {
|
||||||
await methodChannel.invokeMethod<void>(
|
bool read = true,
|
||||||
'releasePersistedPermission',
|
bool write = false,
|
||||||
{
|
}) async {
|
||||||
'uri': uri,
|
await methodChannel.invokeMethod<void>('releasePersistedPermission', {
|
||||||
'read': read,
|
'uri': uri,
|
||||||
'write': write,
|
'read': read,
|
||||||
},
|
'write': write,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ class SafDocumentFile {
|
|||||||
|
|
||||||
static SafDocumentFile fromMap(Map<dynamic, dynamic> map) {
|
static SafDocumentFile fromMap(Map<dynamic, dynamic> map) {
|
||||||
return SafDocumentFile(
|
return SafDocumentFile(
|
||||||
uri: map['uri'],
|
uri: map['uri'] as String,
|
||||||
name: map['name'],
|
name: map['name'] as String,
|
||||||
isDir: map['isDir'] ?? false,
|
isDir: map['isDir'] as bool? ?? false,
|
||||||
length: map['length'] ?? 0,
|
length: map['length'] as int? ?? -1,
|
||||||
lastModified: map['lastModified'] ?? 0,
|
lastModified: map['lastModified'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,17 +54,19 @@ abstract class SafUtilPlatform extends PlatformInterface {
|
|||||||
_instance = instance;
|
_instance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SafDocumentFile?> pickDirectory(
|
Future<SafDocumentFile?> pickDirectory({
|
||||||
{String? initialUri,
|
String? initialUri,
|
||||||
bool? writePermission,
|
bool? writePermission,
|
||||||
bool? persistablePermission}) {
|
bool? persistablePermission,
|
||||||
|
}) {
|
||||||
throw UnimplementedError('pickDirectory() has not been implemented.');
|
throw UnimplementedError('pickDirectory() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> openDirectory(
|
Future<String?> openDirectory({
|
||||||
{String? initialUri,
|
String? initialUri,
|
||||||
bool? writePermission,
|
bool? writePermission,
|
||||||
bool? persistablePermission}) {
|
bool? persistablePermission,
|
||||||
|
}) {
|
||||||
throw UnimplementedError('openDirectory() has not been implemented.');
|
throw UnimplementedError('openDirectory() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,25 +77,29 @@ abstract class SafUtilPlatform extends PlatformInterface {
|
|||||||
throw UnimplementedError('pickFile() has not been implemented.');
|
throw UnimplementedError('pickFile() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> openFile({
|
Future<String?> openFile({String? initialUri, List<String>? mimeTypes}) {
|
||||||
String? initialUri,
|
|
||||||
List<String>? mimeTypes,
|
|
||||||
}) {
|
|
||||||
throw UnimplementedError('openFile() has not been implemented.');
|
throw UnimplementedError('openFile() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SafDocumentFile>?> pickFiles({
|
Future<List<SafDocumentFile>?> pickFiles({
|
||||||
String? initialUri,
|
String? initialUri,
|
||||||
List<String>? mimeTypes,
|
List<String>? mimeTypes,
|
||||||
multiple = true,
|
bool multiple = true,
|
||||||
}) {
|
}) {
|
||||||
throw UnimplementedError('pickFiles() has not been implemented.');
|
throw UnimplementedError('pickFiles() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<SafDocumentFile>?> pickMedia({
|
||||||
|
bool multiple = true,
|
||||||
|
String mode = 'all',
|
||||||
|
}) {
|
||||||
|
throw UnimplementedError('pickMedia() has not been implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<String>?> openFiles({
|
Future<List<String>?> openFiles({
|
||||||
String? initialUri,
|
String? initialUri,
|
||||||
List<String>? mimeTypes,
|
List<String>? mimeTypes,
|
||||||
multiple = true,
|
bool multiple = true,
|
||||||
}) {
|
}) {
|
||||||
throw UnimplementedError('openFiles() has not been implemented.');
|
throw UnimplementedError('openFiles() has not been implemented.');
|
||||||
}
|
}
|
||||||
@@ -106,7 +112,7 @@ abstract class SafUtilPlatform extends PlatformInterface {
|
|||||||
throw UnimplementedError('documentFileFromUri() has not been implemented.');
|
throw UnimplementedError('documentFileFromUri() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SafDocumentFile?> stat(String uri, bool? isDir) {
|
Future<SafDocumentFile?> stat(String uri, bool? isDir, {bool? throws}) {
|
||||||
throw UnimplementedError('stat() has not been implemented.');
|
throw UnimplementedError('stat() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +137,11 @@ abstract class SafUtilPlatform extends PlatformInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<SafDocumentFile> moveTo(
|
Future<SafDocumentFile> moveTo(
|
||||||
String uri, bool isDir, String parentUri, String newParentUri) {
|
String uri,
|
||||||
|
bool isDir,
|
||||||
|
String parentUri,
|
||||||
|
String newParentUri,
|
||||||
|
) {
|
||||||
throw UnimplementedError('moveTo() has not been implemented.');
|
throw UnimplementedError('moveTo() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,12 +174,17 @@ abstract class SafUtilPlatform extends PlatformInterface {
|
|||||||
bool checkWrite = false,
|
bool checkWrite = false,
|
||||||
}) {
|
}) {
|
||||||
throw UnimplementedError(
|
throw UnimplementedError(
|
||||||
'hasPersistedPermission() has not been implemented.');
|
'hasPersistedPermission() has not been implemented.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> releasePersistedPermission(String uri,
|
Future<void> releasePersistedPermission(
|
||||||
{bool read = true, bool write = false}) {
|
String uri, {
|
||||||
|
bool read = true,
|
||||||
|
bool write = false,
|
||||||
|
}) {
|
||||||
throw UnimplementedError(
|
throw UnimplementedError(
|
||||||
'releasePersistedPermission() has not been implemented.');
|
'releasePersistedPermission() has not been implemented.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ packages:
|
|||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
@@ -123,6 +123,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.18.0"
|
||||||
|
mgenware_dart_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: mgenware_dart_lints
|
||||||
|
sha256: "14f47c0ba0073c1980298ee10f602c19fa244151c68ec75fde602642c3de3a4e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -209,5 +217,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.0"
|
version: "15.2.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.10.0-0 <4.0.0"
|
dart: ">=3.12.0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.44.0"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
name: saf_util
|
name: saf_util
|
||||||
description: "Util functions for SAF (Storage Access Framework)."
|
description: "Util functions for SAF (Storage Access Framework)."
|
||||||
version: 2.0.0
|
version: 3.1.0
|
||||||
homepage: https://github.com/flutter-cavalry/saf_util
|
homepage: https://github.com/flutter-cavalry/saf_util
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.18.0 <4.0.0"
|
sdk: ^3.12.0
|
||||||
flutter: ">=2.5.0"
|
flutter: ">=3.44.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -15,7 +15,7 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^6.0.0
|
mgenware_dart_lints: ^8.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2020-2023, creativecreatorormaybenot
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/libraries
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.cxx
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
group 'dev.fluttercommunity.plus.wakelock'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '2.2.0'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.12.1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'dev.fluttercommunity.plus.wakelock'
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '17'
|
||||||
|
}
|
||||||
|
lintOptions {
|
||||||
|
disable 'InvalidPackage'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
test.java.srcDirs += 'src/test/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// Use flutter.minSdkVersion once the minimum supported Flutter version is 3.35 or higher.
|
||||||
|
minSdkVersion flutter.minSdkVersion
|
||||||
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
testImplementation 'org.jetbrains.kotlin:kotlin-test'
|
||||||
|
testImplementation 'org.mockito:mockito-core:5.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests.includeAndroidResources = true
|
||||||
|
unitTests.all {
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
testLogging {
|
||||||
|
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
|
||||||
|
outputs.upToDateWhen { false }
|
||||||
|
showStandardStreams = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'wakelock_plus'
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="dev.fluttercommunity.plus.wakelock">
|
||||||
|
</manifest>
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package dev.fluttercommunity.plus.wakelock
|
||||||
|
|
||||||
|
import IsEnabledMessage
|
||||||
|
import ToggleMessage
|
||||||
|
import android.app.Activity
|
||||||
|
import android.view.WindowManager
|
||||||
|
|
||||||
|
internal class Wakelock {
|
||||||
|
var activity: Activity? = null
|
||||||
|
|
||||||
|
private val enabled
|
||||||
|
get() = activity!!.window.attributes.flags and
|
||||||
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON != 0
|
||||||
|
|
||||||
|
fun toggle(message: ToggleMessage) {
|
||||||
|
if (activity == null) {
|
||||||
|
throw NoActivityException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val activity = this.activity!!
|
||||||
|
val enabled = this.enabled
|
||||||
|
|
||||||
|
if (message.enable!!) {
|
||||||
|
if (!enabled) activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
} else if (enabled) {
|
||||||
|
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEnabled(): IsEnabledMessage {
|
||||||
|
if (activity == null) {
|
||||||
|
throw NoActivityException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsEnabledMessage(enabled = enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoActivityException : Exception("wakelock requires a foreground activity")
|
||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.2.3), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||||
|
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.flutter.plugin.common.BasicMessageChannel
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.MessageCodec
|
||||||
|
import io.flutter.plugin.common.StandardMethodCodec
|
||||||
|
import io.flutter.plugin.common.StandardMessageCodec
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
private object WakelockPlusMessagesPigeonUtils {
|
||||||
|
|
||||||
|
fun wrapResult(result: Any?): List<Any?> {
|
||||||
|
return listOf(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wrapError(exception: Throwable): List<Any?> {
|
||||||
|
return if (exception is WakelockPlusFlutterError) {
|
||||||
|
listOf(
|
||||||
|
exception.code,
|
||||||
|
exception.message,
|
||||||
|
exception.details
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
exception.javaClass.simpleName,
|
||||||
|
exception.toString(),
|
||||||
|
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun deepEquals(a: Any?, b: Any?): Boolean {
|
||||||
|
if (a is ByteArray && b is ByteArray) {
|
||||||
|
return a.contentEquals(b)
|
||||||
|
}
|
||||||
|
if (a is IntArray && b is IntArray) {
|
||||||
|
return a.contentEquals(b)
|
||||||
|
}
|
||||||
|
if (a is LongArray && b is LongArray) {
|
||||||
|
return a.contentEquals(b)
|
||||||
|
}
|
||||||
|
if (a is DoubleArray && b is DoubleArray) {
|
||||||
|
return a.contentEquals(b)
|
||||||
|
}
|
||||||
|
if (a is Array<*> && b is Array<*>) {
|
||||||
|
return a.size == b.size &&
|
||||||
|
a.indices.all{ deepEquals(a[it], b[it]) }
|
||||||
|
}
|
||||||
|
if (a is List<*> && b is List<*>) {
|
||||||
|
return a.size == b.size &&
|
||||||
|
a.indices.all{ deepEquals(a[it], b[it]) }
|
||||||
|
}
|
||||||
|
if (a is Map<*, *> && b is Map<*, *>) {
|
||||||
|
return a.size == b.size && a.all {
|
||||||
|
(b as Map<Any?, Any?>).contains(it.key) &&
|
||||||
|
deepEquals(it.value, b[it.key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
||||||
|
* @property code The error code.
|
||||||
|
* @property message The error message.
|
||||||
|
* @property details The error details. Must be a datatype supported by the api codec.
|
||||||
|
*/
|
||||||
|
class WakelockPlusFlutterError (
|
||||||
|
val code: String,
|
||||||
|
override val message: String? = null,
|
||||||
|
val details: Any? = null
|
||||||
|
) : Throwable()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message for toggling the wakelock on the platform side.
|
||||||
|
*
|
||||||
|
* Generated class from Pigeon that represents data sent in messages.
|
||||||
|
*/
|
||||||
|
data class ToggleMessage (
|
||||||
|
val enable: Boolean? = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): ToggleMessage {
|
||||||
|
val enable = pigeonVar_list[0] as Boolean?
|
||||||
|
return ToggleMessage(enable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
enable,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is ToggleMessage) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return WakelockPlusMessagesPigeonUtils.deepEquals(toList(), other.toList()) }
|
||||||
|
|
||||||
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message for reporting the wakelock state from the platform side.
|
||||||
|
*
|
||||||
|
* Generated class from Pigeon that represents data sent in messages.
|
||||||
|
*/
|
||||||
|
data class IsEnabledMessage (
|
||||||
|
val enabled: Boolean? = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): IsEnabledMessage {
|
||||||
|
val enabled = pigeonVar_list[0] as Boolean?
|
||||||
|
return IsEnabledMessage(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
enabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is IsEnabledMessage) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return WakelockPlusMessagesPigeonUtils.deepEquals(toList(), other.toList()) }
|
||||||
|
|
||||||
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
|
}
|
||||||
|
private open class WakelockPlusMessagesPigeonCodec : StandardMessageCodec() {
|
||||||
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
|
return when (type) {
|
||||||
|
129.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
ToggleMessage.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
130.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
IsEnabledMessage.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> super.readValueOfType(type, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||||
|
when (value) {
|
||||||
|
is ToggleMessage -> {
|
||||||
|
stream.write(129)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is IsEnabledMessage -> {
|
||||||
|
stream.write(130)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
else -> super.writeValue(stream, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
|
interface WakelockPlusApi {
|
||||||
|
fun toggle(msg: ToggleMessage)
|
||||||
|
fun isEnabled(): IsEnabledMessage
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by WakelockPlusApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
WakelockPlusMessagesPigeonCodec()
|
||||||
|
}
|
||||||
|
/** Sets up an instance of `WakelockPlusApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@JvmOverloads
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: WakelockPlusApi?, messageChannelSuffix: String = "") {
|
||||||
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.wakelock_plus_platform_interface.WakelockPlusApi.toggle$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val msgArg = args[0] as ToggleMessage
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
api.toggle(msgArg)
|
||||||
|
listOf(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
WakelockPlusMessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.wakelock_plus_platform_interface.WakelockPlusApi.isEnabled$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
listOf(api.isEnabled())
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
WakelockPlusMessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package dev.fluttercommunity.plus.wakelock
|
||||||
|
|
||||||
|
import IsEnabledMessage
|
||||||
|
import ToggleMessage
|
||||||
|
import WakelockPlusApi
|
||||||
|
|
||||||
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
|
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||||
|
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||||
|
|
||||||
|
/** WakelockPlusPlugin */
|
||||||
|
class WakelockPlusPlugin: FlutterPlugin, WakelockPlusApi, ActivityAware {
|
||||||
|
private var wakelock: Wakelock? = null
|
||||||
|
|
||||||
|
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
|
WakelockPlusApi.setUp(flutterPluginBinding.binaryMessenger, this)
|
||||||
|
wakelock = Wakelock()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
|
WakelockPlusApi.setUp(binding.binaryMessenger, null)
|
||||||
|
wakelock = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||||
|
wakelock?.activity = binding.activity
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromActivity() {
|
||||||
|
wakelock?.activity = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||||
|
onAttachedToActivity(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromActivityForConfigChanges() {
|
||||||
|
onDetachedFromActivity()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggle(msg: ToggleMessage) {
|
||||||
|
wakelock!!.toggle(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isEnabled(): IsEnabledMessage {
|
||||||
|
return wakelock!!.isEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
.idea/
|
||||||
|
.vagrant/
|
||||||
|
.sconsign.dblite
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
profile
|
||||||
|
|
||||||
|
DerivedData/
|
||||||
|
build/
|
||||||
|
.build/
|
||||||
|
.index-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
|
||||||
|
/Flutter/ephemeral/
|
||||||
|
/Flutter/flutter_export_environment.sh
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#
|
||||||
|
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||||
|
# Run `pod lib lint wakelock_plus.podspec` to validate before publishing.
|
||||||
|
#
|
||||||
|
Pod::Spec.new do |s|
|
||||||
|
s.name = 'wakelock_plus'
|
||||||
|
s.version = '0.0.1'
|
||||||
|
s.summary = 'Plugin that allows you to keep the device screen awake, i.e. prevent the screen from sleeping on Android, iOS, macOS, Windows, Linux, and web.'
|
||||||
|
s.description = <<-DESC
|
||||||
|
Plugin that allows you to keep the device screen awake, i.e. prevent the screen from sleeping on Android, iOS, macOS, Windows, Linux, and web.
|
||||||
|
DESC
|
||||||
|
s.homepage = 'https://github.com/fluttercommunity/wakelock_plus'
|
||||||
|
s.license = { :file => '../LICENSE' }
|
||||||
|
s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' }
|
||||||
|
s.source = { :path => '.' }
|
||||||
|
s.source_files = 'wakelock_plus/Sources/wakelock_plus/**/*.{h,m}'
|
||||||
|
s.public_header_files = 'wakelock_plus/Sources/wakelock_plus/include/**/*.h'
|
||||||
|
s.dependency 'Flutter'
|
||||||
|
s.ios.deployment_target = '12.0'
|
||||||
|
s.tvos.deployment_target = '12.0'
|
||||||
|
|
||||||
|
# Flutter.framework does not contain a i386 slice.
|
||||||
|
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
|
||||||
|
s.resource_bundles = {'wakelock_plus_privacy' => ['wakelock_plus/Sources/wakelock_plus/Resources/PrivacyInfo.xcprivacy']}
|
||||||
|
end
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// swift-tools-version: 5.9
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "wakelock_plus",
|
||||||
|
platforms: [
|
||||||
|
.iOS("11.0")
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
.library(name: "wakelock-plus", targets: ["wakelock_plus"])
|
||||||
|
],
|
||||||
|
dependencies: [],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "wakelock_plus",
|
||||||
|
dependencies: [],
|
||||||
|
resources: [
|
||||||
|
.process("Resources")
|
||||||
|
],
|
||||||
|
cSettings: [
|
||||||
|
.headerSearchPath("include/wakelock_plus")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// UIApplication+idleTimerLock.m
|
||||||
|
// wakelock
|
||||||
|
//
|
||||||
|
// Created by suyao on 2021/12/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "./include/wakelock_plus/UIApplication+idleTimerLock.h"
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
|
||||||
|
static NSString *idleTimerLockKey = @"idleTimerLockKey";
|
||||||
|
|
||||||
|
|
||||||
|
@implementation UIApplication (idleTimerLock)
|
||||||
|
|
||||||
|
+ (void)load {
|
||||||
|
Method setIdleTimerDisabled = class_getInstanceMethod(self, @selector(setIdleTimerDisabled:));
|
||||||
|
|
||||||
|
Method lock_setIdleTimerDisabled = class_getInstanceMethod(self, @selector(lock_setIdleTimerDisabled:));
|
||||||
|
|
||||||
|
method_exchangeImplementations(setIdleTimerDisabled, lock_setIdleTimerDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)lock_setIdleTimerDisabled:(BOOL)enable {
|
||||||
|
if ([self lock_idleTimerlockEnable]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[self lock_setIdleTimerDisabled:enable];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)lock_idleTimerlockEnable:(BOOL)enable {
|
||||||
|
objc_setAssociatedObject(self, &idleTimerLockKey, @(enable), OBJC_ASSOCIATION_COPY);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)lock_idleTimerlockEnable
|
||||||
|
{
|
||||||
|
return [objc_getAssociatedObject(self, &idleTimerLockKey) boolValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#import "./include/wakelock_plus/WakelockPlusPlugin.h"
|
||||||
|
#import "./include/wakelock_plus/messages.g.h"
|
||||||
|
#import "./include/wakelock_plus/UIApplication+idleTimerLock.h"
|
||||||
|
|
||||||
|
@interface WakelockPlusPlugin () <WAKELOCKPLUSWakelockPlusApi>
|
||||||
|
|
||||||
|
@property (nonatomic, assign) BOOL enable;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation WakelockPlusPlugin
|
||||||
|
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
|
||||||
|
WakelockPlusPlugin* instance = [[WakelockPlusPlugin alloc] init];
|
||||||
|
SetUpWAKELOCKPLUSWakelockPlusApi(registrar.messenger, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleMsg:(WAKELOCKPLUSToggleMessage*)input error:(FlutterError**)error {
|
||||||
|
BOOL enable = [input.enable boolValue];
|
||||||
|
if (!enable) {
|
||||||
|
[[UIApplication sharedApplication] lock_idleTimerlockEnable:enable];//should disable first
|
||||||
|
[self setIdleTimerDisabled:enable];
|
||||||
|
} else {
|
||||||
|
[self setIdleTimerDisabled:enable];
|
||||||
|
[[UIApplication sharedApplication] lock_idleTimerlockEnable:enable];
|
||||||
|
}
|
||||||
|
self.enable = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setIdleTimerDisabled:(BOOL)enable {
|
||||||
|
BOOL enabled = [[UIApplication sharedApplication] isIdleTimerDisabled];
|
||||||
|
if (enable!= enabled) {
|
||||||
|
[[UIApplication sharedApplication] setIdleTimerDisabled:enable];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (WAKELOCKPLUSIsEnabledMessage*)isEnabledWithError:(FlutterError* __autoreleasing *)error {
|
||||||
|
NSNumber *enabled = [NSNumber numberWithBool:[[UIApplication sharedApplication] isIdleTimerDisabled]];
|
||||||
|
WAKELOCKPLUSIsEnabledMessage* result = [[WAKELOCKPLUSIsEnabledMessage alloc] init];
|
||||||
|
result.enabled = enabled;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setEnable:(BOOL)enable {
|
||||||
|
_enable = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// UIApplication+idleTimerLock.h
|
||||||
|
// wakelock
|
||||||
|
//
|
||||||
|
// Created by suyao on 2021/12/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface UIApplication (idleTimerLock)
|
||||||
|
|
||||||
|
- (void)lock_idleTimerlockEnable:(BOOL)enable;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#import <Flutter/Flutter.h>
|
||||||
|
|
||||||
|
@interface WakelockPlusPlugin : NSObject<FlutterPlugin>
|
||||||
|
@end
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.2.3), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
|
||||||
|
@import Foundation;
|
||||||
|
|
||||||
|
@protocol FlutterBinaryMessenger;
|
||||||
|
@protocol FlutterMessageCodec;
|
||||||
|
@class FlutterError;
|
||||||
|
@class FlutterStandardTypedData;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@class WAKELOCKPLUSToggleMessage;
|
||||||
|
@class WAKELOCKPLUSIsEnabledMessage;
|
||||||
|
|
||||||
|
/// Message for toggling the wakelock on the platform side.
|
||||||
|
@interface WAKELOCKPLUSToggleMessage : NSObject
|
||||||
|
+ (instancetype)makeWithEnable:(nullable NSNumber *)enable;
|
||||||
|
@property(nonatomic, strong, nullable) NSNumber * enable;
|
||||||
|
@end
|
||||||
|
|
||||||
|
/// Message for reporting the wakelock state from the platform side.
|
||||||
|
@interface WAKELOCKPLUSIsEnabledMessage : NSObject
|
||||||
|
+ (instancetype)makeWithEnabled:(nullable NSNumber *)enabled;
|
||||||
|
@property(nonatomic, strong, nullable) NSNumber * enabled;
|
||||||
|
@end
|
||||||
|
|
||||||
|
/// The codec used by all APIs.
|
||||||
|
NSObject<FlutterMessageCodec> *WAKELOCKPLUSGetMessagesCodec(void);
|
||||||
|
|
||||||
|
@protocol WAKELOCKPLUSWakelockPlusApi
|
||||||
|
- (void)toggleMsg:(WAKELOCKPLUSToggleMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error;
|
||||||
|
/// @return `nil` only when `error != nil`.
|
||||||
|
- (nullable WAKELOCKPLUSIsEnabledMessage *)isEnabledWithError:(FlutterError *_Nullable *_Nonnull)error;
|
||||||
|
@end
|
||||||
|
|
||||||
|
extern void SetUpWAKELOCKPLUSWakelockPlusApi(id<FlutterBinaryMessenger> binaryMessenger, NSObject<WAKELOCKPLUSWakelockPlusApi> *_Nullable api);
|
||||||
|
|
||||||
|
extern void SetUpWAKELOCKPLUSWakelockPlusApiWithSuffix(id<FlutterBinaryMessenger> binaryMessenger, NSObject<WAKELOCKPLUSWakelockPlusApi> *_Nullable api, NSString *messageChannelSuffix);
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.2.3), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
|
||||||
|
#import "./include/wakelock_plus/messages.g.h"
|
||||||
|
|
||||||
|
#if TARGET_OS_OSX
|
||||||
|
@import FlutterMacOS;
|
||||||
|
#else
|
||||||
|
@import Flutter;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static NSArray<id> *wrapResult(id result, FlutterError *error) {
|
||||||
|
if (error) {
|
||||||
|
return @[
|
||||||
|
error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return @[ result ?: [NSNull null] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
static id GetNullableObjectAtIndex(NSArray<id> *array, NSInteger key) {
|
||||||
|
id result = array[key];
|
||||||
|
return (result == [NSNull null]) ? nil : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface WAKELOCKPLUSToggleMessage ()
|
||||||
|
+ (WAKELOCKPLUSToggleMessage *)fromList:(NSArray<id> *)list;
|
||||||
|
+ (nullable WAKELOCKPLUSToggleMessage *)nullableFromList:(NSArray<id> *)list;
|
||||||
|
- (NSArray<id> *)toList;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface WAKELOCKPLUSIsEnabledMessage ()
|
||||||
|
+ (WAKELOCKPLUSIsEnabledMessage *)fromList:(NSArray<id> *)list;
|
||||||
|
+ (nullable WAKELOCKPLUSIsEnabledMessage *)nullableFromList:(NSArray<id> *)list;
|
||||||
|
- (NSArray<id> *)toList;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation WAKELOCKPLUSToggleMessage
|
||||||
|
+ (instancetype)makeWithEnable:(nullable NSNumber *)enable {
|
||||||
|
WAKELOCKPLUSToggleMessage* pigeonResult = [[WAKELOCKPLUSToggleMessage alloc] init];
|
||||||
|
pigeonResult.enable = enable;
|
||||||
|
return pigeonResult;
|
||||||
|
}
|
||||||
|
+ (WAKELOCKPLUSToggleMessage *)fromList:(NSArray<id> *)list {
|
||||||
|
WAKELOCKPLUSToggleMessage *pigeonResult = [[WAKELOCKPLUSToggleMessage alloc] init];
|
||||||
|
pigeonResult.enable = GetNullableObjectAtIndex(list, 0);
|
||||||
|
return pigeonResult;
|
||||||
|
}
|
||||||
|
+ (nullable WAKELOCKPLUSToggleMessage *)nullableFromList:(NSArray<id> *)list {
|
||||||
|
return (list) ? [WAKELOCKPLUSToggleMessage fromList:list] : nil;
|
||||||
|
}
|
||||||
|
- (NSArray<id> *)toList {
|
||||||
|
return @[
|
||||||
|
self.enable ?: [NSNull null],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation WAKELOCKPLUSIsEnabledMessage
|
||||||
|
+ (instancetype)makeWithEnabled:(nullable NSNumber *)enabled {
|
||||||
|
WAKELOCKPLUSIsEnabledMessage* pigeonResult = [[WAKELOCKPLUSIsEnabledMessage alloc] init];
|
||||||
|
pigeonResult.enabled = enabled;
|
||||||
|
return pigeonResult;
|
||||||
|
}
|
||||||
|
+ (WAKELOCKPLUSIsEnabledMessage *)fromList:(NSArray<id> *)list {
|
||||||
|
WAKELOCKPLUSIsEnabledMessage *pigeonResult = [[WAKELOCKPLUSIsEnabledMessage alloc] init];
|
||||||
|
pigeonResult.enabled = GetNullableObjectAtIndex(list, 0);
|
||||||
|
return pigeonResult;
|
||||||
|
}
|
||||||
|
+ (nullable WAKELOCKPLUSIsEnabledMessage *)nullableFromList:(NSArray<id> *)list {
|
||||||
|
return (list) ? [WAKELOCKPLUSIsEnabledMessage fromList:list] : nil;
|
||||||
|
}
|
||||||
|
- (NSArray<id> *)toList {
|
||||||
|
return @[
|
||||||
|
self.enabled ?: [NSNull null],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface WAKELOCKPLUSMessagesPigeonCodecReader : FlutterStandardReader
|
||||||
|
@end
|
||||||
|
@implementation WAKELOCKPLUSMessagesPigeonCodecReader
|
||||||
|
- (nullable id)readValueOfType:(UInt8)type {
|
||||||
|
switch (type) {
|
||||||
|
case 129:
|
||||||
|
return [WAKELOCKPLUSToggleMessage fromList:[self readValue]];
|
||||||
|
case 130:
|
||||||
|
return [WAKELOCKPLUSIsEnabledMessage fromList:[self readValue]];
|
||||||
|
default:
|
||||||
|
return [super readValueOfType:type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface WAKELOCKPLUSMessagesPigeonCodecWriter : FlutterStandardWriter
|
||||||
|
@end
|
||||||
|
@implementation WAKELOCKPLUSMessagesPigeonCodecWriter
|
||||||
|
- (void)writeValue:(id)value {
|
||||||
|
if ([value isKindOfClass:[WAKELOCKPLUSToggleMessage class]]) {
|
||||||
|
[self writeByte:129];
|
||||||
|
[self writeValue:[value toList]];
|
||||||
|
} else if ([value isKindOfClass:[WAKELOCKPLUSIsEnabledMessage class]]) {
|
||||||
|
[self writeByte:130];
|
||||||
|
[self writeValue:[value toList]];
|
||||||
|
} else {
|
||||||
|
[super writeValue:value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface WAKELOCKPLUSMessagesPigeonCodecReaderWriter : FlutterStandardReaderWriter
|
||||||
|
@end
|
||||||
|
@implementation WAKELOCKPLUSMessagesPigeonCodecReaderWriter
|
||||||
|
- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data {
|
||||||
|
return [[WAKELOCKPLUSMessagesPigeonCodecWriter alloc] initWithData:data];
|
||||||
|
}
|
||||||
|
- (FlutterStandardReader *)readerWithData:(NSData *)data {
|
||||||
|
return [[WAKELOCKPLUSMessagesPigeonCodecReader alloc] initWithData:data];
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
NSObject<FlutterMessageCodec> *WAKELOCKPLUSGetMessagesCodec(void) {
|
||||||
|
static FlutterStandardMessageCodec *sSharedObject = nil;
|
||||||
|
static dispatch_once_t sPred = 0;
|
||||||
|
dispatch_once(&sPred, ^{
|
||||||
|
WAKELOCKPLUSMessagesPigeonCodecReaderWriter *readerWriter = [[WAKELOCKPLUSMessagesPigeonCodecReaderWriter alloc] init];
|
||||||
|
sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter];
|
||||||
|
});
|
||||||
|
return sSharedObject;
|
||||||
|
}
|
||||||
|
void SetUpWAKELOCKPLUSWakelockPlusApi(id<FlutterBinaryMessenger> binaryMessenger, NSObject<WAKELOCKPLUSWakelockPlusApi> *api) {
|
||||||
|
SetUpWAKELOCKPLUSWakelockPlusApiWithSuffix(binaryMessenger, api, @"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUpWAKELOCKPLUSWakelockPlusApiWithSuffix(id<FlutterBinaryMessenger> binaryMessenger, NSObject<WAKELOCKPLUSWakelockPlusApi> *api, NSString *messageChannelSuffix) {
|
||||||
|
messageChannelSuffix = messageChannelSuffix.length > 0 ? [NSString stringWithFormat: @".%@", messageChannelSuffix] : @"";
|
||||||
|
{
|
||||||
|
FlutterBasicMessageChannel *channel =
|
||||||
|
[[FlutterBasicMessageChannel alloc]
|
||||||
|
initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.wakelock_plus_platform_interface.WakelockPlusApi.toggle", messageChannelSuffix]
|
||||||
|
binaryMessenger:binaryMessenger
|
||||||
|
codec:WAKELOCKPLUSGetMessagesCodec()];
|
||||||
|
if (api) {
|
||||||
|
NSCAssert([api respondsToSelector:@selector(toggleMsg:error:)], @"WAKELOCKPLUSWakelockPlusApi api (%@) doesn't respond to @selector(toggleMsg:error:)", api);
|
||||||
|
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
|
||||||
|
NSArray<id> *args = message;
|
||||||
|
WAKELOCKPLUSToggleMessage *arg_msg = GetNullableObjectAtIndex(args, 0);
|
||||||
|
FlutterError *error;
|
||||||
|
[api toggleMsg:arg_msg error:&error];
|
||||||
|
callback(wrapResult(nil, error));
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
[channel setMessageHandler:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
FlutterBasicMessageChannel *channel =
|
||||||
|
[[FlutterBasicMessageChannel alloc]
|
||||||
|
initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.wakelock_plus_platform_interface.WakelockPlusApi.isEnabled", messageChannelSuffix]
|
||||||
|
binaryMessenger:binaryMessenger
|
||||||
|
codec:WAKELOCKPLUSGetMessagesCodec()];
|
||||||
|
if (api) {
|
||||||
|
NSCAssert([api respondsToSelector:@selector(isEnabledWithError:)], @"WAKELOCKPLUSWakelockPlusApi api (%@) doesn't respond to @selector(isEnabledWithError:)", api);
|
||||||
|
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
|
||||||
|
FlutterError *error;
|
||||||
|
WAKELOCKPLUSIsEnabledMessage *output = [api isEnabledWithError:&error];
|
||||||
|
callback(wrapResult(output, error));
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
[channel setMessageHandler:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
|||||||
|
export 'wakelock_plus_linux_plugin.dart';
|
||||||
|
export 'wakelock_plus_macos_plugin.dart';
|
||||||
|
export 'wakelock_plus_windows_plugin.dart';
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:dbus/dbus.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart';
|
||||||
|
|
||||||
|
/// The Linux implementation of the [WakelockPlusPlatformInterface].
|
||||||
|
///
|
||||||
|
/// This class implements the `wakelock_plus` plugin functionality for Linux
|
||||||
|
/// using the `org.freedesktop.portal.Inhibit` D-Bus API
|
||||||
|
/// (see https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit).
|
||||||
|
class WakelockPlusLinuxPlugin extends WakelockPlusPlatformInterface {
|
||||||
|
/// Registers this class as the default instance of [WakelockPlatformInterface].
|
||||||
|
static void registerWith() {
|
||||||
|
WakelockPlusPlatformInterface.instance = WakelockPlusLinuxPlugin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs an instance of [WakelockPlusLinuxPlugin].
|
||||||
|
factory WakelockPlusLinuxPlugin({
|
||||||
|
@visibleForTesting DBusClient? client,
|
||||||
|
@visibleForTesting DBusRemoteObject? object,
|
||||||
|
@visibleForTesting Future<String> Function()? appNameGetter,
|
||||||
|
}) {
|
||||||
|
final dbusClient = client ?? DBusClient.session();
|
||||||
|
final remoteObject =
|
||||||
|
object ??
|
||||||
|
DBusRemoteObject(
|
||||||
|
dbusClient,
|
||||||
|
name: 'org.freedesktop.portal.Desktop',
|
||||||
|
path: DBusObjectPath('/org/freedesktop/portal/desktop'),
|
||||||
|
);
|
||||||
|
return WakelockPlusLinuxPlugin._internal(
|
||||||
|
dbusClient,
|
||||||
|
remoteObject,
|
||||||
|
appNameGetter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
WakelockPlusLinuxPlugin._internal(
|
||||||
|
this._client,
|
||||||
|
this._object,
|
||||||
|
this._appNameGetter,
|
||||||
|
);
|
||||||
|
|
||||||
|
final DBusClient _client;
|
||||||
|
final DBusRemoteObject _object;
|
||||||
|
final Future<String> Function()? _appNameGetter;
|
||||||
|
DBusObjectPath? _requestHandle;
|
||||||
|
|
||||||
|
Future<String> get _appName =>
|
||||||
|
_appNameGetter?.call() ??
|
||||||
|
PackageInfo.fromPlatform().then((info) => info.appName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> toggle({required bool enable}) async {
|
||||||
|
if (enable) {
|
||||||
|
final appName = await _appName;
|
||||||
|
_requestHandle = await _object
|
||||||
|
.callMethod(
|
||||||
|
'org.freedesktop.portal.Inhibit',
|
||||||
|
'Inhibit',
|
||||||
|
[
|
||||||
|
const DBusString(''),
|
||||||
|
const DBusUint32(8),
|
||||||
|
DBusDict.stringVariant({
|
||||||
|
'reason': DBusString('$appName: wakelock active'),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
replySignature: DBusSignature('o'),
|
||||||
|
)
|
||||||
|
.then((response) => response.returnValues.single.asObjectPath());
|
||||||
|
} else if (_requestHandle != null) {
|
||||||
|
final requestObject = DBusRemoteObject(
|
||||||
|
_client,
|
||||||
|
name: 'org.freedesktop.portal.Desktop',
|
||||||
|
path: _requestHandle!,
|
||||||
|
);
|
||||||
|
await requestObject.callMethod(
|
||||||
|
'org.freedesktop.portal.Request',
|
||||||
|
'Close',
|
||||||
|
[],
|
||||||
|
replySignature: DBusSignature.empty,
|
||||||
|
);
|
||||||
|
_requestHandle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> get enabled async => _requestHandle != null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart';
|
||||||
|
|
||||||
|
/// The macOS implementation of the [WakelockPlusPlatformInterface].
|
||||||
|
///
|
||||||
|
/// This class implements the `wakelock_plus` plugin functionality for macOS.
|
||||||
|
///
|
||||||
|
/// Note that this is *also* a method channel implementation (like the default
|
||||||
|
/// instance). We use manual method channel calls instead of `pigeon` for the
|
||||||
|
/// moment because macOS support for `pigeon` is not clear yet.
|
||||||
|
/// See https://github.com/flutter/flutter/issues/73738.
|
||||||
|
class WakelockPlusMacOSPlugin extends WakelockPlusPlatformInterface {
|
||||||
|
static const MethodChannel _channel = MethodChannel('wakelock_plus_macos');
|
||||||
|
|
||||||
|
/// Registers this class as the default instance of [WakelockPlatformInterface].
|
||||||
|
static void registerWith() {
|
||||||
|
WakelockPlusPlatformInterface.instance = WakelockPlusMacOSPlugin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> toggle({required bool enable}) async {
|
||||||
|
await _channel.invokeMethod('toggle', <String, dynamic>{'enable': enable});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> get enabled async =>
|
||||||
|
await _channel.invokeMethod('enabled') as bool;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||||
|
import 'package:wakelock_plus/src/web_impl/import_js_library.dart';
|
||||||
|
import 'package:wakelock_plus/src/web_impl/js_wakelock.dart'
|
||||||
|
as wakelock_plus_web;
|
||||||
|
import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart';
|
||||||
|
|
||||||
|
/// The web implementation of the [WakelockPlatformInterface].
|
||||||
|
///
|
||||||
|
/// This class implements the `wakelock_plus` plugin functionality for web.
|
||||||
|
class WakelockPlusWebPlugin extends WakelockPlusPlatformInterface {
|
||||||
|
/// Registers [WakelockPlusWebPlugin] as the default instance of the
|
||||||
|
/// [WakelockPlatformInterface].
|
||||||
|
static void registerWith(Registrar registrar) {
|
||||||
|
WakelockPlusPlatformInterface.instance = WakelockPlusWebPlugin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The future that signals when the JS is loaded.
|
||||||
|
// This needs to be `await`ed before accessing any methods of the
|
||||||
|
// JS-interop layer.
|
||||||
|
Future<void>? _jsLoaded;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lazily imports the JS library once, then awaits to ensure that
|
||||||
|
// it's loaded into the DOM.
|
||||||
|
//
|
||||||
|
Future<void> _ensureJsLoaded() async {
|
||||||
|
_jsLoaded ??= importJsLibrary(
|
||||||
|
url: 'assets/no_sleep.js',
|
||||||
|
flutterPluginName: 'wakelock_plus',
|
||||||
|
);
|
||||||
|
return _jsLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> toggle({required bool enable}) async {
|
||||||
|
// Make sure the JS library is loaded before calling it.
|
||||||
|
await _ensureJsLoaded();
|
||||||
|
await wakelock_plus_web.toggle(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> get enabled async {
|
||||||
|
// Make sure the JS library is loaded before calling it.
|
||||||
|
await _ensureJsLoaded();
|
||||||
|
return wakelock_plus_web.enabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart';
|
||||||
|
import 'package:win32/win32.dart';
|
||||||
|
|
||||||
|
/// The Windows implementation of the [WakelockPlusPlatformInterface].
|
||||||
|
///
|
||||||
|
/// This class implements the `wakelock_plus` plugin functionality for Windows
|
||||||
|
/// using the `SetThreadExecutionState` win32 API
|
||||||
|
/// (see https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setthreadexecutionstate).
|
||||||
|
class WakelockPlusWindowsPlugin extends WakelockPlusPlatformInterface {
|
||||||
|
/// Registers this class as the default instance of [WakelockPlatformInterface].
|
||||||
|
static void registerWith() {
|
||||||
|
WakelockPlusPlatformInterface.instance = WakelockPlusWindowsPlugin();
|
||||||
|
}
|
||||||
|
|
||||||
|
var _enabled = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> toggle({required bool enable}) async {
|
||||||
|
final int response;
|
||||||
|
if (enable) {
|
||||||
|
response = SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
|
||||||
|
} else {
|
||||||
|
response = SetThreadExecutionState(ES_CONTINUOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetThreadExecutionState returns 0 if the operation failed.
|
||||||
|
if (response != 0) {
|
||||||
|
_enabled = enable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> get enabled async => _enabled;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isMock => false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui_web' as ui_web;
|
||||||
|
|
||||||
|
import 'package:web/web.dart' as web;
|
||||||
|
|
||||||
|
/// This is an implementation of the `import_js_library` plugin that is used
|
||||||
|
/// until that plugin is migrated to null safety.
|
||||||
|
/// See https://github.com/florent37/flutter_web_import_js_library/pull/6#issuecomment-735349208.
|
||||||
|
|
||||||
|
/// Imports a JS script file from the given [url] given the relative
|
||||||
|
/// [flutterPluginName].
|
||||||
|
Future<void> importJsLibrary({required String url, String? flutterPluginName}) async {
|
||||||
|
if (flutterPluginName == null) {
|
||||||
|
return _importJSLibraries([url]);
|
||||||
|
} else {
|
||||||
|
return _importJSLibraries([_libraryUrl(url, flutterPluginName)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _libraryUrl(String url, String pluginName) {
|
||||||
|
// Added suggested changes as per
|
||||||
|
// https://github.com/fluttercommunity/wakelock_plus/issues/19#issuecomment-2301963609
|
||||||
|
if (url.startsWith('./')) {
|
||||||
|
url = url.replaceFirst('./', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.startsWith('assets/')) {
|
||||||
|
if (const bool.fromEnvironment('WEB_PLUGIN_TESTS', defaultValue: false)) {
|
||||||
|
// Flutter tests running on Chrome just need to use the library path
|
||||||
|
// without pre-pending "assets/".
|
||||||
|
//
|
||||||
|
// In other words, don't use the asset manager since it's not currently
|
||||||
|
// supported for Chrome-based Flutter tests.
|
||||||
|
//
|
||||||
|
// See https://github.com/flutter/flutter/issues/159879 for more details.
|
||||||
|
// TODO: Remove the workaround once test asset support is added
|
||||||
|
// for tests running in Chrome.
|
||||||
|
return 'packages/$pluginName/$url';
|
||||||
|
}
|
||||||
|
return ui_web.assetManager.getAssetUrl('packages/$pluginName/$url');
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future? _importRunning;
|
||||||
|
Map<String, String> _loadedLibraries = {};
|
||||||
|
int _nextLibraryId = 0;
|
||||||
|
|
||||||
|
web.HTMLScriptElement _createScriptTag(String library) {
|
||||||
|
final scriptId = 'imported-js-library-${_nextLibraryId++}';
|
||||||
|
final script = web.document.createElement('script') as web.HTMLScriptElement
|
||||||
|
..type = 'text/javascript'
|
||||||
|
..charset = 'utf-8'
|
||||||
|
..async = true
|
||||||
|
..src = library
|
||||||
|
..id = scriptId;
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects a bunch of libraries in the `<head>` and returns a
|
||||||
|
/// Future that resolves when all load.
|
||||||
|
Future<void> _importJSLibraries(List<String> libraries) async {
|
||||||
|
// we add the library to _loadedLibraries asynchronously, so we need locking.
|
||||||
|
// Dart uses voluntary preemption, so everything between two `await`s can be
|
||||||
|
// considered locked
|
||||||
|
while (_importRunning != null) {
|
||||||
|
await _importRunning;
|
||||||
|
}
|
||||||
|
final importLockCompleter = Completer();
|
||||||
|
_importRunning = importLockCompleter.future;
|
||||||
|
final loading = <Future<void>>[];
|
||||||
|
final head = web.document.head;
|
||||||
|
|
||||||
|
for (final library in libraries) {
|
||||||
|
if (!_isImported(library)) {
|
||||||
|
final scriptTag = _createScriptTag(library);
|
||||||
|
head!.appendChild(scriptTag);
|
||||||
|
final completer = Completer();
|
||||||
|
loading.add(completer.future);
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
scriptTag.onLoad.first.then((_) {
|
||||||
|
_loadedLibraries[library] = scriptTag.id;
|
||||||
|
completer.complete();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
unawaited(scriptTag.onError.first.then((event) => completer.completeError(Exception('Error loading: $library'))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Future.wait(loading, eagerError: true);
|
||||||
|
} finally {
|
||||||
|
// first "unlock" future, then complete the completer for anyone already waiting.
|
||||||
|
// I'm not sure if `.complete()` is yielding execution, so this is the safe order
|
||||||
|
_importRunning = null;
|
||||||
|
importLockCompleter.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isImported(String url) {
|
||||||
|
final head = web.document.head!;
|
||||||
|
return _isLoaded(head, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isLoaded(web.HTMLHeadElement head, String url) {
|
||||||
|
final scriptId = _loadedLibraries[url];
|
||||||
|
if (scriptId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return head.querySelector('#$scriptId') != null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@JS('Wakelock')
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'dart:js_interop';
|
||||||
|
|
||||||
|
@JS('toggle')
|
||||||
|
external JSPromise<JSAny?> _toggle(JSBoolean enable);
|
||||||
|
|
||||||
|
/// Toggles the JS wakelock.
|
||||||
|
Future<void> toggle(bool enable) {
|
||||||
|
return _toggle(enable.toJS).toDart.then((_) => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JS('enabled')
|
||||||
|
external JSPromise<JSBoolean> _enabled();
|
||||||
|
|
||||||
|
/// Returns a JS promise of whether the wakelock is enabled or not.
|
||||||
|
Future<bool> enabled() {
|
||||||
|
return _enabled().toDart.then((enabled) => enabled.toDart);
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart';
|
||||||
|
|
||||||
|
export 'src/wakelock_plus_io_plugin.dart'
|
||||||
|
if (dart.library.js_interop) 'src/wakelock_plus_web_plugin.dart';
|
||||||
|
|
||||||
|
/// The [WakelockPlusPlatformInterface] that is used by [WakelockPlus].
|
||||||
|
///
|
||||||
|
/// This needs to be exposed for testing as unit tests might run on macOS.
|
||||||
|
/// In that case, the "hacky" instance override that we use here would be
|
||||||
|
/// triggered for the unit tests, even though the unit tests should actually
|
||||||
|
/// test the `pigeon` method channel implementation. Therefore, we want to
|
||||||
|
/// override this in tests that run on macOS (where there is no actual device).
|
||||||
|
@visibleForTesting
|
||||||
|
var wakelockPlusPlatformInstance = WakelockPlusPlatformInterface.instance;
|
||||||
|
|
||||||
|
/// Class providing all wakelock functionality using static members.
|
||||||
|
///
|
||||||
|
/// To enable the wakelock, you can use [WakelockPlus.enable] and to disable it,
|
||||||
|
/// you can call [WakelockPlus.disable].
|
||||||
|
/// You do not need to worry about making redundant calls, e.g. calling
|
||||||
|
/// [WakelockPlus.enable] when the wakelock is already enabled as the plugin handles
|
||||||
|
/// this for you, i.e. it checks the status to determine if the wakelock is
|
||||||
|
/// already enabled or disabled.
|
||||||
|
/// If you want the flexibility to pass a [bool] to control whether the wakelock
|
||||||
|
/// should be enabled or disabled, you can use [WakelockPlus.toggle].
|
||||||
|
///
|
||||||
|
/// The [WakelockPlus.enabled] getter allows you to retrieve the current wakelock
|
||||||
|
/// status of the device..
|
||||||
|
class WakelockPlus {
|
||||||
|
/// Enables the wakelock.
|
||||||
|
///
|
||||||
|
/// This can simply be called using `WakelockPlus.enable()` and does not return
|
||||||
|
/// anything.
|
||||||
|
/// You can await the [Future] to wait for the operation to complete.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [toggle], which allows to enable or disable using a [bool] parameter.
|
||||||
|
static Future<void> enable() => toggle(enable: true);
|
||||||
|
|
||||||
|
/// Disables the wakelock.
|
||||||
|
///
|
||||||
|
/// This can simply be called using `WakelockPlus.disable()` and does not return
|
||||||
|
/// anything.
|
||||||
|
/// You can await the [Future] to wait for the operation to complete.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [toggle], which allows to enable or disable using a [bool] parameter.
|
||||||
|
static Future<void> disable() => toggle(enable: false);
|
||||||
|
|
||||||
|
/// Toggles the wakelock on or off.
|
||||||
|
///
|
||||||
|
/// You can simply use this function to toggle the wakelock using a [bool]
|
||||||
|
/// value (for the [enable] parameter).
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// // This line keeps the screen on.
|
||||||
|
/// WakelockPlus.toggle(enable: true);
|
||||||
|
///
|
||||||
|
/// bool enableWakelock = false;
|
||||||
|
/// // The following line disables the WakelockPlus.
|
||||||
|
/// WakelockPlus.toggle(enable: enableWakelock);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You can await the [Future] to wait for the operation to complete.
|
||||||
|
static Future<void> toggle({required bool enable}) {
|
||||||
|
return wakelockPlusPlatformInstance.toggle(enable: enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the wakelock is currently enabled or not.
|
||||||
|
///
|
||||||
|
/// If you want to retrieve the current wakelock status, you will have to call
|
||||||
|
/// [WakelockPlus.enabled] and await its result:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// bool wakelockEnabled = await WakelockPlus.enabled;
|
||||||
|
/// ```
|
||||||
|
static Future<bool> get enabled => wakelockPlusPlatformInstance.enabled;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#
|
||||||
|
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
|
||||||
|
#
|
||||||
|
Pod::Spec.new do |s|
|
||||||
|
s.name = 'wakelock_plus'
|
||||||
|
s.version = '0.0.1'
|
||||||
|
s.summary = 'Plugin that allows you to keep the device screen awake, i.e. prevent the screen from sleeping on Android, iOS, macOS, Windows, and web.'
|
||||||
|
s.description = <<-DESC
|
||||||
|
Plugin that allows you to keep the device screen awake, i.e. prevent the screen from sleeping on Android, iOS, macOS, Windows, and web.
|
||||||
|
DESC
|
||||||
|
s.homepage = 'https://github.com/fluttercommunity/wakelock_plus'
|
||||||
|
s.license = { :type => 'BSD', :file => '../LICENSE' }
|
||||||
|
s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' }
|
||||||
|
s.source = { :http => 'https://github.com/fluttercommunity/wakelock_plus/tree/main/packages/wakelock_plus_macos' }
|
||||||
|
s.source_files = 'wakelock_plus/Sources/wakelock_plus/**/*.swift'
|
||||||
|
s.dependency 'FlutterMacOS'
|
||||||
|
s.osx.deployment_target = '10.15'
|
||||||
|
# Flutter.framework does not contain a i386 slice.
|
||||||
|
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
|
||||||
|
s.swift_version = '5.0'
|
||||||
|
s.resource_bundles = {'wakelock_plus' => ['wakelock_plus/Sources/wakelock_plus/Resources/PrivacyInfo.xcprivacy']}
|
||||||
|
end
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// swift-tools-version: 5.9
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "wakelock_plus",
|
||||||
|
platforms: [
|
||||||
|
.macOS("10.15")
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
.library(name: "wakelock-plus", targets: ["wakelock_plus"])
|
||||||
|
],
|
||||||
|
dependencies: [],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "wakelock_plus",
|
||||||
|
dependencies: [],
|
||||||
|
resources: [
|
||||||
|
.process("Resources"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
import IOKit.pwr_mgt
|
||||||
|
|
||||||
|
public class WakelockPlusMacosPlugin: NSObject, FlutterPlugin {
|
||||||
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
|
let channel = FlutterMethodChannel(name: "wakelock_plus_macos", binaryMessenger: registrar.messenger)
|
||||||
|
let instance = WakelockPlusMacosPlugin()
|
||||||
|
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
var assertionID: IOPMAssertionID = 0
|
||||||
|
var wakelockEnabled = false
|
||||||
|
|
||||||
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
switch call.method {
|
||||||
|
case "toggle":
|
||||||
|
let args = call.arguments as? Dictionary<String, Any>
|
||||||
|
let enable = args!["enable"] as! Bool
|
||||||
|
if enable {
|
||||||
|
enableWakelock()
|
||||||
|
} else {
|
||||||
|
disableWakelock();
|
||||||
|
}
|
||||||
|
result(true)
|
||||||
|
case "enabled":
|
||||||
|
result(wakelockEnabled)
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableWakelock(reason: String = "Disabling display sleep") {
|
||||||
|
if !wakelockEnabled {
|
||||||
|
wakelockEnabled = IOPMAssertionCreateWithName( kIOPMAssertionTypeNoDisplaySleep as CFString,
|
||||||
|
IOPMAssertionLevel(kIOPMAssertionLevelOn),
|
||||||
|
reason as CFString,
|
||||||
|
&assertionID) == kIOReturnSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableWakelock() {
|
||||||
|
if wakelockEnabled {
|
||||||
|
IOPMAssertionRelease(assertionID)
|
||||||
|
wakelockEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# Vendored from fluttercommunity/wakelock_plus at
|
||||||
|
# 4f4be85aafe1f8216c2fdd3376263d6f40529684. Local changes are the tvOS
|
||||||
|
# deployment target and explicit unawaited wrappers required by the root lint
|
||||||
|
# policy. Refresh from the newest upstream release compatible with win32 5.x,
|
||||||
|
# reapply both changes, then run pub get and the tvOS pod build.
|
||||||
|
name: wakelock_plus
|
||||||
|
description: >-2
|
||||||
|
Plugin that allows you to keep the device screen awake, i.e. prevent the screen from sleeping on
|
||||||
|
Android, iOS, macOS, Windows, Linux, and web.
|
||||||
|
version: 1.5.2
|
||||||
|
repository: https://github.com/fluttercommunity/wakelock_plus/tree/main/wakelock_plus
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.10.0 <4.0.0'
|
||||||
|
flutter: ">=3.38.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_web_plugins:
|
||||||
|
sdk: flutter
|
||||||
|
meta: ^1.17.0
|
||||||
|
wakelock_plus_platform_interface: ^1.4.0
|
||||||
|
|
||||||
|
# Windows dependencies
|
||||||
|
# win32 is compatible across v5 for Win32 only (not COM)
|
||||||
|
win32: ">=5.6.1 <6.0.0"
|
||||||
|
|
||||||
|
# Linux dependencies
|
||||||
|
dbus: ^0.7.12
|
||||||
|
package_info_plus: ^9.0.0
|
||||||
|
|
||||||
|
# Web dependencies
|
||||||
|
web: ">=0.5.1 <2.0.0"
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
|
pigeon: ^26.2.3 # dart run pigeon --input "pigeons/messages.dart"
|
||||||
|
mocktail: ^1.0.4
|
||||||
|
|
||||||
|
# For information on the generic Dart part of this file, see the
|
||||||
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
# The following section is specific to Flutter packages.
|
||||||
|
flutter:
|
||||||
|
# This section identifies this Flutter project as a plugin project.
|
||||||
|
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
|
||||||
|
# which should be registered in the plugin registry. This is required for
|
||||||
|
# using method channels.
|
||||||
|
# The Android 'package' specifies package in which the registered class is.
|
||||||
|
# This is required for using method channels on Android.
|
||||||
|
# The 'ffiPlugin' specifies that native code should be built and bundled.
|
||||||
|
# This is required for using `dart:ffi`.
|
||||||
|
# All these are used by the tooling to maintain consistency when
|
||||||
|
# adding or updating assets for this project.
|
||||||
|
plugin:
|
||||||
|
platforms:
|
||||||
|
android:
|
||||||
|
package: dev.fluttercommunity.plus.wakelock
|
||||||
|
pluginClass: WakelockPlusPlugin
|
||||||
|
ios:
|
||||||
|
pluginClass: WakelockPlusPlugin
|
||||||
|
windows:
|
||||||
|
dartPluginClass: WakelockPlusWindowsPlugin
|
||||||
|
macos:
|
||||||
|
pluginClass: WakelockPlusMacosPlugin
|
||||||
|
dartPluginClass: WakelockPlusMacOSPlugin
|
||||||
|
linux:
|
||||||
|
dartPluginClass: WakelockPlusLinuxPlugin
|
||||||
|
web:
|
||||||
|
pluginClass: WakelockPlusWebPlugin
|
||||||
|
fileName: src/wakelock_plus_web_plugin.dart
|
||||||
|
|
||||||
|
# To add assets to your plugin package, add an assets section, like this:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
#
|
||||||
|
# For details regarding assets in packages, see
|
||||||
|
# https://flutter.dev/assets-and-images/#from-packages
|
||||||
|
#
|
||||||
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|
||||||
|
# To add custom fonts to your plugin package, add a fonts section here,
|
||||||
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
# list giving the asset and other descriptors for the font. For
|
||||||
|
# example:
|
||||||
|
# fonts:
|
||||||
|
# - family: Schyler
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
|
# style: italic
|
||||||
|
# - family: Trajan Pro
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/TrajanPro.ttf
|
||||||
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
#
|
||||||
|
# For details regarding fonts in packages, see
|
||||||
|
# https://flutter.dev/custom-fonts/#from-packages
|
||||||
|
|
||||||
|
assets:
|
||||||
|
- packages/wakelock_plus/assets/no_sleep.js
|
||||||
+25
-30
@@ -242,11 +242,11 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "packages/connectivity_plus/connectivity_plus"
|
path: "packages/connectivity_plus/connectivity_plus"
|
||||||
ref: "4c0d4071150481f76d753c6ce1ff325bb959b9b5"
|
ref: bf04cdf66598dc3fca274b8b1db2b92b0bf6b73e
|
||||||
resolved-ref: "4c0d4071150481f76d753c6ce1ff325bb959b9b5"
|
resolved-ref: bf04cdf66598dc3fca274b8b1db2b92b0bf6b73e
|
||||||
url: "https://github.com/edde746/plus_plugins"
|
url: "https://github.com/fluttercommunity/plus_plugins"
|
||||||
source: git
|
source: git
|
||||||
version: "7.1.1"
|
version: "7.2.0"
|
||||||
connectivity_plus_platform_interface:
|
connectivity_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -702,12 +702,11 @@ packages:
|
|||||||
material_symbols_icons:
|
material_symbols_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
name: material_symbols_icons
|
||||||
ref: "1d8cd83"
|
sha256: "49c532dd0b74544e9d8d93ec0f821d52ec532e7c9263c889ebe71b1be4f34ba7"
|
||||||
resolved-ref: "1d8cd8325db29691a01738a25f220dd3515bd1e2"
|
url: "https://pub.dev"
|
||||||
url: "https://github.com/edde746/material_symbols_icons"
|
source: hosted
|
||||||
source: git
|
version: "4.2951.0"
|
||||||
version: "4.2906.0"
|
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -987,7 +986,7 @@ packages:
|
|||||||
path: "packages/saf_util"
|
path: "packages/saf_util"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "2.0.0"
|
version: "3.1.0"
|
||||||
screen_retriever:
|
screen_retriever:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1029,14 +1028,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
sentry:
|
sentry:
|
||||||
dependency: "direct overridden"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
path: "packages/dart"
|
name: sentry
|
||||||
ref: "build/fetch-native-zip"
|
sha256: "09c573f98ff6c5e98d527f351f037eb8ea9ff684a6e9e84b2e91357144016254"
|
||||||
resolved-ref: "711f54f34d6cda9cccfb57519b9e3b72dd116b69"
|
url: "https://pub.dev"
|
||||||
url: "https://github.com/edde746/sentry-dart"
|
source: hosted
|
||||||
source: git
|
version: "9.24.0"
|
||||||
version: "9.16.0"
|
|
||||||
sentry_dart_plugin:
|
sentry_dart_plugin:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -1048,12 +1046,11 @@ packages:
|
|||||||
sentry_flutter:
|
sentry_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "packages/flutter"
|
name: sentry_flutter
|
||||||
ref: "build/fetch-native-zip"
|
sha256: "4a04b7e1901b8128df783b6d36d48706b07b0cd055e621517c998de87f5dafb9"
|
||||||
resolved-ref: "711f54f34d6cda9cccfb57519b9e3b72dd116b69"
|
url: "https://pub.dev"
|
||||||
url: "https://github.com/edde746/sentry-dart"
|
source: hosted
|
||||||
source: git
|
version: "9.24.0"
|
||||||
version: "9.16.0"
|
|
||||||
serial_csv:
|
serial_csv:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1406,12 +1403,10 @@ packages:
|
|||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: wakelock_plus
|
path: "packages/wakelock_plus"
|
||||||
ref: "8595fee595c952b73d430f2eab05f2e0d858290e"
|
relative: true
|
||||||
resolved-ref: "8595fee595c952b73d430f2eab05f2e0d858290e"
|
source: path
|
||||||
url: "https://github.com/edde746/wakelock_plus"
|
version: "1.5.2"
|
||||||
source: git
|
|
||||||
version: "1.4.0"
|
|
||||||
wakelock_plus_platform_interface:
|
wakelock_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+15
-28
@@ -29,8 +29,8 @@ dependencies:
|
|||||||
duration: ^4.0.3
|
duration: ^4.0.3
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/edde746/plus_plugins
|
url: https://github.com/fluttercommunity/plus_plugins
|
||||||
ref: 4c0d4071150481f76d753c6ce1ff325bb959b9b5
|
ref: bf04cdf66598dc3fca274b8b1db2b92b0bf6b73e
|
||||||
path: packages/connectivity_plus/connectivity_plus
|
path: packages/connectivity_plus/connectivity_plus
|
||||||
os_media_controls:
|
os_media_controls:
|
||||||
git:
|
git:
|
||||||
@@ -38,10 +38,7 @@ dependencies:
|
|||||||
ref: 75556a968a4a1ebc42fbb8c31942c2418d4326e1
|
ref: 75556a968a4a1ebc42fbb8c31942c2418d4326e1
|
||||||
rate_limiter: ^1.0.0
|
rate_limiter: ^1.0.0
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
git:
|
path: packages/wakelock_plus
|
||||||
url: https://github.com/edde746/wakelock_plus
|
|
||||||
ref: 8595fee595c952b73d430f2eab05f2e0d858290e
|
|
||||||
path: wakelock_plus
|
|
||||||
path_provider: ^2.1.0
|
path_provider: ^2.1.0
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
universal_gamepad: ^1.5.7
|
universal_gamepad: ^1.5.7
|
||||||
@@ -51,23 +48,27 @@ dependencies:
|
|||||||
cryptography: ^2.9.0
|
cryptography: ^2.9.0
|
||||||
file_picker: ^10.3.10
|
file_picker: ^10.3.10
|
||||||
saf_util:
|
saf_util:
|
||||||
# Vendored 2.0.0 with a pendingResult lifecycle fix (see its CHANGELOG) —
|
# Vendored upstream 3.1.0 with a pending Result lifecycle fix (CHANGELOG) —
|
||||||
# upstream double-replies MethodChannel results and crashes the activity.
|
# upstream still double-replies MethodChannel results and crashes the Activity.
|
||||||
path: packages/saf_util
|
path: packages/saf_util
|
||||||
saf_stream: ^2.0.0
|
saf_stream: ^2.0.0
|
||||||
material_symbols_icons: ^4.2906.0
|
material_symbols_icons: ^4.2951.0
|
||||||
web_socket_channel: ^3.0.1
|
web_socket_channel: ^3.0.1
|
||||||
dart_discord_presence: ^1.2.0
|
dart_discord_presence: ^1.2.0
|
||||||
flutter_svg: ^2.2.3
|
flutter_svg: ^2.2.3
|
||||||
|
# Fork note: 41c16be creates the desktop temp parent before opening the
|
||||||
|
# partial file, independently fixing upstream #649's missing-directory case.
|
||||||
|
# Upstream's later target-directory fallback is unnecessary while this fork
|
||||||
|
# retains its same-filesystem move and resume-data cleanup implementation.
|
||||||
|
# No per-host trust bypass is carried: Plezy has no app-wide certificate
|
||||||
|
# exception policy, and weakening only background downloads would make API
|
||||||
|
# and playback behavior inconsistent. Revisit only with an explicit,
|
||||||
|
# host-scoped trust setting shared by every media-server HTTP client.
|
||||||
background_downloader:
|
background_downloader:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/edde746/background_downloader
|
url: https://github.com/edde746/background_downloader
|
||||||
ref: 021e2075260e324b7440a5c81b57bf0242d35020
|
ref: 021e2075260e324b7440a5c81b57bf0242d35020
|
||||||
sentry_flutter:
|
sentry_flutter: ^9.24.0
|
||||||
git:
|
|
||||||
url: https://github.com/edde746/sentry-dart
|
|
||||||
path: packages/flutter
|
|
||||||
ref: build/fetch-native-zip
|
|
||||||
auto_updater:
|
auto_updater:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/edde746/auto_updater
|
url: https://github.com/edde746/auto_updater
|
||||||
@@ -113,20 +114,6 @@ dependency_overrides:
|
|||||||
url: https://github.com/edde746/auto_updater
|
url: https://github.com/edde746/auto_updater
|
||||||
ref: 9e150f7
|
ref: 9e150f7
|
||||||
path: packages/auto_updater_windows
|
path: packages/auto_updater_windows
|
||||||
sentry:
|
|
||||||
git:
|
|
||||||
url: https://github.com/edde746/sentry-dart
|
|
||||||
path: packages/dart
|
|
||||||
ref: build/fetch-native-zip
|
|
||||||
sentry_flutter:
|
|
||||||
git:
|
|
||||||
url: https://github.com/edde746/sentry-dart
|
|
||||||
path: packages/flutter
|
|
||||||
ref: build/fetch-native-zip
|
|
||||||
material_symbols_icons:
|
|
||||||
git:
|
|
||||||
url: https://github.com/edde746/material_symbols_icons
|
|
||||||
ref: 1d8cd83
|
|
||||||
sentry:
|
sentry:
|
||||||
org: plezy
|
org: plezy
|
||||||
project: plezy
|
project: plezy
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
// Copyright 2013 The Flutter Authors
|
// Copyright 2013 The Flutter Authors
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
// Plezy tvOS copy: shared_preferences_foundation 2.5.6 from flutter/packages
|
||||||
|
// commit 18b9cc5f2b0820cb6164f0150958ba76db75c61a. Local delta: add
|
||||||
|
// `os(tvOS)` to the three iOS import/messenger guards. To update, copy this
|
||||||
|
// file and messages.g.swift from the newly locked package version, reapply only
|
||||||
|
// those guards, then build Runner for Apple TV simulator and device.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@@ -111,8 +116,7 @@ public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi {
|
|||||||
return Array(try getAll(allowList: allowList, options: options).keys)
|
return Array(try getAll(allowList: allowList, options: options).keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAll(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String: Any]
|
func getAll(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String: Any] {
|
||||||
{
|
|
||||||
return try SharedPreferencesPlugin.getAllPrefs(allowList: allowList, options: options)
|
return try SharedPreferencesPlugin.getAllPrefs(allowList: allowList, options: options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
|
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
|
||||||
// See also: https://pub.dev/packages/pigeon
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
// Plezy tvOS copy: shared_preferences_foundation 2.5.6 from flutter/packages
|
||||||
|
// commit 18b9cc5f2b0820cb6164f0150958ba76db75c61a. Local delta: add
|
||||||
|
// `os(tvOS)` to the Flutter import guard. Update this together with
|
||||||
|
// SharedPreferencesPlugin.swift using the guidance in that file.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@@ -146,8 +150,7 @@ struct SharedPreferencesPigeonOptions: Hashable {
|
|||||||
suiteName
|
suiteName
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
static func == (lhs: SharedPreferencesPigeonOptions, rhs: SharedPreferencesPigeonOptions) -> Bool
|
static func == (lhs: SharedPreferencesPigeonOptions, rhs: SharedPreferencesPigeonOptions) -> Bool {
|
||||||
{
|
|
||||||
return deepEqualsmessages(lhs.toList(), rhs.toList())
|
return deepEqualsmessages(lhs.toList(), rhs.toList())
|
||||||
}
|
}
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
|
|||||||
Reference in New Issue
Block a user