chore(android,base,interface): Move notification stuff into Moxxy

This commit is contained in:
PapaTutuWawa 2023-09-03 13:03:51 +02:00
parent 7cc2d0e4be
commit f2b140de18
16 changed files with 38 additions and 3214 deletions

View File

@ -4,7 +4,5 @@ class MoxplatformPlugin {
static IsolateHandler get handler => MoxplatformInterface.handler; static IsolateHandler get handler => MoxplatformInterface.handler;
static CryptographyImplementation get crypto => MoxplatformInterface.crypto; static CryptographyImplementation get crypto => MoxplatformInterface.crypto;
static ContactsImplementation get contacts => MoxplatformInterface.contacts; static ContactsImplementation get contacts => MoxplatformInterface.contacts;
static NotificationsImplementation get notifications =>
MoxplatformInterface.notifications;
static PlatformImplementation get platform => MoxplatformInterface.platform; static PlatformImplementation get platform => MoxplatformInterface.platform;
} }

View File

@ -8,16 +8,6 @@
<application> <application>
<provider
android:name="me.polynom.moxplatform_android.MoxplatformFileProvider"
android:authorities="me.polynom.moxplatform_android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<service <service
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
@ -38,7 +28,5 @@
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".NotificationReceiver" />
</application> </application>
</manifest> </manifest>

View File

@ -1,6 +0,0 @@
package me.polynom.moxplatform_android
import androidx.core.content.FileProvider
class MoxplatformFileProvider : FileProvider(R.xml.file_paths) {
}

View File

@ -6,13 +6,12 @@ import static androidx.core.content.ContextCompat.startActivity;
import static me.polynom.moxplatform_android.ConstantsKt.MOXPLATFORM_FILEPROVIDER_ID; import static me.polynom.moxplatform_android.ConstantsKt.MOXPLATFORM_FILEPROVIDER_ID;
import static me.polynom.moxplatform_android.ConstantsKt.SHARED_PREFERENCES_KEY; import static me.polynom.moxplatform_android.ConstantsKt.SHARED_PREFERENCES_KEY;
import static me.polynom.moxplatform_android.CryptoKt.*; import static me.polynom.moxplatform_android.CryptoKt.*;
import static me.polynom.moxplatform_android.NotificationsKt.createNotificationChannelsImpl;
import static me.polynom.moxplatform_android.NotificationsKt.createNotificationGroupsImpl;
import static me.polynom.moxplatform_android.RecordSentMessageKt.*; import static me.polynom.moxplatform_android.RecordSentMessageKt.*;
import static me.polynom.moxplatform_android.ThumbnailsKt.generateVideoThumbnailImplementation; import static me.polynom.moxplatform_android.ThumbnailsKt.generateVideoThumbnailImplementation;
import me.polynom.moxplatform_android.Api.*; import me.polynom.moxplatform_android.Api.*;
import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -60,7 +59,7 @@ import io.flutter.plugin.common.JSONMethodCodec;
import kotlin.Unit; import kotlin.Unit;
import kotlin.jvm.functions.Function1; import kotlin.jvm.functions.Function1;
public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ServiceAware, MoxplatformApi { public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware, MoxplatformApi {
public static final String entrypointKey = "entrypoint_handle"; public static final String entrypointKey = "entrypoint_handle";
public static final String extraDataKey = "extra_data"; public static final String extraDataKey = "extra_data";
private static final String autoStartAtBootKey = "auto_start_at_boot"; private static final String autoStartAtBootKey = "auto_start_at_boot";
@ -71,8 +70,8 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
private static final List<MoxplatformAndroidPlugin> _instances = new ArrayList<>(); private static final List<MoxplatformAndroidPlugin> _instances = new ArrayList<>();
private BackgroundService service; private BackgroundService service;
private MethodChannel channel; private MethodChannel channel;
private static EventChannel notificationChannel;
public static EventSink notificationSink; public static Activity activity;
private Context context; private Context context;
@ -86,10 +85,6 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
channel.setMethodCallHandler(this); channel.setMethodCallHandler(this);
context = flutterPluginBinding.getApplicationContext(); context = flutterPluginBinding.getApplicationContext();
notificationChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "me.polynom/notification_stream");
notificationChannel.setStreamHandler(this);
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey)); localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey));
@ -102,6 +97,7 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context()); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context());
final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin(); final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin();
localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey)); localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey));
activity = registrar.activity();
final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE); final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE);
channel.setMethodCallHandler(plugin); channel.setMethodCallHandler(plugin);
@ -110,18 +106,6 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
Log.d(TAG, "Registered against registrar"); Log.d(TAG, "Registered against registrar");
} }
@Override
public void onCancel(Object arguments) {
Log.d(TAG, "Removed listener");
notificationSink = null;
}
@Override
public void onListen(Object arguments, EventChannel.EventSink eventSink) {
Log.d(TAG, "Attached listener");
notificationSink = eventSink;
}
/// Store the entrypoint handle and extra data for the background service. /// Store the entrypoint handle and extra data for the background service.
private void configure(long entrypointHandle, String extraData) { private void configure(long entrypointHandle, String extraData) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
@ -226,60 +210,6 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
this.service = null; this.service = null;
} }
@Override
public void createNotificationGroups(@NonNull List<NotificationGroup> groups) {
createNotificationGroupsImpl(context, groups);
}
@Override
public void deleteNotificationGroups(@NonNull List<String> ids) {
final NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
for (final String id : ids) {
notificationManager.deleteNotificationChannelGroup(id);
}
}
@Override
public void createNotificationChannels(@NonNull List<Api.NotificationChannel> channels) {
createNotificationChannelsImpl(context, channels);
}
@Override
public void deleteNotificationChannels(@NonNull List<String> ids) {
final NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
for (final String id : ids) {
notificationManager.deleteNotificationChannel(id);
}
}
@Override
public void showMessagingNotification(@NonNull MessagingNotification notification) {
NotificationsKt.showMessagingNotification(context, notification);
}
@Override
public void showNotification(@NonNull RegularNotification notification) {
NotificationsKt.showNotification(context, notification);
}
@Override
public void dismissNotification(@NonNull Long id) {
NotificationManagerCompat.from(context).cancel(id.intValue());
}
@Override
public void setNotificationSelfAvatar(@NonNull String path) {
NotificationDataManager.INSTANCE.setAvatarPath(context, path);
}
@Override
public void setNotificationI18n(@NonNull NotificationI18nData data) {
// Configure i18n
NotificationDataManager.INSTANCE.setYou(context, data.getYou());
NotificationDataManager.INSTANCE.setReply(context, data.getReply());
NotificationDataManager.INSTANCE.setMarkAsRead(context, data.getMarkAsRead());
}
@NonNull @NonNull
@Override @Override
public String getPersistentDataPath() { public String getPersistentDataPath() {
@ -349,9 +279,4 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
public Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth) { public Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth) {
return generateVideoThumbnailImplementation(src, dest, maxWidth); return generateVideoThumbnailImplementation(src, dest, maxWidth);
} }
@Override
public void eventStub(@NonNull NotificationEvent event) {
// Stub to trick pigeon into
}
} }

View File

