diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/MoxxyNativePlugin.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/MoxxyNativePlugin.kt index 2236926..fa66d28 100644 --- a/android/src/main/kotlin/org/moxxy/moxxy_native/MoxxyNativePlugin.kt +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/MoxxyNativePlugin.kt @@ -13,6 +13,8 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.EventChannel +import org.moxxy.moxxy_native.contacts.ContactsImplementation +import org.moxxy.moxxy_native.contacts.MoxxyContactsApi import org.moxxy.moxxy_native.cryptography.CryptographyImplementation import org.moxxy.moxxy_native.cryptography.MoxxyCryptographyApi import org.moxxy.moxxy_native.notifications.MessagingNotification @@ -60,12 +62,19 @@ class MoxxyNativePlugin : FlutterPlugin, ActivityAware, MoxxyPickerApi, MoxxyNot private lateinit var activityClass: Class private lateinit var pickerListener: PickerResultListener private val cryptographyImplementation = CryptographyImplementation() + private lateinit var contactsImplementation: ContactsImplementation override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { context = flutterPluginBinding.applicationContext + contactsImplementation = ContactsImplementation(context!!) + + // Register the pigeon handlers MoxxyPickerApi.setUp(flutterPluginBinding.binaryMessenger, this) MoxxyNotificationsApi.setUp(flutterPluginBinding.binaryMessenger, this) MoxxyCryptographyApi.setUp(flutterPluginBinding.binaryMessenger, cryptographyImplementation) + MoxxyContactsApi.setUp(flutterPluginBinding.binaryMessenger, contactsImplementation) + + // Register the picker handler pickerListener = PickerResultListener(context!!) Log.d(TAG, "Attached to engine") } diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/contacts/ContactsApi.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/contacts/ContactsApi.kt new file mode 100644 index 0000000..5236e9c --- /dev/null +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/contacts/ContactsApi.kt @@ -0,0 +1,94 @@ +// Autogenerated from Pigeon (v11.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package org.moxxy.moxxy_native.contacts + +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 { + 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) + ) + } +} + +/** + * 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() + +/** The type of icon to use when no avatar path is provided. */ +enum class FallbackIconType(val raw: Int) { + NONE(0), + PERSON(1), + NOTES(2); + + companion object { + fun ofRaw(raw: Int): FallbackIconType? { + return values().firstOrNull { it.raw == raw } + } + } +} +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface MoxxyContactsApi { + fun recordSentMessage(name: String, jid: String, avatarPath: String?, fallbackIcon: FallbackIconType) + + companion object { + /** The codec used by MoxxyContactsApi. */ + val codec: MessageCodec by lazy { + StandardMessageCodec() + } + /** Sets up an instance of `MoxxyContactsApi` to handle messages through the `binaryMessenger`. */ + @Suppress("UNCHECKED_CAST") + fun setUp(binaryMessenger: BinaryMessenger, api: MoxxyContactsApi?) { + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyContactsApi.recordSentMessage", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val nameArg = args[0] as String + val jidArg = args[1] as String + val avatarPathArg = args[2] as String? + val fallbackIconArg = FallbackIconType.ofRaw(args[3] as Int)!! + var wrapped: List + try { + api.recordSentMessage(nameArg, jidArg, avatarPathArg, fallbackIconArg) + 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/contacts/ContactsImplementation.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/contacts/ContactsImplementation.kt new file mode 100644 index 0000000..3390624 --- /dev/null +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/contacts/ContactsImplementation.kt @@ -0,0 +1,67 @@ +package org.moxxy.moxxy_native.contacts + +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import androidx.core.app.Person +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import org.moxxy.moxxy_native.R + +/* + * Implementation of Moxxy's contact APIs. + * */ +class ContactsImplementation(private val context: Context) : MoxxyContactsApi { + override fun recordSentMessage( + name: String, + jid: String, + avatarPath: String?, + fallbackIcon: FallbackIconType + ) { + val pkgName = context.packageName + val intent = Intent(context, Class.forName("$pkgName.MainActivity")).apply { + action = Intent.ACTION_SEND + + // Compatibility with share_handler + putExtra("conversationIdentifier", jid) + } + + val shortcutTarget = "$pkgName.dynamic_share_target" + val shortcutBuilder = ShortcutInfoCompat.Builder(context, jid).apply { + setShortLabel(name) + setIsConversation() + setCategories(setOf(shortcutTarget)) + setIntent(intent) + setLongLived(true) + } + + val personBuilder = Person.Builder().apply { + setKey(jid) + setName(name) + } + + // Either set an avatar image OR a fallback icon + if (avatarPath != null) { + val icon = IconCompat.createWithAdaptiveBitmap( + BitmapFactory.decodeFile(avatarPath), + ) + shortcutBuilder.setIcon(icon) + personBuilder.setIcon(icon) + } else { + val resourceId = when(fallbackIcon) { + FallbackIconType.NONE, FallbackIconType.PERSON -> R.mipmap.person + FallbackIconType.NOTES -> R.mipmap.notes + } + val icon = IconCompat.createWithResource(context, resourceId) + shortcutBuilder.setIcon(icon) + personBuilder.setIcon(icon) + } + + shortcutBuilder.setPerson(personBuilder.build()) + ShortcutManagerCompat.addDynamicShortcuts( + context, + listOf(shortcutBuilder.build()), + ) + } +} \ No newline at end of file diff --git a/android/src/main/res/mipmap-hdpi/notes.png b/android/src/main/res/mipmap-hdpi/notes.png new file mode 100644 index 0000000..0b7a673 Binary files /dev/null and b/android/src/main/res/mipmap-hdpi/notes.png differ diff --git a/android/src/main/res/mipmap-hdpi/person.png b/android/src/main/res/mipmap-hdpi/person.png new file mode 100644 index 0000000..5e52764 Binary files /dev/null and b/android/src/main/res/mipmap-hdpi/person.png differ diff --git a/android/src/main/res/mipmap-mdpi/notes.png b/android/src/main/res/mipmap-mdpi/notes.png new file mode 100644 index 0000000..820af16 Binary files /dev/null and b/android/src/main/res/mipmap-mdpi/notes.png differ diff --git a/android/src/main/res/mipmap-mdpi/person.png b/android/src/main/res/mipmap-mdpi/person.png new file mode 100644 index 0000000..24b3b06 Binary files /dev/null and b/android/src/main/res/mipmap-mdpi/person.png differ diff --git a/android/src/main/res/mipmap-xhdpi/notes.png b/android/src/main/res/mipmap-xhdpi/notes.png new file mode 100644 index 0000000..b5f0f96 Binary files /dev/null and b/android/src/main/res/mipmap-xhdpi/notes.png differ diff --git a/android/src/main/res/mipmap-xhdpi/person.png b/android/src/main/res/mipmap-xhdpi/person.png new file mode 100644 index 0000000..69068b6 Binary files /dev/null and b/android/src/main/res/mipmap-xhdpi/person.png differ diff --git a/android/src/main/res/mipmap-xxhdpi/notes.png b/android/src/main/res/mipmap-xxhdpi/notes.png new file mode 100644 index 0000000..c81ae47 Binary files /dev/null and b/android/src/main/res/mipmap-xxhdpi/notes.png differ diff --git a/android/src/main/res/mipmap-xxhdpi/person.png b/android/src/main/res/mipmap-xxhdpi/person.png new file mode 100644 index 0000000..55addb5 Binary files /dev/null and b/android/src/main/res/mipmap-xxhdpi/person.png differ diff --git a/android/src/main/res/mipmap-xxxhdpi/notes.png b/android/src/main/res/mipmap-xxxhdpi/notes.png new file mode 100644 index 0000000..2f074bb Binary files /dev/null and b/android/src/main/res/mipmap-xxxhdpi/notes.png differ diff --git a/android/src/main/res/mipmap-xxxhdpi/person.png b/android/src/main/res/mipmap-xxxhdpi/person.png new file mode 100644 index 0000000..983f36a Binary files /dev/null and b/android/src/main/res/mipmap-xxxhdpi/person.png differ diff --git a/lib/moxxy_native.dart b/lib/moxxy_native.dart index 929e431..268f872 100644 --- a/lib/moxxy_native.dart +++ b/lib/moxxy_native.dart @@ -1,3 +1,4 @@ +export 'pigeon/contacts.g.dart'; export 'pigeon/cryptography.g.dart'; export 'pigeon/notifications.g.dart'; export 'pigeon/picker.g.dart'; diff --git a/lib/pigeon/contacts.g.dart b/lib/pigeon/contacts.g.dart new file mode 100644 index 0000000..1deb5bb --- /dev/null +++ b/lib/pigeon/contacts.g.dart @@ -0,0 +1,49 @@ +// 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'; + +/// The type of icon to use when no avatar path is provided. +enum FallbackIconType { + none, + person, + notes, +} + +class MoxxyContactsApi { + /// Constructor for [MoxxyContactsApi]. 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. + MoxxyContactsApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future recordSentMessage(String arg_name, String arg_jid, String? arg_avatarPath, FallbackIconType arg_fallbackIcon) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxxy_native.MoxxyContactsApi.recordSentMessage', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_name, arg_jid, arg_avatarPath, arg_fallbackIcon.index]) 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/contacts.dart b/pigeon/contacts.dart new file mode 100644 index 0000000..0f61260 --- /dev/null +++ b/pigeon/contacts.dart @@ -0,0 +1,29 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/pigeon/contacts.g.dart', + kotlinOut: + 'android/src/main/kotlin/org/moxxy/moxxy_native/contacts/ContactsApi.kt', + kotlinOptions: KotlinOptions( + package: 'org.moxxy.moxxy_native.contacts', + ), + ), +) + +/// The type of icon to use when no avatar path is provided. +enum FallbackIconType { + none, + person, + notes; +} + +@HostApi() +abstract class MoxxyContactsApi { + void recordSentMessage( + String name, + String jid, + String? avatarPath, + FallbackIconType fallbackIcon, + ); +}