From 105255f3ab1bf7c26b328f429daa14b9205bb8e4 Mon Sep 17 00:00:00 2001 From: Midori Date: Thu, 27 Jul 2023 19:15:57 +0900 Subject: [PATCH] Add functions --- .../studio/midoridesign/gal/GalPlugin.kt | 213 ++++++++++++++++-- example/pubspec.lock | 34 +-- 2 files changed, 205 insertions(+), 42 deletions(-) diff --git a/android/src/main/kotlin/studio/midoridesign/gal/GalPlugin.kt b/android/src/main/kotlin/studio/midoridesign/gal/GalPlugin.kt index 52ea9ffd..affa9fdf 100644 --- a/android/src/main/kotlin/studio/midoridesign/gal/GalPlugin.kt +++ b/android/src/main/kotlin/studio/midoridesign/gal/GalPlugin.kt @@ -1,35 +1,198 @@ package studio.midoridesign.gal +import android.Manifest +import android.app.Activity +import android.content.ContentResolver +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.provider.MediaStore import androidx.annotation.NonNull - +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry +import java.io.ByteArrayInputStream +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + + +class GalPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.RequestPermissionsResultListener { + private val PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE + private val IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + private val VIDEO_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + private val PERMISSION_REQUEST_CODE = 1317298 // Anything unique in the app. + private val hasAccessByDefault = Build.VERSION.SDK_INT < 23 || (Build.VERSION.SDK_INT >= 29 && Build.VERSION.SDK_INT < 30) + + private var channel: MethodChannel? = null + private var pluginBinding: FlutterPluginBinding? = null + private var activity: Activity? = null + private var callback: Callback? = null + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "gal") + channel?.setMethodCallHandler(this) + pluginBinding = flutterPluginBinding + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + when (call.method) { + "putVideo", "putImage" -> { + Thread { + try { + putMedia(pluginBinding?.applicationContext!!, call.argument("path") as String, + call.method.contains("Image")) + Handler(Looper.getMainLooper()).post { result.success(null) } + } catch (e: Exception) { + handleError(e, result) + } + }.start() + } + "putImageBytes" -> { + Thread { + try { + putImageBytes(pluginBinding?.applicationContext!!, call.argument("bytes") as ByteArray) + Handler(Looper.getMainLooper()).post { result.success(null) } + } catch (e: Exception) { + handleError(e, result) + } + }.start() + } + "open" -> { + open() + Handler(Looper.getMainLooper()).post { result.success(null) } + } + "hasAccess" -> { + result.success(if (hasAccessByDefault) true else hasAccess()) + } + "requestAccess" -> { + if (hasAccessByDefault) { + result.success(true) + } else { + requestAccess(object : Callback { + override fun onComplete(res: Boolean) { + result.success(res) + } + }) + } + } + else -> result.notImplemented() + } + } + + @Throws(IOException::class, SecurityException::class, FileNotFoundException::class) + private fun putMedia(context: Context, path: String, isImage: Boolean) { + val file = File(path) + FileInputStream(file).use { `in` -> writeContent(context, `in`, isImage) } + } + + @Throws(IOException::class, SecurityException::class) + private fun putImageBytes(context: Context, bytes: ByteArray) { + ByteArrayInputStream(bytes).use { `in` -> writeContent(context, `in`, true) } + } + + @Throws(IOException::class, SecurityException::class) + private fun writeContent(context: Context, `in`: InputStream, isImage: Boolean) { + val resolver: ContentResolver = context.contentResolver + val values = ContentValues() + values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis()) + val mediaUri: Uri? = resolver.insert(if (isImage) IMAGE_URI else VIDEO_URI, values) + resolver.openOutputStream(mediaUri!!).use { out -> + val buffer = ByteArray(8192) + var bytesRead: Int + while (`in`.read(buffer).also { bytesRead = it } != -1) { + out?.write(buffer, 0, bytesRead) + } + } + } + + private fun open() { + val context: Context = pluginBinding!!.applicationContext + val intent = Intent() + intent.action = Intent.ACTION_VIEW + intent.type = "image/*" + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intent) + } + + private fun hasAccess(): Boolean { + val context: Context = pluginBinding!!.applicationContext + val status = ContextCompat.checkSelfPermission(context, PERMISSION) + return status == PackageManager.PERMISSION_GRANTED + } + + private fun requestAccess(callback: Callback) { + this.callback = callback + ActivityCompat.requestPermissions(activity!!, arrayOf(PERMISSION), PERMISSION_REQUEST_CODE) + } + + private fun sendError(errorCode: String, message: String, stackTrace: Array, result: Result) { + val trace = StringBuilder() + for (st in stackTrace) { + trace.append(st.toString()) + trace.append("\n") + } + Handler(Looper.getMainLooper()).post { result.error(errorCode, message, trace.toString()) } + } + + private fun handleError(e: Exception, result: Result) { + val errorCode: String = when (e) { + is SecurityException -> "ACCESS_DENIED" + is FileNotFoundException -> "NOT_SUPPORTED_FORMAT" + is IOException -> if (e.toString().contains("No space left on device")) "NOT_ENOUGH_SPACE" else "UNEXPECTED" + else -> "UNEXPECTED" + } + sendError(errorCode, e.toString(), e.stackTrace, result) + } + + override fun onAttachedToActivity(@NonNull activityPluginBinding: ActivityPluginBinding) { + activity = activityPluginBinding.activity + activityPluginBinding.addRequestPermissionsResultListener(this) + } + + override fun onDetachedFromActivity() { + activity = null + } + + override fun onReattachedToActivityForConfigChanges(@NonNull activityPluginBinding: ActivityPluginBinding) { + activity = activityPluginBinding.activity + activityPluginBinding.addRequestPermissionsResultListener(this) + } + + override fun onDetachedFromActivityForConfigChanges() { + activity = null + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray): Boolean { + if (requestCode != PERMISSION_REQUEST_CODE || grantResults.isEmpty()) { + return false + } + callback?.onComplete(grantResults[0] == PackageManager.PERMISSION_GRANTED) + return true + } + + override fun onDetachedFromEngine(@NonNull binding: FlutterPluginBinding) { + channel?.setMethodCallHandler(null) + pluginBinding = null + } +} -/** GalPlugin */ -class GalPlugin: FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// 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 - - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "gal") - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - if (call.method == "getPlatformVersion") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() - } - } - - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } +interface Callback { + fun onComplete(result: Boolean) } diff --git a/example/pubspec.lock b/example/pubspec.lock index a1fbce86..0fc35999 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -113,14 +113,6 @@ packages: description: flutter source: sdk version: "0.0.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -202,18 +194,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -242,10 +234,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -266,10 +258,18 @@ packages: dependency: transitive description: name: vm_service - sha256: f3743ca475e0c9ef71df4ba15eb2d7684eecd5c8ba20a462462e4e8b561b2e11 + sha256: ada49637c27973c183dad90beb6bd781eea4c9f5f955d35da172de0af7bd3440 + url: "https://pub.dev" + source: hosted + version: "11.8.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "11.6.0" + version: "0.1.4-beta" webdriver: dependency: transitive description: @@ -279,5 +279,5 @@ packages: source: hosted version: "3.0.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.3.0"