feat: Move the notification code back into moxxy_native
This commit is contained in:
parent
d8a4394f17
commit
fd91ccc46f
@ -48,4 +48,5 @@ android {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
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"
|
||||
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>
|
||||
|
@ -2,6 +2,34 @@ package org.moxxy.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
|
||||
const val PICK_FILE_REQUEST = 42
|
||||
const val PICK_FILES_REQUEST = 43
|
||||
|
@ -1,110 +1,200 @@
|
||||
package org.moxxy.moxxy_native
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.NonNull
|
||||
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import org.moxxy.moxxy_native.generated.FilePickerType
|
||||
import org.moxxy.moxxy_native.generated.MoxxyPickerApi
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
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
|
||||
|
||||
class MoxxyNativePlugin: FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
||||
private var context: Context? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var pickerListener: PickerResultListener
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
context = flutterPluginBinding.applicationContext
|
||||
MoxxyPickerApi.setUp(flutterPluginBinding.binaryMessenger, this)
|
||||
pickerListener = PickerResultListener(context!!)
|
||||
Log.d(TAG, "Attached to engine")
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
Log.d(TAG, "Detached from engine")
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
binding.addActivityResultListener(pickerListener)
|
||||
Log.d(TAG, "Attached to activity")
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {
|
||||
activity = null
|
||||
Log.d(TAG, "Detached from activity")
|
||||
}
|
||||
|
||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivity() {
|
||||
activity = null
|
||||
Log.d(TAG, "Detached from activity")
|
||||
}
|
||||
|
||||
override fun pickFiles(
|
||||
type: FilePickerType,
|
||||
multiple: Boolean,
|
||||
callback: (Result<List<String>>) -> Unit
|
||||
) {
|
||||
val requestCode = if (multiple) PICK_FILES_REQUEST else PICK_FILE_REQUEST
|
||||
AsyncRequestTracker.requestTracker[requestCode] = callback as (Result<Any>) -> Unit
|
||||
if (type == FilePickerType.GENERIC) {
|
||||
val pickIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
this.type = "*/*"
|
||||
|
||||
// Allow/disallow picking multiple files
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple)
|
||||
}
|
||||
activity?.startActivityForResult(pickIntent, requestCode)
|
||||
return
|
||||
}
|
||||
|
||||
val contract = when (multiple) {
|
||||
false -> ActivityResultContracts.PickVisualMedia()
|
||||
true -> ActivityResultContracts.PickMultipleVisualMedia()
|
||||
}
|
||||
val pickType = when (type) {
|
||||
// We keep FilePickerType.GENERIC here, even though we know that @type will never be
|
||||
// GENERIC to make Kotlin happy.
|
||||
FilePickerType.GENERIC, FilePickerType.IMAGE -> ActivityResultContracts.PickVisualMedia.ImageOnly
|
||||
FilePickerType.VIDEO -> ActivityResultContracts.PickVisualMedia.VideoOnly
|
||||
FilePickerType.IMAGEANDVIDEO -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
|
||||
}
|
||||
val pickIntent = contract.createIntent(context!!, PickVisualMediaRequest(pickType))
|
||||
activity?.startActivityForResult(pickIntent, requestCode)
|
||||
}
|
||||
|
||||
override fun pickFileWithData(type: FilePickerType, callback: (Result<ByteArray?>) -> Unit) {
|
||||
AsyncRequestTracker.requestTracker[PICK_FILE_WITH_DATA_REQUEST] = callback as (Result<Any>) -> Unit
|
||||
if (type == FilePickerType.GENERIC) {
|
||||
val pickIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
this.type = "*/*"
|
||||
}
|
||||
activity?.startActivityForResult(pickIntent, PICK_FILE_WITH_DATA_REQUEST)
|
||||
return
|
||||
}
|
||||
|
||||
val pickType = when (type) {
|
||||
// We keep FilePickerType.GENERIC here, even though we know that @type will never be
|
||||
// GENERIC to make Kotlin happy.
|
||||
FilePickerType.GENERIC, FilePickerType.IMAGE -> ActivityResultContracts.PickVisualMedia.ImageOnly
|
||||
FilePickerType.VIDEO -> ActivityResultContracts.PickVisualMedia.VideoOnly
|
||||
FilePickerType.IMAGEANDVIDEO -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
|
||||
}
|
||||
val contract = ActivityResultContracts.PickVisualMedia()
|
||||
val pickIntent = contract.createIntent(context!!, PickVisualMediaRequest(pickType))
|
||||
activity?.startActivityForResult(pickIntent, PICK_FILE_WITH_DATA_REQUEST)
|
||||
}
|
||||
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 activity: Activity? = null
|
||||
private lateinit var activityClass: Class<Any>
|
||||
private lateinit var pickerListener: PickerResultListener
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
context = flutterPluginBinding.applicationContext
|
||||
MoxxyPickerApi.setUp(flutterPluginBinding.binaryMessenger, this)
|
||||
MoxxyNotificationsApi.setUp(flutterPluginBinding.binaryMessenger, this)
|
||||
pickerListener = PickerResultListener(context!!)
|
||||
Log.d(TAG, "Attached to engine")
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
Log.d(TAG, "Detached from engine")
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
activityClass = activity!!.javaClass
|
||||
binding.addActivityResultListener(pickerListener)
|
||||
Log.d(TAG, "Attached to activity")
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {
|
||||
activity = null
|
||||
Log.d(TAG, "Detached from activity")
|
||||
}
|
||||
|
||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivity() {
|
||||
activity = null
|
||||
Log.d(TAG, "Detached from activity")
|
||||
}
|
||||
|
||||
override fun pickFiles(
|
||||
type: FilePickerType,
|
||||
multiple: Boolean,
|
||||
callback: (Result<List<String>>) -> Unit,
|
||||
) {
|
||||
val requestCode = if (multiple) PICK_FILES_REQUEST else PICK_FILE_REQUEST
|
||||
AsyncRequestTracker.requestTracker[requestCode] = callback as (Result<Any>) -> Unit
|
||||
if (type == FilePickerType.GENERIC) {
|
||||
val pickIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
this.type = "*/*"
|
||||
|
||||
// Allow/disallow picking multiple files
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple)
|
||||
}
|
||||
activity?.startActivityForResult(pickIntent, requestCode)
|
||||
return
|
||||
}
|
||||
|
||||
val contract = when (multiple) {
|
||||
false -> ActivityResultContracts.PickVisualMedia()
|
||||
true -> ActivityResultContracts.PickMultipleVisualMedia()
|
||||
}
|
||||
val pickType = when (type) {
|
||||
// We keep FilePickerType.GENERIC here, even though we know that @type will never be
|
||||
// GENERIC to make Kotlin happy.
|
||||
FilePickerType.GENERIC, FilePickerType.IMAGE -> ActivityResultContracts.PickVisualMedia.ImageOnly
|
||||
FilePickerType.VIDEO -> ActivityResultContracts.PickVisualMedia.VideoOnly
|
||||
FilePickerType.IMAGEANDVIDEO -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
|
||||
}
|
||||
val pickIntent = contract.createIntent(context!!, PickVisualMediaRequest(pickType))
|
||||
activity?.startActivityForResult(pickIntent, requestCode)
|
||||
}
|
||||
|
||||
override fun pickFileWithData(type: FilePickerType, callback: (Result<ByteArray?>) -> Unit) {
|
||||
AsyncRequestTracker.requestTracker[PICK_FILE_WITH_DATA_REQUEST] = callback as (Result<Any>) -> Unit
|
||||
if (type == FilePickerType.GENERIC) {
|
||||
val pickIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
this.type = "*/*"
|
||||
}
|
||||
activity?.startActivityForResult(pickIntent, PICK_FILE_WITH_DATA_REQUEST)
|
||||
return
|
||||
}
|
||||
|
||||
val pickType = when (type) {
|
||||
// We keep FilePickerType.GENERIC here, even though we know that @type will never be
|
||||
// GENERIC to make Kotlin happy.
|
||||
FilePickerType.GENERIC, FilePickerType.IMAGE -> ActivityResultContracts.PickVisualMedia.ImageOnly
|
||||
FilePickerType.VIDEO -> ActivityResultContracts.PickVisualMedia.VideoOnly
|
||||
FilePickerType.IMAGEANDVIDEO -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
|
||||
}
|
||||
val contract = ActivityResultContracts.PickVisualMedia()
|
||||
val pickIntent = contract.createIntent(context!!, PickVisualMediaRequest(pickType))
|
||||
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
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import io.flutter.embedding.android.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:moxxy_native/moxxy_native.dart';
|
||||
|
||||
@ -21,7 +19,8 @@ class MyApp extends StatelessWidget {
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final result = await MoxxyPickerApi().pickFiles(FilePickerType.image, false);
|
||||
final result = await MoxxyPickerApi()
|
||||
.pickFiles(FilePickerType.image, false);
|
||||
// ignore: avoid_print
|
||||
print('User picked: $result');
|
||||
},
|
||||
@ -29,7 +28,8 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final result = await MoxxyPickerApi().pickFiles(FilePickerType.imageAndVideo, true);
|
||||
final result = await MoxxyPickerApi()
|
||||
.pickFiles(FilePickerType.imageAndVideo, true);
|
||||
// ignore: avoid_print
|
||||
print('User picked: $result');
|
||||
},
|
||||
@ -37,7 +37,8 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final result = await MoxxyPickerApi().pickFiles(FilePickerType.generic, true);
|
||||
final result = await MoxxyPickerApi()
|
||||
.pickFiles(FilePickerType.generic, true);
|
||||
// ignore: avoid_print
|
||||
print('User picked: $result');
|
||||
},
|
||||
|
@ -130,6 +130,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -193,4 +241,4 @@ packages:
|
||||
version: "2.1.4"
|
||||
sdks:
|
||||
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 {
|
||||
/// Pick only image(s)
|
||||
image,
|
||||
|
||||
/// Pick only video(s)
|
||||
video,
|
||||
|
||||
/// Pick image(s) and video(s)
|
||||
imageAndVideo,
|
||||
|
||||
/// Pick any kind of file(s)
|
||||
generic,
|
||||
}
|
||||
@ -37,12 +40,13 @@ class MoxxyPickerApi {
|
||||
///
|
||||
/// [multiple] controls whether multiple files can be picked (true) or just a single file
|
||||
/// 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?>(
|
||||
'dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFiles', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(<Object?>[arg_type.index, arg_multiple]) as List<Object?>?;
|
||||
final List<Object?>? replyList = await channel
|
||||
.send(<Object?>[arg_type.index, arg_multiple]) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
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.
|
||||
Future<Uint8List?> pickFileWithData(FilePickerType arg_type) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFileWithData', codec,
|
||||
'dev.flutter.pigeon.moxxy_native.MoxxyPickerApi.pickFileWithData',
|
||||
codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
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(
|
||||
PigeonOptions(
|
||||
dartOut: 'lib/pigeon/picker.dart',
|
||||
kotlinOut: 'android/src/main/kotlin/org/moxxy/moxxy_native/generated/PickerApi.kt',
|
||||
dartOut: 'lib/pigeon/picker.g.dart',
|
||||
kotlinOut: 'android/src/main/kotlin/org/moxxy/moxxy_native/picker/PickerApi.kt',
|
||||
kotlinOptions: KotlinOptions(
|
||||
package: 'org.moxxy.moxxy_native.generated',
|
||||
package: 'org.moxxy.moxxy_native.picker',
|
||||
),
|
||||
),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user