From ba15446394fc9ebd058ad454ccb58bf516a5e924 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Fri, 17 Nov 2023 14:24:57 +0800 Subject: [PATCH] fix: `deleteWithIds` method not working (#1038) Signed-off-by: CaiJingLong --- CHANGELOG.md | 6 + .../core/PhotoManagerDeleteManager.kt | 117 +++++++++++++++++- .../photo_manager/core/PhotoManagerPlugin.kt | 8 ++ .../page/developer/issues_page/issue_988.dart | 6 +- example/lib/widget/nav_column.dart | 12 +- pubspec.yaml | 2 +- 6 files changed, 142 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7440f7..b8c31e64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ that can be found in the LICENSE file. --> # CHANGELOG +## 3.0.0-dev.2 + +Fix: + +- Fix `PhotoManager.editor.deleteWithIds` method not working on Android API 29. + ## 3.0.0-dev.1 ***Breaking changes*** for remove some methods and classes. diff --git a/android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerDeleteManager.kt b/android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerDeleteManager.kt index 7035ad66..5d1a4454 100644 --- a/android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerDeleteManager.kt +++ b/android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerDeleteManager.kt @@ -1,6 +1,7 @@ package com.fluttercandies.photo_manager.core import android.app.Activity +import android.app.RecoverableSecurityException import android.content.ContentResolver import android.content.Context import android.content.Intent @@ -9,8 +10,10 @@ import android.os.Build import android.provider.MediaStore import androidx.annotation.RequiresApi import com.fluttercandies.photo_manager.core.utils.IDBUtils +import com.fluttercandies.photo_manager.util.LogUtils import com.fluttercandies.photo_manager.util.ResultHandler import io.flutter.plugin.common.PluginRegistry +import java.util.LinkedList class PhotoManagerDeleteManager(val context: Context, private var activity: Activity?) : PluginRegistry.ActivityResultListener { @@ -19,17 +22,59 @@ class PhotoManagerDeleteManager(val context: Context, private var activity: Acti this.activity = activity } + private var androidQDeleteRequestCode = 40070 + private val androidQUriMap = mutableMapOf() + private val androidQSuccessIds = mutableListOf() + + @RequiresApi(Build.VERSION_CODES.Q) + inner class AndroidQDeleteTask( + val id: String, + val uri: Uri, + private val exception: RecoverableSecurityException + ) { + fun requestPermission() { + val intent = Intent().apply { + data = uri + } + activity?.startIntentSenderForResult( + exception.userAction.actionIntent.intentSender, + androidQDeleteRequestCode, + intent, + 0, + 0, + 0 + ) + } + + fun handleResult(resultCode: Int) { + if (resultCode == Activity.RESULT_OK) { + androidQSuccessIds.add(id) + } + requestAndroidQNextPermission() + } + + } + + private var waitPermissionQueue = LinkedList() + private var currentTask: AndroidQDeleteTask? = null + private var androidRDeleteRequestCode = 40069 private val cr: ContentResolver get() = context.contentResolver - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?): Boolean { if (requestCode == androidRDeleteRequestCode) { handleAndroidRDelete(resultCode) return true } - return true + if (requestCode == androidQDeleteRequestCode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + currentTask?.handleResult(resultCode) + } + return true + } + return false } private fun handleAndroidRDelete(resultCode: Int) { @@ -58,6 +103,7 @@ class PhotoManagerDeleteManager(val context: Context, private var activity: Acti // } private var androidRHandler: ResultHandler? = null + private var androidQHandler: ResultHandler? = null @RequiresApi(Build.VERSION_CODES.R) fun deleteInApi30(uris: List, resultHandler: ResultHandler) { @@ -73,6 +119,73 @@ class PhotoManagerDeleteManager(val context: Context, private var activity: Acti ) } + private fun findIdByUriInApi29(uri: Uri): String? { + for (entry in androidQUriMap) { + if (entry.value == uri) { + return entry.key + } + } + return null + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun requestAndroidQNextPermission() { + val task = waitPermissionQueue.poll() + + if (task == null) { + // all permission is granted or denied + replyAndroidQDeleteResult() + return + } + + currentTask = task + task.requestPermission() + } + + @RequiresApi(Build.VERSION_CODES.Q) + fun deleteJustInApi29(uris: HashMap, resultHandler: ResultHandler) { + this.androidQHandler = resultHandler + + androidQUriMap.clear() + androidQUriMap.putAll(uris) + androidQSuccessIds.clear() + waitPermissionQueue.clear() + + for (entry in uris) { + val uri = entry.value ?: continue + val id = entry.key + try { + cr.delete(uri, null, null) + } catch (e: Exception) { + // request delete permission + if (e is RecoverableSecurityException) { + val task = AndroidQDeleteTask(id, uri, e) + waitPermissionQueue.add(task) + } else { + LogUtils.error("delete assets error in api 29", e) + replyAndroidQDeleteResult() + return + } + } + } + + requestAndroidQNextPermission() + } + + private fun replyAndroidQDeleteResult() { + if (androidQSuccessIds.isNotEmpty()) { + // execute real delete + for (id in androidQSuccessIds) { + val uri = androidQUriMap[id] ?: continue + cr.delete(uri, null, null) + } + } + + androidQHandler?.reply(androidQSuccessIds.toList()) + androidQSuccessIds.clear() + androidQHandler = null + } + @RequiresApi(Build.VERSION_CODES.R) fun moveToTrashInApi30(uris: List, resultHandler: ResultHandler) { this.androidRHandler = resultHandler diff --git a/android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerPlugin.kt b/android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerPlugin.kt index 96bfd7f0..825f6f52 100644 --- a/android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerPlugin.kt +++ b/android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerPlugin.kt @@ -2,6 +2,7 @@ package com.fluttercandies.photo_manager.core import android.app.Activity import android.content.Context +import android.net.Uri import android.os.Build import android.os.Handler import android.os.Looper @@ -537,6 +538,13 @@ class PhotoManagerPlugin( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val uris = ids.map { photoManager.getUri(it) }.toList() deleteManager.deleteInApi30(uris, resultHandler) + } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + val idUriMap = HashMap() + for (id in ids) { + val uri = photoManager.getUri(id) + idUriMap[id] = uri + } + deleteManager.deleteJustInApi29(idUriMap, resultHandler) } else { deleteManager.deleteInApi28(ids) resultHandler.reply(ids) diff --git a/example/lib/page/developer/issues_page/issue_988.dart b/example/lib/page/developer/issues_page/issue_988.dart index 91890ad7..756345c0 100644 --- a/example/lib/page/developer/issues_page/issue_988.dart +++ b/example/lib/page/developer/issues_page/issue_988.dart @@ -226,7 +226,11 @@ class __DeleteAssetImageListState extends State<_DeleteAssetImageList> { .deleteWithIds(checked.map((e) => e.id).toList()); showToast('Delete success, ids: $ids'); - + + if (ids.isNotEmpty) { + checked.removeWhere((element) => ids.contains(element.id)); + } + // refresh the list loadAssets(); }, diff --git a/example/lib/widget/nav_column.dart b/example/lib/widget/nav_column.dart index 738bacc1..96e28bb0 100644 --- a/example/lib/widget/nav_column.dart +++ b/example/lib/widget/nav_column.dart @@ -19,11 +19,13 @@ class NavColumn extends StatelessWidget { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), - child: Center( - child: Column( - children: [ - for (final Widget item in children) buildItem(context, item), - ], + child: SingleChildScrollView( + child: Center( + child: Column( + children: [ + for (final Widget item in children) buildItem(context, item), + ], + ), ), ), ); diff --git a/pubspec.yaml b/pubspec.yaml index fca28494..0fe0c14b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: photo_manager description: A Flutter plugin that provides assets abstraction management APIs on Android, iOS, and macOS. repository: https://github.com/fluttercandies/flutter_photo_manager -version: 3.0.0-dev.1 +version: 3.0.0-dev.2 environment: sdk: ">=2.13.0 <4.0.0"