From 6da35cd0ba9e3e67a649bcf9f688048aed461f44 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 29 Jul 2023 13:12:41 +0200 Subject: [PATCH] feat: Allow showing regular notifications --- example/lib/main.dart | 34 ++ .../me/polynom/moxplatform_android/Api.java | 193 +++++++ .../MoxplatformAndroidPlugin.java | 497 +++++++++--------- .../moxplatform_android/Notifications.kt | 16 + .../android/src/main/res/drawable/error.xml | 5 + .../android/src/main/res/drawable/warning.xml | 5 + .../lib/src/notifications_android.dart | 5 + .../lib/src/api.g.dart | 79 +++ .../lib/src/notifications.dart | 3 + .../lib/src/notifications_stub.dart | 4 + pigeons/api.dart | 26 + 11 files changed, 604 insertions(+), 263 deletions(-) create mode 100644 packages/moxplatform_android/android/src/main/res/drawable/error.xml create mode 100644 packages/moxplatform_android/android/src/main/res/drawable/warning.xml diff --git a/example/lib/main.dart b/example/lib/main.dart index b4cd52a..c438a51 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -10,6 +10,7 @@ import 'package:file_picker/file_picker.dart'; /// The id of the notification channel. const channelId = "me.polynom.moxplatform.testing3"; +const otherChannelId = "me.polynom.moxplatform.testing4"; void main() { runApp(const MyApp()); @@ -46,6 +47,11 @@ class MyAppState extends State { channelId, false, ); + await MoxplatformPlugin.notifications.createNotificationChannel( + "Test notification channel for warnings", + otherChannelId, + false, + ); await MoxplatformPlugin.notifications.setI18n( NotificationI18nData( reply: "答える", @@ -190,6 +196,34 @@ class MyHomePage extends StatelessWidget { }, child: const Text('Show messaging notification'), ), + ElevatedButton( + onPressed: () { + MoxplatformPlugin.notifications.showNotification( + RegularNotification( + id: 4384, + title: 'Warning', + body: 'Something brokey', + channelId: otherChannelId, + icon: NotificationIcon.warning, + ), + ); + }, + child: const Text('Show warning notification'), + ), + ElevatedButton( + onPressed: () { + MoxplatformPlugin.notifications.showNotification( + RegularNotification( + id: 4384, + title: 'Error', + body: "Lol, you're on your own", + channelId: otherChannelId, + icon: NotificationIcon.error, + ), + ); + }, + child: const Text('Show error notification'), + ), ElevatedButton( onPressed: () async { final result = await FilePicker.platform.pickFiles( diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java index 4e910d7..2e62642 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java @@ -57,6 +57,18 @@ public class Api { return errorList; } + public enum NotificationIcon { + WARNING(0), + ERROR(1), + NONE(2); + + final int index; + + private NotificationIcon(final int index) { + this.index = index; + } + } + public enum NotificationEventType { MARK_AS_READ(0), REPLY(1), @@ -453,6 +465,156 @@ public class Api { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class RegularNotification { + /** The title of the notification. */ + private @NonNull String title; + + public @NonNull String getTitle() { + return title; + } + + public void setTitle(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"title\" is null."); + } + this.title = setterArg; + } + + /** The body of the notification. */ + private @NonNull String body; + + public @NonNull String getBody() { + return body; + } + + public void setBody(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"body\" is null."); + } + this.body = setterArg; + } + + /** The id of the channel to show the notification on. */ + private @NonNull String channelId; + + public @NonNull String getChannelId() { + return channelId; + } + + public void setChannelId(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"channelId\" is null."); + } + this.channelId = setterArg; + } + + /** The id of the notification. */ + private @NonNull Long id; + + public @NonNull Long getId() { + return id; + } + + public void setId(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"id\" is null."); + } + this.id = setterArg; + } + + /** The icon to use. */ + private @NonNull NotificationIcon icon; + + public @NonNull NotificationIcon getIcon() { + return icon; + } + + public void setIcon(@NonNull NotificationIcon setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"icon\" is null."); + } + this.icon = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + RegularNotification() {} + + public static final class Builder { + + private @Nullable String title; + + public @NonNull Builder setTitle(@NonNull String setterArg) { + this.title = setterArg; + return this; + } + + private @Nullable String body; + + public @NonNull Builder setBody(@NonNull String setterArg) { + this.body = setterArg; + return this; + } + + private @Nullable String channelId; + + public @NonNull Builder setChannelId(@NonNull String setterArg) { + this.channelId = setterArg; + return this; + } + + private @Nullable Long id; + + public @NonNull Builder setId(@NonNull Long setterArg) { + this.id = setterArg; + return this; + } + + private @Nullable NotificationIcon icon; + + public @NonNull Builder setIcon(@NonNull NotificationIcon setterArg) { + this.icon = setterArg; + return this; + } + + public @NonNull RegularNotification build() { + RegularNotification pigeonReturn = new RegularNotification(); + pigeonReturn.setTitle(title); + pigeonReturn.setBody(body); + pigeonReturn.setChannelId(channelId); + pigeonReturn.setId(id); + pigeonReturn.setIcon(icon); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(5); + toListResult.add(title); + toListResult.add(body); + toListResult.add(channelId); + toListResult.add(id); + toListResult.add(icon == null ? null : icon.index); + return toListResult; + } + + static @NonNull RegularNotification fromList(@NonNull ArrayList list) { + RegularNotification pigeonResult = new RegularNotification(); + Object title = list.get(0); + pigeonResult.setTitle((String) title); + Object body = list.get(1); + pigeonResult.setBody((String) body); + Object channelId = list.get(2); + pigeonResult.setChannelId((String) channelId); + Object id = list.get(3); + pigeonResult.setId((id == null) ? null : ((id instanceof Integer) ? (Integer) id : (Long) id)); + Object icon = list.get(4); + pigeonResult.setIcon(icon == null ? null : NotificationIcon.values()[(int) icon]); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class NotificationEvent { /** The JID the notification was for. */ @@ -672,6 +834,8 @@ public class Api { return NotificationMessage.fromList((ArrayList) readValue(buffer)); case (byte) 132: return NotificationMessageContent.fromList((ArrayList) readValue(buffer)); + case (byte) 133: + return RegularNotification.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -694,6 +858,9 @@ public class Api { } else if (value instanceof NotificationMessageContent) { stream.write(132); writeValue(stream, ((NotificationMessageContent) value).toList()); + } else if (value instanceof RegularNotification) { + stream.write(133); + writeValue(stream, ((RegularNotification) value).toList()); } else { super.writeValue(stream, value); } @@ -707,6 +874,8 @@ public class Api { void showMessagingNotification(@NonNull MessagingNotification notification); + void showNotification(@NonNull RegularNotification notification); + void setNotificationSelfAvatar(@NonNull String path); void setNotificationI18n(@NonNull NotificationI18nData data); @@ -765,6 +934,30 @@ public class Api { api.showMessagingNotification(notificationArg); wrapped.add(0, null); } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.showNotification", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + RegularNotification notificationArg = (RegularNotification) args.get(0); + try { + api.showNotification(notificationArg); + wrapped.add(0, null); + } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java index 599b9bf..ac2f555 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java @@ -4,6 +4,7 @@ import static androidx.core.content.ContextCompat.getSystemService; import static me.polynom.moxplatform_android.ConstantsKt.SHARED_PREFERENCES_KEY; import static me.polynom.moxplatform_android.RecordSentMessageKt.recordSentMessage; import static me.polynom.moxplatform_android.CryptoKt.*; + import me.polynom.moxplatform_android.Api.*; import android.app.ActivityManager; @@ -38,300 +39,270 @@ import io.flutter.plugin.common.JSONMethodCodec; import kotlin.Unit; import kotlin.jvm.functions.Function1; - public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ServiceAware, MoxplatformApi { - public static final String entrypointKey = "entrypoint_handle"; - public static final String extraDataKey = "extra_data"; - private static final String autoStartAtBootKey = "auto_start_at_boot"; - private static final String TAG = "moxplatform_android"; - public static final String methodChannelKey = "me.polynom.moxplatform_android"; - public static final String dataReceivedMethodName = "dataReceived"; +public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ServiceAware, MoxplatformApi { + public static final String entrypointKey = "entrypoint_handle"; + public static final String extraDataKey = "extra_data"; + private static final String autoStartAtBootKey = "auto_start_at_boot"; + private static final String TAG = "moxplatform_android"; + public static final String methodChannelKey = "me.polynom.moxplatform_android"; + public static final String dataReceivedMethodName = "dataReceived"; - private static final List _instances = new ArrayList<>(); - private BackgroundService service; - private MethodChannel channel; - private static EventChannel notificationChannel; - public static EventSink notificationSink; + private static final List _instances = new ArrayList<>(); + private BackgroundService service; + private MethodChannel channel; + private static EventChannel notificationChannel; + public static EventSink notificationSink; private Context context; - public MoxplatformAndroidPlugin() { - _instances.add(this); - } - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { - channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), methodChannelKey); - channel.setMethodCallHandler(this); - context = flutterPluginBinding.getApplicationContext(); - - notificationChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "me.polynom/notification_stream"); - notificationChannel.setStreamHandler( - this - ); - - - LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context); - localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey)); - - MoxplatformApi.setup(flutterPluginBinding.getBinaryMessenger(), this); - - Log.d(TAG, "Attached to engine"); - } - - static void registerWith(Registrar registrar) { - LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context()); - final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin(); - localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey)); - - final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE); - channel.setMethodCallHandler(plugin); - plugin.channel = channel; - - 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. - private void configure(long entrypointHandle, String extraData) { - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); - prefs.edit() - .putLong(entrypointKey, entrypointHandle) - .putString(extraDataKey, extraData) - .apply(); - } - - public static long getHandle(Context c) { - return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getLong(entrypointKey, 0); - } - - public static String getExtraData(Context c) { - return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getString(extraDataKey, ""); - } - - public static void setStartAtBoot(Context c, boolean value) { - c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) - .edit() - .putBoolean(autoStartAtBootKey, value) - .apply(); - } - public static boolean getStartAtBoot(Context c) { - return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getBoolean(autoStartAtBootKey, false); - } - - private boolean isRunning() { - ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) { - if (BackgroundService.class.getName().equals(info.service.getClassName())) { - return true; - } + public MoxplatformAndroidPlugin() { + _instances.add(this); } - return false; - } + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), methodChannelKey); + channel.setMethodCallHandler(this); + context = flutterPluginBinding.getApplicationContext(); - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull io.flutter.plugin.common.MethodChannel.Result result) { - switch (call.method) { - case "configure": - ArrayList args = (ArrayList) call.arguments; - long handle = (long) args.get(0); - String extraData = (String) args.get(1); + notificationChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "me.polynom/notification_stream"); + notificationChannel.setStreamHandler(this); - configure(handle, extraData); - result.success(true); - break; - case "isRunning": - result.success(isRunning()); - break; - case "start": - MoxplatformAndroidPlugin.setStartAtBoot(context, true); - BackgroundService.enqueue(context); - Intent intent = new Intent(context, BackgroundService.class); - ContextCompat.startForegroundService(context, intent); - Log.d(TAG, "Service started"); - result.success(true); - break; - case "sendData": - for (MoxplatformAndroidPlugin plugin : _instances) { - if (plugin.service != null) { - plugin.service.receiveData((String) call.arguments); - break; - } + + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context); + localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey)); + + MoxplatformApi.setup(flutterPluginBinding.getBinaryMessenger(), this); + + Log.d(TAG, "Attached to engine"); + } + + static void registerWith(Registrar registrar) { + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context()); + final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin(); + localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey)); + + final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE); + channel.setMethodCallHandler(plugin); + plugin.channel = channel; + + 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. + private void configure(long entrypointHandle, String extraData) { + SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); + prefs.edit().putLong(entrypointKey, entrypointHandle).putString(extraDataKey, extraData).apply(); + } + + public static long getHandle(Context c) { + return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getLong(entrypointKey, 0); + } + + public static String getExtraData(Context c) { + return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getString(extraDataKey, ""); + } + + public static void setStartAtBoot(Context c, boolean value) { + c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).edit().putBoolean(autoStartAtBootKey, value).apply(); + } + + public static boolean getStartAtBoot(Context c) { + return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getBoolean(autoStartAtBootKey, false); + } + + private boolean isRunning() { + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) { + if (BackgroundService.class.getName().equals(info.service.getClassName())) { + return true; + } } - result.success(true); - break; - case "encryptFile": - Thread encryptionThread = new Thread(new Runnable() { - @Override - public void run() { - ArrayList args = (ArrayList) call.arguments; - String src = (String) args.get(0); - String dest = (String) args.get(1); - byte[] key = (byte[]) args.get(2); - byte[] iv = (byte[]) args.get(3); - int algorithm = (int) args.get(4); - String hashSpec = (String) args.get(5); - result.success( - encryptAndHash( - src, - dest, - key, - iv, - getCipherSpecFromInteger(algorithm), - hashSpec - ) - ); - } - }); - encryptionThread.start(); - break; - case "decryptFile": - Thread decryptionThread = new Thread(new Runnable() { - @Override - public void run() { - ArrayList args = (ArrayList) call.arguments; - String src = (String) args.get(0); - String dest = (String) args.get(1); - byte[] key = (byte[]) args.get(2); - byte[] iv = (byte[]) args.get(3); - int algorithm = (int) args.get(4); - String hashSpec = (String) args.get(5); - - result.success( - decryptAndHash( - src, - dest, - key, - iv, - getCipherSpecFromInteger(algorithm), - hashSpec - ) - ); - } - }); - decryptionThread.start(); - break; - case "hashFile": - Thread hashingThread = new Thread(new Runnable() { - @Override - public void run() { - ArrayList args = (ArrayList) call.arguments; - String src = (String) args.get(0); - String hashSpec = (String) args.get(1); - - result.success(hashFile(src, hashSpec)); - } - }); - hashingThread.start(); - break; - case "recordSentMessage": - ArrayList rargs = (ArrayList) call.arguments; - recordSentMessage( - context, - (String) rargs.get(0), - (String) rargs.get(1), - (String) rargs.get(2), - (int) rargs.get(3) - ); - result.success(true); - break; - default: - result.notImplemented(); - break; + return false; } - } - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction() == null) return; + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull io.flutter.plugin.common.MethodChannel.Result result) { + switch (call.method) { + case "configure": + ArrayList args = (ArrayList) call.arguments; + long handle = (long) args.get(0); + String extraData = (String) args.get(1); - if (intent.getAction().equalsIgnoreCase(methodChannelKey)) { - String data = intent.getStringExtra("data"); + configure(handle, extraData); + result.success(true); + break; + case "isRunning": + result.success(isRunning()); + break; + case "start": + MoxplatformAndroidPlugin.setStartAtBoot(context, true); + BackgroundService.enqueue(context); + Intent intent = new Intent(context, BackgroundService.class); + ContextCompat.startForegroundService(context, intent); + Log.d(TAG, "Service started"); + result.success(true); + break; + case "sendData": + for (MoxplatformAndroidPlugin plugin : _instances) { + if (plugin.service != null) { + plugin.service.receiveData((String) call.arguments); + break; + } + } + result.success(true); + break; + case "encryptFile": + Thread encryptionThread = new Thread(new Runnable() { + @Override + public void run() { + ArrayList args = (ArrayList) call.arguments; + String src = (String) args.get(0); + String dest = (String) args.get(1); + byte[] key = (byte[]) args.get(2); + byte[] iv = (byte[]) args.get(3); + int algorithm = (int) args.get(4); + String hashSpec = (String) args.get(5); - if (channel != null) { - channel.invokeMethod(dataReceivedMethodName, data); - } + result.success(encryptAndHash(src, dest, key, iv, getCipherSpecFromInteger(algorithm), hashSpec)); + } + }); + encryptionThread.start(); + break; + case "decryptFile": + Thread decryptionThread = new Thread(new Runnable() { + @Override + public void run() { + ArrayList args = (ArrayList) call.arguments; + String src = (String) args.get(0); + String dest = (String) args.get(1); + byte[] key = (byte[]) args.get(2); + byte[] iv = (byte[]) args.get(3); + int algorithm = (int) args.get(4); + String hashSpec = (String) args.get(5); + + result.success(decryptAndHash(src, dest, key, iv, getCipherSpecFromInteger(algorithm), hashSpec)); + } + }); + decryptionThread.start(); + break; + case "hashFile": + Thread hashingThread = new Thread(new Runnable() { + @Override + public void run() { + ArrayList args = (ArrayList) call.arguments; + String src = (String) args.get(0); + String hashSpec = (String) args.get(1); + + result.success(hashFile(src, hashSpec)); + } + }); + hashingThread.start(); + break; + case "recordSentMessage": + ArrayList rargs = (ArrayList) call.arguments; + recordSentMessage(context, (String) rargs.get(0), (String) rargs.get(1), (String) rargs.get(2), (int) rargs.get(3)); + result.success(true); + break; + default: + result.notImplemented(); + break; + } } - } - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - channel.setMethodCallHandler(null); - LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context); - localBroadcastManager.unregisterReceiver(this); + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() == null) return; - Log.d(TAG, "Detached from engine"); - } + if (intent.getAction().equalsIgnoreCase(methodChannelKey)) { + String data = intent.getStringExtra("data"); - @Override - public void onAttachedToService(@NonNull ServicePluginBinding binding) { - Log.d(TAG, "Attached to service"); - this.service = (BackgroundService) binding.getService(); - } + if (channel != null) { + channel.invokeMethod(dataReceivedMethodName, data); + } + } + } - @Override - public void onDetachedFromService() { - Log.d(TAG, "Detached from service"); - this.service = null; - } + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context); + localBroadcastManager.unregisterReceiver(this); - @Override - public void createNotificationChannel(@NonNull String title, @NonNull String id, @NonNull Boolean urgent) { - final NotificationChannel channel = new NotificationChannel( - id, - title, - urgent ? NotificationManager.IMPORTANCE_HIGH : NotificationManager.IMPORTANCE_DEFAULT - ); - channel.enableVibration(true); - channel.enableLights(true); - final NotificationManager manager = getSystemService(context, NotificationManager.class); - manager.createNotificationChannel(channel); - } + Log.d(TAG, "Detached from engine"); + } - @Override - public void showMessagingNotification(@NonNull MessagingNotification notification) { - NotificationsKt.showMessagingNotification(context, notification); - } + @Override + public void onAttachedToService(@NonNull ServicePluginBinding binding) { + Log.d(TAG, "Attached to service"); + this.service = (BackgroundService) binding.getService(); + } + + @Override + public void onDetachedFromService() { + Log.d(TAG, "Detached from service"); + this.service = null; + } + + @Override + public void createNotificationChannel(@NonNull String title, @NonNull String id, @NonNull Boolean urgent) { + final NotificationChannel channel = new NotificationChannel(id, title, urgent ? NotificationManager.IMPORTANCE_HIGH : NotificationManager.IMPORTANCE_DEFAULT); + channel.enableVibration(true); + channel.enableLights(true); + final NotificationManager manager = getSystemService(context, NotificationManager.class); + manager.createNotificationChannel(channel); + } + + @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 setNotificationSelfAvatar(@NonNull String path) { - NotificationDataManager.INSTANCE.setAvatarPath(path); + NotificationDataManager.INSTANCE.setAvatarPath(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()); + // Configure i18n + NotificationDataManager.INSTANCE.setYou(context, data.getYou()); + NotificationDataManager.INSTANCE.setReply(context, data.getReply()); + NotificationDataManager.INSTANCE.setMarkAsRead(context, data.getMarkAsRead()); } @NonNull - @Override - public String getPersistentDataPath() { - return context.getFilesDir().getPath(); - } + @Override + public String getPersistentDataPath() { + return context.getFilesDir().getPath(); + } - @NonNull - @Override - public String getCacheDataPath() { - return context.getCacheDir().getPath(); - } + @NonNull + @Override + public String getCacheDataPath() { + return context.getCacheDir().getPath(); + } - @Override - public void eventStub(@NonNull NotificationEvent event) { - // Stub to trick pigeon into - } + @Override + public void eventStub(@NonNull NotificationEvent event) { + // Stub to trick pigeon into + } } diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Notifications.kt b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Notifications.kt index 377bc8a..3b89ca1 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Notifications.kt +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Notifications.kt @@ -211,4 +211,20 @@ fun showMessagingNotification(context: Context, notification: Api.MessagingNotif 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 -> {} + } + }.build() + + // Post the notification + NotificationManagerCompat.from(context).notify(notification.id.toInt(), builtNotification) } \ No newline at end of file diff --git a/packages/moxplatform_android/android/src/main/res/drawable/error.xml b/packages/moxplatform_android/android/src/main/res/drawable/error.xml new file mode 100644 index 0000000..1757571 --- /dev/null +++ b/packages/moxplatform_android/android/src/main/res/drawable/error.xml @@ -0,0 +1,5 @@ + + + diff --git a/packages/moxplatform_android/android/src/main/res/drawable/warning.xml b/packages/moxplatform_android/android/src/main/res/drawable/warning.xml new file mode 100644 index 0000000..3c9a4b3 --- /dev/null +++ b/packages/moxplatform_android/android/src/main/res/drawable/warning.xml @@ -0,0 +1,5 @@ + + + diff --git a/packages/moxplatform_android/lib/src/notifications_android.dart b/packages/moxplatform_android/lib/src/notifications_android.dart index 7fd95fa..9f3494d 100644 --- a/packages/moxplatform_android/lib/src/notifications_android.dart +++ b/packages/moxplatform_android/lib/src/notifications_android.dart @@ -24,6 +24,11 @@ class AndroidNotificationsImplementation extends NotificationsImplementation { return _api.showMessagingNotification(notification); } + @override + Future showNotification(RegularNotification notification) async { + return _api.showNotification(notification); + } + @override Future setNotificationSelfAvatar(String path) async { return _api.setNotificationSelfAvatar(path); diff --git a/packages/moxplatform_platform_interface/lib/src/api.g.dart b/packages/moxplatform_platform_interface/lib/src/api.g.dart index d81daaa..36fd541 100644 --- a/packages/moxplatform_platform_interface/lib/src/api.g.dart +++ b/packages/moxplatform_platform_interface/lib/src/api.g.dart @@ -8,6 +8,12 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +enum NotificationIcon { + warning, + error, + none, +} + enum NotificationEventType { markAsRead, reply, @@ -139,6 +145,52 @@ class MessagingNotification { } } +class RegularNotification { + RegularNotification({ + required this.title, + required this.body, + required this.channelId, + 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 of the notification. + int id; + + /// The icon to use. + NotificationIcon icon; + + Object encode() { + return [ + title, + body, + channelId, + id, + icon.index, + ]; + } + + static RegularNotification decode(Object result) { + result as List; + return RegularNotification( + title: result[0]! as String, + body: result[1]! as String, + channelId: result[2]! as String, + id: result[3]! as int, + icon: NotificationIcon.values[result[4]! as int], + ); + } +} + class NotificationEvent { NotificationEvent({ required this.jid, @@ -228,6 +280,9 @@ class _MoxplatformApiCodec extends StandardMessageCodec { } else if (value is NotificationMessageContent) { buffer.putUint8(132); writeValue(buffer, value.encode()); + } else if (value is RegularNotification) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -246,6 +301,8 @@ class _MoxplatformApiCodec extends StandardMessageCodec { return NotificationMessage.decode(readValue(buffer)!); case 132: return NotificationMessageContent.decode(readValue(buffer)!); + case 133: + return RegularNotification.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -306,6 +363,28 @@ class MoxplatformApi { } } + Future showNotification(RegularNotification arg_notification) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.showNotification', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_notification]) 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; + } + } + Future setNotificationSelfAvatar(String arg_path) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.setNotificationSelfAvatar', codec, diff --git a/packages/moxplatform_platform_interface/lib/src/notifications.dart b/packages/moxplatform_platform_interface/lib/src/notifications.dart index 31ef838..2c69bf5 100644 --- a/packages/moxplatform_platform_interface/lib/src/notifications.dart +++ b/packages/moxplatform_platform_interface/lib/src/notifications.dart @@ -9,6 +9,9 @@ abstract class NotificationsImplementation { /// Shows a notification [notification] in the messaging style with everyting it needs. Future showMessagingNotification(MessagingNotification notification); + /// Shows a regular notification [notification]. + Future showNotification(RegularNotification notification); + /// Sets the path to the self-avatar for in-notification replies. Future setNotificationSelfAvatar(String path); diff --git a/packages/moxplatform_platform_interface/lib/src/notifications_stub.dart b/packages/moxplatform_platform_interface/lib/src/notifications_stub.dart index 2bc36fe..d847a1e 100644 --- a/packages/moxplatform_platform_interface/lib/src/notifications_stub.dart +++ b/packages/moxplatform_platform_interface/lib/src/notifications_stub.dart @@ -9,6 +9,10 @@ class StubNotificationsImplementation extends NotificationsImplementation { @override Future showMessagingNotification(MessagingNotification notification) async {} + @override + Future showNotification(RegularNotification notification) async {} + + @override Future setNotificationSelfAvatar(String path) async {} diff --git a/pigeons/api.dart b/pigeons/api.dart index d1877fc..a156cef 100644 --- a/pigeons/api.dart +++ b/pigeons/api.dart @@ -72,6 +72,31 @@ class MessagingNotification { final List messages; } +enum NotificationIcon { + warning, + error, + none, +} + +class RegularNotification { + const RegularNotification(this.title, this.body, this.channelId, this.id, this.icon); + + /// 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 of the notification. + final int id; + + /// The icon to use. + final NotificationIcon icon; +} + enum NotificationEventType { markAsRead, reply, @@ -114,6 +139,7 @@ class NotificationI18nData { abstract class MoxplatformApi { void createNotificationChannel(String title, String id, bool urgent); void showMessagingNotification(MessagingNotification notification); + void showNotification(RegularNotification notification); void setNotificationSelfAvatar(String path); void setNotificationI18n(NotificationI18nData data); String getPersistentDataPath();