@ -1,197 +0,0 @@
package me.polynom.moxplatform_android
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
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.Person
import androidx.core.app.RemoteInput
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.IconCompat
import me.polynom.moxplatform_android.Api.NotificationEvent
import java.io.File
import java.time.Instant
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 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
}
private fun handleMarkAsRead(context: Context, intent: Intent) {
MoxplatformAndroidPlugin.notificationSink?.success(
NotificationEvent().apply {
id = intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1)
jid = intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!
type = Api.NotificationEventType.MARK_AS_READ
payload = null
extra = extractPayloadMapFromIntent(intent)
}.toList()
)
NotificationManagerCompat.from(context).cancel(intent.getLongExtra(MARK_AS_READ_ID_KEY, -1).toInt())
dismissNotification(context, intent);
}
private fun handleReply(context: Context, intent: Intent) {
val remoteInput = RemoteInput.getResultsFromIntent(intent) ?: return
val replyPayload = remoteInput.getCharSequence(REPLY_TEXT_KEY)
MoxplatformAndroidPlugin.notificationSink?.success(
NotificationEvent().apply {
id = intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1)
jid = intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!
type = Api.NotificationEventType.REPLY
payload = replyPayload.toString()
extra = 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,
MOXPLATFORM_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)
}
NotificationManagerCompat.from(context).notify(id, recoveredBuilder.build())
}
private fun handleTap(context: Context, intent: Intent) {
MoxplatformAndroidPlugin.notificationSink?.success(
NotificationEvent().apply {
id = intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1)
jid = intent.getStringExtra(NOTIFICATION_EXTRA_JID_KEY)!!
type = Api.NotificationEventType.OPEN
payload = null
extra = extractPayloadMapFromIntent(intent)
}.toList()
)
// Bring the app into the foreground
val tapIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)!!
context.startActivity(tapIntent)
// Dismiss the 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)
}
}
}

View File

@ -1,328 +0,0 @@
package me.polynom.moxplatform_android
import android.app.Notification
import android.app.NotificationChannel
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.os.Build
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.content.FileProvider
import androidx.core.graphics.drawable.IconCompat
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<Api.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<Api.NotificationChannel>) {
val notificationManager = context.getSystemService(NotificationManager::class.java)
for (channel in channels) {
val importance = when(channel.importance) {
Api.NotificationChannelImportance.DEFAULT -> NotificationManager.IMPORTANCE_DEFAULT
Api.NotificationChannelImportance.MIN -> NotificationManager.IMPORTANCE_MIN
Api.NotificationChannelImportance.HIGH -> NotificationManager.IMPORTANCE_HIGH
}
val notificationChannel = 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.
fun showMessagingNotification(context: Context, notification: Api.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 https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java#L246
val tapIntent = Intent(context, NotificationReceiver::class.java).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)
}
}
val tapPendingIntent = PendingIntent.getBroadcast(
context,
notification.id.toInt(),
tapIntent,
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,
MOXPLATFORM_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_icon)
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)
}.build()
// Post the notification
NotificationManagerCompat.from(context).notify(
notification.id.toInt(),
finalNotification,
)
}
fun showNotification(context: Context, notification: Api.RegularNotification) {
val builtNotification = NotificationCompat.Builder(context, notification.channelId).apply {
setContentTitle(notification.title)
setContentText(notification.body)
when (notification.icon) {
Api.NotificationIcon.ERROR -> setSmallIcon(R.drawable.error)
Api.NotificationIcon.WARNING -> setSmallIcon(R.drawable.warning)
Api.NotificationIcon.NONE -> {}
}
if (notification.groupId != null) {
setGroup(notification.groupId)
}
}.build()
// Post the notification
NotificationManagerCompat.from(context).notify(notification.id.toInt(), builtNotification)
}

View File

@ -1,7 +0,0 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- For testing -->
<cache-path name="file_picker" path="file_picker/"/>
<!-- Moxxy -->
<files-path name="media" path="media/" />
</paths>

View File

