Delete files via MediaStore api

This commit is contained in:
Ming Ming 2022-05-05 00:54:20 +08:00
parent 4da8b95c61
commit d33e3af806
7 changed files with 160 additions and 4 deletions

View file

@ -5,7 +5,7 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage" />
<application
@ -13,7 +13,8 @@
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="false"
android:largeHeap="true">
android:largeHeap="true"
android:requestLegacyExternalStorage="true">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"

View file

@ -9,6 +9,8 @@ interface K {
const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002
const val PERMISSION_REQUEST_CODE = 11011
const val MEDIA_STORE_DELETE_REQUEST_CODE = 11012
const val LIB_ID = "com.nkming.nc_photos.plugin"
const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL"

View file

@ -3,13 +3,17 @@ package com.nkming.nc_photos.plugin
import android.app.Activity
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.util.Log
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
import java.io.File
/*
@ -24,8 +28,10 @@ import java.io.File
* fun queryFiles(relativePath: String): List<Map>
*/
class MediaStoreChannelHandler(context: Context) :
MethodChannel.MethodCallHandler, ActivityAware {
MethodChannel.MethodCallHandler, EventChannel.StreamHandler,
ActivityAware, PluginRegistry.ActivityResultListener {
companion object {
const val EVENT_CHANNEL = "${K.LIB_ID}/media_store"
const val METHOD_CHANNEL = "${K.LIB_ID}/media_store_method"
private const val TAG = "MediaStoreChannelHandler"
@ -49,6 +55,27 @@ class MediaStoreChannelHandler(context: Context) :
activity = null
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, data: Intent?
): Boolean {
if (requestCode == K.MEDIA_STORE_DELETE_REQUEST_CODE) {
eventSink?.success(buildMap {
put("event", "DeleteRequestResult")
put("resultCode", resultCode)
})
return true
}
return false
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"saveFileToDownload" -> {
@ -81,6 +108,14 @@ class MediaStoreChannelHandler(context: Context) :
}
}
"deleteFiles" -> {
try {
deleteFiles(call.argument("uris")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
else -> result.notImplemented()
}
}
@ -196,6 +231,37 @@ class MediaStoreChannelHandler(context: Context) :
result.success(files)
}
private fun deleteFiles(uris: List<String>, result: MethodChannel.Result) {
val urisTyped = uris.map(Uri::parse)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val pi = MediaStore.createDeleteRequest(
context.contentResolver, urisTyped
)
activity!!.startIntentSenderForResult(
pi.intentSender, K.MEDIA_STORE_DELETE_REQUEST_CODE, null, 0, 0,
0
)
result.success(null)
} else {
if (!PermissionUtil.hasWriteExternalStorage(context)) {
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
return
}
val failed = mutableListOf<String>()
for (uri in urisTyped) {
try {
context.contentResolver.delete(uri, null, null)
} catch (e: Throwable) {
Log.e(TAG, "[deleteFiles] Failed while delete", e)
failed += uri.toString()
}
}
result.success(failed)
}
}
private fun inputToUri(fromFile: String): Uri {
val testUri = Uri.parse(fromFile)
return if (testUri.scheme == null) {
@ -209,4 +275,5 @@ class MediaStoreChannelHandler(context: Context) :
private val context = context
private var activity: Activity? = null
private var eventSink: EventChannel.EventSink? = null
}

View file

@ -1,17 +1,23 @@
package com.nkming.nc_photos.plugin
import android.content.Intent
import android.util.Log
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
class NcPhotosPlugin : FlutterPlugin, ActivityAware {
class NcPhotosPlugin : FlutterPlugin, ActivityAware,
PluginRegistry.ActivityResultListener {
companion object {
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT
const val EXTRA_IMAGE_RESULT_URI = K.EXTRA_IMAGE_RESULT_URI
private const val TAG = "NcPhotosPlugin"
}
override fun onAttachedToEngine(
@ -47,6 +53,11 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware {
mediaStoreChannelHandler =
MediaStoreChannelHandler(flutterPluginBinding.applicationContext)
mediaStoreChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
MediaStoreChannelHandler.EVENT_CHANNEL
)
mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler)
mediaStoreMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
MediaStoreChannelHandler.METHOD_CHANNEL
@ -87,26 +98,56 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware {
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
mediaStoreChannelHandler.onAttachedToActivity(binding)
pluginBinding = binding
binding.addActivityResultListener(this)
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding)
pluginBinding = binding
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivity() {
mediaStoreChannelHandler.onDetachedFromActivity()
pluginBinding?.removeActivityResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges()
pluginBinding?.removeActivityResultListener(this)
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, data: Intent?
): Boolean {
return try {
when (requestCode) {
K.MEDIA_STORE_DELETE_REQUEST_CODE -> {
mediaStoreChannelHandler.onActivityResult(
requestCode, resultCode, data
)
}
else -> false
}
} catch (e: Throwable) {
Log.e(
TAG, "Failed while onActivityResult, requestCode=$requestCode"
)
false
}
}
private var pluginBinding: ActivityPluginBinding? = null
private lateinit var lockChannel: MethodChannel
private lateinit var notificationChannel: MethodChannel
private lateinit var nativeEventChannel: EventChannel
private lateinit var nativeEventMethodChannel: MethodChannel
private lateinit var mediaStoreChannel: EventChannel
private lateinit var mediaStoreMethodChannel: MethodChannel
private lateinit var imageProcessorMethodChannel: MethodChannel
private lateinit var contentUriMethodChannel: MethodChannel

View file

@ -1,6 +1,7 @@
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos_plugin/src/exception.dart';
import 'package:nc_photos_plugin/src/k.dart' as k;
@ -16,6 +17,12 @@ class MediaStoreQueryResult {
final int? dateTaken;
}
class MediaStoreDeleteRequestResultEvent {
const MediaStoreDeleteRequestResultEvent(this.resultCode);
final int resultCode;
}
class MediaStore {
static Future<String> saveFileToDownload(
Uint8List content,
@ -85,7 +92,36 @@ class MediaStore {
}
}
static Future<List<String>?> deleteFiles(List<String> uris) async {
return (await _methodChannel
.invokeMethod<List>("deleteFiles", <String, dynamic>{
"uris": uris,
}))
?.cast<String>();
}
static Stream get stream => _eventStream;
static late final _eventStream =
_eventChannel.receiveBroadcastStream().map((event) {
if (event is Map) {
switch (event["event"]) {
case _eventDeleteRequestResult:
return MediaStoreDeleteRequestResultEvent(event["resultCode"]);
default:
_log.shout("[_eventStream] Unknown event: ${event["event"]}");
}
} else {
return event;
}
});
static const _eventChannel = EventChannel("${k.libId}/media_store");
static const _methodChannel = MethodChannel("${k.libId}/media_store_method");
static const _exceptionCodePermissionError = "permissionError";
static const _eventDeleteRequestResult = "DeleteRequestResult";
static final _log = Logger("media_store.MediaStore");
}

View file

@ -34,6 +34,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
logging:
dependency: "direct main"
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
material_color_utilities:
dependency: transitive
description:

View file

@ -11,6 +11,8 @@ dependencies:
flutter:
sdk: flutter
logging: ^1.0.2
dev_dependencies:
flutter_lints: ^1.0.0