mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-25 00:14:42 +01:00
Delete files via MediaStore api
This commit is contained in:
parent
4da8b95c61
commit
d33e3af806
7 changed files with 160 additions and 4 deletions
|
@ -5,7 +5,7 @@
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="28"
|
android:maxSdkVersion="29"
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
@ -13,7 +13,8 @@
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:largeHeap="true">
|
android:largeHeap="true"
|
||||||
|
android:requestLegacyExternalStorage="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
|
|
|
@ -9,6 +9,8 @@ interface K {
|
||||||
const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002
|
const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002
|
||||||
|
|
||||||
const val PERMISSION_REQUEST_CODE = 11011
|
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 LIB_ID = "com.nkming.nc_photos.plugin"
|
||||||
|
|
||||||
const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL"
|
const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL"
|
||||||
|
|
|
@ -3,13 +3,17 @@ package com.nkming.nc_photos.plugin
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import android.util.Log
|
||||||
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
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
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 java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -24,8 +28,10 @@ import java.io.File
|
||||||
* fun queryFiles(relativePath: String): List<Map>
|
* fun queryFiles(relativePath: String): List<Map>
|
||||||
*/
|
*/
|
||||||
class MediaStoreChannelHandler(context: Context) :
|
class MediaStoreChannelHandler(context: Context) :
|
||||||
MethodChannel.MethodCallHandler, ActivityAware {
|
MethodChannel.MethodCallHandler, EventChannel.StreamHandler,
|
||||||
|
ActivityAware, PluginRegistry.ActivityResultListener {
|
||||||
companion object {
|
companion object {
|
||||||
|
const val EVENT_CHANNEL = "${K.LIB_ID}/media_store"
|
||||||
const val METHOD_CHANNEL = "${K.LIB_ID}/media_store_method"
|
const val METHOD_CHANNEL = "${K.LIB_ID}/media_store_method"
|
||||||
|
|
||||||
private const val TAG = "MediaStoreChannelHandler"
|
private const val TAG = "MediaStoreChannelHandler"
|
||||||
|
@ -49,6 +55,27 @@ class MediaStoreChannelHandler(context: Context) :
|
||||||
activity = null
|
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) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"saveFileToDownload" -> {
|
"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()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,6 +231,37 @@ class MediaStoreChannelHandler(context: Context) :
|
||||||
result.success(files)
|
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 {
|
private fun inputToUri(fromFile: String): Uri {
|
||||||
val testUri = Uri.parse(fromFile)
|
val testUri = Uri.parse(fromFile)
|
||||||
return if (testUri.scheme == null) {
|
return if (testUri.scheme == null) {
|
||||||
|
@ -209,4 +275,5 @@ class MediaStoreChannelHandler(context: Context) :
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
package com.nkming.nc_photos.plugin
|
package com.nkming.nc_photos.plugin
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
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
|
||||||
import io.flutter.plugin.common.EventChannel
|
import io.flutter.plugin.common.EventChannel
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import io.flutter.plugin.common.PluginRegistry
|
||||||
|
|
||||||
class NcPhotosPlugin : FlutterPlugin, ActivityAware {
|
class NcPhotosPlugin : FlutterPlugin, ActivityAware,
|
||||||
|
PluginRegistry.ActivityResultListener {
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
|
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
|
||||||
K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT
|
K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT
|
||||||
const val EXTRA_IMAGE_RESULT_URI = K.EXTRA_IMAGE_RESULT_URI
|
const val EXTRA_IMAGE_RESULT_URI = K.EXTRA_IMAGE_RESULT_URI
|
||||||
|
|
||||||
|
private const val TAG = "NcPhotosPlugin"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToEngine(
|
override fun onAttachedToEngine(
|
||||||
|
@ -47,6 +53,11 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware {
|
||||||
|
|
||||||
mediaStoreChannelHandler =
|
mediaStoreChannelHandler =
|
||||||
MediaStoreChannelHandler(flutterPluginBinding.applicationContext)
|
MediaStoreChannelHandler(flutterPluginBinding.applicationContext)
|
||||||
|
mediaStoreChannel = EventChannel(
|
||||||
|
flutterPluginBinding.binaryMessenger,
|
||||||
|
MediaStoreChannelHandler.EVENT_CHANNEL
|
||||||
|
)
|
||||||
|
mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler)
|
||||||
mediaStoreMethodChannel = MethodChannel(
|
mediaStoreMethodChannel = MethodChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
MediaStoreChannelHandler.METHOD_CHANNEL
|
MediaStoreChannelHandler.METHOD_CHANNEL
|
||||||
|
@ -87,26 +98,56 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware {
|
||||||
|
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||||
mediaStoreChannelHandler.onAttachedToActivity(binding)
|
mediaStoreChannelHandler.onAttachedToActivity(binding)
|
||||||
|
pluginBinding = binding
|
||||||
|
binding.addActivityResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(
|
override fun onReattachedToActivityForConfigChanges(
|
||||||
binding: ActivityPluginBinding
|
binding: ActivityPluginBinding
|
||||||
) {
|
) {
|
||||||
mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding)
|
mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding)
|
||||||
|
pluginBinding = binding
|
||||||
|
binding.addActivityResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivity() {
|
override fun onDetachedFromActivity() {
|
||||||
mediaStoreChannelHandler.onDetachedFromActivity()
|
mediaStoreChannelHandler.onDetachedFromActivity()
|
||||||
|
pluginBinding?.removeActivityResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
override fun onDetachedFromActivityForConfigChanges() {
|
||||||
mediaStoreChannelHandler.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 lockChannel: MethodChannel
|
||||||
private lateinit var notificationChannel: MethodChannel
|
private lateinit var notificationChannel: MethodChannel
|
||||||
private lateinit var nativeEventChannel: EventChannel
|
private lateinit var nativeEventChannel: EventChannel
|
||||||
private lateinit var nativeEventMethodChannel: MethodChannel
|
private lateinit var nativeEventMethodChannel: MethodChannel
|
||||||
|
private lateinit var mediaStoreChannel: EventChannel
|
||||||
private lateinit var mediaStoreMethodChannel: MethodChannel
|
private lateinit var mediaStoreMethodChannel: MethodChannel
|
||||||
private lateinit var imageProcessorMethodChannel: MethodChannel
|
private lateinit var imageProcessorMethodChannel: MethodChannel
|
||||||
private lateinit var contentUriMethodChannel: MethodChannel
|
private lateinit var contentUriMethodChannel: MethodChannel
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos_plugin/src/exception.dart';
|
import 'package:nc_photos_plugin/src/exception.dart';
|
||||||
import 'package:nc_photos_plugin/src/k.dart' as k;
|
import 'package:nc_photos_plugin/src/k.dart' as k;
|
||||||
|
|
||||||
|
@ -16,6 +17,12 @@ class MediaStoreQueryResult {
|
||||||
final int? dateTaken;
|
final int? dateTaken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MediaStoreDeleteRequestResultEvent {
|
||||||
|
const MediaStoreDeleteRequestResultEvent(this.resultCode);
|
||||||
|
|
||||||
|
final int resultCode;
|
||||||
|
}
|
||||||
|
|
||||||
class MediaStore {
|
class MediaStore {
|
||||||
static Future<String> saveFileToDownload(
|
static Future<String> saveFileToDownload(
|
||||||
Uint8List content,
|
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 _methodChannel = MethodChannel("${k.libId}/media_store_method");
|
||||||
|
|
||||||
static const _exceptionCodePermissionError = "permissionError";
|
static const _exceptionCodePermissionError = "permissionError";
|
||||||
|
static const _eventDeleteRequestResult = "DeleteRequestResult";
|
||||||
|
|
||||||
|
static final _log = Logger("media_store.MediaStore");
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
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:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -11,6 +11,8 @@ dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
logging: ^1.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^1.0.0
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue