Compare commits
11 Commits
1852f2d198
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ba5674850 | |||
| ed2a928d12 | |||
| 3c0db620c8 | |||
| c14b3a7f58 | |||
| 53b6b65e70 | |||
| 2adefecc92 | |||
| 4ec44ab3e1 | |||
| cab031bd07 | |||
| 6b4b15bb87 | |||
| c905b3242d | |||
| f949b008b3 |
@@ -27,3 +27,7 @@ Thanks to [ekasetiawans](https://github.com/ekasetiawans) for [flutter_backgroun
|
|||||||
was essentially the blueprint for the service and background service APIs. They were reimplemented
|
was essentially the blueprint for the service and background service APIs. They were reimplemented
|
||||||
to allow the root isolate to pass some additional data to the service, which `flutter_background_service`
|
to allow the root isolate to pass some additional data to the service, which `flutter_background_service`
|
||||||
did not support.
|
did not support.
|
||||||
|
|
||||||
|
Thanks to [nschairer](https://github.com/nschairer) for [flutter_keyboard_height](https://github.com/nschairer/keyboard_height_plugin), which was the base for keeping track of the keyboard height.
|
||||||
|
Due to having an issue with the height calculation if the Android device uses gesture navigation, I
|
||||||
|
[forked the package](https://git.polynom.me/moxxy/keyboard_height_plugin) and modified the height calculation.
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ package org.moxxy.moxxy_native
|
|||||||
|
|
||||||
const val TAG = "moxxy_native"
|
const val TAG = "moxxy_native"
|
||||||
|
|
||||||
|
// The event channel name for the keyboard height
|
||||||
|
const val KEYBOARD_HEIGHT_EVENT_CHANNEL_NAME = "org.moxxy.moxxyv2/keyboard_stream"
|
||||||
|
|
||||||
|
// The event channel name for notification events
|
||||||
|
const val NOTIFICATION_EVENT_CHANNEL_NAME = "org.moxxy.moxxyv2/notification_stream"
|
||||||
|
|
||||||
// The size of buffers to use for various operations
|
// The size of buffers to use for various operations
|
||||||
const val BUFFER_SIZE = 4096
|
const val BUFFER_SIZE = 4096
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ import org.moxxy.moxxy_native.media.MediaImplementation
|
|||||||
import org.moxxy.moxxy_native.media.MoxxyMediaApi
|
import org.moxxy.moxxy_native.media.MoxxyMediaApi
|
||||||
import org.moxxy.moxxy_native.notifications.MoxxyNotificationsApi
|
import org.moxxy.moxxy_native.notifications.MoxxyNotificationsApi
|
||||||
import org.moxxy.moxxy_native.notifications.NotificationEvent
|
import org.moxxy.moxxy_native.notifications.NotificationEvent
|
||||||
|
import org.moxxy.moxxy_native.notifications.NotificationStreamHandler
|
||||||
import org.moxxy.moxxy_native.notifications.NotificationsImplementation
|
import org.moxxy.moxxy_native.notifications.NotificationsImplementation
|
||||||
import org.moxxy.moxxy_native.picker.FilePickerType
|
import org.moxxy.moxxy_native.picker.FilePickerType
|
||||||
import org.moxxy.moxxy_native.picker.MoxxyPickerApi
|
import org.moxxy.moxxy_native.picker.MoxxyPickerApi
|
||||||
import org.moxxy.moxxy_native.picker.PickerResultListener
|
import org.moxxy.moxxy_native.picker.PickerResultListener
|
||||||
|
import org.moxxy.moxxy_native.platform.KeyboardStreamHandler
|
||||||
import org.moxxy.moxxy_native.platform.MoxxyPlatformApi
|
import org.moxxy.moxxy_native.platform.MoxxyPlatformApi
|
||||||
import org.moxxy.moxxy_native.platform.PlatformImplementation
|
import org.moxxy.moxxy_native.platform.PlatformImplementation
|
||||||
import org.moxxy.moxxy_native.service.BackgroundService
|
import org.moxxy.moxxy_native.service.BackgroundService
|
||||||
@@ -36,23 +38,6 @@ import org.moxxy.moxxy_native.service.MoxxyServiceApi
|
|||||||
import org.moxxy.moxxy_native.service.PluginTracker
|
import org.moxxy.moxxy_native.service.PluginTracker
|
||||||
import org.moxxy.moxxy_native.service.ServiceImplementation
|
import org.moxxy.moxxy_native.service.ServiceImplementation
|
||||||
|
|
||||||
object MoxxyEventChannels {
|
|
||||||
var notificationChannel: EventChannel? = null
|
|
||||||
var notificationEventSink: EventChannel.EventSink? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
object NotificationStreamHandler : EventChannel.StreamHandler {
|
|
||||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
|
||||||
Log.d(TAG, "NotificationStreamHandler: Attached stream")
|
|
||||||
MoxxyEventChannels.notificationEventSink = events
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {
|
|
||||||
Log.d(TAG, "NotificationStreamHandler: Detached stream")
|
|
||||||
MoxxyEventChannels.notificationEventSink = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hold the last notification event in case we did a cold start.
|
* Hold the last notification event in case we did a cold start.
|
||||||
*/
|
*/
|
||||||
@@ -102,6 +87,14 @@ class MoxxyNativePlugin : FlutterPlugin, ActivityAware, ServiceAware, BroadcastR
|
|||||||
IntentFilter(SERVICE_FOREGROUND_METHOD_CHANNEL_KEY),
|
IntentFilter(SERVICE_FOREGROUND_METHOD_CHANNEL_KEY),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Special handling for the keyboard height
|
||||||
|
val keyboardChannel = EventChannel(flutterPluginBinding.getBinaryMessenger(), KEYBOARD_HEIGHT_EVENT_CHANNEL_NAME)
|
||||||
|
keyboardChannel?.setStreamHandler(KeyboardStreamHandler)
|
||||||
|
|
||||||
|
// Special handling from notification events
|
||||||
|
val notificationChannel = EventChannel(flutterPluginBinding.getBinaryMessenger(), NOTIFICATION_EVENT_CHANNEL_NAME)
|
||||||
|
notificationChannel?.setStreamHandler(NotificationStreamHandler)
|
||||||
|
|
||||||
// Register the picker handler
|
// Register the picker handler
|
||||||
pickerListener = PickerResultListener(context!!)
|
pickerListener = PickerResultListener(context!!)
|
||||||
Log.d(TAG, "Attached to engine")
|
Log.d(TAG, "Attached to engine")
|
||||||
@@ -118,20 +111,24 @@ class MoxxyNativePlugin : FlutterPlugin, ActivityAware, ServiceAware, BroadcastR
|
|||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||||
activity = binding.activity
|
activity = binding.activity
|
||||||
binding.addActivityResultListener(pickerListener)
|
binding.addActivityResultListener(pickerListener)
|
||||||
|
KeyboardStreamHandler.activity = activity
|
||||||
Log.d(TAG, "Attached to activity")
|
Log.d(TAG, "Attached to activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
override fun onDetachedFromActivityForConfigChanges() {
|
||||||
activity = null
|
activity = null
|
||||||
|
KeyboardStreamHandler.activity = null
|
||||||
Log.d(TAG, "Detached from activity")
|
Log.d(TAG, "Detached from activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||||
activity = binding.activity
|
activity = binding.activity
|
||||||
|
KeyboardStreamHandler.activity = activity
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivity() {
|
override fun onDetachedFromActivity() {
|
||||||
activity = null
|
activity = null
|
||||||
|
KeyboardStreamHandler.activity = null
|
||||||
Log.d(TAG, "Detached from activity")
|
Log.d(TAG, "Detached from activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.moxxy.moxxy_native.notifications
|
package org.moxxy.moxxy_native.notifications
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
|
||||||
import org.moxxy.moxxy_native.TAG
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Extract all user-added extra key-value pairs from @intent.
|
* Extract all user-added extra key-value pairs from @intent.
|
||||||
@@ -10,9 +8,7 @@ import org.moxxy.moxxy_native.TAG
|
|||||||
fun extractPayloadMapFromIntent(intent: Intent): Map<String?, String?> {
|
fun extractPayloadMapFromIntent(intent: Intent): Map<String?, String?> {
|
||||||
val extras = mutableMapOf<String?, String?>()
|
val extras = mutableMapOf<String?, String?>()
|
||||||
intent.extras?.keySet()!!.forEach {
|
intent.extras?.keySet()!!.forEach {
|
||||||
Log.d(TAG, "Checking $it -> ${intent.extras!!.get(it)}")
|
|
||||||
if (it.startsWith("payload_")) {
|
if (it.startsWith("payload_")) {
|
||||||
Log.d(TAG, "Adding $it")
|
|
||||||
extras[it.substring(8)] = intent.extras!!.getString(it)
|
extras[it.substring(8)] = intent.extras!!.getString(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import androidx.core.app.RemoteInput
|
|||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import org.moxxy.moxxy_native.MARK_AS_READ_ACTION
|
import org.moxxy.moxxy_native.MARK_AS_READ_ACTION
|
||||||
import org.moxxy.moxxy_native.MOXXY_FILEPROVIDER_ID
|
import org.moxxy.moxxy_native.MOXXY_FILEPROVIDER_ID
|
||||||
import org.moxxy.moxxy_native.MoxxyEventChannels
|
|
||||||
import org.moxxy.moxxy_native.NOTIFICATION_EXTRA_ID_KEY
|
import org.moxxy.moxxy_native.NOTIFICATION_EXTRA_ID_KEY
|
||||||
import org.moxxy.moxxy_native.NOTIFICATION_EXTRA_JID_KEY
|
import org.moxxy.moxxy_native.NOTIFICATION_EXTRA_JID_KEY
|
||||||
import org.moxxy.moxxy_native.NOTIFICATION_MESSAGE_EXTRA_MIME
|
import org.moxxy.moxxy_native.NOTIFICATION_MESSAGE_EXTRA_MIME
|
||||||
@@ -50,7 +49,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMarkAsRead(context: Context, intent: Intent) {
|
private fun handleMarkAsRead(context: Context, intent: Intent) {
|
||||||
MoxxyEventChannels.notificationEventSink?.success(
|
NotificationStreamHandler.sink?.success(
|
||||||
NotificationEvent(
|
NotificationEvent(
|
||||||
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
||||||
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
||||||
@@ -65,7 +64,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
private fun handleReply(context: Context, intent: Intent) {
|
private fun handleReply(context: Context, intent: Intent) {
|
||||||
val remoteInput = RemoteInput.getResultsFromIntent(intent) ?: return
|
val remoteInput = RemoteInput.getResultsFromIntent(intent) ?: return
|
||||||
val replyPayload = remoteInput.getCharSequence(REPLY_TEXT_KEY)
|
val replyPayload = remoteInput.getCharSequence(REPLY_TEXT_KEY)
|
||||||
MoxxyEventChannels.notificationEventSink?.success(
|
NotificationStreamHandler.sink?.success(
|
||||||
NotificationEvent(
|
NotificationEvent(
|
||||||
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
||||||
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
||||||
@@ -164,8 +163,8 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleTap(context: Context, intent: Intent) {
|
private fun handleTap(context: Context, intent: Intent) {
|
||||||
MoxxyEventChannels.notificationEventSink?.success(
|
NotificationStreamHandler.sink?.success(
|
||||||
NotificationEvent(
|
NotificationEvent(
|
||||||
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
||||||
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.moxxy.moxxy_native.notifications
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import org.moxxy.moxxy_native.TAG
|
||||||
|
|
||||||
|
object NotificationStreamHandler : EventChannel.StreamHandler {
|
||||||
|
// The event sink to use for sending notification events to the service.
|
||||||
|
var sink: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||||
|
// "register" the event sink
|
||||||
|
sink = events
|
||||||
|
|
||||||
|
Log.d(TAG, "NotificationStreamHandler: Attached stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
sink = null
|
||||||
|
Log.d(TAG, "NotificationStreamHandler: Detached stream")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -94,7 +94,7 @@ class NotificationsImplementation(private val context: Context) : MoxxyNotificat
|
|||||||
context.applicationContext,
|
context.applicationContext,
|
||||||
0,
|
0,
|
||||||
replyIntent,
|
replyIntent,
|
||||||
PendingIntent.FLAG_MUTABLE,
|
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
)
|
)
|
||||||
val replyAction = NotificationCompat.Action.Builder(
|
val replyAction = NotificationCompat.Action.Builder(
|
||||||
R.drawable.reply,
|
R.drawable.reply,
|
||||||
@@ -113,13 +113,14 @@ class NotificationsImplementation(private val context: Context) : MoxxyNotificat
|
|||||||
|
|
||||||
notification.extra?.forEach {
|
notification.extra?.forEach {
|
||||||
putExtra("payload_${it.key}", it.value)
|
putExtra("payload_${it.key}", it.value)
|
||||||
|
Log.d(TAG, "Adding payload_${it.key} -> ${it.value}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val markAsReadPendingIntent = PendingIntent.getBroadcast(
|
val markAsReadPendingIntent = PendingIntent.getBroadcast(
|
||||||
context.applicationContext,
|
context.applicationContext,
|
||||||
0,
|
0,
|
||||||
markAsReadIntent,
|
markAsReadIntent,
|
||||||
PendingIntent.FLAG_IMMUTABLE,
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
)
|
)
|
||||||
val markAsReadAction = NotificationCompat.Action.Builder(
|
val markAsReadAction = NotificationCompat.Action.Builder(
|
||||||
R.drawable.mark_as_read,
|
R.drawable.mark_as_read,
|
||||||
@@ -250,7 +251,7 @@ class NotificationsImplementation(private val context: Context) : MoxxyNotificat
|
|||||||
setCategory(Notification.CATEGORY_MESSAGE)
|
setCategory(Notification.CATEGORY_MESSAGE)
|
||||||
|
|
||||||
// Prevent no notification when we replied before
|
// Prevent no notification when we replied before
|
||||||
setOnlyAlertOnce(false)
|
setOnlyAlertOnce(true)
|
||||||
|
|
||||||
// Automatically dismiss the notification on tap
|
// Automatically dismiss the notification on tap
|
||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.moxxy.moxxy_native.picker
|
||||||
|
|
||||||
|
object MimeUtils {
|
||||||
|
// A reverse-mapping of image mime types to their commonly used file extension.
|
||||||
|
val imageMimeTypesToFileExtension = mapOf(
|
||||||
|
"image/png" to ".png",
|
||||||
|
"image/apng" to ".apng",
|
||||||
|
"image/avif" to ".avif",
|
||||||
|
"image/gif" to ".gif",
|
||||||
|
"image/jpeg" to ".jpg",
|
||||||
|
"image/webp" to ".webp",
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,10 +6,11 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.OpenableColumns
|
import android.provider.MediaStore.Images
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener
|
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener
|
||||||
import org.moxxy.moxxy_native.AsyncRequestTracker
|
import org.moxxy.moxxy_native.AsyncRequestTracker
|
||||||
|
import org.moxxy.moxxy_native.BUFFER_SIZE
|
||||||
import org.moxxy.moxxy_native.PICK_FILES_REQUEST
|
import org.moxxy.moxxy_native.PICK_FILES_REQUEST
|
||||||
import org.moxxy.moxxy_native.PICK_FILE_REQUEST
|
import org.moxxy.moxxy_native.PICK_FILE_REQUEST
|
||||||
import org.moxxy.moxxy_native.PICK_FILE_WITH_DATA_REQUEST
|
import org.moxxy.moxxy_native.PICK_FILE_WITH_DATA_REQUEST
|
||||||
@@ -20,6 +21,26 @@ import java.io.IOException
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attempt to replace the file extension in @fileName with @newExtension. If @newExtension is null,
|
||||||
|
* then @fileName is returned verbatim.
|
||||||
|
* */
|
||||||
|
private fun maybeReplaceExtension(fileName: String, newExtension: String?): String {
|
||||||
|
if (newExtension == null) {
|
||||||
|
return fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(newExtension[0] == '.')
|
||||||
|
val parts = fileName.split(".")
|
||||||
|
return if (parts.size == 1) {
|
||||||
|
"$fileName$newExtension"
|
||||||
|
} else {
|
||||||
|
// Split at the ".", join all but the list end together and append the new extension
|
||||||
|
val fileNameWithoutExtension = parts.subList(0, parts.size - 1).joinToString(".")
|
||||||
|
"$fileNameWithoutExtension$newExtension"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PickerResultListener(private val context: Context) : ActivityResultListener {
|
class PickerResultListener(private val context: Context) : ActivityResultListener {
|
||||||
/*
|
/*
|
||||||
* Attempt to deduce the filename for the URI @uri.
|
* Attempt to deduce the filename for the URI @uri.
|
||||||
@@ -29,10 +50,22 @@ class PickerResultListener(private val context: Context) : ActivityResultListene
|
|||||||
private fun queryFileName(context: Context, uri: Uri): String {
|
private fun queryFileName(context: Context, uri: Uri): String {
|
||||||
var result: String? = null
|
var result: String? = null
|
||||||
if (uri.scheme == "content") {
|
if (uri.scheme == "content") {
|
||||||
|
val projection = arrayOf(
|
||||||
|
Images.Media._ID,
|
||||||
|
Images.Media.MIME_TYPE,
|
||||||
|
Images.Media.DISPLAY_NAME,
|
||||||
|
)
|
||||||
val cursor = context.contentResolver.query(uri, null, null, null, null)
|
val cursor = context.contentResolver.query(uri, null, null, null, null)
|
||||||
cursor.use { cursor ->
|
cursor.use { cursor ->
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
val mimeType = cursor.getString(cursor.getColumnIndex(Images.Media.MIME_TYPE))
|
||||||
|
val displayName = cursor.getString(cursor.getColumnIndex(Images.Media.DISPLAY_NAME))
|
||||||
|
val fileExtension = MimeUtils.imageMimeTypesToFileExtension[mimeType]
|
||||||
|
|
||||||
|
// Note: This is a workaround for the Dart image library failing to parse the file
|
||||||
|
// because displayName somehow is always ".jpg", which confuses image.
|
||||||
|
result = maybeReplaceExtension(displayName, fileExtension)
|
||||||
|
Log.d(TAG, "Returning $result as filename (MIME: $mimeType)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +84,7 @@ class PickerResultListener(private val context: Context) : ActivityResultListene
|
|||||||
if (Build.VERSION.SDK_INT >= 33) {
|
if (Build.VERSION.SDK_INT >= 33) {
|
||||||
android.os.FileUtils.copy(input, output)
|
android.os.FileUtils.copy(input, output)
|
||||||
} else {
|
} else {
|
||||||
val buffer = ByteArray(4096)
|
val buffer = ByteArray(BUFFER_SIZE)
|
||||||
while (input.read(buffer).also {} != -1) {
|
while (input.read(buffer).also {} != -1) {
|
||||||
output.write(buffer)
|
output.write(buffer)
|
||||||
}
|
}
|
||||||
@@ -94,7 +127,7 @@ class PickerResultListener(private val context: Context) : ActivityResultListene
|
|||||||
}
|
}
|
||||||
|
|
||||||
val returnBuffer = mutableListOf<Byte>()
|
val returnBuffer = mutableListOf<Byte>()
|
||||||
val readBuffer = ByteArray(4096)
|
val readBuffer = ByteArray(BUFFER_SIZE)
|
||||||
try {
|
try {
|
||||||
val inputStream = context.contentResolver.openInputStream(data!!.data!!)!!
|
val inputStream = context.contentResolver.openInputStream(data!!.data!!)!!
|
||||||
while (inputStream.read(readBuffer).also {} != -1) {
|
while (inputStream.read(readBuffer).also {} != -1) {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package org.moxxy.moxxy_native.platform
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import org.moxxy.moxxy_native.TAG
|
||||||
|
|
||||||
|
object KeyboardStreamHandler : EventChannel.StreamHandler {
|
||||||
|
// The currently active activity. Set by @MoxxyNativePlugin.
|
||||||
|
var activity: Activity? = null
|
||||||
|
|
||||||
|
// The current bottom inset.
|
||||||
|
private var bottomInset: Int = 0
|
||||||
|
|
||||||
|
// The current event sink to use for sending events to the UI.
|
||||||
|
private var sink: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
private fun handleKeyboardHeightCheck(rootView: View?) {
|
||||||
|
rootView?.viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
val r = Rect()
|
||||||
|
rootView.getWindowVisibleDisplayFrame(r)
|
||||||
|
|
||||||
|
val screenHeight = rootView.height
|
||||||
|
// Also subtract the height of the bottom inset as the SafeArea with "bottom: false"
|
||||||
|
// allows us to draw under the bottom system bar, if it is there.
|
||||||
|
val keypadHeight = screenHeight - r.bottom - bottomInset
|
||||||
|
|
||||||
|
val displayMetrics = activity?.resources?.displayMetrics
|
||||||
|
val logicalKeypadHeight = keypadHeight / (displayMetrics?.density ?: 1f)
|
||||||
|
|
||||||
|
if (keypadHeight > screenHeight * 0.15) {
|
||||||
|
sink?.success(logicalKeypadHeight.toDouble())
|
||||||
|
} else {
|
||||||
|
sink?.success(0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||||
|
// "register" the event sink
|
||||||
|
sink = events
|
||||||
|
|
||||||
|
val rootView = activity?.window?.decorView?.rootView
|
||||||
|
handleKeyboardHeightCheck(rootView)
|
||||||
|
|
||||||
|
if (rootView != null) {
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(rootView!!) { _, windowInsets ->
|
||||||
|
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
val triggerEvent = bottomInset != insets.bottom
|
||||||
|
bottomInset = insets.bottom
|
||||||
|
|
||||||
|
// Notify in case the inset changed
|
||||||
|
if (triggerEvent) handleKeyboardHeightCheck(rootView)
|
||||||
|
|
||||||
|
WindowInsetsCompat.CONSUMED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "KeyboardStreamHandler: Attached stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
sink = null
|
||||||
|
Log.d(TAG, "KeyboardStreamHandler: Detached stream")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
// ignore_for_file: avoid_print
|
// ignore_for_file: avoid_print
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -51,6 +50,19 @@ class TestEvent extends BackgroundEvent {
|
|||||||
class MyAppState extends State<MyApp> {
|
class MyAppState extends State<MyApp> {
|
||||||
String? imagePath;
|
String? imagePath;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
const EventChannel('org.moxxy.moxxyv2/notification_stream')
|
||||||
|
.receiveBroadcastStream()
|
||||||
|
.listen(
|
||||||
|
(event) {
|
||||||
|
print('Keyboard height: ${event as double}');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
@@ -221,6 +233,7 @@ class MyAppState extends State<MyApp> {
|
|||||||
},
|
},
|
||||||
child: const Text('Share some text'),
|
child: const Text('Share some text'),
|
||||||
),
|
),
|
||||||
|
const TextField(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: moxxy_native
|
name: moxxy_native
|
||||||
description: Interactions with the system for Moxxy
|
description: Interactions with the system for Moxxy
|
||||||
version: 0.2.0
|
version: 0.3.2
|
||||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
homepage:
|
homepage:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user