Rewrite the notification code in Kotlin

This commit is contained in:
2023-07-28 00:46:19 +02:00
parent 1771c0e1b6
commit 864b868f45
25 changed files with 318 additions and 129 deletions

View File

@@ -21,7 +21,7 @@ import java.util.Map;
/** Generated class from Pigeon. */
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
public class Notifications {
public class Api {
/** Error class for passing custom error details to Flutter via a thrown PlatformException. */
public static class FlutterError extends RuntimeException {
@@ -416,10 +416,10 @@ public class Notifications {
}
}
private static class NotificationsImplementationApiCodec extends StandardMessageCodec {
public static final NotificationsImplementationApiCodec INSTANCE = new NotificationsImplementationApiCodec();
private static class MoxplatformApiCodec extends StandardMessageCodec {
public static final MoxplatformApiCodec INSTANCE = new MoxplatformApiCodec();
private NotificationsImplementationApiCodec() {}
private MoxplatformApiCodec() {}
@Override
protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
@@ -453,22 +453,28 @@ public class Notifications {
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface NotificationsImplementationApi {
public interface MoxplatformApi {
void createNotificationChannel(@NonNull String title, @NonNull String id, @NonNull Boolean urgent);
void showMessagingNotification(@NonNull MessagingNotification notification);
/** The codec used by NotificationsImplementationApi. */
@NonNull
String getPersistentDataPath();
@NonNull
String getCacheDataPath();
/** The codec used by MoxplatformApi. */
static @NonNull MessageCodec<Object> getCodec() {
return NotificationsImplementationApiCodec.INSTANCE;
return MoxplatformApiCodec.INSTANCE;
}
/**Sets up an instance of `NotificationsImplementationApi` to handle messages through the `binaryMessenger`. */
static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable NotificationsImplementationApi api) {
/**Sets up an instance of `MoxplatformApi` to handle messages through the `binaryMessenger`. */
static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable MoxplatformApi api) {
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.NotificationsImplementationApi.createNotificationChannel", getCodec());
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationChannel", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
@@ -494,7 +500,7 @@ public class Notifications {
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.NotificationsImplementationApi.showMessagingNotification", getCodec());
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.showMessagingNotification", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
@@ -505,6 +511,50 @@ public class Notifications {
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.getPersistentDataPath", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
String output = api.getPersistentDataPath();
wrapped.add(0, output);
}
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.getCacheDataPath", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
String output = api.getCacheDataPath();
wrapped.add(0, output);
}
catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;

View File

@@ -1,36 +1,24 @@
package me.polynom.moxplatform_android;
import static androidx.core.content.ContextCompat.getSystemService;
import static me.polynom.moxplatform_android.ConstantsKt.MARK_AS_READ_ACTION;
import static me.polynom.moxplatform_android.ConstantsKt.MARK_AS_READ_ID_KEY;
import static me.polynom.moxplatform_android.ConstantsKt.REPLY_TEXT_KEY;
import static me.polynom.moxplatform_android.RecordSentMessageKt.recordSentMessage;
import static me.polynom.moxplatform_android.CryptoKt.*;
import me.polynom.moxplatform_android.Notifications.*;
import me.polynom.moxplatform_android.Api.*;
import android.app.ActivityManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.RemoteInput;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.Person;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -46,7 +34,7 @@ import io.flutter.plugin.common.JSONMethodCodec;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware, NotificationsImplementationApi {
public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, 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";
@@ -60,8 +48,6 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
private MethodChannel channel;
private Context context;
private FileProvider provider = new FileProvider();
public MoxplatformAndroidPlugin() {
_instances.add(this);
}
@@ -75,7 +61,7 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey));
NotificationsImplementationApi.setup(flutterPluginBinding.getBinaryMessenger(), this);
MoxplatformApi.setup(flutterPluginBinding.getBinaryMessenger(), this);
Log.d(TAG, "Attached to engine");
}
@@ -292,62 +278,18 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
@Override
public void showMessagingNotification(@NonNull MessagingNotification notification) {
// Create a reply button
// TODO: i18n
RemoteInput remoteInput = new RemoteInput.Builder(REPLY_TEXT_KEY).setLabel("Reply").build();
final Intent replyIntent = new Intent(context, NotificationReceiver.class);
final PendingIntent replyPendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// TODO: i18n
// TODO: Correct icon
final NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_service_icon, "Reply", replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
NotificationsKt.showMessagingNotification(context, notification);
}
// Create the "mark as read" button
final Intent markAsReadIntent = new Intent(context, NotificationReceiver.class);
markAsReadIntent.setAction(MARK_AS_READ_ACTION);
markAsReadIntent.putExtra(MARK_AS_READ_ID_KEY, notification.getId());
// TODO: Replace with something more useful
markAsReadIntent.putExtra("title", notification.getTitle());
final PendingIntent markAsReadPendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, readIntent,PendingIntent.FLAG_CANCEL_CURRENT);
@NonNull
@Override
public String getPersistentDataPath() {
return context.getFilesDir().getPath();
}
final NotificationCompat.MessagingStyle style = new NotificationCompat.MessagingStyle("Me")
.setConversationTitle(notification.getTitle());
for (final NotificationMessage message : notification.getMessages()) {
// Build the sender of the message
final Person.Builder personBuilder = new Person.Builder()
.setName(message.getSender())
.setKey(message.getJid());
if (message.getAvatarPath() != null) {
final IconCompat icon = IconCompat.createWithAdaptiveBitmap(
BitmapFactory.decodeFile(message.getAvatarPath())
);
personBuilder.setIcon(icon);
}
// Build the message
final String content = message.getContent().getBody() == null ? "" : message.getContent().getBody();
final NotificationCompat.MessagingStyle.Message msg = new NotificationCompat.MessagingStyle.Message(
content,
message.getTimestamp(),
personBuilder.build()
);
// Turn the image path to a content Uri, if a media file was specified
if (message.getContent().getMime() != null && message.getContent().getPath() != null) {
final Uri fileUri = androidx.core.content.FileProvider.getUriForFile(context, "me.polynom.moxplatform_android.fileprovider", new File(message.getContent().getPath()));
msg.setData(message.getContent().getMime(), fileUri);
}
style.addMessage(msg);
}
// Build the notification and send it
final NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, notification.getChannelId())
.setStyle(style)
// TODO: This is wrong
.setSmallIcon(R.drawable.ic_service_icon)
.addAction(action)
.addAction(R.drawable.ic_service_icon, "Mark as read", markAsReadPendingIntent);
NotificationManagerCompat.from(context).notify(notification.getId().intValue(), notificationBuilder.build());
@NonNull
@Override
public String getCacheDataPath() {
return context.getCacheDir().getPath();
}
}

View File

@@ -0,0 +1,115 @@
package me.polynom.moxplatform_android
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
import androidx.core.app.RemoteInput
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.IconCompat
import java.io.File
/// Show a messaging style notification described by @notification.
fun showMessagingNotification(context: Context, notification: Api.MessagingNotification) {
// Build the actions
// -> Reply action
val remoteInput = RemoteInput.Builder(REPLY_TEXT_KEY).apply {
// TODO: i18n
setLabel("Reply")
}.build()
val replyIntent = Intent(context, NotificationReceiver::class.java)
val replyPendingIntent = PendingIntent.getBroadcast(
context.applicationContext,
0,
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT,
)
val replyAction = NotificationCompat.Action.Builder(
// TODO: Wrong icon?
R.drawable.ic_service_icon,
// TODO: i18n
"Reply",
replyPendingIntent,
).apply {
addRemoteInput(remoteInput)
}.build()
// -> Mark as read action
val markAsReadIntent = Intent(context, NotificationReceiver::class.java).apply {
action = MARK_AS_READ_ACTION
// TODO: Put the JID here
putExtra("title", notification.title)
}
val markAsReadPendingIntent = PendingIntent.getBroadcast(
context.applicationContext,
0,
markAsReadIntent,
0,
)
// Build the notification
// TODO: Use a person
// TODO: i18n
val style = NotificationCompat.MessagingStyle("Me");
for (message in notification.messages) {
// Build the sender
val sender = Person.Builder().apply {
setName(message.sender)
setKey(message.jid)
// Set the avatar, if available
if (message.avatarPath != null) {
setIcon(
IconCompat.createWithAdaptiveBitmap(
BitmapFactory.decodeFile(message.avatarPath),
),
)
}
}.build()
// Build the message
val body = message.content.body ?: ""
val msg = NotificationCompat.MessagingStyle.Message(
body,
message.timestamp,
sender,
)
// If we got an image, turn it into a content URI and set it
if (message.content.mime != null && message.content.path != null) {
val fileUri = FileProvider.getUriForFile(
context,
"me.polynom.moxplatform_android.fileprovider",
File(message.content.path),
)
msg.setData(message.content.mime, fileUri)
}
// Append the message
style.addMessage(msg)
}
// Assemble the notification
val finalNotification = NotificationCompat.Builder(context, notification.channelId).apply {
setStyle(style)
// TODO: I think this is wrong
setSmallIcon(R.drawable.ic_service_icon)
addAction(replyAction)
addAction(
// TODO: Wrong icon
R.drawable.ic_service_icon,
// TODO: i18n
"Mark as read",
markAsReadPendingIntent,
)
}.build()
// Post the notification
NotificationManagerCompat.from(context).notify(
notification.id.toInt(),
finalNotification,
)
}

View File

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