-
Notifications
You must be signed in to change notification settings - Fork 365
Feat/android batch move assets #1319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c419616
93dc675
d5c311e
bf653d8
42380a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,191 @@ | ||||||
| package com.fluttercandies.photo_manager.core | ||||||
|
|
||||||
| import android.app.Activity | ||||||
| import android.content.ContentResolver | ||||||
| import android.content.ContentValues | ||||||
| import android.content.Context | ||||||
| import android.content.Intent | ||||||
| import android.net.Uri | ||||||
| import android.os.Build | ||||||
| import android.provider.MediaStore | ||||||
| import androidx.annotation.RequiresApi | ||||||
| import com.fluttercandies.photo_manager.util.LogUtils | ||||||
| import com.fluttercandies.photo_manager.util.ResultHandler | ||||||
| import io.flutter.plugin.common.PluginRegistry | ||||||
|
|
||||||
| /** | ||||||
| * Manager for handling write requests (modifications) on Android 11+ (API 30+) | ||||||
| * Uses MediaStore.createWriteRequest() to request user permission for batch modifications | ||||||
| */ | ||||||
| class PhotoManagerWriteManager(val context: Context, private var activity: Activity?) : | ||||||
| PluginRegistry.ActivityResultListener { | ||||||
|
|
||||||
| fun bindActivity(activity: Activity?) { | ||||||
| this.activity = activity | ||||||
| } | ||||||
|
|
||||||
| private var androidRWriteRequestCode = 40071 | ||||||
| private var writeHandler: ResultHandler? = null | ||||||
| private var pendingOperation: WriteOperation? = null | ||||||
|
|
||||||
| private val cr: ContentResolver | ||||||
| get() = context.contentResolver | ||||||
|
|
||||||
| /** | ||||||
| * Represents a pending write operation that will be executed after user grants permission | ||||||
| */ | ||||||
| private data class WriteOperation( | ||||||
| val uris: List<Uri>, | ||||||
| val targetPath: String, | ||||||
| val operationType: OperationType | ||||||
| ) | ||||||
|
|
||||||
| enum class OperationType { | ||||||
| MOVE, // Move files to another folder | ||||||
| UPDATE // Generic update operation | ||||||
| } | ||||||
|
|
||||||
| override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?): Boolean { | ||||||
| if (requestCode == androidRWriteRequestCode) { | ||||||
| handleWriteResult(resultCode) | ||||||
| return true | ||||||
| } | ||||||
| return false | ||||||
| } | ||||||
|
|
||||||
| private fun handleWriteResult(resultCode: Int) { | ||||||
| if (resultCode == Activity.RESULT_OK) { | ||||||
| // User granted permission, execute the pending operation | ||||||
| val operation = pendingOperation | ||||||
| if (operation != null) { | ||||||
| val success = when (operation.operationType) { | ||||||
| OperationType.MOVE -> performMove(operation.uris, operation.targetPath) | ||||||
| OperationType.UPDATE -> performUpdate(operation.uris, operation.targetPath) | ||||||
| } | ||||||
| writeHandler?.reply(success) | ||||||
| } else { | ||||||
| LogUtils.error("No pending operation found after write permission granted") | ||||||
| writeHandler?.reply(false) | ||||||
| } | ||||||
| } else { | ||||||
| // User denied permission | ||||||
| LogUtils.info("User denied write permission") | ||||||
| writeHandler?.reply(false) | ||||||
| } | ||||||
|
|
||||||
| // Clean up | ||||||
| pendingOperation = null | ||||||
| writeHandler = null | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Request permission to move assets to a different album/folder on Android 11+ (API 30+) | ||||||
| * | ||||||
| * @param uris List of content URIs to move | ||||||
| * @param targetPath Target RELATIVE_PATH (e.g., "Pictures/MyAlbum") | ||||||
| * @param resultHandler Callback with result (true if successful, false otherwise) | ||||||
| */ | ||||||
| @RequiresApi(Build.VERSION_CODES.R) | ||||||
| fun moveToPathWithPermission(uris: List<Uri>, targetPath: String, resultHandler: ResultHandler) { | ||||||
| if (activity == null) { | ||||||
| LogUtils.error("Activity is null, cannot request write permission") | ||||||
| resultHandler.reply(false) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| this.writeHandler = resultHandler | ||||||
| this.pendingOperation = WriteOperation(uris, targetPath, OperationType.MOVE) | ||||||
|
|
||||||
| try { | ||||||
| val pendingIntent = MediaStore.createWriteRequest(cr, uris) | ||||||
| activity?.startIntentSenderForResult( | ||||||
|
||||||
| activity?.startIntentSenderForResult( | |
| activity.startIntentSenderForResult( |
Copilot
AI
Nov 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant null-safe call on activity. The method already checks if activity == null at line 167 and returns early if it is. The null-safe call activity?.startIntentSenderForResult is unnecessary here and should be activity.startIntentSenderForResult since activity is guaranteed to be non-null at this point.
This makes the code clearer and avoids confusion about whether the operation could silently fail.
| activity?.startIntentSenderForResult( | |
| activity.startIntentSenderForResult( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
mapNotNulloperation doesn't handle exceptions thrown byphotoManager.getUri(it). ThegetUrimethod can throw aRuntimeExceptionwhen an asset is not found (line 257 in PhotoManager.kt). These exceptions won't be caught by the outer try-catch block and could crash the operation.Consider wrapping the mapping operation in a try-catch: