From 44187675c7ed9f612b2352ad5fb979a5da2a11f7 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Mon, 18 Sep 2023 17:58:16 +0200 Subject: [PATCH] feat(android): Implement sharing internal files and text --- android/src/main/AndroidManifest.xml | 2 +- .../org/moxxy/moxxy_native/Constants.kt | 2 +- .../moxxy_native/content/MoxxyFileProvider.kt | 20 +- .../notifications/NotificationsApi.kt | 1135 +++++++++-------- .../NotificationsImplementation.kt | 10 +- .../moxxy_native/platform/PlatformApi.kt | 73 +- .../platform/PlatformImplementation.kt | 26 + example/android/build.gradle | 2 +- example/lib/main.dart | 125 +- example/pubspec.lock | 38 +- example/pubspec.yaml | 1 + flake.nix | 2 +- lib/pigeon/platform.g.dart | 79 +- pigeon/platform.dart | 9 + 14 files changed, 891 insertions(+), 633 deletions(-) diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index ee1ef55..99186e9 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -4,7 +4,7 @@ { - return listOf(result) + return listOf(result) } private fun wrapError(exception: Throwable): List { - 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) - ) - } + 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), + ) + } } /** @@ -37,630 +37,635 @@ private fun wrapError(exception: Throwable): List { * @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 +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); + WARNING(0), + ERROR(1), + NONE(2), + ; - companion object { - fun ofRaw(raw: Int): NotificationIcon? { - return values().firstOrNull { it.raw == raw } + 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); + MARKASREAD(0), + REPLY(1), + OPEN(2), + ; - companion object { - fun ofRaw(raw: Int): NotificationEventType? { - return values().firstOrNull { it.raw == raw } + 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); + MIN(0), + HIGH(1), + DEFAULT(2), + ; - companion object { - fun ofRaw(raw: Int): NotificationChannelImportance? { - return values().firstOrNull { it.raw == raw } + 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 +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): NotificationMessageContent { - val body = list[0] as String? - val mime = list[1] as String? - val path = list[2] as String? - return NotificationMessageContent(body, mime, path) + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): 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 { + return listOf( + body, + mime, + path, + ) } - } - fun toList(): List { - return listOf( - 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 +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): 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) - 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) + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): 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) + 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 { + return listOf( + groupId, + sender, + jid, + content.toList(), + timestamp, + avatarPath, + ) } - } - fun toList(): List { - return listOf( - 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, - /** 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? = null +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, + /** 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? = null, ) { - companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): 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 - val isGroupchat = list[5] as Boolean - val groupId = list[6] as String? - val extra = list[7] as Map? - return MessagingNotification(title, id, channelId, jid, messages, isGroupchat, groupId, extra) + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): 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 + val isGroupchat = list[5] as Boolean + val groupId = list[6] as String? + val extra = list[7] as Map? + return MessagingNotification(title, id, channelId, jid, messages, isGroupchat, groupId, extra) + } + } + fun toList(): List { + return listOf( + title, + id, + channelId, + jid, + messages, + isGroupchat, + groupId, + extra, + ) } - } - fun toList(): List { - return listOf( - 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 +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): 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) + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): 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 { + return listOf( + title, + body, + channelId, + groupId, + id, + icon.raw, + ) } - } - fun toList(): List { - return listOf( - 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? = null +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? = null, ) { - companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): 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? - return NotificationEvent(id, jid, type, payload, extra) + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): 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? + return NotificationEvent(id, jid, type, payload, extra) + } + } + fun toList(): List { + return listOf( + id, + jid, + type.raw, + payload, + extra, + ) } - } - fun toList(): List { - return listOf( - 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 +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): NotificationI18nData { - val reply = list[0] as String - val markAsRead = list[1] as String - val you = list[2] as String - return NotificationI18nData(reply, markAsRead, you) + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): 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 { + return listOf( + reply, + markAsRead, + you, + ) } - } - fun toList(): List { - return listOf( - reply, - markAsRead, - you, - ) - } } /** Generated class from Pigeon that represents data sent in messages. */ -data class NotificationGroup ( - val id: String, - val description: String +data class NotificationGroup( + val id: String, + val description: String, ) { - companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): NotificationGroup { - val id = list[0] as String - val description = list[1] as String - return NotificationGroup(id, description) + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): NotificationGroup { + val id = list[0] as String + val description = list[1] as String + return NotificationGroup(id, description) + } + } + fun toList(): List { + return listOf( + id, + description, + ) } - } - fun toList(): List { - return listOf( - 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 +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): 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) + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): 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 { + return listOf( + title, + description, + id, + importance.raw, + showBadge, + groupId, + vibration, + enableLights, + ) } - } - fun toList(): List { - return listOf( - 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)?.let { - MessagingNotification.fromList(it) + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 128.toByte() -> { + return (readValue(buffer) as? List)?.let { + MessagingNotification.fromList(it) + } + } + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { + NotificationChannel.fromList(it) + } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { + NotificationEvent.fromList(it) + } + } + 131.toByte() -> { + return (readValue(buffer) as? List)?.let { + NotificationGroup.fromList(it) + } + } + 132.toByte() -> { + return (readValue(buffer) as? List)?.let { + NotificationI18nData.fromList(it) + } + } + 133.toByte() -> { + return (readValue(buffer) as? List)?.let { + NotificationMessage.fromList(it) + } + } + 134.toByte() -> { + return (readValue(buffer) as? List)?.let { + NotificationMessageContent.fromList(it) + } + } + 135.toByte() -> { + return (readValue(buffer) as? List)?.let { + RegularNotification.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) } - } - 129.toByte() -> { - return (readValue(buffer) as? List)?.let { - NotificationChannel.fromList(it) - } - } - 130.toByte() -> { - return (readValue(buffer) as? List)?.let { - NotificationEvent.fromList(it) - } - } - 131.toByte() -> { - return (readValue(buffer) as? List)?.let { - NotificationGroup.fromList(it) - } - } - 132.toByte() -> { - return (readValue(buffer) as? List)?.let { - NotificationI18nData.fromList(it) - } - } - 133.toByte() -> { - return (readValue(buffer) as? List)?.let { - NotificationMessage.fromList(it) - } - } - 134.toByte() -> { - return (readValue(buffer) as? List)?.let { - NotificationMessageContent.fromList(it) - } - } - 135.toByte() -> { - return (readValue(buffer) as? List)?.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) + 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) - fun deleteNotificationGroups(ids: List) - fun createNotificationChannels(channels: List) - fun deleteNotificationChannels(ids: List) - fun showMessagingNotification(notification: MessagingNotification) - fun showNotification(notification: RegularNotification) - fun dismissNotification(id: Long) - fun setNotificationSelfAvatar(path: String) - fun setNotificationI18n(data: NotificationI18nData) - fun notificationStub(event: NotificationEvent) + /** Notification APIs */ + fun createNotificationGroups(groups: List) + fun deleteNotificationGroups(ids: List) + fun createNotificationChannels(channels: List) + fun deleteNotificationChannels(ids: List) + 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 by lazy { - MoxxyNotificationsApiCodec + companion object { + /** The codec used by MoxxyNotificationsApi. */ + val codec: MessageCodec 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(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.createNotificationGroups", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val groupsArg = args[0] as List + var wrapped: List + try { + api.createNotificationGroups(groupsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.deleteNotificationGroups", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val idsArg = args[0] as List + var wrapped: List + try { + api.deleteNotificationGroups(idsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.createNotificationChannels", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val channelsArg = args[0] as List + var wrapped: List + try { + api.createNotificationChannels(channelsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.deleteNotificationChannels", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val idsArg = args[0] as List + var wrapped: List + try { + api.deleteNotificationChannels(idsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.showMessagingNotification", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val notificationArg = args[0] as MessagingNotification + var wrapped: List + try { + api.showMessagingNotification(notificationArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.showNotification", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val notificationArg = args[0] as RegularNotification + var wrapped: List + try { + api.showNotification(notificationArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.dismissNotification", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val idArg = args[0].let { if (it is Int) it.toLong() else it as Long } + var wrapped: List + try { + api.dismissNotification(idArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.setNotificationSelfAvatar", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pathArg = args[0] as String + var wrapped: List + try { + api.setNotificationSelfAvatar(pathArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.setNotificationI18n", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val dataArg = args[0] as NotificationI18nData + var wrapped: List + try { + api.setNotificationI18n(dataArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.notificationStub", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val eventArg = args[0] as NotificationEvent + var wrapped: List + try { + api.notificationStub(eventArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } } - /** 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(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.createNotificationGroups", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val groupsArg = args[0] as List - var wrapped: List - try { - api.createNotificationGroups(groupsArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.deleteNotificationGroups", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val idsArg = args[0] as List - var wrapped: List - try { - api.deleteNotificationGroups(idsArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.createNotificationChannels", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val channelsArg = args[0] as List - var wrapped: List - try { - api.createNotificationChannels(channelsArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.deleteNotificationChannels", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val idsArg = args[0] as List - var wrapped: List - try { - api.deleteNotificationChannels(idsArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.showMessagingNotification", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val notificationArg = args[0] as MessagingNotification - var wrapped: List - try { - api.showMessagingNotification(notificationArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.showNotification", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val notificationArg = args[0] as RegularNotification - var wrapped: List - try { - api.showNotification(notificationArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.dismissNotification", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val idArg = args[0].let { if (it is Int) it.toLong() else it as Long } - var wrapped: List - try { - api.dismissNotification(idArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.setNotificationSelfAvatar", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val pathArg = args[0] as String - var wrapped: List - try { - api.setNotificationSelfAvatar(pathArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.setNotificationI18n", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val dataArg = args[0] as NotificationI18nData - var wrapped: List - try { - api.setNotificationI18n(dataArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyNotificationsApi.notificationStub", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val eventArg = args[0] as NotificationEvent - var wrapped: List - try { - api.notificationStub(eventArg) - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - } - } } diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationsImplementation.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationsImplementation.kt index 3a534bb..21f8274 100644 --- a/android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationsImplementation.kt +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationsImplementation.kt @@ -14,10 +14,8 @@ 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 @@ -27,7 +25,7 @@ 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 org.moxxy.moxxy_native.content.MoxxyFileProvider class NotificationsImplementation(private val context: Context) : MoxxyNotificationsApi { override fun createNotificationGroups(groups: List) { @@ -207,11 +205,7 @@ class NotificationsImplementation(private val context: Context) : MoxxyNotificat ) // 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), - ) + val fileUri = MoxxyFileProvider.getUriForPath(context, message.content.path) msg.apply { setData(message.content.mime, fileUri) diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/platform/PlatformApi.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/platform/PlatformApi.kt index 8f74a73..1f257fe 100644 --- a/android/src/main/kotlin/org/moxxy/moxxy_native/platform/PlatformApi.kt +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/platform/PlatformApi.kt @@ -8,6 +8,8 @@ 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 { return listOf(result) @@ -41,17 +43,66 @@ class FlutterError( val details: Any? = null, ) : Throwable() +/** Generated class from Pigeon that represents data sent in messages. */ +data class ShareItem( + val path: String? = null, + val mime: String, + val text: String? = null, + +) { + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): ShareItem { + val path = list[0] as String? + val mime = list[1] as String + val text = list[2] as String? + return ShareItem(path, mime, text) + } + } + fun toList(): List { + return listOf( + path, + mime, + text, + ) + } +} + +@Suppress("UNCHECKED_CAST") +private object MoxxyPlatformApiCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 128.toByte() -> { + return (readValue(buffer) as? List)?.let { + ShareItem.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is ShareItem -> { + stream.write(128) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface MoxxyPlatformApi { fun getPersistentDataPath(): String fun getCacheDataPath(): String fun openBatteryOptimisationSettings() fun isIgnoringBatteryOptimizations(): Boolean + fun shareItems(items: List, genericMimeType: String) companion object { /** The codec used by MoxxyPlatformApi. */ val codec: MessageCodec by lazy { - StandardMessageCodec() + MoxxyPlatformApiCodec } /** Sets up an instance of `MoxxyPlatformApi` to handle messages through the `binaryMessenger`. */ @@ -122,6 +173,26 @@ interface MoxxyPlatformApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyPlatformApi.shareItems", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val itemsArg = args[0] as List + val genericMimeTypeArg = args[1] as String + var wrapped: List + try { + api.shareItems(itemsArg, genericMimeTypeArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/platform/PlatformImplementation.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/platform/PlatformImplementation.kt index 66d27df..8b39c21 100644 --- a/android/src/main/kotlin/org/moxxy/moxxy_native/platform/PlatformImplementation.kt +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/platform/PlatformImplementation.kt @@ -5,6 +5,8 @@ import android.content.Intent import android.net.Uri import android.os.PowerManager import android.provider.Settings +import androidx.core.app.ShareCompat +import org.moxxy.moxxy_native.content.MoxxyFileProvider class PlatformImplementation(private val context: Context) : MoxxyPlatformApi { override fun getPersistentDataPath(): String { @@ -27,4 +29,28 @@ class PlatformImplementation(private val context: Context) : MoxxyPlatformApi { val pm = context.getSystemService(PowerManager::class.java) return pm.isIgnoringBatteryOptimizations(context.packageName) } + + override fun shareItems(items: List, genericMimeType: String) { + // Empty lists make no sense + assert(items.isNotEmpty()) + + // Convert the paths to content URIs + val builder = ShareCompat.IntentBuilder(context).setType(genericMimeType) + for (item in items) { + assert(item.text == null && item.path != null || item.text != null && item.path == null) + + if (item.text != null) { + builder.setText(item.text) + } else if (item.path != null) { + builder.addStream(MoxxyFileProvider.getUriForPath(context, item.path)) + } + } + + // We cannot just use startChooser() because then Android complains that we're not attached + // to an Activity. So, we just ask it to start a new one. + val intent = builder.createChooserIntent().apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + } } diff --git a/example/android/build.gradle b/example/android/build.gradle index 58a8c74..713d7f6 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/lib/main.dart b/example/lib/main.dart index ff841d1..b96455e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get_it/get_it.dart'; import 'package:moxxy_native/moxxy_native.dart'; +import 'package:path/path.dart' as p; import 'package:permission_handler/permission_handler.dart'; @pragma('vm:entrypoint') @@ -125,45 +126,101 @@ class MyAppState extends State { ), if (imagePath != null) Image.file(File(imagePath!)), TextButton( - onPressed: () async { - // Create channel - if (Platform.isAndroid) { - await MoxxyNotificationsApi().createNotificationChannels( - [ - NotificationChannel( - id: 'foreground_service', - title: 'Foreground service', - description: 'lol', - importance: NotificationChannelImportance.MIN, - showBadge: false, - vibration: false, - enableLights: false, - ), - ], - ); + onPressed: () async { + // Create channel + if (Platform.isAndroid) { + await MoxxyNotificationsApi().createNotificationChannels( + [ + NotificationChannel( + id: 'foreground_service', + title: 'Foreground service', + description: 'lol', + importance: NotificationChannelImportance.MIN, + showBadge: false, + vibration: false, + enableLights: false, + ), + ], + ); - await Permission.notification.request(); - } + await Permission.notification.request(); + } - final srv = getForegroundService(); - await srv.start( - const ServiceConfig( - serviceEntrypoint, - serviceHandleData, - 'en', + final srv = getForegroundService(); + await srv.start( + const ServiceConfig( + serviceEntrypoint, + serviceHandleData, + 'en', + ), + (data) async { + print('[FG] Received data $data'); + }, + ); + + await Future.delayed(const Duration(milliseconds: 600)); + await getForegroundService().send( + TestCommand(), + awaitable: false, + ); + }, + child: const Text('Start foreground service'), + ), + TextButton( + onPressed: () async { + // Pick a file and copy it into the internal storage directory + final mediaDir = Directory( + p.join( + await MoxxyPlatformApi().getPersistentDataPath(), + 'media', + ), + ); + if (!mediaDir.existsSync()) { + await mediaDir.create(recursive: true); + } + final pickResult = await MoxxyPickerApi() + .pickFiles(FilePickerType.image, true); + if (pickResult.isEmpty) return; + + final shareItems = List.empty(growable: true); + for (final result in pickResult) { + final mediaDirPath = p.join( + mediaDir.path, + p.basename(result!), + ); + await File(result).copy(mediaDirPath); + + shareItems.add( + ShareItem( + path: mediaDirPath, + mime: 'image/jpeg', ), - (data) async { - print('[FG] Received data $data'); - }, ); + } - await Future.delayed(const Duration(milliseconds: 600)); - await getForegroundService().send( - TestCommand(), - awaitable: false, - ); - }, - child: const Text('Start foreground service')), + // Share with the system + await MoxxyPlatformApi().shareItems( + shareItems, + 'image/*', + ); + }, + child: const Text('Share internal files'), + ), + TextButton( + onPressed: () async { + // Share with the system + await MoxxyPlatformApi().shareItems( + [ + ShareItem( + mime: 'text/plain', + text: 'Hello World!', + ), + ], + 'text/*', + ); + }, + child: const Text('Share some text'), + ), ], ), ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 65b07c2..4177027 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" crypto: dependency: transitive description: @@ -95,10 +95,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: @@ -119,10 +119,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -135,10 +135,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" moxlib: dependency: transitive description: @@ -153,15 +153,15 @@ packages: path: ".." relative: true source: path - version: "0.1.0" + version: "0.2.0" path: - dependency: transitive + dependency: "direct main" description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" permission_handler: dependency: "direct main" description: @@ -267,10 +267,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" typed_data: dependency: transitive description: @@ -296,5 +296,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.6 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=2.8.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f19c40f..684b809 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: permission_handler: ^10.4.5 get_it: ^7.6.0 logging: ^1.2.0 + path: ^1.8.3 dev_dependencies: flutter_test: diff --git a/flake.nix b/flake.nix index 46f5370..ea19a2f 100644 --- a/flake.nix +++ b/flake.nix @@ -44,7 +44,7 @@ ]); lib = pkgs.lib; pinnedJDK = pkgs.jdk17; - flutterVersion = pkgs.flutter37; + flutterVersion = pkgs.flutter; in { devShell = pkgs.mkShell { buildInputs = with pkgs; [ diff --git a/lib/pigeon/platform.g.dart b/lib/pigeon/platform.g.dart index a40b056..1dfbec9 100644 --- a/lib/pigeon/platform.g.dart +++ b/lib/pigeon/platform.g.dart @@ -8,6 +8,60 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +class ShareItem { + ShareItem({ + this.path, + required this.mime, + this.text, + }); + + String? path; + + String mime; + + String? text; + + Object encode() { + return [ + path, + mime, + text, + ]; + } + + static ShareItem decode(Object result) { + result as List; + return ShareItem( + path: result[0] as String?, + mime: result[1]! as String, + text: result[2] as String?, + ); + } +} + +class _MoxxyPlatformApiCodec extends StandardMessageCodec { + const _MoxxyPlatformApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is ShareItem) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return ShareItem.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + class MoxxyPlatformApi { /// Constructor for [MoxxyPlatformApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -16,7 +70,7 @@ class MoxxyPlatformApi { : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = StandardMessageCodec(); + static const MessageCodec codec = _MoxxyPlatformApiCodec(); Future getPersistentDataPath() async { final BasicMessageChannel channel = BasicMessageChannel( @@ -120,4 +174,27 @@ class MoxxyPlatformApi { return (replyList[0] as bool?)!; } } + + Future shareItems( + List arg_items, String arg_genericMimeType) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxxy_native.MoxxyPlatformApi.shareItems', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_items, arg_genericMimeType]) as List?; + 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; + } + } } diff --git a/pigeon/platform.dart b/pigeon/platform.dart index ed93216..335535d 100644 --- a/pigeon/platform.dart +++ b/pigeon/platform.dart @@ -10,6 +10,13 @@ import 'package:pigeon/pigeon.dart'; ), ), ) +class ShareItem { + const ShareItem(this.path, this.mime, this.text); + final String? path; + final String mime; + final String? text; +} + @HostApi() abstract class MoxxyPlatformApi { String getPersistentDataPath(); @@ -19,4 +26,6 @@ abstract class MoxxyPlatformApi { void openBatteryOptimisationSettings(); bool isIgnoringBatteryOptimizations(); + + void shareItems(List items, String genericMimeType); }