@ -1,64 +0,0 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
class AndroidNotificationsImplementation extends NotificationsImplementation {
final MoxplatformApi _api = MoxplatformApi();
final EventChannel _channel =
const EventChannel('me.polynom/notification_stream');
@override
Future<void> createNotificationChannels(
List<NotificationChannel> channels) async {
return _api.createNotificationChannels(channels);
}
@override
Future<void> deleteNotificationChannels(List<String> ids) {
return _api.deleteNotificationChannels(ids);
}
@override
Future<void> createNotificationGroups(List<NotificationGroup> groups) async {
return _api.createNotificationGroups(groups);
}
@override
Future<void> deleteNotificationGroups(List<String> ids) {
return _api.deleteNotificationGroups(ids);
}
@override
Future<void> showMessagingNotification(
MessagingNotification notification,
) async {
return _api.showMessagingNotification(notification);
}
@override
Future<void> showNotification(RegularNotification notification) async {
return _api.showNotification(notification);
}
@override
Future<void> dismissNotification(int id) async {
return _api.dismissNotification(id);
}
@override
Future<void> setNotificationSelfAvatar(String path) async {
return _api.setNotificationSelfAvatar(path);
}
@override
Future<void> setI18n(NotificationI18nData data) {
return _api.setNotificationI18n(data);
}
@override
Stream<NotificationEvent> getEventStream() => _channel
.receiveBroadcastStream()
.cast<Object>()
.map(NotificationEvent.decode);
}

View File

@ -1,7 +1,6 @@
import 'package:moxplatform_android/src/contacts_android.dart'; import 'package:moxplatform_android/src/contacts_android.dart';
import 'package:moxplatform_android/src/crypto_android.dart'; import 'package:moxplatform_android/src/crypto_android.dart';
import 'package:moxplatform_android/src/isolate_android.dart'; import 'package:moxplatform_android/src/isolate_android.dart';
import 'package:moxplatform_android/src/notifications_android.dart';
import 'package:moxplatform_android/src/platform_android.dart'; import 'package:moxplatform_android/src/platform_android.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart'; import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
@ -12,7 +11,6 @@ class MoxplatformAndroidPlugin extends MoxplatformInterface {
MoxplatformInterface.contacts = AndroidContactsImplementation(); MoxplatformInterface.contacts = AndroidContactsImplementation();
MoxplatformInterface.crypto = AndroidCryptographyImplementation(); MoxplatformInterface.crypto = AndroidCryptographyImplementation();
MoxplatformInterface.handler = AndroidIsolateHandler(); MoxplatformInterface.handler = AndroidIsolateHandler();
MoxplatformInterface.notifications = AndroidNotificationsImplementation();
MoxplatformInterface.platform = AndroidPlatformImplementation(); MoxplatformInterface.platform = AndroidPlatformImplementation();
} }

View File

@ -8,8 +8,6 @@ export 'src/crypto_stub.dart';
export 'src/interface.dart'; export 'src/interface.dart';
export 'src/isolate.dart'; export 'src/isolate.dart';
export 'src/isolate_stub.dart'; export 'src/isolate_stub.dart';
export 'src/notifications.dart';
export 'src/notifications_stub.dart';
export 'src/platform.dart'; export 'src/platform.dart';
export 'src/platform_stub.dart'; export 'src/platform_stub.dart';
export 'src/service.dart'; export 'src/service.dart';

View File

