feat: Move the notification code back into moxxy_native
This commit is contained in:
parent
d8a4394f17
commit
fd91ccc46f
@ -48,4 +48,5 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "androidx.activity:activity-ktx:1.7.2"
|
implementation "androidx.activity:activity-ktx:1.7.2"
|
||||||
|
implementation "androidx.datastore:datastore-preferences:1.0.0"
|
||||||
}
|
}
|
@ -1,3 +1,19 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.moxxy.moxxy_native">
|
package="org.moxxy.moxxy_native">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<provider
|
||||||
|
android:name="org.moxxy.moxxy_native.content.MoxxyFileProvider"
|
||||||
|
android:authorities="org.moxxy.moxxyv2.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
<receiver android:name="org.moxxy.moxxy_native.notifications.NotificationReceiver" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -2,6 +2,34 @@ package org.moxxy.moxxy_native
|
|||||||
|
|
||||||
const val TAG = "moxxy_native"
|
const val TAG = "moxxy_native"
|
||||||
|
|
||||||
|
// The data key for text entered in the notification's reply field
|
||||||
|
const val REPLY_TEXT_KEY = "key_reply_text"
|
||||||
|
|
||||||
|
// The key for the notification id to mark as read
|
||||||
|
const val MARK_AS_READ_ID_KEY = "notification_id"
|
||||||
|
|
||||||
|
// Values for actions performed through the notification
|
||||||
|
const val REPLY_ACTION = "reply"
|
||||||
|
const val MARK_AS_READ_ACTION = "mark_as_read"
|
||||||
|
const val TAP_ACTION = "tap"
|
||||||
|
|
||||||
|
// Extra data keys for the intents that reach the NotificationReceiver
|
||||||
|
const val NOTIFICATION_EXTRA_JID_KEY = "jid"
|
||||||
|
const val NOTIFICATION_EXTRA_ID_KEY = "notification_id"
|
||||||
|
|
||||||
|
// Extra data keys for messages embedded inside the notification style
|
||||||
|
const val NOTIFICATION_MESSAGE_EXTRA_MIME = "mime"
|
||||||
|
const val NOTIFICATION_MESSAGE_EXTRA_PATH = "path"
|
||||||
|
|
||||||
|
const val MOXXY_FILEPROVIDER_ID = "org.moxxy.moxxyv2.fileprovider"
|
||||||
|
|
||||||
|
// Shared preferences keys
|
||||||
|
const val SHARED_PREFERENCES_KEY = "org.moxxy.moxxyv2"
|
||||||
|
const val SHARED_PREFERENCES_YOU_KEY = "you"
|
||||||
|
const val SHARED_PREFERENCES_MARK_AS_READ_KEY = "mark_as_read"
|
||||||
|
const val SHARED_PREFERENCES_REPLY_KEY = "reply"
|
||||||
|
const val SHARED_PREFERENCES_AVATAR_KEY = "avatar_path"
|
||||||
|
|
||||||
// Request codes
|
// Request codes
|
||||||
const val PICK_FILE_REQUEST = 42
|
const val PICK_FILE_REQUEST = 42
|
||||||
const val PICK_FILES_REQUEST = 43
|
const val PICK_FILES_REQUEST = 43
|
||||||
|
@ -1,28 +1,67 @@
|
|||||||
package org.moxxy.moxxy_native
|
package org.moxxy.moxxy_native
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.result.PickVisualMediaRequest
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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 org.moxxy.moxxy_native.generated.FilePickerType
|
import io.flutter.plugin.common.EventChannel
|
||||||
import org.moxxy.moxxy_native.generated.MoxxyPickerApi
|
import org.moxxy.moxxy_native.notifications.MessagingNotification
|
||||||
|
import org.moxxy.moxxy_native.notifications.MoxxyNotificationsApi
|
||||||
|
import org.moxxy.moxxy_native.notifications.NotificationChannel
|
||||||
|
import org.moxxy.moxxy_native.notifications.NotificationDataManager
|
||||||
|
import org.moxxy.moxxy_native.notifications.NotificationEvent
|
||||||
|
import org.moxxy.moxxy_native.notifications.NotificationGroup
|
||||||
|
import org.moxxy.moxxy_native.notifications.NotificationI18nData
|
||||||
|
import org.moxxy.moxxy_native.notifications.RegularNotification
|
||||||
|
import org.moxxy.moxxy_native.notifications.createNotificationChannelsImpl
|
||||||
|
import org.moxxy.moxxy_native.notifications.createNotificationGroupsImpl
|
||||||
|
import org.moxxy.moxxy_native.notifications.showNotificationImpl
|
||||||
|
import org.moxxy.moxxy_native.picker.FilePickerType
|
||||||
|
import org.moxxy.moxxy_native.picker.MoxxyPickerApi
|
||||||
import org.moxxy.moxxy_native.picker.PickerResultListener
|
import org.moxxy.moxxy_native.picker.PickerResultListener
|
||||||
|
|
||||||
class MoxxyNativePlugin: FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
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.
|
||||||
|
*/
|
||||||
|
object NotificationCache {
|
||||||
|
var lastEvent: NotificationEvent? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoxxyNativePlugin : FlutterPlugin, ActivityAware, MoxxyPickerApi, MoxxyNotificationsApi {
|
||||||
private var context: Context? = null
|
private var context: Context? = null
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
|
private lateinit var activityClass: Class<Any>
|
||||||
private lateinit var pickerListener: PickerResultListener
|
private lateinit var pickerListener: PickerResultListener
|
||||||
|
|
||||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
context = flutterPluginBinding.applicationContext
|
context = flutterPluginBinding.applicationContext
|
||||||
MoxxyPickerApi.setUp(flutterPluginBinding.binaryMessenger, this)
|
MoxxyPickerApi.setUp(flutterPluginBinding.binaryMessenger, this)
|
||||||
|
MoxxyNotificationsApi.setUp(flutterPluginBinding.binaryMessenger, this)
|
||||||
pickerListener = PickerResultListener(context!!)
|
pickerListener = PickerResultListener(context!!)
|
||||||
Log.d(TAG, "Attached to engine")
|
Log.d(TAG, "Attached to engine")
|
||||||
}
|
}
|
||||||
@ -33,6 +72,7 @@ class MoxxyNativePlugin: FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
|||||||
|
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||||
activity = binding.activity
|
activity = binding.activity
|
||||||
|
activityClass = activity!!.javaClass
|
||||||
binding.addActivityResultListener(pickerListener)
|
binding.addActivityResultListener(pickerListener)
|
||||||
Log.d(TAG, "Attached to activity")
|
Log.d(TAG, "Attached to activity")
|
||||||
}
|
}
|
||||||
@ -54,7 +94,7 @@ class MoxxyNativePlugin: FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
|||||||
override fun pickFiles(
|
override fun pickFiles(
|
||||||
type: FilePickerType,
|
type: FilePickerType,
|
||||||
multiple: Boolean,
|
multiple: Boolean,
|
||||||
callback: (Result<List<String>>) -> Unit
|
callback: (Result<List<String>>) -> Unit,
|
||||||
) {
|
) {
|
||||||
val requestCode = if (multiple) PICK_FILES_REQUEST else PICK_FILE_REQUEST
|
val requestCode = if (multiple) PICK_FILES_REQUEST else PICK_FILE_REQUEST
|
||||||
AsyncRequestTracker.requestTracker[requestCode] = callback as (Result<Any>) -> Unit
|
AsyncRequestTracker.requestTracker[requestCode] = callback as (Result<Any>) -> Unit
|
||||||
@ -107,4 +147,54 @@ class MoxxyNativePlugin: FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
|||||||
val pickIntent = contract.createIntent(context!!, PickVisualMediaRequest(pickType))
|
val pickIntent = contract.createIntent(context!!, PickVisualMediaRequest(pickType))
|
||||||
activity?.startActivityForResult(pickIntent, PICK_FILE_WITH_DATA_REQUEST)
|
activity?.startActivityForResult(pickIntent, PICK_FILE_WITH_DATA_REQUEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createNotificationGroups(groups: List<NotificationGroup>) {
|
||||||
|
createNotificationGroupsImpl(context!!, groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteNotificationGroups(ids: List<String>) {
|
||||||
|
val notificationManager = context!!.getSystemService(NotificationManager::class.java)
|
||||||
|
for (id in ids) {
|
||||||
|
notificationManager.deleteNotificationChannelGroup(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createNotificationChannels(channels: List<NotificationChannel>) {
|
||||||
|
createNotificationChannelsImpl(context!!, channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteNotificationChannels(ids: List<String>) {
|
||||||
|
val notificationManager = context!!.getSystemService(NotificationManager::class.java)
|
||||||
|
for (id in ids) {
|
||||||
|
notificationManager.deleteNotificationChannel(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showMessagingNotification(notification: MessagingNotification) {
|
||||||
|
org.moxxy.moxxy_native.notifications.showMessagingNotification(context!!, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showNotification(notification: RegularNotification) {
|
||||||
|
showNotificationImpl(context!!, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dismissNotification(id: Long) {
|
||||||
|
NotificationManagerCompat.from(context!!).cancel(id.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNotificationSelfAvatar(path: String) {
|
||||||
|
NotificationDataManager.setAvatarPath(context!!, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNotificationI18n(data: NotificationI18nData) {
|
||||||
|
NotificationDataManager.apply {
|
||||||
|
setYou(context!!, data.you)
|
||||||
|
setReply(context!!, data.reply)
|
||||||
|
setMarkAsRead(context!!, data.markAsRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun notificationStub(event: NotificationEvent) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package org.moxxy.moxxy_native.content
|
||||||
|
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import org.moxxy.moxxy_native.R
|
||||||
|
|
||||||
|
class MoxxyFileProvider : FileProvider(R.xml.file_paths)
|
@ -1,130 +0,0 @@
|
|||||||
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
|
|
||||||
// See also: https://pub.dev/packages/pigeon
|
|
||||||
|
|
||||||
package org.moxxy.moxxy_native.generated
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import io.flutter.plugin.common.BasicMessageChannel
|
|
||||||
import io.flutter.plugin.common.BinaryMessenger
|
|
||||||
import io.flutter.plugin.common.MessageCodec
|
|
||||||
import io.flutter.plugin.common.StandardMessageCodec
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
private fun wrapResult(result: Any?): List<Any?> {
|
|
||||||
return listOf(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun wrapError(exception: Throwable): List<Any?> {
|
|
||||||
if (exception is FlutterError) {
|
|
||||||
return listOf(
|
|
||||||
exception.code,
|
|
||||||
exception.message,
|
|
||||||
exception.details
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return listOf(
|
|
||||||
exception.javaClass.simpleName,
|
|
||||||
exception.toString(),
|
|
||||||
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
|
||||||
* @property code The error code.
|
|
||||||
* @property message The error message.
|
|
||||||
* @property details The error details. Must be a datatype supported by the api codec.
|
|
||||||
*/
|
|
||||||
class FlutterError (
|
|
||||||
val code: String,
|
|
||||||
override val message: String? = null,
|
|
||||||
val details: Any? = null
|
|
||||||
) : Throwable()
|
|
||||||
|
|
||||||
enum class FilePickerType(val raw: Int) {
|
|
||||||
/** Pick only image(s) */
|
|
||||||
IMAGE(0),
|
|
||||||
/** Pick only video(s) */
|
|
||||||
VIDEO(1),
|
|
||||||
/** Pick image(s) and video(s) */
|
|
||||||
IMAGEANDVIDEO(2),
|
|
||||||
/** Pick any kind of file(s) */
|
|
||||||
GENERIC(3);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun ofRaw(raw: Int): FilePickerType? {
|
|
||||||
return values().firstOrNull { it.raw == raw }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
|
||||||
interface MoxxyPickerApi {
|
|
||||||
/**
|
|
||||||
* Open either the photo picker or the generic file picker to get a list of paths that were
|
|
||||||
* selected and are accessable. If the list is empty, then the user dismissed the picker without
|
|
||||||
* selecting anything.
|
|
||||||
*
|
|
||||||
* [type] specifies what kind of file(s) should be picked.
|
|
||||||
*
|
|
||||||
* [multiple] controls whether multiple files can be picked (true) or just a single file
|
|
||||||
* is enough (false).
|
|
||||||
*/
|
|
||||||
fun pickFiles(type: FilePickerType, multiple: Boolean, callback: (Result<List<String>>) -> Unit)
|
|
||||||
/** Like [pickFiles] but sets multiple to false and returns the raw binary data from the file. */
|
|
||||||
fun pickFileWithData(type: FilePickerType, callback: (Result<ByteArray?>) -> Unit)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/** The codec used by MoxxyPickerApi. */
|
|
||||||
val codec: MessageCodec<Any?> by lazy {
|
|
||||||
StandardMessageCodec()
|
|
||||||
}
|
|
||||||
/** Sets up an instance of `MoxxyPickerApi` to handle messages through the `binaryMessenger`. */
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
fun setUp(binaryMessenger: BinaryMessenger, api: MoxxyPickerApi?) {
|
|
||||||
run {
|
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFiles", codec)
|
|
||||||
if (api != null) {
|
|
||||||
channel.setMessageHandler { message, reply ->
|
|
||||||
val args = message as List<Any?>
|
|
||||||
val typeArg = FilePickerType.ofRaw(args[0] as Int)!!
|
|
||||||
val multipleArg = args[1] as Boolean
|
|
||||||
api.pickFiles(typeArg, multipleArg) { result: Result<List<String>> ->
|
|
||||||
val error = result.exceptionOrNull()
|
|
||||||
if (error != null) {
|
|
||||||
reply.reply(wrapError(error))
|
|
||||||
} else {
|
|
||||||
val data = result.getOrNull()
|
|
||||||
reply.reply(wrapResult(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.setMessageHandler(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run {
|
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFileWithData", codec)
|
|
||||||
if (api != null) {
|
|
||||||
channel.setMessageHandler { message, reply ->
|
|
||||||
val args = message as List<Any?>
|
|
||||||
val typeArg = FilePickerType.ofRaw(args[0] as Int)!!
|
|
||||||
api.pickFileWithData(typeArg) { result: Result<ByteArray?> ->
|
|
||||||
val error = result.exceptionOrNull()
|
|
||||||
if (error != null) {
|
|
||||||
reply.reply(wrapError(error))
|
|
||||||
} else {
|
|
||||||
val data = result.getOrNull()
|
|
||||||
reply.reply(wrapResult(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.setMessageHandler(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,212 @@
|
|||||||
|
package org.moxxy.moxxy_native.notifications
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.app.RemoteInput
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import org.moxxy.moxxy_native.MARK_AS_READ_ACTION
|
||||||
|
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_JID_KEY
|
||||||
|
import org.moxxy.moxxy_native.NOTIFICATION_MESSAGE_EXTRA_MIME
|
||||||
|
import org.moxxy.moxxy_native.NOTIFICATION_MESSAGE_EXTRA_PATH
|
||||||
|
import org.moxxy.moxxy_native.REPLY_ACTION
|
||||||
|
import org.moxxy.moxxy_native.REPLY_TEXT_KEY
|
||||||
|
import org.moxxy.moxxy_native.TAG
|
||||||
|
import org.moxxy.moxxy_native.TAP_ACTION
|
||||||
|
import java.io.File
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
fun extractPayloadMapFromIntent(intent: Intent): Map<String?, String?> {
|
||||||
|
val extras = mutableMapOf<String?, String?>()
|
||||||
|
intent.extras?.keySet()!!.forEach {
|
||||||
|
Log.d(TAG, "Checking $it -> ${intent.extras!!.get(it)}")
|
||||||
|
if (it.startsWith("payload_")) {
|
||||||
|
Log.d(TAG, "Adding $it")
|
||||||
|
extras[it.substring(8)] = intent.extras!!.getString(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extras
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationReceiver : BroadcastReceiver() {
|
||||||
|
/*
|
||||||
|
* Dismisses the notification through which we received @intent.
|
||||||
|
* */
|
||||||
|
private fun dismissNotification(context: Context, intent: Intent) {
|
||||||
|
// Dismiss the notification
|
||||||
|
val notificationId = intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1).toInt()
|
||||||
|
if (notificationId != -1) {
|
||||||
|
NotificationManagerCompat.from(context).cancel(
|
||||||
|
notificationId,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Log.e("NotificationReceiver", "No id specified. Cannot dismiss notification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findActiveNotification(context: Context, id: Int): Notification? {
|
||||||
|
return (context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
|
||||||
|
.activeNotifications
|
||||||
|
.find { it.id == id }?.notification
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleMarkAsRead(context: Context, intent: Intent) {
|
||||||
|
MoxxyEventChannels.notificationEventSink?.success(
|
||||||
|
NotificationEvent(
|
||||||
|
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
||||||
|
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
||||||
|
NotificationEventType.MARKASREAD,
|
||||||
|
null,
|
||||||
|
extractPayloadMapFromIntent(intent),
|
||||||
|
).toList(),
|
||||||
|
)
|
||||||
|
dismissNotification(context, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleReply(context: Context, intent: Intent) {
|
||||||
|
val remoteInput = RemoteInput.getResultsFromIntent(intent) ?: return
|
||||||
|
val replyPayload = remoteInput.getCharSequence(REPLY_TEXT_KEY)
|
||||||
|
MoxxyEventChannels.notificationEventSink?.success(
|
||||||
|
NotificationEvent(
|
||||||
|
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
||||||
|
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
||||||
|
NotificationEventType.REPLY,
|
||||||
|
replyPayload.toString(),
|
||||||
|
extractPayloadMapFromIntent(intent),
|
||||||
|
).toList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val id = intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1).toInt()
|
||||||
|
if (id == -1) {
|
||||||
|
Log.e(TAG, "Failed to find notification id for reply")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val notification = findActiveNotification(context, id)
|
||||||
|
if (notification == null) {
|
||||||
|
Log.e(TAG, "Failed to find notification for id $id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thanks https://medium.com/@sidorovroman3/android-how-to-use-messagingstyle-for-notifications-without-caching-messages-c414ef2b816c
|
||||||
|
val recoveredStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification)!!
|
||||||
|
val newStyle = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
Notification.MessagingStyle(
|
||||||
|
android.app.Person.Builder().apply {
|
||||||
|
setName(NotificationDataManager.getYou(context))
|
||||||
|
|
||||||
|
// Set an avatar, if we have one
|
||||||
|
val avatarPath = NotificationDataManager.getAvatarPath(context)
|
||||||
|
if (avatarPath != null) {
|
||||||
|
setIcon(
|
||||||
|
Icon.createWithAdaptiveBitmap(
|
||||||
|
BitmapFactory.decodeFile(avatarPath),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.build(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Notification.MessagingStyle(NotificationDataManager.getYou(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
newStyle.apply {
|
||||||
|
conversationTitle = recoveredStyle.conversationTitle
|
||||||
|
recoveredStyle.messages.forEach {
|
||||||
|
// Check if we have to request (or refresh) the content URI to be able to still
|
||||||
|
// see the embedded image.
|
||||||
|
val mime = it.extras.getString(NOTIFICATION_MESSAGE_EXTRA_MIME)
|
||||||
|
val path = it.extras.getString(NOTIFICATION_MESSAGE_EXTRA_PATH)
|
||||||
|
val message = Notification.MessagingStyle.Message(it.text, it.timestamp, it.sender)
|
||||||
|
if (mime != null && path != null) {
|
||||||
|
// Request a new URI from the file provider to ensure we can still see the image
|
||||||
|
// in the notification
|
||||||
|
val fileUri = FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
MOXXY_FILEPROVIDER_ID,
|
||||||
|
File(path),
|
||||||
|
)
|
||||||
|
message.setData(
|
||||||
|
mime,
|
||||||
|
fileUri,
|
||||||
|
)
|
||||||
|
|
||||||
|
// As we're creating a new message, also recreate the additional metadata
|
||||||
|
message.extras.apply {
|
||||||
|
putString(NOTIFICATION_MESSAGE_EXTRA_MIME, mime)
|
||||||
|
putString(NOTIFICATION_MESSAGE_EXTRA_PATH, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the old message
|
||||||
|
addMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append our new message
|
||||||
|
newStyle.addMessage(
|
||||||
|
Notification.MessagingStyle.Message(
|
||||||
|
replyPayload!!,
|
||||||
|
Instant.now().toEpochMilli(),
|
||||||
|
null as CharSequence?,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Post the new notification
|
||||||
|
val recoveredBuilder = Notification.Builder.recoverBuilder(context, notification).apply {
|
||||||
|
style = newStyle
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
NotificationManagerCompat.from(context).notify(id, recoveredBuilder.build())
|
||||||
|
} catch (ex: SecurityException) {
|
||||||
|
Log.e(TAG, "Failed to post reply-notification: ${ex.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleTap(context: Context, intent: Intent) {
|
||||||
|
MoxxyEventChannels.notificationEventSink?.success(
|
||||||
|
NotificationEvent(
|
||||||
|
intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1),
|
||||||
|
intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!,
|
||||||
|
NotificationEventType.OPEN,
|
||||||
|
null,
|
||||||
|
extractPayloadMapFromIntent(intent),
|
||||||
|
).toList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bring the app into the foreground
|
||||||
|
Log.d(TAG, "Querying launch intent for ${context.packageName}")
|
||||||
|
val tapIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)!!
|
||||||
|
Log.d(TAG, "Starting activity")
|
||||||
|
context.startActivity(tapIntent)
|
||||||
|
|
||||||
|
// Dismiss the notification
|
||||||
|
Log.d(TAG, "Dismissing notification")
|
||||||
|
dismissNotification(context, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
// TODO: We need to be careful to ensure that the Flutter engine is running.
|
||||||
|
// If it's not, we have to start it. However, that's only an issue when we expect to
|
||||||
|
// receive notifications while not running, i.e. Push Notifications.
|
||||||
|
when (intent.action) {
|
||||||
|
MARK_AS_READ_ACTION -> handleMarkAsRead(context, intent)
|
||||||
|
REPLY_ACTION -> handleReply(context, intent)
|
||||||
|
TAP_ACTION -> handleTap(context, intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,362 @@
|
|||||||
|
package org.moxxy.moxxy_native.notifications
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannelGroup
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.app.Person
|
||||||
|
import androidx.core.app.RemoteInput
|
||||||
|
import androidx.core.app.TaskStackBuilder
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import org.moxxy.moxxy_native.MARK_AS_READ_ACTION
|
||||||
|
import org.moxxy.moxxy_native.MOXXY_FILEPROVIDER_ID
|
||||||
|
import org.moxxy.moxxy_native.NOTIFICATION_EXTRA_ID_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_PATH
|
||||||
|
import org.moxxy.moxxy_native.R
|
||||||
|
import org.moxxy.moxxy_native.REPLY_ACTION
|
||||||
|
import org.moxxy.moxxy_native.REPLY_TEXT_KEY
|
||||||
|
import org.moxxy.moxxy_native.SHARED_PREFERENCES_AVATAR_KEY
|
||||||
|
import org.moxxy.moxxy_native.SHARED_PREFERENCES_KEY
|
||||||
|
import org.moxxy.moxxy_native.SHARED_PREFERENCES_MARK_AS_READ_KEY
|
||||||
|
import org.moxxy.moxxy_native.SHARED_PREFERENCES_REPLY_KEY
|
||||||
|
import org.moxxy.moxxy_native.SHARED_PREFERENCES_YOU_KEY
|
||||||
|
import org.moxxy.moxxy_native.TAG
|
||||||
|
import org.moxxy.moxxy_native.TAP_ACTION
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Holds "persistent" data for notifications, like i18n strings. While not useful now, this is
|
||||||
|
* useful for when the app is dead and we receive a notification.
|
||||||
|
* */
|
||||||
|
object NotificationDataManager {
|
||||||
|
private var you: String? = null
|
||||||
|
private var markAsRead: String? = null
|
||||||
|
private var reply: String? = null
|
||||||
|
|
||||||
|
private var fetchedAvatarPath = false
|
||||||
|
private var avatarPath: String? = null
|
||||||
|
|
||||||
|
private fun getString(context: Context, key: String, fallback: String): String {
|
||||||
|
return context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)!!.getString(key, fallback)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setString(context: Context, key: String, value: String) {
|
||||||
|
val prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
|
||||||
|
prefs.edit()
|
||||||
|
.putString(key, value)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getYou(context: Context): String {
|
||||||
|
if (you == null) you = getString(context, SHARED_PREFERENCES_YOU_KEY, "You")
|
||||||
|
return you!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setYou(context: Context, value: String) {
|
||||||
|
setString(context, SHARED_PREFERENCES_YOU_KEY, value)
|
||||||
|
you = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMarkAsRead(context: Context): String {
|
||||||
|
if (markAsRead == null) markAsRead = getString(context, SHARED_PREFERENCES_MARK_AS_READ_KEY, "Mark as read")
|
||||||
|
return markAsRead!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMarkAsRead(context: Context, value: String) {
|
||||||
|
setString(context, SHARED_PREFERENCES_MARK_AS_READ_KEY, value)
|
||||||
|
markAsRead = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReply(context: Context): String {
|
||||||
|
if (reply != null) reply = getString(context, SHARED_PREFERENCES_REPLY_KEY, "Reply")
|
||||||
|
return reply!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setReply(context: Context, value: String) {
|
||||||
|
setString(context, SHARED_PREFERENCES_REPLY_KEY, value)
|
||||||
|
reply = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAvatarPath(context: Context): String? {
|
||||||
|
if (avatarPath == null && !fetchedAvatarPath) {
|
||||||
|
val path = getString(context, SHARED_PREFERENCES_AVATAR_KEY, "")
|
||||||
|
if (path.isNotEmpty()) {
|
||||||
|
avatarPath = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatarPath
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAvatarPath(context: Context, value: String) {
|
||||||
|
setString(context, SHARED_PREFERENCES_AVATAR_KEY, value)
|
||||||
|
fetchedAvatarPath = true
|
||||||
|
avatarPath = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNotificationGroupsImpl(context: Context, groups: List<NotificationGroup>) {
|
||||||
|
val notificationManager = context.getSystemService(NotificationManager::class.java)
|
||||||
|
for (group in groups) {
|
||||||
|
notificationManager.createNotificationChannelGroup(
|
||||||
|
NotificationChannelGroup(group.id, group.description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNotificationChannelsImpl(context: Context, channels: List<NotificationChannel>) {
|
||||||
|
val notificationManager = context.getSystemService(NotificationManager::class.java)
|
||||||
|
for (channel in channels) {
|
||||||
|
val importance = when (channel.importance) {
|
||||||
|
NotificationChannelImportance.DEFAULT -> NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
NotificationChannelImportance.MIN -> NotificationManager.IMPORTANCE_MIN
|
||||||
|
NotificationChannelImportance.HIGH -> NotificationManager.IMPORTANCE_HIGH
|
||||||
|
}
|
||||||
|
val notificationChannel = android.app.NotificationChannel(channel.id, channel.title, importance).apply {
|
||||||
|
description = channel.description
|
||||||
|
|
||||||
|
enableVibration(channel.vibration)
|
||||||
|
enableLights(channel.enableLights)
|
||||||
|
setShowBadge(channel.showBadge)
|
||||||
|
|
||||||
|
if (channel.groupId != null) {
|
||||||
|
group = channel.groupId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notificationManager.createNotificationChannel(notificationChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// / Show a messaging style notification described by @notification.
|
||||||
|
@SuppressLint("WrongConstant")
|
||||||
|
fun showMessagingNotification(context: Context, notification: MessagingNotification) {
|
||||||
|
// Build the actions
|
||||||
|
// -> Reply action
|
||||||
|
val remoteInput = RemoteInput.Builder(REPLY_TEXT_KEY).apply {
|
||||||
|
setLabel(NotificationDataManager.getReply(context))
|
||||||
|
}.build()
|
||||||
|
val replyIntent = Intent(context, NotificationReceiver::class.java).apply {
|
||||||
|
action = REPLY_ACTION
|
||||||
|
putExtra(NOTIFICATION_EXTRA_JID_KEY, notification.jid)
|
||||||
|
putExtra(NOTIFICATION_EXTRA_ID_KEY, notification.id)
|
||||||
|
|
||||||
|
notification.extra?.forEach {
|
||||||
|
putExtra("payload_${it.key}", it.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val replyPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context.applicationContext,
|
||||||
|
0,
|
||||||
|
replyIntent,
|
||||||
|
PendingIntent.FLAG_MUTABLE,
|
||||||
|
)
|
||||||
|
val replyAction = NotificationCompat.Action.Builder(
|
||||||
|
R.drawable.reply,
|
||||||
|
NotificationDataManager.getReply(context),
|
||||||
|
replyPendingIntent,
|
||||||
|
).apply {
|
||||||
|
addRemoteInput(remoteInput)
|
||||||
|
setAllowGeneratedReplies(true)
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
// -> Mark as read action
|
||||||
|
val markAsReadIntent = Intent(context, NotificationReceiver::class.java).apply {
|
||||||
|
action = MARK_AS_READ_ACTION
|
||||||
|
putExtra(NOTIFICATION_EXTRA_JID_KEY, notification.jid)
|
||||||
|
putExtra(NOTIFICATION_EXTRA_ID_KEY, notification.id)
|
||||||
|
|
||||||
|
notification.extra?.forEach {
|
||||||
|
putExtra("payload_${it.key}", it.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val markAsReadPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context.applicationContext,
|
||||||
|
0,
|
||||||
|
markAsReadIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE,
|
||||||
|
)
|
||||||
|
val markAsReadAction = NotificationCompat.Action.Builder(
|
||||||
|
R.drawable.mark_as_read,
|
||||||
|
NotificationDataManager.getMarkAsRead(context),
|
||||||
|
markAsReadPendingIntent,
|
||||||
|
).build()
|
||||||
|
|
||||||
|
// -> Tap action
|
||||||
|
// Thanks to flutter_local_notifications for this "workaround"
|
||||||
|
val tapIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)!!.apply {
|
||||||
|
action = TAP_ACTION
|
||||||
|
putExtra(NOTIFICATION_EXTRA_JID_KEY, notification.jid)
|
||||||
|
putExtra(NOTIFICATION_EXTRA_ID_KEY, notification.id)
|
||||||
|
|
||||||
|
notification.extra?.forEach {
|
||||||
|
putExtra("payload_${it.key}", it.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not launch a new task
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
}
|
||||||
|
val tapPendingIntent = TaskStackBuilder.create(context).run {
|
||||||
|
addNextIntentWithParentStack(tapIntent)
|
||||||
|
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the notification
|
||||||
|
val selfPerson = Person.Builder().apply {
|
||||||
|
setName(NotificationDataManager.getYou(context))
|
||||||
|
|
||||||
|
// Set an avatar, if we have one
|
||||||
|
val avatarPath = NotificationDataManager.getAvatarPath(context)
|
||||||
|
if (avatarPath != null) {
|
||||||
|
setIcon(
|
||||||
|
IconCompat.createWithAdaptiveBitmap(
|
||||||
|
BitmapFactory.decodeFile(avatarPath),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
val style = NotificationCompat.MessagingStyle(selfPerson)
|
||||||
|
style.isGroupConversation = notification.isGroupchat
|
||||||
|
if (notification.isGroupchat) {
|
||||||
|
style.conversationTitle = notification.title
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in notification.messages.indices) {
|
||||||
|
val message = notification.messages[i]!!
|
||||||
|
|
||||||
|
// Build the sender
|
||||||
|
// NOTE: Note that we set it to null if message.sender == null because otherwise this results in
|
||||||
|
// a bogus Person object which messes with the "self-message" display as Android expects
|
||||||
|
// null in that case.
|
||||||
|
val sender = if (message.sender == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
Person.Builder().apply {
|
||||||
|
setName(message.sender)
|
||||||
|
setKey(message.jid)
|
||||||
|
|
||||||
|
// Set the avatar, if available
|
||||||
|
if (message.avatarPath != null) {
|
||||||
|
try {
|
||||||
|
setIcon(
|
||||||
|
IconCompat.createWithAdaptiveBitmap(
|
||||||
|
BitmapFactory.decodeFile(message.avatarPath),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
Log.w(TAG, "Failed to open avatar at ${message.avatarPath}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the message
|
||||||
|
val body = message.content.body ?: ""
|
||||||
|
val msg = NotificationCompat.MessagingStyle.Message(
|
||||||
|
body,
|
||||||
|
message.timestamp,
|
||||||
|
sender,
|
||||||
|
)
|
||||||
|
// If we got an image, turn it into a content URI and set it
|
||||||
|
if (message.content.mime != null && message.content.path != null) {
|
||||||
|
val fileUri = FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
MOXXY_FILEPROVIDER_ID,
|
||||||
|
File(message.content.path),
|
||||||
|
)
|
||||||
|
msg.apply {
|
||||||
|
setData(message.content.mime, fileUri)
|
||||||
|
|
||||||
|
extras.apply {
|
||||||
|
putString(NOTIFICATION_MESSAGE_EXTRA_MIME, message.content.mime)
|
||||||
|
putString(NOTIFICATION_MESSAGE_EXTRA_PATH, message.content.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the message
|
||||||
|
style.addMessage(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the notification
|
||||||
|
val finalNotification = NotificationCompat.Builder(context, notification.channelId).apply {
|
||||||
|
setStyle(style)
|
||||||
|
// NOTE: It's okay to use the service icon here as I cannot get Android to display the
|
||||||
|
// actual logo. So we'll have to make do with the silhouette and the color purple.
|
||||||
|
setSmallIcon(R.drawable.ic_service)
|
||||||
|
color = Color.argb(255, 207, 74, 255)
|
||||||
|
setColorized(true)
|
||||||
|
|
||||||
|
// Tap action
|
||||||
|
setContentIntent(tapPendingIntent)
|
||||||
|
|
||||||
|
// Notification actions
|
||||||
|
addAction(replyAction)
|
||||||
|
addAction(markAsReadAction)
|
||||||
|
|
||||||
|
// Groupchat title
|
||||||
|
if (notification.isGroupchat) {
|
||||||
|
setContentTitle(notification.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent grouping with the foreground service
|
||||||
|
if (notification.groupId != null) {
|
||||||
|
setGroup(notification.groupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
setAllowSystemGeneratedContextualActions(true)
|
||||||
|
setCategory(Notification.CATEGORY_MESSAGE)
|
||||||
|
|
||||||
|
// Prevent no notification when we replied before
|
||||||
|
setOnlyAlertOnce(false)
|
||||||
|
|
||||||
|
// Automatically dismiss the notification on tap
|
||||||
|
setAutoCancel(true)
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
// Post the notification
|
||||||
|
try {
|
||||||
|
NotificationManagerCompat.from(context).notify(
|
||||||
|
notification.id.toInt(),
|
||||||
|
finalNotification,
|
||||||
|
)
|
||||||
|
} catch (ex: SecurityException) {
|
||||||
|
// Should never happen as Moxxy checks for the permission before posting the notification
|
||||||
|
Log.e(TAG, "Failed to post notification: ${ex.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showNotificationImpl(context: Context, notification: RegularNotification) {
|
||||||
|
val builtNotification = NotificationCompat.Builder(context, notification.channelId).apply {
|
||||||
|
setContentTitle(notification.title)
|
||||||
|
setContentText(notification.body)
|
||||||
|
|
||||||
|
when (notification.icon) {
|
||||||
|
NotificationIcon.ERROR -> setSmallIcon(R.drawable.error)
|
||||||
|
NotificationIcon.WARNING -> setSmallIcon(R.drawable.warning)
|
||||||
|
NotificationIcon.NONE -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification.groupId != null) {
|
||||||
|
setGroup(notification.groupId)
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
// Post the notification
|
||||||
|
try {
|
||||||
|
NotificationManagerCompat.from(context).notify(notification.id.toInt(), builtNotification)
|
||||||
|
} catch (ex: SecurityException) {
|
||||||
|
// Should never happen as Moxxy checks for the permission before posting the notification
|
||||||
|
Log.e(TAG, "Failed to post notification: ${ex.message}")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,671 @@
|
|||||||
|
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
|
||||||
|
package org.moxxy.moxxy_native.notifications
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.flutter.plugin.common.BasicMessageChannel
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.MessageCodec
|
||||||
|
import io.flutter.plugin.common.StandardMessageCodec
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
private fun wrapResult(result: Any?): List<Any?> {
|
||||||
|
return listOf(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun wrapError(exception: Throwable): List<Any?> {
|
||||||
|
if (exception is FlutterError) {
|
||||||
|
return listOf(
|
||||||
|
exception.code,
|
||||||
|
exception.message,
|
||||||
|
exception.details,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return listOf(
|
||||||
|
exception.javaClass.simpleName,
|
||||||
|
exception.toString(),
|
||||||
|
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
||||||
|
* @property code The error code.
|
||||||
|
* @property message The error message.
|
||||||
|
* @property details The error details. Must be a datatype supported by the api codec.
|
||||||
|
*/
|
||||||
|
class FlutterError(
|
||||||
|
val code: String,
|
||||||
|
override val message: String? = null,
|
||||||
|
val details: Any? = null,
|
||||||
|
) : Throwable()
|
||||||
|
|
||||||
|
enum class NotificationIcon(val raw: Int) {
|
||||||
|
WARNING(0),
|
||||||
|
ERROR(1),
|
||||||
|
NONE(2),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun ofRaw(raw: Int): NotificationIcon? {
|
||||||
|
return values().firstOrNull { it.raw == raw }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class NotificationEventType(val raw: Int) {
|
||||||
|
MARKASREAD(0),
|
||||||
|
REPLY(1),
|
||||||
|
OPEN(2),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun ofRaw(raw: Int): NotificationEventType? {
|
||||||
|
return values().firstOrNull { it.raw == raw }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class NotificationChannelImportance(val raw: Int) {
|
||||||
|
MIN(0),
|
||||||
|
HIGH(1),
|
||||||
|
DEFAULT(2),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun ofRaw(raw: Int): NotificationChannelImportance? {
|
||||||
|
return values().firstOrNull { it.raw == raw }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class NotificationMessageContent(
|
||||||
|
/** The textual body of the message. */
|
||||||
|
val body: String? = null,
|
||||||
|
/** The path and mime type of the media to show. */
|
||||||
|
val mime: String? = null,
|
||||||
|
val path: String? = null,
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun fromList(list: List<Any?>): NotificationMessageContent {
|
||||||
|
val body = list[0] as String?
|
||||||
|
val mime = list[1] as String?
|
||||||
|
val path = list[2] as String?
|
||||||
|
return NotificationMessageContent(body, mime, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf<Any?>(
|
||||||
|
body,
|
||||||
|
mime,
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class NotificationMessage(
|
||||||
|
/** The grouping key for the notification. */
|
||||||
|
val groupId: String? = null,
|
||||||
|
/** The sender of the message. */
|
||||||
|
val sender: String? = null,
|
||||||
|
/** The jid of the sender. */
|
||||||
|
val jid: String? = null,
|
||||||
|
/** The body of the message. */
|
||||||
|
val content: NotificationMessageContent,
|
||||||
|
/** Milliseconds since epoch. */
|
||||||
|
val timestamp: Long,
|
||||||
|
/** The path to the avatar to use */
|
||||||
|
val avatarPath: String? = null,
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun fromList(list: List<Any?>): NotificationMessage {
|
||||||
|
val groupId = list[0] as String?
|
||||||
|
val sender = list[1] as String?
|
||||||
|
val jid = list[2] as String?
|
||||||
|
val content = NotificationMessageContent.fromList(list[3] as List<Any?>)
|
||||||
|
val timestamp = list[4].let { if (it is Int) it.toLong() else it as Long }
|
||||||
|
val avatarPath = list[5] as String?
|
||||||
|
return NotificationMessage(groupId, sender, jid, content, timestamp, avatarPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf<Any?>(
|
||||||
|
groupId,
|
||||||
|
sender,
|
||||||
|
jid,
|
||||||
|
content.toList(),
|
||||||
|
timestamp,
|
||||||
|
avatarPath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class MessagingNotification(
|
||||||
|
/** The title of the conversation. */
|
||||||
|
val title: String,
|
||||||
|
/** The id of the notification. */
|
||||||
|
val id: Long,
|
||||||
|
/** The id of the notification channel the notification should appear on. */
|
||||||
|
val channelId: String,
|
||||||
|
/** The JID of the chat in which the notifications happen. */
|
||||||
|
val jid: String,
|
||||||
|
/** Messages to show. */
|
||||||
|
val messages: List<NotificationMessage?>,
|
||||||
|
/** Flag indicating whether this notification is from a groupchat or not. */
|
||||||
|
val isGroupchat: Boolean,
|
||||||
|
/** The id for notification grouping. */
|
||||||
|
val groupId: String? = null,
|
||||||
|
/** Additional data to include. */
|
||||||
|
val extra: Map<String?, String?>? = null,
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun fromList(list: List<Any?>): MessagingNotification {
|
||||||
|
val title = list[0] as String
|
||||||
|
val id = list[1].let { if (it is Int) it.toLong() else it as Long }
|
||||||
|
val channelId = list[2] as String
|
||||||
|
val jid = list[3] as String
|
||||||
|
val messages = list[4] as List<NotificationMessage?>
|
||||||
|
val isGroupchat = list[5] as Boolean
|
||||||
|
val groupId = list[6] as String?
|
||||||
|
val extra = list[7] as Map<String?, String?>?
|
||||||
|
return MessagingNotification(title, id, channelId, jid, messages, isGroupchat, groupId, extra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf<Any?>(
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
channelId,
|
||||||
|
jid,
|
||||||
|
messages,
|
||||||
|
isGroupchat,
|
||||||
|
groupId,
|
||||||
|
extra,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class RegularNotification(
|
||||||
|
/** The title of the notification. */
|
||||||
|
val title: String,
|
||||||
|
/** The body of the notification. */
|
||||||
|
val body: String,
|
||||||
|
/** The id of the channel to show the notification on. */
|
||||||
|
val channelId: String,
|
||||||
|
/** The id for notification grouping. */
|
||||||
|
val groupId: String? = null,
|
||||||
|
/** The id of the notification. */
|
||||||
|
val id: Long,
|
||||||
|
/** The icon to use. */
|
||||||
|
val icon: NotificationIcon,
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun fromList(list: List<Any?>): RegularNotification {
|
||||||
|
val title = list[0] as String
|
||||||
|
val body = list[1] as String
|
||||||
|
val channelId = list[2] as String
|
||||||
|
val groupId = list[3] as String?
|
||||||
|
val id = list[4].let { if (it is Int) it.toLong() else it as Long }
|
||||||
|
val icon = NotificationIcon.ofRaw(list[5] as Int)!!
|
||||||
|
return RegularNotification(title, body, channelId, groupId, id, icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf<Any?>(
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
channelId,
|
||||||
|
groupId,
|
||||||
|
id,
|
||||||
|
icon.raw,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class NotificationEvent(
|
||||||
|
/** The notification id. */
|
||||||
|
val id: Long,
|
||||||
|
/** The JID the notification was for. */
|
||||||
|
val jid: String,
|
||||||
|
/** The type of event. */
|
||||||
|
val type: NotificationEventType,
|
||||||
|
/**
|
||||||
|
* An optional payload.
|
||||||
|
* - type == NotificationType.reply: The reply message text.
|
||||||
|
* Otherwise: undefined.
|
||||||
|
*/
|
||||||
|
val payload: String? = null,
|
||||||
|
/** Extra data. Only set when type == NotificationType.reply. */
|
||||||
|
val extra: Map<String?, String?>? = null,
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun fromList(list: List<Any?>): NotificationEvent {
|
||||||
|
val id = list[0].let { if (it is Int) it.toLong() else it as Long }
|
||||||
|
val jid = list[1] as String
|
||||||
|
val type = NotificationEventType.ofRaw(list[2] as Int)!!
|
||||||
|
val payload = list[3] as String?
|
||||||
|
val extra = list[4] as Map<String?, String?>?
|
||||||
|
return NotificationEvent(id, jid, type, payload, extra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf<Any?>(
|
||||||
|
id,
|
||||||
|
jid,
|
||||||
|
type.raw,
|
||||||
|
payload,
|
||||||
|
extra,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class NotificationI18nData(
|
||||||
|
/** The content of the reply button. */
|
||||||
|
val reply: String,
|
||||||
|
/** The content of the "mark as read" button. */
|
||||||
|
val markAsRead: String,
|
||||||
|
/** The text to show when *you* reply. */
|
||||||
|
val you: String,
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun fromList(list: List<Any?>): NotificationI18nData {
|
||||||
|
val reply = list[0] as String
|
||||||
|
val markAsRead = list[1] as String
|
||||||
|
val you = list[2] as String
|
||||||
|
return NotificationI18nData(reply, markAsRead, you)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf<Any?>(
|
||||||
|
reply,
|
||||||
|
markAsRead,
|
||||||
|
you,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class NotificationGroup(
|
||||||
|
val id: String,
|
||||||
|
val description: String,
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun fromList(list: List<Any?>): NotificationGroup {
|
||||||
|
val id = list[0] as String
|
||||||
|
val description = list[1] as String
|
||||||
|
return NotificationGroup(id, description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf<Any?>(
|
||||||
|
id,
|
||||||
|
description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class NotificationChannel(
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val id: String,
|
||||||
|
val importance: NotificationChannelImportance,
|
||||||
|
val showBadge: Boolean,
|
||||||
|
val groupId: String? = null,
|
||||||
|
val vibration: Boolean,
|
||||||
|
val enableLights: Boolean,
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun fromList(list: List<Any?>): NotificationChannel {
|
||||||
|
val title = list[0] as String
|
||||||
|
val description = list[1] as String
|
||||||
|
val id = list[2] as String
|
||||||
|
val importance = NotificationChannelImportance.ofRaw(list[3] as Int)!!
|
||||||
|
val showBadge = list[4] as Boolean
|
||||||
|
val groupId = list[5] as String?
|
||||||
|
val vibration = list[6] as Boolean
|
||||||
|
val enableLights = list[7] as Boolean
|
||||||
|
return NotificationChannel(title, description, id, importance, showBadge, groupId, vibration, enableLights)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf<Any?>(
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
id,
|
||||||
|
importance.raw,
|
||||||
|
showBadge,
|
||||||
|
groupId,
|
||||||
|
vibration,
|
||||||
|
enableLights,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private object MoxxyNotificationsApiCodec : StandardMessageCodec() {
|
||||||
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
|
return when (type) {
|
||||||
|
128.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
MessagingNotification.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
129.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
NotificationChannel.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
130.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
NotificationEvent.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
131.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
NotificationGroup.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
132.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
NotificationI18nData.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
133.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
NotificationMessage.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
134.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
NotificationMessageContent.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
135.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
RegularNotification.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> super.readValueOfType(type, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||||
|
when (value) {
|
||||||
|
is MessagingNotification -> {
|
||||||
|
stream.write(128)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is NotificationChannel -> {
|
||||||
|
stream.write(129)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is NotificationEvent -> {
|
||||||
|
stream.write(130)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is NotificationGroup -> {
|
||||||
|
stream.write(131)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is NotificationI18nData -> {
|
||||||
|
stream.write(132)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is NotificationMessage -> {
|
||||||
|
stream.write(133)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is NotificationMessageContent -> {
|
||||||
|
stream.write(134)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is RegularNotification -> {
|
||||||
|
stream.write(135)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
else -> super.writeValue(stream, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
|
interface MoxxyNotificationsApi {
|
||||||
|
/** Notification APIs */
|
||||||
|
fun createNotificationGroups(groups: List<NotificationGroup>)
|
||||||
|
fun deleteNotificationGroups(ids: List<String>)
|
||||||
|
fun createNotificationChannels(channels: List<NotificationChannel>)
|
||||||
|
fun deleteNotificationChannels(ids: List<String>)
|
||||||
|
fun showMessagingNotification(notification: MessagingNotification)
|
||||||
|
fun showNotification(notification: RegularNotification)
|
||||||
|
fun dismissNotification(id: Long)
|
||||||
|
fun setNotificationSelfAvatar(path: String)
|
||||||
|
fun setNotificationI18n(data: NotificationI18nData)
|
||||||
|
fun notificationStub(event: NotificationEvent)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by MoxxyNotificationsApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
MoxxyNotificationsApiCodec
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets up an instance of `MoxxyNotificationsApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: MoxxyNotificationsApi?) {
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.createNotificationGroups", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val groupsArg = args[0] as List<NotificationGroup>
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.createNotificationGroups(groupsArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.deleteNotificationGroups", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val idsArg = args[0] as List<String>
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.deleteNotificationGroups(idsArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.createNotificationChannels", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val channelsArg = args[0] as List<NotificationChannel>
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.createNotificationChannels(channelsArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.deleteNotificationChannels", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val idsArg = args[0] as List<String>
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.deleteNotificationChannels(idsArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.showMessagingNotification", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val notificationArg = args[0] as MessagingNotification
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.showMessagingNotification(notificationArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.showNotification", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val notificationArg = args[0] as RegularNotification
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.showNotification(notificationArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.dismissNotification", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val idArg = args[0].let { if (it is Int) it.toLong() else it as Long }
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.dismissNotification(idArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.setNotificationSelfAvatar", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val pathArg = args[0] as String
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.setNotificationSelfAvatar(pathArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.setNotificationI18n", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val dataArg = args[0] as NotificationI18nData
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.setNotificationI18n(dataArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.notificationStub", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val eventArg = args[0] as NotificationEvent
|
||||||
|
var wrapped: List<Any?>
|
||||||
|
try {
|
||||||
|
api.notificationStub(eventArg)
|
||||||
|
wrapped = listOf<Any?>(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapped = wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
|
||||||
|
package org.moxxy.moxxy_native.picker
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.flutter.plugin.common.BasicMessageChannel
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.MessageCodec
|
||||||
|
import io.flutter.plugin.common.StandardMessageCodec
|
||||||
|
|
||||||
|
private fun wrapResult(result: Any?): List<Any?> {
|
||||||
|
return listOf(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun wrapError(exception: Throwable): List<Any?> {
|
||||||
|
if (exception is FlutterError) {
|
||||||
|
return listOf(
|
||||||
|
exception.code,
|
||||||
|
exception.message,
|
||||||
|
exception.details,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return listOf(
|
||||||
|
exception.javaClass.simpleName,
|
||||||
|
exception.toString(),
|
||||||
|
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
||||||
|
* @property code The error code.
|
||||||
|
* @property message The error message.
|
||||||
|
* @property details The error details. Must be a datatype supported by the api codec.
|
||||||
|
*/
|
||||||
|
class FlutterError(
|
||||||
|
val code: String,
|
||||||
|
override val message: String? = null,
|
||||||
|
val details: Any? = null,
|
||||||
|
) : Throwable()
|
||||||
|
|
||||||
|
enum class FilePickerType(val raw: Int) {
|
||||||
|
/** Pick only image(s) */
|
||||||
|
IMAGE(0),
|
||||||
|
|
||||||
|
/** Pick only video(s) */
|
||||||
|
VIDEO(1),
|
||||||
|
|
||||||
|
/** Pick image(s) and video(s) */
|
||||||
|
IMAGEANDVIDEO(2),
|
||||||
|
|
||||||
|
/** Pick any kind of file(s) */
|
||||||
|
GENERIC(3),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun ofRaw(raw: Int): FilePickerType? {
|
||||||
|
return values().firstOrNull { it.raw == raw }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
|
interface MoxxyPickerApi {
|
||||||
|
/**
|
||||||
|
* Open either the photo picker or the generic file picker to get a list of paths that were
|
||||||
|
* selected and are accessable. If the list is empty, then the user dismissed the picker without
|
||||||
|
* selecting anything.
|
||||||
|
*
|
||||||
|
* [type] specifies what kind of file(s) should be picked.
|
||||||
|
*
|
||||||
|
* [multiple] controls whether multiple files can be picked (true) or just a single file
|
||||||
|
* is enough (false).
|
||||||
|
*/
|
||||||
|
fun pickFiles(type: FilePickerType, multiple: Boolean, callback: (Result<List<String>>) -> Unit)
|
||||||
|
|
||||||
|
/** Like [pickFiles] but sets multiple to false and returns the raw binary data from the file. */
|
||||||
|
fun pickFileWithData(type: FilePickerType, callback: (Result<ByteArray?>) -> Unit)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by MoxxyPickerApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
StandardMessageCodec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets up an instance of `MoxxyPickerApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: MoxxyPickerApi?) {
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFiles", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val typeArg = FilePickerType.ofRaw(args[0] as Int)!!
|
||||||
|
val multipleArg = args[1] as Boolean
|
||||||
|
api.pickFiles(typeArg, multipleArg) { result: Result<List<String>> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(wrapError(error))
|
||||||
|
} else {
|
||||||
|
val data = result.getOrNull()
|
||||||
|
reply.reply(wrapResult(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFileWithData", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val typeArg = FilePickerType.ofRaw(args[0] as Int)!!
|
||||||
|
api.pickFileWithData(typeArg) { result: Result<ByteArray?> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(wrapError(error))
|
||||||
|
} else {
|
||||||
|
val data = result.getOrNull()
|
||||||
|
reply.reply(wrapResult(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
android/src/main/res/drawable-hdpi/ic_service.png
Normal file
BIN
android/src/main/res/drawable-hdpi/ic_service.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 464 B |
BIN
android/src/main/res/drawable-mdpi/ic_service.png
Normal file
BIN
android/src/main/res/drawable-mdpi/ic_service.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 327 B |
BIN
android/src/main/res/drawable-xhdpi/ic_service.png
Normal file
BIN
android/src/main/res/drawable-xhdpi/ic_service.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 642 B |
BIN
android/src/main/res/drawable-xxhdpi/ic_service.png
Normal file
BIN
android/src/main/res/drawable-xxhdpi/ic_service.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
android/src/main/res/drawable-xxxhdpi/ic_service.png
Normal file
BIN
android/src/main/res/drawable-xxxhdpi/ic_service.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
5
android/src/main/res/drawable/error.xml
Normal file
5
android/src/main/res/drawable/error.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
|
||||||
|
</vector>
|
5
android/src/main/res/drawable/mark_as_read.xml
Normal file
5
android/src/main/res/drawable/mark_as_read.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17.34,20l-3.54,-3.54l1.41,-1.41l2.12,2.12l4.24,-4.24L23,14.34L17.34,20zM12,17c0,-3.87 3.13,-7 7,-7c1.08,0 2.09,0.25 3,0.68V4c0,-1.1 -0.9,-2 -2,-2H4C2.9,2 2,2.9 2,4v18l4,-4h6v0c0,-0.17 0.01,-0.33 0.03,-0.5C12.01,17.34 12,17.17 12,17z"/>
|
||||||
|
</vector>
|
5
android/src/main/res/drawable/reply.xml
Normal file
5
android/src/main/res/drawable/reply.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:autoMirrored="true" android:height="24dp"
|
||||||
|
android:tint="#FFFFFF" android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
|
||||||
|
</vector>
|
5
android/src/main/res/drawable/warning.xml
Normal file
5
android/src/main/res/drawable/warning.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
|
||||||
|
</vector>
|
7
android/src/main/res/xml/file_paths.xml
Normal file
7
android/src/main/res/xml/file_paths.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- Media files -->
|
||||||
|
<files-path name="media" path="media/" />
|
||||||
|
|
||||||
|
<!-- Media thumbnails -->
|
||||||
|
<cache-path name="thumbnails" path="thumbnails/" />
|
||||||
|
</paths>
|
@ -1,6 +1,20 @@
|
|||||||
package org.moxxy.moxxy_native_example
|
package org.moxxy.moxxy_native_example
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
Log.d("moxxy_native", "onCreate intent ${intent?.action}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
|
Log.d("moxxy_native", "New intent ${intent.action}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:moxxy_native/moxxy_native.dart';
|
import 'package:moxxy_native/moxxy_native.dart';
|
||||||
|
|
||||||
@ -21,7 +19,8 @@ class MyApp extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await MoxxyPickerApi().pickFiles(FilePickerType.image, false);
|
final result = await MoxxyPickerApi()
|
||||||
|
.pickFiles(FilePickerType.image, false);
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print('User picked: $result');
|
print('User picked: $result');
|
||||||
},
|
},
|
||||||
@ -29,7 +28,8 @@ class MyApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await MoxxyPickerApi().pickFiles(FilePickerType.imageAndVideo, true);
|
final result = await MoxxyPickerApi()
|
||||||
|
.pickFiles(FilePickerType.imageAndVideo, true);
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print('User picked: $result');
|
print('User picked: $result');
|
||||||
},
|
},
|
||||||
@ -37,7 +37,8 @@ class MyApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await MoxxyPickerApi().pickFiles(FilePickerType.generic, true);
|
final result = await MoxxyPickerApi()
|
||||||
|
.pickFiles(FilePickerType.generic, true);
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print('User picked: $result');
|
print('User picked: $result');
|
||||||
},
|
},
|
||||||
|
@ -130,6 +130,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.8.2"
|
||||||
|
permission_handler:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: permission_handler
|
||||||
|
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.4.5"
|
||||||
|
permission_handler_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_android
|
||||||
|
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.3.6"
|
||||||
|
permission_handler_apple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_apple
|
||||||
|
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.1.4"
|
||||||
|
permission_handler_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_platform_interface
|
||||||
|
sha256: f2343e9fa9c22ae4fd92d4732755bfe452214e7189afcc097380950cf567b4b2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.11.5"
|
||||||
|
permission_handler_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_windows
|
||||||
|
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.6"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -193,4 +241,4 @@ packages:
|
|||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.19.6 <3.0.0"
|
dart: ">=2.19.6 <3.0.0"
|
||||||
flutter: ">=2.5.0"
|
flutter: ">=2.8.0"
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'package:moxxy_native_example/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(const MyApp());
|
|
||||||
|
|
||||||
// Verify that platform version is retrieved.
|
|
||||||
expect(
|
|
||||||
find.byWidgetPredicate(
|
|
||||||
(Widget widget) => widget is Text &&
|
|
||||||
widget.data!.startsWith('Running on:'),
|
|
||||||
),
|
|
||||||
findsOneWidget,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
@ -1 +1,2 @@
|
|||||||
export 'pigeon/picker.dart';
|
export 'pigeon/notifications.g.dart';
|
||||||
|
export 'pigeon/picker.g.dart';
|
||||||
|
695
lib/pigeon/notifications.g.dart
Normal file
695
lib/pigeon/notifications.g.dart
Normal file
@ -0,0 +1,695 @@
|
|||||||
|
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
enum NotificationIcon {
|
||||||
|
warning,
|
||||||
|
error,
|
||||||
|
none,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationEventType {
|
||||||
|
markAsRead,
|
||||||
|
reply,
|
||||||
|
open,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationChannelImportance {
|
||||||
|
MIN,
|
||||||
|
HIGH,
|
||||||
|
DEFAULT,
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationMessageContent {
|
||||||
|
NotificationMessageContent({
|
||||||
|
this.body,
|
||||||
|
this.mime,
|
||||||
|
this.path,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The textual body of the message.
|
||||||
|
String? body;
|
||||||
|
|
||||||
|
/// The path and mime type of the media to show.
|
||||||
|
String? mime;
|
||||||
|
|
||||||
|
String? path;
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return <Object?>[
|
||||||
|
body,
|
||||||
|
mime,
|
||||||
|
path,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static NotificationMessageContent decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return NotificationMessageContent(
|
||||||
|
body: result[0] as String?,
|
||||||
|
mime: result[1] as String?,
|
||||||
|
path: result[2] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationMessage {
|
||||||
|
NotificationMessage({
|
||||||
|
this.groupId,
|
||||||
|
this.sender,
|
||||||
|
this.jid,
|
||||||
|
required this.content,
|
||||||
|
required this.timestamp,
|
||||||
|
this.avatarPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The grouping key for the notification.
|
||||||
|
String? groupId;
|
||||||
|
|
||||||
|
/// The sender of the message.
|
||||||
|
String? sender;
|
||||||
|
|
||||||
|
/// The jid of the sender.
|
||||||
|
String? jid;
|
||||||
|
|
||||||
|
/// The body of the message.
|
||||||
|
NotificationMessageContent content;
|
||||||
|
|
||||||
|
/// Milliseconds since epoch.
|
||||||
|
int timestamp;
|
||||||
|
|
||||||
|
/// The path to the avatar to use
|
||||||
|
String? avatarPath;
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return <Object?>[
|
||||||
|
groupId,
|
||||||
|
sender,
|
||||||
|
jid,
|
||||||
|
content.encode(),
|
||||||
|
timestamp,
|
||||||
|
avatarPath,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static NotificationMessage decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return NotificationMessage(
|
||||||
|
groupId: result[0] as String?,
|
||||||
|
sender: result[1] as String?,
|
||||||
|
jid: result[2] as String?,
|
||||||
|
content: NotificationMessageContent.decode(result[3]! as List<Object?>),
|
||||||
|
timestamp: result[4]! as int,
|
||||||
|
avatarPath: result[5] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessagingNotification {
|
||||||
|
MessagingNotification({
|
||||||
|
required this.title,
|
||||||
|
required this.id,
|
||||||
|
required this.channelId,
|
||||||
|
required this.jid,
|
||||||
|
required this.messages,
|
||||||
|
required this.isGroupchat,
|
||||||
|
this.groupId,
|
||||||
|
this.extra,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The title of the conversation.
|
||||||
|
String title;
|
||||||
|
|
||||||
|
/// The id of the notification.
|
||||||
|
int id;
|
||||||
|
|
||||||
|
/// The id of the notification channel the notification should appear on.
|
||||||
|
String channelId;
|
||||||
|
|
||||||
|
/// The JID of the chat in which the notifications happen.
|
||||||
|
String jid;
|
||||||
|
|
||||||
|
/// Messages to show.
|
||||||
|
List<NotificationMessage?> messages;
|
||||||
|
|
||||||
|
/// Flag indicating whether this notification is from a groupchat or not.
|
||||||
|
bool isGroupchat;
|
||||||
|
|
||||||
|
/// The id for notification grouping.
|
||||||
|
String? groupId;
|
||||||
|
|
||||||
|
/// Additional data to include.
|
||||||
|
Map<String?, String?>? extra;
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return <Object?>[
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
channelId,
|
||||||
|
jid,
|
||||||
|
messages,
|
||||||
|
isGroupchat,
|
||||||
|
groupId,
|
||||||
|
extra,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static MessagingNotification decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return MessagingNotification(
|
||||||
|
title: result[0]! as String,
|
||||||
|
id: result[1]! as int,
|
||||||
|
channelId: result[2]! as String,
|
||||||
|
jid: result[3]! as String,
|
||||||
|
messages: (result[4] as List<Object?>?)!.cast<NotificationMessage?>(),
|
||||||
|
isGroupchat: result[5]! as bool,
|
||||||
|
groupId: result[6] as String?,
|
||||||
|
extra: (result[7] as Map<Object?, Object?>?)?.cast<String?, String?>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RegularNotification {
|
||||||
|
RegularNotification({
|
||||||
|
required this.title,
|
||||||
|
required this.body,
|
||||||
|
required this.channelId,
|
||||||
|
this.groupId,
|
||||||
|
required this.id,
|
||||||
|
required this.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The title of the notification.
|
||||||
|
String title;
|
||||||
|
|
||||||
|
/// The body of the notification.
|
||||||
|
String body;
|
||||||
|
|
||||||
|
/// The id of the channel to show the notification on.
|
||||||
|
String channelId;
|
||||||
|
|
||||||
|
/// The id for notification grouping.
|
||||||
|
String? groupId;
|
||||||
|
|
||||||
|
/// The id of the notification.
|
||||||
|
int id;
|
||||||
|
|
||||||
|
/// The icon to use.
|
||||||
|
NotificationIcon icon;
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return <Object?>[
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
channelId,
|
||||||
|
groupId,
|
||||||
|
id,
|
||||||
|
icon.index,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static RegularNotification decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return RegularNotification(
|
||||||
|
title: result[0]! as String,
|
||||||
|
body: result[1]! as String,
|
||||||
|
channelId: result[2]! as String,
|
||||||
|
groupId: result[3] as String?,
|
||||||
|
id: result[4]! as int,
|
||||||
|
icon: NotificationIcon.values[result[5]! as int],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationEvent {
|
||||||
|
NotificationEvent({
|
||||||
|
required this.id,
|
||||||
|
required this.jid,
|
||||||
|
required this.type,
|
||||||
|
this.payload,
|
||||||
|
this.extra,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The notification id.
|
||||||
|
int id;
|
||||||
|
|
||||||
|
/// The JID the notification was for.
|
||||||
|
String jid;
|
||||||
|
|
||||||
|
/// The type of event.
|
||||||
|
NotificationEventType type;
|
||||||
|
|
||||||
|
/// An optional payload.
|
||||||
|
/// - type == NotificationType.reply: The reply message text.
|
||||||
|
/// Otherwise: undefined.
|
||||||
|
String? payload;
|
||||||
|
|
||||||
|
/// Extra data. Only set when type == NotificationType.reply.
|
||||||
|
Map<String?, String?>? extra;
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return <Object?>[
|
||||||
|
id,
|
||||||
|
jid,
|
||||||
|
type.index,
|
||||||
|
payload,
|
||||||
|
extra,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static NotificationEvent decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return NotificationEvent(
|
||||||
|
id: result[0]! as int,
|
||||||
|
jid: result[1]! as String,
|
||||||
|
type: NotificationEventType.values[result[2]! as int],
|
||||||
|
payload: result[3] as String?,
|
||||||
|
extra: (result[4] as Map<Object?, Object?>?)?.cast<String?, String?>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationI18nData {
|
||||||
|
NotificationI18nData({
|
||||||
|
required this.reply,
|
||||||
|
required this.markAsRead,
|
||||||
|
required this.you,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The content of the reply button.
|
||||||
|
String reply;
|
||||||
|
|
||||||
|
/// The content of the "mark as read" button.
|
||||||
|
String markAsRead;
|
||||||
|
|
||||||
|
/// The text to show when *you* reply.
|
||||||
|
String you;
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return <Object?>[
|
||||||
|
reply,
|
||||||
|
markAsRead,
|
||||||
|
you,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static NotificationI18nData decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return NotificationI18nData(
|
||||||
|
reply: result[0]! as String,
|
||||||
|
markAsRead: result[1]! as String,
|
||||||
|
you: result[2]! as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationGroup {
|
||||||
|
NotificationGroup({
|
||||||
|
required this.id,
|
||||||
|
required this.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
String id;
|
||||||
|
|
||||||
|
String description;
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return <Object?>[
|
||||||
|
id,
|
||||||
|
description,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static NotificationGroup decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return NotificationGroup(
|
||||||
|
id: result[0]! as String,
|
||||||
|
description: result[1]! as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationChannel {
|
||||||
|
NotificationChannel({
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
required this.id,
|
||||||
|
required this.importance,
|
||||||
|
required this.showBadge,
|
||||||
|
this.groupId,
|
||||||
|
required this.vibration,
|
||||||
|
required this.enableLights,
|
||||||
|
});
|
||||||
|
|
||||||
|
String title;
|
||||||
|
|
||||||
|
String description;
|
||||||
|
|
||||||
|
String id;
|
||||||
|
|
||||||
|
NotificationChannelImportance importance;
|
||||||
|
|
||||||
|
bool showBadge;
|
||||||
|
|
||||||
|
String? groupId;
|
||||||
|
|
||||||
|
bool vibration;
|
||||||
|
|
||||||
|
bool enableLights;
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return <Object?>[
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
id,
|
||||||
|
importance.index,
|
||||||
|
showBadge,
|
||||||
|
groupId,
|
||||||
|
vibration,
|
||||||
|
enableLights,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static NotificationChannel decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return NotificationChannel(
|
||||||
|
title: result[0]! as String,
|
||||||
|
description: result[1]! as String,
|
||||||
|
id: result[2]! as String,
|
||||||
|
importance: NotificationChannelImportance.values[result[3]! as int],
|
||||||
|
showBadge: result[4]! as bool,
|
||||||
|
groupId: result[5] as String?,
|
||||||
|
vibration: result[6]! as bool,
|
||||||
|
enableLights: result[7]! as bool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoxxyNotificationsApiCodec extends StandardMessageCodec {
|
||||||
|
const _MoxxyNotificationsApiCodec();
|
||||||
|
@override
|
||||||
|
void writeValue(WriteBuffer buffer, Object? value) {
|
||||||
|
if (value is MessagingNotification) {
|
||||||
|
buffer.putUint8(128);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is NotificationChannel) {
|
||||||
|
buffer.putUint8(129);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is NotificationEvent) {
|
||||||
|
buffer.putUint8(130);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is NotificationGroup) {
|
||||||
|
buffer.putUint8(131);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is NotificationI18nData) {
|
||||||
|
buffer.putUint8(132);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is NotificationMessage) {
|
||||||
|
buffer.putUint8(133);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is NotificationMessageContent) {
|
||||||
|
buffer.putUint8(134);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is RegularNotification) {
|
||||||
|
buffer.putUint8(135);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else {
|
||||||
|
super.writeValue(buffer, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
|
switch (type) {
|
||||||
|
case 128:
|
||||||
|
return MessagingNotification.decode(readValue(buffer)!);
|
||||||
|
case 129:
|
||||||
|
return NotificationChannel.decode(readValue(buffer)!);
|
||||||
|
case 130:
|
||||||
|
return NotificationEvent.decode(readValue(buffer)!);
|
||||||
|
case 131:
|
||||||
|
return NotificationGroup.decode(readValue(buffer)!);
|
||||||
|
case 132:
|
||||||
|
return NotificationI18nData.decode(readValue(buffer)!);
|
||||||
|
case 133:
|
||||||
|
return NotificationMessage.decode(readValue(buffer)!);
|
||||||
|
case 134:
|
||||||
|
return NotificationMessageContent.decode(readValue(buffer)!);
|
||||||
|
case 135:
|
||||||
|
return RegularNotification.decode(readValue(buffer)!);
|
||||||
|
default:
|
||||||
|
return super.readValueOfType(type, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoxxyNotificationsApi {
|
||||||
|
/// Constructor for [MoxxyNotificationsApi]. The [binaryMessenger] named argument is
|
||||||
|
/// available for dependency injection. If it is left null, the default
|
||||||
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
|
MoxxyNotificationsApi({BinaryMessenger? binaryMessenger})
|
||||||
|
: _binaryMessenger = binaryMessenger;
|
||||||
|
final BinaryMessenger? _binaryMessenger;
|
||||||
|
|
||||||
|
static const MessageCodec<Object?> codec = _MoxxyNotificationsApiCodec();
|
||||||
|
|
||||||
|
/// Notification APIs
|
||||||
|
Future<void> createNotificationGroups(
|
||||||
|
List<NotificationGroup?> arg_groups) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.createNotificationGroups',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_groups]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteNotificationGroups(List<String?> arg_ids) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.deleteNotificationGroups',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_ids]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createNotificationChannels(
|
||||||
|
List<NotificationChannel?> arg_channels) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.createNotificationChannels',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_channels]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteNotificationChannels(List<String?> arg_ids) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.deleteNotificationChannels',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_ids]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showMessagingNotification(
|
||||||
|
MessagingNotification arg_notification) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.showMessagingNotification',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_notification]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showNotification(RegularNotification arg_notification) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.showNotification',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_notification]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dismissNotification(int arg_id) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.dismissNotification',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_id]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setNotificationSelfAvatar(String arg_path) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.setNotificationSelfAvatar',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_path]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setNotificationI18n(NotificationI18nData arg_data) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.setNotificationI18n',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_data]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> notificationStub(NotificationEvent arg_event) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.notificationStub',
|
||||||
|
codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_event]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,10 +11,13 @@ import 'package:flutter/services.dart';
|
|||||||
enum FilePickerType {
|
enum FilePickerType {
|
||||||
/// Pick only image(s)
|
/// Pick only image(s)
|
||||||
image,
|
image,
|
||||||
|
|
||||||
/// Pick only video(s)
|
/// Pick only video(s)
|
||||||
video,
|
video,
|
||||||
|
|
||||||
/// Pick image(s) and video(s)
|
/// Pick image(s) and video(s)
|
||||||
imageAndVideo,
|
imageAndVideo,
|
||||||
|
|
||||||
/// Pick any kind of file(s)
|
/// Pick any kind of file(s)
|
||||||
generic,
|
generic,
|
||||||
}
|
}
|
||||||
@ -37,12 +40,13 @@ class MoxxyPickerApi {
|
|||||||
///
|
///
|
||||||
/// [multiple] controls whether multiple files can be picked (true) or just a single file
|
/// [multiple] controls whether multiple files can be picked (true) or just a single file
|
||||||
/// is enough (false).
|
/// is enough (false).
|
||||||
Future<List<String?>> pickFiles(FilePickerType arg_type, bool arg_multiple) async {
|
Future<List<String?>> pickFiles(
|
||||||
|
FilePickerType arg_type, bool arg_multiple) async {
|
||||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFiles', codec,
|
'dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFiles', codec,
|
||||||
binaryMessenger: _binaryMessenger);
|
binaryMessenger: _binaryMessenger);
|
||||||
final List<Object?>? replyList =
|
final List<Object?>? replyList = await channel
|
||||||
await channel.send(<Object?>[arg_type.index, arg_multiple]) as List<Object?>?;
|
.send(<Object?>[arg_type.index, arg_multiple]) as List<Object?>?;
|
||||||
if (replyList == null) {
|
if (replyList == null) {
|
||||||
throw PlatformException(
|
throw PlatformException(
|
||||||
code: 'channel-error',
|
code: 'channel-error',
|
||||||
@ -67,7 +71,8 @@ class MoxxyPickerApi {
|
|||||||
/// Like [pickFiles] but sets multiple to false and returns the raw binary data from the file.
|
/// Like [pickFiles] but sets multiple to false and returns the raw binary data from the file.
|
||||||
Future<Uint8List?> pickFileWithData(FilePickerType arg_type) async {
|
Future<Uint8List?> pickFileWithData(FilePickerType arg_type) async {
|
||||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFileWithData', codec,
|
'dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFileWithData',
|
||||||
|
codec,
|
||||||
binaryMessenger: _binaryMessenger);
|
binaryMessenger: _binaryMessenger);
|
||||||
final List<Object?>? replyList =
|
final List<Object?>? replyList =
|
||||||
await channel.send(<Object?>[arg_type.index]) as List<Object?>?;
|
await channel.send(<Object?>[arg_type.index]) as List<Object?>?;
|
206
pigeon/notifications.dart
Normal file
206
pigeon/notifications.dart
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import 'package:pigeon/pigeon.dart';
|
||||||
|
|
||||||
|
@ConfigurePigeon(
|
||||||
|
PigeonOptions(
|
||||||
|
dartOut: 'lib/pigeon/notifications.g.dart',
|
||||||
|
kotlinOut: 'android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationsApi.kt',
|
||||||
|
kotlinOptions: KotlinOptions(
|
||||||
|
package: 'org.moxxy.moxxy_native.notifications',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
class NotificationMessageContent {
|
||||||
|
const NotificationMessageContent(
|
||||||
|
this.body,
|
||||||
|
this.mime,
|
||||||
|
this.path,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The textual body of the message.
|
||||||
|
final String? body;
|
||||||
|
|
||||||
|
/// The path and mime type of the media to show.
|
||||||
|
final String? mime;
|
||||||
|
final String? path;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationMessage {
|
||||||
|
const NotificationMessage(
|
||||||
|
this.sender,
|
||||||
|
this.content,
|
||||||
|
this.jid,
|
||||||
|
this.timestamp,
|
||||||
|
this.avatarPath, {
|
||||||
|
this.groupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The grouping key for the notification.
|
||||||
|
final String? groupId;
|
||||||
|
|
||||||
|
/// The sender of the message.
|
||||||
|
final String? sender;
|
||||||
|
|
||||||
|
/// The jid of the sender.
|
||||||
|
final String? jid;
|
||||||
|
|
||||||
|
/// The body of the message.
|
||||||
|
final NotificationMessageContent content;
|
||||||
|
|
||||||
|
/// Milliseconds since epoch.
|
||||||
|
final int timestamp;
|
||||||
|
|
||||||
|
/// The path to the avatar to use
|
||||||
|
final String? avatarPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessagingNotification {
|
||||||
|
const MessagingNotification(this.title, this.id, this.jid, this.messages,
|
||||||
|
this.channelId, this.isGroupchat, this.extra,
|
||||||
|
{this.groupId});
|
||||||
|
|
||||||
|
/// The title of the conversation.
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
/// The id of the notification.
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
/// The id of the notification channel the notification should appear on.
|
||||||
|
final String channelId;
|
||||||
|
|
||||||
|
/// The JID of the chat in which the notifications happen.
|
||||||
|
final String jid;
|
||||||
|
|
||||||
|
/// Messages to show.
|
||||||
|
final List<NotificationMessage?> messages;
|
||||||
|
|
||||||
|
/// Flag indicating whether this notification is from a groupchat or not.
|
||||||
|
final bool isGroupchat;
|
||||||
|
|
||||||
|
/// The id for notification grouping.
|
||||||
|
final String? groupId;
|
||||||
|
|
||||||
|
/// Additional data to include.
|
||||||
|
final Map<String?, String?>? extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationIcon {
|
||||||
|
warning,
|
||||||
|
error,
|
||||||
|
none,
|
||||||
|
}
|
||||||
|
|
||||||
|
class RegularNotification {
|
||||||
|
const RegularNotification(
|
||||||
|
this.title, this.body, this.channelId, this.id, this.icon,
|
||||||
|
{this.groupId});
|
||||||
|
|
||||||
|
/// The title of the notification.
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
/// The body of the notification.
|
||||||
|
final String body;
|
||||||
|
|
||||||
|
/// The id of the channel to show the notification on.
|
||||||
|
final String channelId;
|
||||||
|
|
||||||
|
/// The id for notification grouping.
|
||||||
|
final String? groupId;
|
||||||
|
|
||||||
|
/// The id of the notification.
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
/// The icon to use.
|
||||||
|
final NotificationIcon icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationEventType {
|
||||||
|
markAsRead,
|
||||||
|
reply,
|
||||||
|
open,
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationEvent {
|
||||||
|
const NotificationEvent(
|
||||||
|
this.id,
|
||||||
|
this.jid,
|
||||||
|
this.type,
|
||||||
|
this.payload,
|
||||||
|
this.extra,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The notification id.
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
/// The JID the notification was for.
|
||||||
|
final String jid;
|
||||||
|
|
||||||
|
/// The type of event.
|
||||||
|
final NotificationEventType type;
|
||||||
|
|
||||||
|
/// An optional payload.
|
||||||
|
/// - type == NotificationType.reply: The reply message text.
|
||||||
|
/// Otherwise: undefined.
|
||||||
|
final String? payload;
|
||||||
|
|
||||||
|
/// Extra data. Only set when type == NotificationType.reply.
|
||||||
|
final Map<String?, String?>? extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationI18nData {
|
||||||
|
const NotificationI18nData(this.reply, this.markAsRead, this.you);
|
||||||
|
|
||||||
|
/// The content of the reply button.
|
||||||
|
final String reply;
|
||||||
|
|
||||||
|
/// The content of the "mark as read" button.
|
||||||
|
final String markAsRead;
|
||||||
|
|
||||||
|
/// The text to show when *you* reply.
|
||||||
|
final String you;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationGroup {
|
||||||
|
const NotificationGroup(this.id, this.description);
|
||||||
|
final String id;
|
||||||
|
final String description;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationChannelImportance { MIN, HIGH, DEFAULT }
|
||||||
|
|
||||||
|
class NotificationChannel {
|
||||||
|
const NotificationChannel(
|
||||||
|
this.id,
|
||||||
|
this.title,
|
||||||
|
this.description, {
|
||||||
|
this.importance = NotificationChannelImportance.DEFAULT,
|
||||||
|
this.showBadge = true,
|
||||||
|
this.groupId,
|
||||||
|
this.vibration = true,
|
||||||
|
this.enableLights = true,
|
||||||
|
});
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final String id;
|
||||||
|
final NotificationChannelImportance importance;
|
||||||
|
final bool showBadge;
|
||||||
|
final String? groupId;
|
||||||
|
final bool vibration;
|
||||||
|
final bool enableLights;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostApi()
|
||||||
|
abstract class MoxxyNotificationsApi {
|
||||||
|
/// Notification APIs
|
||||||
|
void createNotificationGroups(List<NotificationGroup> groups);
|
||||||
|
void deleteNotificationGroups(List<String> ids);
|
||||||
|
void createNotificationChannels(List<NotificationChannel> channels);
|
||||||
|
void deleteNotificationChannels(List<String> ids);
|
||||||
|
void showMessagingNotification(MessagingNotification notification);
|
||||||
|
void showNotification(RegularNotification notification);
|
||||||
|
void dismissNotification(int id);
|
||||||
|
void setNotificationSelfAvatar(String path);
|
||||||
|
void setNotificationI18n(NotificationI18nData data);
|
||||||
|
|
||||||
|
// Stubs for generating event classes
|
||||||
|
void notificationStub(NotificationEvent event);
|
||||||
|
}
|
@ -2,10 +2,10 @@ import 'package:pigeon/pigeon.dart';
|
|||||||
|
|
||||||
@ConfigurePigeon(
|
@ConfigurePigeon(
|
||||||
PigeonOptions(
|
PigeonOptions(
|
||||||
dartOut: 'lib/pigeon/picker.dart',
|
dartOut: 'lib/pigeon/picker.g.dart',
|
||||||
kotlinOut: 'android/src/main/kotlin/org/moxxy/moxxy_native/generated/PickerApi.kt',
|
kotlinOut: 'android/src/main/kotlin/org/moxxy/moxxy_native/picker/PickerApi.kt',
|
||||||
kotlinOptions: KotlinOptions(
|
kotlinOptions: KotlinOptions(
|
||||||
package: 'org.moxxy.moxxy_native.generated',
|
package: 'org.moxxy.moxxy_native.picker',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user