Skip to content

Commit

Permalink
Add functions
Browse files Browse the repository at this point in the history
  • Loading branch information
natsuk4ze committed Jul 27, 2023
1 parent 14bae6b commit 105255f
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 42 deletions.
213 changes: 188 additions & 25 deletions android/src/main/kotlin/studio/midoridesign/gal/GalPlugin.kt
Original file line number Diff line number Diff line change
@@ -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<Any>("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<Any>("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<StackTraceElement>, 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<String>, 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)
}
34 changes: 17 additions & 17 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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"

0 comments on commit 105255f

Please sign in to comment.