@ -8,18 +8,6 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
enum NotificationIcon {
warning,
error,
none,
}
enum NotificationEventType {
markAsRead,
reply,
open,
}
enum CipherAlgorithm { enum CipherAlgorithm {
aes128GcmNoPadding, aes128GcmNoPadding,
aes256GcmNoPadding, aes256GcmNoPadding,
@ -32,295 +20,6 @@ enum FallbackIconType {
notes, notes,
} }
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 CryptographyResult { class CryptographyResult {
CryptographyResult({ CryptographyResult({
required this.plaintextHash, required this.plaintextHash,
@ -347,88 +46,6 @@ class CryptographyResult {
} }
} }
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 _MoxplatformApiCodec extends StandardMessageCodec { class _MoxplatformApiCodec extends StandardMessageCodec {
const _MoxplatformApiCodec(); const _MoxplatformApiCodec();
@override @override
@ -436,30 +53,6 @@ class _MoxplatformApiCodec extends StandardMessageCodec {
if (value is CryptographyResult) { if (value is CryptographyResult) {
buffer.putUint8(128); buffer.putUint8(128);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is MessagingNotification) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is NotificationChannel) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else if (value is NotificationEvent) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else if (value is NotificationGroup) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else if (value is NotificationI18nData) {
buffer.putUint8(133);
writeValue(buffer, value.encode());
} else if (value is NotificationMessage) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else if (value is NotificationMessageContent) {
buffer.putUint8(135);
writeValue(buffer, value.encode());
} else if (value is RegularNotification) {
buffer.putUint8(136);
writeValue(buffer, value.encode());
} else { } else {
super.writeValue(buffer, value); super.writeValue(buffer, value);
} }
@ -470,22 +63,6 @@ class _MoxplatformApiCodec extends StandardMessageCodec {
switch (type) { switch (type) {
case 128: case 128:
return CryptographyResult.decode(readValue(buffer)!); return CryptographyResult.decode(readValue(buffer)!);
case 129:
return MessagingNotification.decode(readValue(buffer)!);
case 130:
return NotificationChannel.decode(readValue(buffer)!);
case 131:
return NotificationEvent.decode(readValue(buffer)!);
case 132:
return NotificationGroup.decode(readValue(buffer)!);
case 133:
return NotificationI18nData.decode(readValue(buffer)!);
case 134:
return NotificationMessage.decode(readValue(buffer)!);
case 135:
return NotificationMessageContent.decode(readValue(buffer)!);
case 136:
return RegularNotification.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
} }
@ -502,224 +79,13 @@ class MoxplatformApi {
static const MessageCodec<Object?> codec = _MoxplatformApiCodec(); static const MessageCodec<Object?> codec = _MoxplatformApiCodec();
/// Notification APIs
Future<void> createNotificationGroups(
List<NotificationGroup?> arg_groups) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.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.moxplatform_platform_interface.MoxplatformApi.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.moxplatform_platform_interface.MoxplatformApi.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.moxplatform_platform_interface.MoxplatformApi.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.moxplatform_platform_interface.MoxplatformApi.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.moxplatform_platform_interface.MoxplatformApi.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.moxplatform_platform_interface.MoxplatformApi.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.moxplatform_platform_interface.MoxplatformApi.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.moxplatform_platform_interface.MoxplatformApi.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;
}
}
/// Platform APIs /// Platform APIs
Future<String> getPersistentDataPath() async { Future<String> getPersistentDataPath() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?; final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -743,10 +109,10 @@ class MoxplatformApi {
Future<String> getCacheDataPath() async { Future<String> getCacheDataPath() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getCacheDataPath', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getCacheDataPath', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?; final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -770,10 +136,10 @@ class MoxplatformApi {
Future<void> openBatteryOptimisationSettings() async { Future<void> openBatteryOptimisationSettings() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.openBatteryOptimisationSettings', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.openBatteryOptimisationSettings', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?; final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -792,10 +158,10 @@ class MoxplatformApi {
Future<bool> isIgnoringBatteryOptimizations() async { Future<bool> isIgnoringBatteryOptimizations() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.isIgnoringBatteryOptimizations', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.isIgnoringBatteryOptimizations', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?; final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -818,18 +184,12 @@ class MoxplatformApi {
} }
/// Contacts APIs /// Contacts APIs
Future<void> recordSentMessage(String arg_name, String arg_jid, Future<void> recordSentMessage(String arg_name, String arg_jid, String? arg_avatarPath, FallbackIconType arg_fallbackIcon) async {
String? arg_avatarPath, FallbackIconType arg_fallbackIcon) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.recordSentMessage', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.recordSentMessage', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(<Object?>[ final List<Object?>? replyList =
arg_name, await channel.send(<Object?>[arg_name, arg_jid, arg_avatarPath, arg_fallbackIcon.index]) as List<Object?>?;
arg_jid,
arg_avatarPath,
arg_fallbackIcon.index
]) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -847,25 +207,12 @@ class MoxplatformApi {
} }
/// Cryptography APIs /// Cryptography APIs
Future<CryptographyResult?> encryptFile( Future<CryptographyResult?> encryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async {
String arg_sourcePath,
String arg_destPath,
Uint8List arg_key,
Uint8List arg_iv,
CipherAlgorithm arg_algorithm,
String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(<Object?>[ final List<Object?>? replyList =
arg_sourcePath, await channel.send(<Object?>[arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) as List<Object?>?;
arg_destPath,
arg_key,
arg_iv,
arg_algorithm.index,
arg_hashSpec
]) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -882,25 +229,12 @@ class MoxplatformApi {
} }
} }
Future<CryptographyResult?> decryptFile( Future<CryptographyResult?> decryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async {
String arg_sourcePath,
String arg_destPath,
Uint8List arg_key,
Uint8List arg_iv,
CipherAlgorithm arg_algorithm,
String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(<Object?>[ final List<Object?>? replyList =
arg_sourcePath, await channel.send(<Object?>[arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) as List<Object?>?;
arg_destPath,
arg_key,
arg_iv,
arg_algorithm.index,
arg_hashSpec
]) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -917,14 +251,12 @@ class MoxplatformApi {
} }
} }
Future<Uint8List?> hashFile( Future<Uint8List?> hashFile(String arg_sourcePath, String arg_hashSpec) async {
String arg_sourcePath, String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel final List<Object?>? replyList =
.send(<Object?>[arg_sourcePath, arg_hashSpec]) as List<Object?>?; await channel.send(<Object?>[arg_sourcePath, arg_hashSpec]) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -942,14 +274,12 @@ class MoxplatformApi {
} }
/// Media APIs /// Media APIs
Future<bool> generateVideoThumbnail( Future<bool> generateVideoThumbnail(String arg_src, String arg_dest, int arg_maxWidth) async {
String arg_src, String arg_dest, int arg_maxWidth) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail', 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail', codec,
codec,
binaryMessenger: _binaryMessenger); binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel final List<Object?>? replyList =
.send(<Object?>[arg_src, arg_dest, arg_maxWidth]) as List<Object?>?; await channel.send(<Object?>[arg_src, arg_dest, arg_maxWidth]) as List<Object?>?;
if (replyList == null) { if (replyList == null) {
throw PlatformException( throw PlatformException(
code: 'channel-error', code: 'channel-error',
@ -970,28 +300,4 @@ class MoxplatformApi {
return (replyList[0] as bool?)!; return (replyList[0] as bool?)!;
} }
} }
/// Stubs
Future<void> eventStub(NotificationEvent arg_event) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.eventStub',
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;
}
}
} }

View File

@ -5,8 +5,6 @@ import 'package:moxplatform_platform_interface/src/crypto.dart';
import 'package:moxplatform_platform_interface/src/crypto_stub.dart'; import 'package:moxplatform_platform_interface/src/crypto_stub.dart';
import 'package:moxplatform_platform_interface/src/isolate.dart'; import 'package:moxplatform_platform_interface/src/isolate.dart';
import 'package:moxplatform_platform_interface/src/isolate_stub.dart'; import 'package:moxplatform_platform_interface/src/isolate_stub.dart';
import 'package:moxplatform_platform_interface/src/notifications.dart';
import 'package:moxplatform_platform_interface/src/notifications_stub.dart';
import 'package:moxplatform_platform_interface/src/platform.dart'; import 'package:moxplatform_platform_interface/src/platform.dart';
import 'package:moxplatform_platform_interface/src/platform_stub.dart'; import 'package:moxplatform_platform_interface/src/platform_stub.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart';
@ -21,8 +19,6 @@ abstract class MoxplatformInterface extends PlatformInterface {
static IsolateHandler handler = StubIsolateHandler(); static IsolateHandler handler = StubIsolateHandler();
static CryptographyImplementation crypto = StubCryptographyImplementation(); static CryptographyImplementation crypto = StubCryptographyImplementation();
static ContactsImplementation contacts = StubContactsImplementation(); static ContactsImplementation contacts = StubContactsImplementation();
static NotificationsImplementation notifications =
StubNotificationsImplementation();
static PlatformImplementation platform = StubPlatformImplementation(); static PlatformImplementation platform = StubPlatformImplementation();
/// Return the current platform name. /// Return the current platform name.

View File

@ -1,32 +0,0 @@
import 'dart:async';
import 'package:moxplatform_platform_interface/src/api.g.dart';
abstract class NotificationsImplementation {
/// Creates a notification channel with the name [title] and id [id]. If [urgent] is true, then
/// it configures the channel as carrying urgent information.
Future<void> createNotificationChannels(List<NotificationChannel> channels);
Future<void> deleteNotificationChannels(List<String> ids);
/// Creates notification groups.
Future<void> createNotificationGroups(List<NotificationGroup> groups);
Future<void> deleteNotificationGroups(List<String> ids);
/// Shows a notification [notification] in the messaging style with everyting it needs.
Future<void> showMessagingNotification(MessagingNotification notification);
/// Shows a regular notification [notification].
Future<void> showNotification(RegularNotification notification);
/// Dismisses the notification with id [id].
Future<void> dismissNotification(int id);
/// Sets the path to the self-avatar for in-notification replies.
Future<void> setNotificationSelfAvatar(String path);
/// Configures the i18n data for usage in notifications.
Future<void> setI18n(NotificationI18nData data);
Stream<NotificationEvent> getEventStream();
}

View File

@ -1,46 +0,0 @@
import 'dart:async';
import 'package:moxplatform_platform_interface/src/api.g.dart';
import 'package:moxplatform_platform_interface/src/notifications.dart';
class StubNotificationsImplementation extends NotificationsImplementation {
@override
Future<void> createNotificationChannels(
List<NotificationChannel> channels,
) async {}
@override
Future<void> deleteNotificationChannels(
List<String> ids,
) async {}
Future<void> createNotificationGroups(
List<NotificationGroup> groups,
) async {}
@override
Future<void> deleteNotificationGroups(
List<String> ids,
) async {}
@override
Future<void> showMessagingNotification(
MessagingNotification notification,
) async {}
@override
Future<void> showNotification(RegularNotification notification) async {}
@override
Future<void> dismissNotification(int id) async {}
@override
Future<void> setNotificationSelfAvatar(String path) async {}
@override
Future<void> setI18n(NotificationI18nData data) async {}
@override
Stream<NotificationEvent> getEventStream() {
return StreamController<NotificationEvent>().stream;
}
}

View File

@ -13,152 +13,6 @@ import 'package:pigeon/pigeon.dart';
), ),
), ),
) )
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;
}
enum CipherAlgorithm { enum CipherAlgorithm {
aes128GcmNoPadding, aes128GcmNoPadding,
aes256GcmNoPadding, aes256GcmNoPadding,
@ -178,52 +32,8 @@ enum FallbackIconType {
notes; notes;
} }
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() @HostApi()
abstract class MoxplatformApi { abstract class MoxplatformApi {
/// 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);
/// Platform APIs /// Platform APIs
String getPersistentDataPath(); String getPersistentDataPath();
String getCacheDataPath(); String getCacheDataPath();
@ -240,7 +50,4 @@ abstract class MoxplatformApi {
/// Media APIs /// Media APIs
bool generateVideoThumbnail(String src, String dest, int maxWidth); bool generateVideoThumbnail(String src, String dest, int maxWidth);
/// Stubs
void eventStub(NotificationEvent event);
} }