feat: Allow showing regular notifications

This commit is contained in:
PapaTutuWawa 2023-07-29 13:12:41 +02:00
parent 30ef477999
commit 6da35cd0ba
11 changed files with 604 additions and 263 deletions

View File

@ -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<MyApp> {
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(

View File

@ -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<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<Object>(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<Object> 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<Object>) readValue(buffer));
case (byte) 132:
return NotificationMessageContent.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 133:
return RegularNotification.fromList((ArrayList<Object>) 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<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.showNotification", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
RegularNotification notificationArg = (RegularNotification) args.get(0);
try {
api.showNotification(notificationArg);
wrapped.add(0, null);
}
catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;

View File

@ -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;
@ -65,9 +66,7 @@ import kotlin.jvm.functions.Function1;
context = flutterPluginBinding.getApplicationContext();
notificationChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "me.polynom/notification_stream");
notificationChannel.setStreamHandler(
this
);
notificationChannel.setStreamHandler(this);
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
@ -105,10 +104,7 @@ import kotlin.jvm.functions.Function1;
/// 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();
prefs.edit().putLong(entrypointKey, entrypointHandle).putString(extraDataKey, extraData).apply();
}
public static long getHandle(Context c) {
@ -120,11 +116,9 @@ import kotlin.jvm.functions.Function1;
}
public static void setStartAtBoot(Context c, boolean value) {
c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
.edit()
.putBoolean(autoStartAtBootKey, value)
.apply();
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);
}
@ -183,16 +177,7 @@ import kotlin.jvm.functions.Function1;
int algorithm = (int) args.get(4);
String hashSpec = (String) args.get(5);
result.success(
encryptAndHash(
src,
dest,
key,
iv,
getCipherSpecFromInteger(algorithm),
hashSpec
)
);
result.success(encryptAndHash(src, dest, key, iv, getCipherSpecFromInteger(algorithm), hashSpec));
}
});
encryptionThread.start();
@ -209,16 +194,7 @@ import kotlin.jvm.functions.Function1;
int algorithm = (int) args.get(4);
String hashSpec = (String) args.get(5);
result.success(
decryptAndHash(
src,
dest,
key,
iv,
getCipherSpecFromInteger(algorithm),
hashSpec
)
);
result.success(decryptAndHash(src, dest, key, iv, getCipherSpecFromInteger(algorithm), hashSpec));
}
});
decryptionThread.start();
@ -238,13 +214,7 @@ import kotlin.jvm.functions.Function1;
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)
);
recordSentMessage(context, (String) rargs.get(0), (String) rargs.get(1), (String) rargs.get(2), (int) rargs.get(3));
result.success(true);
break;
default:
@ -289,11 +259,7 @@ import kotlin.jvm.functions.Function1;
@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
);
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);
@ -305,6 +271,11 @@ import kotlin.jvm.functions.Function1;
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);

View File

@ -212,3 +212,19 @@ fun showMessagingNotification(context: Context, notification: Api.MessagingNotif
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)
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
</vector>

View File

@ -24,6 +24,11 @@ class AndroidNotificationsImplementation extends NotificationsImplementation {
return _api.showMessagingNotification(notification);
}
@override
Future<void> showNotification(RegularNotification notification) async {
return _api.showNotification(notification);
}
@override
Future<void> setNotificationSelfAvatar(String path) async {
return _api.setNotificationSelfAvatar(path);

View File

@ -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 <Object?>[
title,
body,
channelId,
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,
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<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> setNotificationSelfAvatar(String arg_path) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.setNotificationSelfAvatar', codec,

View File

@ -9,6 +9,9 @@ abstract class NotificationsImplementation {
/// 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);
/// Sets the path to the self-avatar for in-notification replies.
Future<void> setNotificationSelfAvatar(String path);

View File

@ -9,6 +9,10 @@ class StubNotificationsImplementation extends NotificationsImplementation {
@override
Future<void> showMessagingNotification(MessagingNotification notification) async {}
@override
Future<void> showNotification(RegularNotification notification) async {}
@override
Future<void> setNotificationSelfAvatar(String path) async {}

View File

@ -72,6 +72,31 @@ class MessagingNotification {
final List<NotificationMessage?> 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();