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 1506515..9440708 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 @@ -105,6 +105,18 @@ public class Api { } } + public enum NotificationChannelImportance { + MIN(0), + HIGH(1), + DEFAULT(2); + + final int index; + + private NotificationChannelImportance(final int index) { + this.index = index; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class NotificationMessageContent { /** The textual body of the message. */ @@ -194,6 +206,17 @@ public class Api { /** Generated class from Pigeon that represents data sent in messages. */ public static final class NotificationMessage { + /** The grouping key for the notification. */ + private @Nullable String groupId; + + public @Nullable String getGroupId() { + return groupId; + } + + public void setGroupId(@Nullable String setterArg) { + this.groupId = setterArg; + } + /** The sender of the message. */ private @Nullable String sender; @@ -260,6 +283,13 @@ public class Api { public static final class Builder { + private @Nullable String groupId; + + public @NonNull Builder setGroupId(@Nullable String setterArg) { + this.groupId = setterArg; + return this; + } + private @Nullable String sender; public @NonNull Builder setSender(@Nullable String setterArg) { @@ -297,6 +327,7 @@ public class Api { public @NonNull NotificationMessage build() { NotificationMessage pigeonReturn = new NotificationMessage(); + pigeonReturn.setGroupId(groupId); pigeonReturn.setSender(sender); pigeonReturn.setJid(jid); pigeonReturn.setContent(content); @@ -308,7 +339,8 @@ public class Api { @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(5); + ArrayList toListResult = new ArrayList(6); + toListResult.add(groupId); toListResult.add(sender); toListResult.add(jid); toListResult.add((content == null) ? null : content.toList()); @@ -319,15 +351,17 @@ public class Api { static @NonNull NotificationMessage fromList(@NonNull ArrayList list) { NotificationMessage pigeonResult = new NotificationMessage(); - Object sender = list.get(0); + Object groupId = list.get(0); + pigeonResult.setGroupId((String) groupId); + Object sender = list.get(1); pigeonResult.setSender((String) sender); - Object jid = list.get(1); + Object jid = list.get(2); pigeonResult.setJid((String) jid); - Object content = list.get(2); + Object content = list.get(3); pigeonResult.setContent((content == null) ? null : NotificationMessageContent.fromList((ArrayList) content)); - Object timestamp = list.get(3); + Object timestamp = list.get(4); pigeonResult.setTimestamp((timestamp == null) ? null : ((timestamp instanceof Integer) ? (Integer) timestamp : (Long) timestamp)); - Object avatarPath = list.get(4); + Object avatarPath = list.get(5); pigeonResult.setAvatarPath((String) avatarPath); return pigeonResult; } @@ -419,6 +453,17 @@ public class Api { this.isGroupchat = setterArg; } + /** The id for notification grouping. */ + private @Nullable String groupId; + + public @Nullable String getGroupId() { + return groupId; + } + + public void setGroupId(@Nullable String setterArg) { + this.groupId = setterArg; + } + /** Additional data to include. */ private @Nullable Map extra; @@ -477,6 +522,13 @@ public class Api { return this; } + private @Nullable String groupId; + + public @NonNull Builder setGroupId(@Nullable String setterArg) { + this.groupId = setterArg; + return this; + } + private @Nullable Map extra; public @NonNull Builder setExtra(@Nullable Map setterArg) { @@ -492,6 +544,7 @@ public class Api { pigeonReturn.setJid(jid); pigeonReturn.setMessages(messages); pigeonReturn.setIsGroupchat(isGroupchat); + pigeonReturn.setGroupId(groupId); pigeonReturn.setExtra(extra); return pigeonReturn; } @@ -499,13 +552,14 @@ public class Api { @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(7); + ArrayList toListResult = new ArrayList(8); toListResult.add(title); toListResult.add(id); toListResult.add(channelId); toListResult.add(jid); toListResult.add(messages); toListResult.add(isGroupchat); + toListResult.add(groupId); toListResult.add(extra); return toListResult; } @@ -524,7 +578,9 @@ public class Api { pigeonResult.setMessages((List) messages); Object isGroupchat = list.get(5); pigeonResult.setIsGroupchat((Boolean) isGroupchat); - Object extra = list.get(6); + Object groupId = list.get(6); + pigeonResult.setGroupId((String) groupId); + Object extra = list.get(7); pigeonResult.setExtra((Map) extra); return pigeonResult; } @@ -574,6 +630,17 @@ public class Api { this.channelId = setterArg; } + /** The id for notification grouping. */ + private @Nullable String groupId; + + public @Nullable String getGroupId() { + return groupId; + } + + public void setGroupId(@Nullable String setterArg) { + this.groupId = setterArg; + } + /** The id of the notification. */ private @NonNull Long id; @@ -628,6 +695,13 @@ public class Api { return this; } + private @Nullable String groupId; + + public @NonNull Builder setGroupId(@Nullable String setterArg) { + this.groupId = setterArg; + return this; + } + private @Nullable Long id; public @NonNull Builder setId(@NonNull Long setterArg) { @@ -647,6 +721,7 @@ public class Api { pigeonReturn.setTitle(title); pigeonReturn.setBody(body); pigeonReturn.setChannelId(channelId); + pigeonReturn.setGroupId(groupId); pigeonReturn.setId(id); pigeonReturn.setIcon(icon); return pigeonReturn; @@ -655,10 +730,11 @@ public class Api { @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(5); + ArrayList toListResult = new ArrayList(6); toListResult.add(title); toListResult.add(body); toListResult.add(channelId); + toListResult.add(groupId); toListResult.add(id); toListResult.add(icon == null ? null : icon.index); return toListResult; @@ -672,9 +748,11 @@ public class Api { pigeonResult.setBody((String) body); Object channelId = list.get(2); pigeonResult.setChannelId((String) channelId); - Object id = list.get(3); + Object groupId = list.get(3); + pigeonResult.setGroupId((String) groupId); + Object id = list.get(4); pigeonResult.setId((id == null) ? null : ((id instanceof Integer) ? (Integer) id : (Long) id)); - Object icon = list.get(4); + Object icon = list.get(5); pigeonResult.setIcon(icon == null ? null : NotificationIcon.values()[(int) icon]); return pigeonResult; } @@ -1001,6 +1079,293 @@ public class Api { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class NotificationGroup { + private @NonNull String id; + + public @NonNull String getId() { + return id; + } + + public void setId(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"id\" is null."); + } + this.id = setterArg; + } + + private @NonNull String description; + + public @NonNull String getDescription() { + return description; + } + + public void setDescription(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"description\" is null."); + } + this.description = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + NotificationGroup() {} + + public static final class Builder { + + private @Nullable String id; + + public @NonNull Builder setId(@NonNull String setterArg) { + this.id = setterArg; + return this; + } + + private @Nullable String description; + + public @NonNull Builder setDescription(@NonNull String setterArg) { + this.description = setterArg; + return this; + } + + public @NonNull NotificationGroup build() { + NotificationGroup pigeonReturn = new NotificationGroup(); + pigeonReturn.setId(id); + pigeonReturn.setDescription(description); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(id); + toListResult.add(description); + return toListResult; + } + + static @NonNull NotificationGroup fromList(@NonNull ArrayList list) { + NotificationGroup pigeonResult = new NotificationGroup(); + Object id = list.get(0); + pigeonResult.setId((String) id); + Object description = list.get(1); + pigeonResult.setDescription((String) description); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class NotificationChannel { + 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; + } + + private @NonNull String description; + + public @NonNull String getDescription() { + return description; + } + + public void setDescription(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"description\" is null."); + } + this.description = setterArg; + } + + private @NonNull String id; + + public @NonNull String getId() { + return id; + } + + public void setId(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"id\" is null."); + } + this.id = setterArg; + } + + private @NonNull NotificationChannelImportance importance; + + public @NonNull NotificationChannelImportance getImportance() { + return importance; + } + + public void setImportance(@NonNull NotificationChannelImportance setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"importance\" is null."); + } + this.importance = setterArg; + } + + private @NonNull Boolean showBadge; + + public @NonNull Boolean getShowBadge() { + return showBadge; + } + + public void setShowBadge(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"showBadge\" is null."); + } + this.showBadge = setterArg; + } + + private @Nullable String groupId; + + public @Nullable String getGroupId() { + return groupId; + } + + public void setGroupId(@Nullable String setterArg) { + this.groupId = setterArg; + } + + private @NonNull Boolean vibration; + + public @NonNull Boolean getVibration() { + return vibration; + } + + public void setVibration(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"vibration\" is null."); + } + this.vibration = setterArg; + } + + private @NonNull Boolean enableLights; + + public @NonNull Boolean getEnableLights() { + return enableLights; + } + + public void setEnableLights(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"enableLights\" is null."); + } + this.enableLights = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + NotificationChannel() {} + + public static final class Builder { + + private @Nullable String title; + + public @NonNull Builder setTitle(@NonNull String setterArg) { + this.title = setterArg; + return this; + } + + private @Nullable String description; + + public @NonNull Builder setDescription(@NonNull String setterArg) { + this.description = setterArg; + return this; + } + + private @Nullable String id; + + public @NonNull Builder setId(@NonNull String setterArg) { + this.id = setterArg; + return this; + } + + private @Nullable NotificationChannelImportance importance; + + public @NonNull Builder setImportance(@NonNull NotificationChannelImportance setterArg) { + this.importance = setterArg; + return this; + } + + private @Nullable Boolean showBadge; + + public @NonNull Builder setShowBadge(@NonNull Boolean setterArg) { + this.showBadge = setterArg; + return this; + } + + private @Nullable String groupId; + + public @NonNull Builder setGroupId(@Nullable String setterArg) { + this.groupId = setterArg; + return this; + } + + private @Nullable Boolean vibration; + + public @NonNull Builder setVibration(@NonNull Boolean setterArg) { + this.vibration = setterArg; + return this; + } + + private @Nullable Boolean enableLights; + + public @NonNull Builder setEnableLights(@NonNull Boolean setterArg) { + this.enableLights = setterArg; + return this; + } + + public @NonNull NotificationChannel build() { + NotificationChannel pigeonReturn = new NotificationChannel(); + pigeonReturn.setTitle(title); + pigeonReturn.setDescription(description); + pigeonReturn.setId(id); + pigeonReturn.setImportance(importance); + pigeonReturn.setShowBadge(showBadge); + pigeonReturn.setGroupId(groupId); + pigeonReturn.setVibration(vibration); + pigeonReturn.setEnableLights(enableLights); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(8); + toListResult.add(title); + toListResult.add(description); + toListResult.add(id); + toListResult.add(importance == null ? null : importance.index); + toListResult.add(showBadge); + toListResult.add(groupId); + toListResult.add(vibration); + toListResult.add(enableLights); + return toListResult; + } + + static @NonNull NotificationChannel fromList(@NonNull ArrayList list) { + NotificationChannel pigeonResult = new NotificationChannel(); + Object title = list.get(0); + pigeonResult.setTitle((String) title); + Object description = list.get(1); + pigeonResult.setDescription((String) description); + Object id = list.get(2); + pigeonResult.setId((String) id); + Object importance = list.get(3); + pigeonResult.setImportance(importance == null ? null : NotificationChannelImportance.values()[(int) importance]); + Object showBadge = list.get(4); + pigeonResult.setShowBadge((Boolean) showBadge); + Object groupId = list.get(5); + pigeonResult.setGroupId((String) groupId); + Object vibration = list.get(6); + pigeonResult.setVibration((Boolean) vibration); + Object enableLights = list.get(7); + pigeonResult.setEnableLights((Boolean) enableLights); + return pigeonResult; + } + } + public interface Result { @SuppressWarnings("UnknownNullness") void success(T result); @@ -1021,14 +1386,18 @@ public class Api { case (byte) 129: return MessagingNotification.fromList((ArrayList) readValue(buffer)); case (byte) 130: - return NotificationEvent.fromList((ArrayList) readValue(buffer)); + return NotificationChannel.fromList((ArrayList) readValue(buffer)); case (byte) 131: - return NotificationI18nData.fromList((ArrayList) readValue(buffer)); + return NotificationEvent.fromList((ArrayList) readValue(buffer)); case (byte) 132: - return NotificationMessage.fromList((ArrayList) readValue(buffer)); + return NotificationGroup.fromList((ArrayList) readValue(buffer)); case (byte) 133: - return NotificationMessageContent.fromList((ArrayList) readValue(buffer)); + return NotificationI18nData.fromList((ArrayList) readValue(buffer)); case (byte) 134: + return NotificationMessage.fromList((ArrayList) readValue(buffer)); + case (byte) 135: + return NotificationMessageContent.fromList((ArrayList) readValue(buffer)); + case (byte) 136: return RegularNotification.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -1043,20 +1412,26 @@ public class Api { } else if (value instanceof MessagingNotification) { stream.write(129); writeValue(stream, ((MessagingNotification) value).toList()); - } else if (value instanceof NotificationEvent) { + } else if (value instanceof NotificationChannel) { stream.write(130); - writeValue(stream, ((NotificationEvent) value).toList()); - } else if (value instanceof NotificationI18nData) { + writeValue(stream, ((NotificationChannel) value).toList()); + } else if (value instanceof NotificationEvent) { stream.write(131); + writeValue(stream, ((NotificationEvent) value).toList()); + } else if (value instanceof NotificationGroup) { + stream.write(132); + writeValue(stream, ((NotificationGroup) value).toList()); + } else if (value instanceof NotificationI18nData) { + stream.write(133); writeValue(stream, ((NotificationI18nData) value).toList()); } else if (value instanceof NotificationMessage) { - stream.write(132); + stream.write(134); writeValue(stream, ((NotificationMessage) value).toList()); } else if (value instanceof NotificationMessageContent) { - stream.write(133); + stream.write(135); writeValue(stream, ((NotificationMessageContent) value).toList()); } else if (value instanceof RegularNotification) { - stream.write(134); + stream.write(136); writeValue(stream, ((RegularNotification) value).toList()); } else { super.writeValue(stream, value); @@ -1067,7 +1442,13 @@ public class Api { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface MoxplatformApi { /** Notification APIs */ - void createNotificationChannel(@NonNull String title, @NonNull String description, @NonNull String id, @NonNull Boolean urgent); + void createNotificationGroups(@NonNull List groups); + + void deleteNotificationGroups(@NonNull List ids); + + void createNotificationChannels(@NonNull List channels); + + void deleteNotificationChannels(@NonNull List ids); void showMessagingNotification(@NonNull MessagingNotification notification); @@ -1112,18 +1493,87 @@ public class Api { { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationChannel", getCodec()); + binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationGroups", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - String titleArg = (String) args.get(0); - String descriptionArg = (String) args.get(1); - String idArg = (String) args.get(2); - Boolean urgentArg = (Boolean) args.get(3); + List groupsArg = (List) args.get(0); try { - api.createNotificationChannel(titleArg, descriptionArg, idArg, urgentArg); + api.createNotificationGroups(groupsArg); + 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.deleteNotificationGroups", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List idsArg = (List) args.get(0); + try { + api.deleteNotificationGroups(idsArg); + 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.createNotificationChannels", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List channelsArg = (List) args.get(0); + try { + api.createNotificationChannels(channelsArg); + 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.deleteNotificationChannels", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List idsArg = (List) args.get(0); + try { + api.deleteNotificationChannels(idsArg); wrapped.add(0, null); } catch (Throwable exception) { diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BackgroundService.java b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BackgroundService.java index 79a6af7..63198be 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BackgroundService.java +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BackgroundService.java @@ -100,46 +100,6 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa .apply(); } - private void createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationManager notificationManager = getSystemService(NotificationManager.class); - - // Create a special notification group for the foreground service notification to prevent - // message notifications from getting grouped with it in >= Android 13. - notificationManager.createNotificationChannelGroup( - new NotificationChannelGroup( - GROUP_KEY_FOREGROUND, - "The foreground notification" - ) - ); - notificationManager.createNotificationChannelGroup( - new NotificationChannelGroup( - GROUP_KEY_MESSAGES, - "Messages" - ) - ); - notificationManager.createNotificationChannelGroup( - new NotificationChannelGroup( - GROUP_KEY_OTHER, - "Other" - ) - ); - - NotificationChannel channel = new NotificationChannel( - "FOREGROUND_DEFAULT", - "Moxxy Background Service", - NotificationManager.IMPORTANCE_LOW - ); - channel.setDescription("Executing Moxxy in the background"); - // Prevent showing a badge in the Launcher - channel.setShowBadge(false); - channel.setGroup("foreground"); - - // Create the channel - notificationManager.createNotificationChannel(channel); - } - } - protected void updateNotificationInfo() { String packageName = getApplicationContext().getPackageName(); Intent i = getPackageManager().getLaunchIntentForPackage(packageName); @@ -269,7 +229,6 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa public void onCreate() { super.onCreate(); - createNotificationChannel(); notificationBody = "Preparing..."; updateNotificationInfo(); } 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 8f48b14..94f0021 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 @@ -6,6 +6,8 @@ import static androidx.core.content.ContextCompat.startActivity; 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.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.ThumbnailsKt.generateVideoThumbnailImplementation; @@ -225,13 +227,29 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt } @Override - public void createNotificationChannel(@NonNull String title, @NonNull String description, @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); - channel.setDescription(description); - final NotificationManager manager = getSystemService(context, NotificationManager.class); - manager.createNotificationChannel(channel); + public void createNotificationGroups(@NonNull List groups) { + createNotificationGroupsImpl(context, groups); + } + + @Override + public void deleteNotificationGroups(@NonNull List ids) { + final NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + for (final String id : ids) { + notificationManager.deleteNotificationChannelGroup(id); + } + } + + @Override + public void createNotificationChannels(@NonNull List channels) { + createNotificationChannelsImpl(context, channels); + } + + @Override + public void deleteNotificationChannels(@NonNull List ids) { + final NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + for (final String id : ids) { + notificationManager.deleteNotificationChannel(id); + } } @Override diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/NotificationReceiver.kt b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/NotificationReceiver.kt index 16d88a0..fe7ea96 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/NotificationReceiver.kt +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/NotificationReceiver.kt @@ -161,7 +161,6 @@ class NotificationReceiver : BroadcastReceiver() { val recoveredBuilder = Notification.Builder.recoverBuilder(context, notification).apply { style = newStyle setOnlyAlertOnce(true) - setGroup(GROUP_KEY_MESSAGES) } NotificationManagerCompat.from(context).notify(id, recoveredBuilder.build()) } 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 32d124d..934ef8e 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 @@ -1,11 +1,15 @@ 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 @@ -14,7 +18,6 @@ import androidx.core.app.RemoteInput import androidx.core.content.FileProvider import androidx.core.graphics.drawable.IconCompat import java.io.File -import java.lang.Exception /* * Holds "persistent" data for notifications, like i18n strings. While not useful now, this is @@ -87,6 +90,37 @@ object NotificationDataManager { } } +fun createNotificationGroupsImpl(context: Context, groups: List) { + val notificationManager = context.getSystemService(NotificationManager::class.java) + for (group in groups) { + notificationManager.createNotificationChannelGroup( + NotificationChannelGroup(group.id, group.description), + ) + } +} + +fun createNotificationChannelsImpl(context: Context, channels: List) { + 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 @@ -255,7 +289,9 @@ fun showMessagingNotification(context: Context, notification: Api.MessagingNotif } // Prevent grouping with the foreground service - setGroup(GROUP_KEY_MESSAGES) + if (notification.groupId != null) { + setGroup(notification.groupId) + } setAllowSystemGeneratedContextualActions(true) setCategory(Notification.CATEGORY_MESSAGE) @@ -282,7 +318,9 @@ fun showNotification(context: Context, notification: Api.RegularNotification) { Api.NotificationIcon.NONE -> {} } - setGroup(GROUP_KEY_OTHER) + if (notification.groupId != null) { + setGroup(notification.groupId) + } }.build() // Post the notification diff --git a/packages/moxplatform_android/lib/src/notifications_android.dart b/packages/moxplatform_android/lib/src/notifications_android.dart index acd0610..c7b2172 100644 --- a/packages/moxplatform_android/lib/src/notifications_android.dart +++ b/packages/moxplatform_android/lib/src/notifications_android.dart @@ -9,13 +9,24 @@ class AndroidNotificationsImplementation extends NotificationsImplementation { const EventChannel('me.polynom/notification_stream'); @override - Future createNotificationChannel( - String title, - String description, - String id, - bool urgent, - ) async { - return _api.createNotificationChannel(title, description, id, urgent); + Future createNotificationChannels( + List channels) async { + return _api.createNotificationChannels(channels); + } + + @override + Future deleteNotificationChannels(List ids) { + return _api.deleteNotificationChannels(ids); + } + + @override + Future createNotificationGroups(List groups) async { + return _api.createNotificationGroups(groups); + } + + @override + Future deleteNotificationGroups(List ids) { + return _api.deleteNotificationGroups(ids); } @override diff --git a/packages/moxplatform_android/packages/moxplatform_android/lib/src/platform_android.dart b/packages/moxplatform_android/packages/moxplatform_android/lib/src/platform_android.dart deleted file mode 100644 index 8b13789..0000000 --- a/packages/moxplatform_android/packages/moxplatform_android/lib/src/platform_android.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/moxplatform_platform_interface/lib/src/api.g.dart b/packages/moxplatform_platform_interface/lib/src/api.g.dart index 1df73ca..13b893a 100644 --- a/packages/moxplatform_platform_interface/lib/src/api.g.dart +++ b/packages/moxplatform_platform_interface/lib/src/api.g.dart @@ -32,6 +32,12 @@ enum FallbackIconType { notes, } +enum NotificationChannelImportance { + MIN, + HIGH, + DEFAULT, +} + class NotificationMessageContent { NotificationMessageContent({ this.body, @@ -67,6 +73,7 @@ class NotificationMessageContent { class NotificationMessage { NotificationMessage({ + this.groupId, this.sender, this.jid, required this.content, @@ -74,6 +81,9 @@ class NotificationMessage { this.avatarPath, }); + /// The grouping key for the notification. + String? groupId; + /// The sender of the message. String? sender; @@ -91,6 +101,7 @@ class NotificationMessage { Object encode() { return [ + groupId, sender, jid, content.encode(), @@ -102,11 +113,12 @@ class NotificationMessage { static NotificationMessage decode(Object result) { result as List; return NotificationMessage( - sender: result[0] as String?, - jid: result[1] as String?, - content: NotificationMessageContent.decode(result[2]! as List), - timestamp: result[3]! as int, - avatarPath: result[4] as String?, + groupId: result[0] as String?, + sender: result[1] as String?, + jid: result[2] as String?, + content: NotificationMessageContent.decode(result[3]! as List), + timestamp: result[4]! as int, + avatarPath: result[5] as String?, ); } } @@ -119,6 +131,7 @@ class MessagingNotification { required this.jid, required this.messages, required this.isGroupchat, + this.groupId, this.extra, }); @@ -140,6 +153,9 @@ class MessagingNotification { /// 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? extra; @@ -151,6 +167,7 @@ class MessagingNotification { jid, messages, isGroupchat, + groupId, extra, ]; } @@ -164,7 +181,8 @@ class MessagingNotification { jid: result[3]! as String, messages: (result[4] as List?)!.cast(), isGroupchat: result[5]! as bool, - extra: (result[6] as Map?)?.cast(), + groupId: result[6] as String?, + extra: (result[7] as Map?)?.cast(), ); } } @@ -174,6 +192,7 @@ class RegularNotification { required this.title, required this.body, required this.channelId, + this.groupId, required this.id, required this.icon, }); @@ -187,6 +206,9 @@ class RegularNotification { /// 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; @@ -198,6 +220,7 @@ class RegularNotification { title, body, channelId, + groupId, id, icon.index, ]; @@ -209,8 +232,9 @@ class 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], + groupId: result[3] as String?, + id: result[4]! as int, + icon: NotificationIcon.values[result[5]! as int], ); } } @@ -323,6 +347,88 @@ class CryptographyResult { } } +class NotificationGroup { + NotificationGroup({ + required this.id, + required this.description, + }); + + String id; + + String description; + + Object encode() { + return [ + id, + description, + ]; + } + + static NotificationGroup decode(Object result) { + result as List; + 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 [ + title, + description, + id, + importance.index, + showBadge, + groupId, + vibration, + enableLights, + ]; + } + + static NotificationChannel decode(Object result) { + result as List; + 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 { const _MoxplatformApiCodec(); @override @@ -333,21 +439,27 @@ class _MoxplatformApiCodec extends StandardMessageCodec { } else if (value is MessagingNotification) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is NotificationEvent) { + } else if (value is NotificationChannel) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is NotificationI18nData) { + } else if (value is NotificationEvent) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is NotificationMessage) { + } else if (value is NotificationGroup) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is NotificationMessageContent) { + } else if (value is NotificationI18nData) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is RegularNotification) { + } 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 { super.writeValue(buffer, value); } @@ -361,14 +473,18 @@ class _MoxplatformApiCodec extends StandardMessageCodec { case 129: return MessagingNotification.decode(readValue(buffer)!); case 130: - return NotificationEvent.decode(readValue(buffer)!); + return NotificationChannel.decode(readValue(buffer)!); case 131: - return NotificationI18nData.decode(readValue(buffer)!); + return NotificationEvent.decode(readValue(buffer)!); case 132: - return NotificationMessage.decode(readValue(buffer)!); + return NotificationGroup.decode(readValue(buffer)!); case 133: - return NotificationMessageContent.decode(readValue(buffer)!); + 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: return super.readValueOfType(type, buffer); @@ -387,15 +503,84 @@ class MoxplatformApi { static const MessageCodec codec = _MoxplatformApiCodec(); /// Notification APIs - Future createNotificationChannel(String arg_title, - String arg_description, String arg_id, bool arg_urgent) async { + Future createNotificationGroups( + List arg_groups) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationChannel', + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationGroups', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_title, arg_description, arg_id, arg_urgent]) - as List?; + final List? replyList = + await channel.send([arg_groups]) 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 deleteNotificationGroups(List arg_ids) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.deleteNotificationGroups', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_ids]) 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 createNotificationChannels( + List arg_channels) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationChannels', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_channels]) 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 deleteNotificationChannels(List arg_ids) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.deleteNotificationChannels', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_ids]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', diff --git a/packages/moxplatform_platform_interface/lib/src/notifications.dart b/packages/moxplatform_platform_interface/lib/src/notifications.dart index cc55742..c4957b8 100644 --- a/packages/moxplatform_platform_interface/lib/src/notifications.dart +++ b/packages/moxplatform_platform_interface/lib/src/notifications.dart @@ -4,12 +4,14 @@ 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 createNotificationChannel( - String title, - String description, - String id, - bool urgent, - ); + Future createNotificationChannels(List channels); + + Future deleteNotificationChannels(List ids); + + /// Creates notification groups. + Future createNotificationGroups(List groups); + + Future deleteNotificationGroups(List ids); /// Shows a notification [notification] in the messaging style with everyting it needs. Future showMessagingNotification(MessagingNotification notification); diff --git a/packages/moxplatform_platform_interface/lib/src/notifications_stub.dart b/packages/moxplatform_platform_interface/lib/src/notifications_stub.dart index af3abb1..98e450f 100644 --- a/packages/moxplatform_platform_interface/lib/src/notifications_stub.dart +++ b/packages/moxplatform_platform_interface/lib/src/notifications_stub.dart @@ -4,11 +4,22 @@ import 'package:moxplatform_platform_interface/src/notifications.dart'; class StubNotificationsImplementation extends NotificationsImplementation { @override - Future createNotificationChannel( - String title, - String description, - String id, - bool urgent, + Future createNotificationChannels( + List channels, + ) async {} + + @override + Future deleteNotificationChannels( + List ids, + ) async {} + + Future createNotificationGroups( + List groups, + ) async {} + + @override + Future deleteNotificationGroups( + List ids, ) async {} @override