Basic stuff

This commit is contained in:
PapaTutuWawa 2023-07-27 20:45:09 +02:00
parent 17642f9fab
commit 1771c0e1b6
26 changed files with 1100 additions and 16 deletions

View File

@ -47,7 +47,7 @@ android {
applicationId "com.example.example" applicationId "com.example.example"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion 26
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

View File

@ -34,5 +34,5 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
</manifest> </manifest>

View File

@ -1,17 +1,49 @@
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:moxplatform/moxplatform.dart'; import 'package:moxplatform/moxplatform.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart'; import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:file_picker/file_picker.dart';
/// The id of the notification channel.
const channelId = "me.polynom.moxplatform.testing3";
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
} }
class MyApp extends StatelessWidget { class Sender {
const Sender(this.name, this.jid);
final String name;
final String jid;
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key); const MyApp({Key? key}) : super(key: key);
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
initStateAsync();
}
Future<void> initStateAsync() async {
await Permission.notification.request();
await MoxplatformPlugin.notifications.createNotificationChannel("Test notification channel", channelId, false);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
@ -19,13 +51,23 @@ class MyApp extends StatelessWidget {
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
), ),
home: const MyHomePage(), home: MyHomePage(),
); );
} }
} }
class MyHomePage extends StatelessWidget { class MyHomePage extends StatelessWidget {
const MyHomePage({super.key}); MyHomePage({super.key});
/// List of "Message senders".
final List<Sender> senders = const [
Sender('Mash Kyrielight', 'mash@example.org'),
Sender('Rio Tsukatsuki', 'rio@millenium'),
Sender('Raiden Shogun', 'raiden@tevhat'),
];
/// List of sent messages.
List<NotificationMessage> messages = List<NotificationMessage>.empty(growable: true);
Future<void> _cryptoTest() async { Future<void> _cryptoTest() async {
final result = await FilePicker.platform.pickFiles(); final result = await FilePicker.platform.pickFiles();
@ -98,6 +140,41 @@ class MyHomePage extends StatelessWidget {
}, },
child: const Text('Test recordSentMessage (notes fallback)'), child: const Text('Test recordSentMessage (notes fallback)'),
), ),
ElevatedButton(
onPressed: () async {
final result = await FilePicker.platform.pickFiles(
type: FileType.image,
);
print('Picked file: ${result?.files.single.path}');
// Create a new message.
final senderIndex = Random().nextInt(senders.length);
final time = DateTime.now().millisecondsSinceEpoch;
messages.add(
NotificationMessage(
jid: senders[senderIndex].jid,
sender: senders[senderIndex].name,
content: NotificationMessageContent(
body: result != null ? null : 'Message #${messages.length}',
mime: 'image/jpeg',
path: result?.files.single.path,
),
timestamp: time,
)
);
await Future<void>.delayed(const Duration(seconds: 4));
await MoxplatformPlugin.notifications.showMessagingNotification(
MessagingNotification(
id: 2343,
title: 'Test conversation',
messages: messages,
channelId: channelId,
),
);
},
child: const Text('Show messaging notification'),
),
], ],
), ),
), ),

View File

@ -38,6 +38,8 @@ dependencies:
version: 0.1.17+1 version: 0.1.17+1
file_picker: 5.2.0+1 file_picker: 5.2.0+1
permission_handler: 10.4.3
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.

View File

@ -5,4 +5,5 @@ class MoxplatformPlugin {
static MediaScannerImplementation get media => MoxplatformInterface.media; static MediaScannerImplementation get media => MoxplatformInterface.media;
static CryptographyImplementation get crypto => MoxplatformInterface.crypto; static CryptographyImplementation get crypto => MoxplatformInterface.crypto;
static ContactsImplementation get contacts => MoxplatformInterface.contacts; static ContactsImplementation get contacts => MoxplatformInterface.contacts;
static NotificationsImplementation get notifications => MoxplatformInterface.notifications;
} }

View File

@ -35,7 +35,8 @@ android {
} }
defaultConfig { defaultConfig {
minSdkVersion 16 // What Moxxy currently uses
minSdkVersion 26
} }
} }

View File

@ -1,11 +1,22 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.polynom.moxplatform_android"> package="me.polynom.moxplatform_android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<application> <application>
<provider
android:name="me.polynom.moxplatform_android.MoxplatformFileProvider"
android:authorities="me.polynom.moxplatform_android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<service <service
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
@ -27,5 +38,6 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".NotificationReceiver" />
</application> </application>
</manifest> </manifest>

View File

@ -6,6 +6,14 @@ const val TAG = "Moxplatform"
// The size of the buffer to hashing, encryption, and decryption in bytes. // The size of the buffer to hashing, encryption, and decryption in bytes.
const val BUFFER_SIZE = 8096 const val BUFFER_SIZE = 8096
// The data key for text entered in the notification's reply field
const val REPLY_TEXT_KEY = "key_reply_text"
// The action for pressing the "Mark as read" button on a notification
const val MARK_AS_READ_ACTION = "mark_as_read"
// The key for the notification id to mark as read
const val MARK_AS_READ_ID_KEY = "notification_id"
// TODO: Maybe try again to rewrite the entire plugin in Kotlin // TODO: Maybe try again to rewrite the entire plugin in Kotlin
//const val METHOD_CHANNEL_KEY = "me.polynom.moxplatform_android" //const val METHOD_CHANNEL_KEY = "me.polynom.moxplatform_android"
//const val BACKGROUND_METHOD_CHANNEL_KEY = METHOD_CHANNEL_KEY + "_bg" //const val BACKGROUND_METHOD_CHANNEL_KEY = METHOD_CHANNEL_KEY + "_bg"

View File

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

View File

@ -1,31 +1,39 @@
package me.polynom.moxplatform_android; 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.RecordSentMessageKt.recordSentMessage;
import static me.polynom.moxplatform_android.CryptoKt.*; import static me.polynom.moxplatform_android.CryptoKt.*;
import me.polynom.moxplatform_android.Notifications.*;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; 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.content.ContextCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.FileInputStream; import java.io.File;
import java.security.MessageDigest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.service.ServiceAware; import io.flutter.embedding.engine.plugins.service.ServiceAware;
import io.flutter.embedding.engine.plugins.service.ServicePluginBinding; import io.flutter.embedding.engine.plugins.service.ServicePluginBinding;
@ -35,8 +43,10 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.JSONMethodCodec;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware { public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware, NotificationsImplementationApi {
public static final String entrypointKey = "entrypoint_handle"; public static final String entrypointKey = "entrypoint_handle";
public static final String extraDataKey = "extra_data"; public static final String extraDataKey = "extra_data";
private static final String autoStartAtBootKey = "auto_start_at_boot"; private static final String autoStartAtBootKey = "auto_start_at_boot";
@ -50,6 +60,8 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
private MethodChannel channel; private MethodChannel channel;
private Context context; private Context context;
private FileProvider provider = new FileProvider();
public MoxplatformAndroidPlugin() { public MoxplatformAndroidPlugin() {
_instances.add(this); _instances.add(this);
} }
@ -63,6 +75,8 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey)); localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey));
NotificationsImplementationApi.setup(flutterPluginBinding.getBinaryMessenger(), this);
Log.d(TAG, "Attached to engine"); Log.d(TAG, "Attached to engine");
} }
@ -117,7 +131,7 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
} }
@Override @Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { public void onMethodCall(@NonNull MethodCall call, @NonNull io.flutter.plugin.common.MethodChannel.Result result) {
switch (call.method) { switch (call.method) {
case "configure": case "configure":
ArrayList args = (ArrayList) call.arguments; ArrayList args = (ArrayList) call.arguments;
@ -262,4 +276,78 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
Log.d(TAG, "Detached from service"); Log.d(TAG, "Detached from service");
this.service = null; 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) {
// 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();
// 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);
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());
}
} }

View File

@ -0,0 +1,28 @@
package me.polynom.moxplatform_android
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
class NotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// If it is a mark as read, dismiss the entire notification and
// send a notification to the app.
// TODO: Notify app
if (intent.action == MARK_AS_READ_ACTION) {
Log.d("NotificationReceiver", "Marking ${intent.getStringExtra("title")} as read")
NotificationManagerCompat.from(context).cancel(intent.getLongExtra(MARK_AS_READ_ID_KEY, -1).toInt())
return
}
val remoteInput = RemoteInput.getResultsFromIntent(intent) ?: return
val title = remoteInput.getCharSequence(REPLY_TEXT_KEY).toString()
Log.d("NotificationReceiver", title)
// TODO: Notify app
}
}

View File

@ -0,0 +1,520 @@
// Autogenerated from Pigeon (v10.1.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
package me.polynom.moxplatform_android;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MessageCodec;
import io.flutter.plugin.common.StandardMessageCodec;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Generated class from Pigeon. */
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
public class Notifications {
/** Error class for passing custom error details to Flutter via a thrown PlatformException. */
public static class FlutterError extends RuntimeException {
/** The error code. */
public final String code;
/** The error details. Must be a datatype supported by the api codec. */
public final Object details;
public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details)
{
super(message);
this.code = code;
this.details = details;
}
}
@NonNull
protected static ArrayList<Object> wrapError(@NonNull Throwable exception) {
ArrayList<Object> errorList = new ArrayList<Object>(3);
if (exception instanceof FlutterError) {
FlutterError error = (FlutterError) exception;
errorList.add(error.code);
errorList.add(error.getMessage());
errorList.add(error.details);
} else {
errorList.add(exception.toString());
errorList.add(exception.getClass().getSimpleName());
errorList.add(
"Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception));
}
return errorList;
}
/** Generated class from Pigeon that represents data sent in messages. */
public static final class NotificationMessageContent {
/** The textual body of the message. */
private @Nullable String body;
public @Nullable String getBody() {
return body;
}
public void setBody(@Nullable String setterArg) {
this.body = setterArg;
}
/** The path and mime type of the media to show. */
private @Nullable String mime;
public @Nullable String getMime() {
return mime;
}
public void setMime(@Nullable String setterArg) {
this.mime = setterArg;
}
private @Nullable String path;
public @Nullable String getPath() {
return path;
}
public void setPath(@Nullable String setterArg) {
this.path = setterArg;
}
public static final class Builder {
private @Nullable String body;
public @NonNull Builder setBody(@Nullable String setterArg) {
this.body = setterArg;
return this;
}
private @Nullable String mime;
public @NonNull Builder setMime(@Nullable String setterArg) {
this.mime = setterArg;
return this;
}
private @Nullable String path;
public @NonNull Builder setPath(@Nullable String setterArg) {
this.path = setterArg;
return this;
}
public @NonNull NotificationMessageContent build() {
NotificationMessageContent pigeonReturn = new NotificationMessageContent();
pigeonReturn.setBody(body);
pigeonReturn.setMime(mime);
pigeonReturn.setPath(path);
return pigeonReturn;
}
}
@NonNull
ArrayList<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<Object>(3);
toListResult.add(body);
toListResult.add(mime);
toListResult.add(path);
return toListResult;
}
static @NonNull NotificationMessageContent fromList(@NonNull ArrayList<Object> list) {
NotificationMessageContent pigeonResult = new NotificationMessageContent();
Object body = list.get(0);
pigeonResult.setBody((String) body);
Object mime = list.get(1);
pigeonResult.setMime((String) mime);
Object path = list.get(2);
pigeonResult.setPath((String) path);
return pigeonResult;
}
}
/** Generated class from Pigeon that represents data sent in messages. */
public static final class NotificationMessage {
/** The sender of the message. */
private @NonNull String sender;
public @NonNull String getSender() {
return sender;
}
public void setSender(@NonNull String setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"sender\" is null.");
}
this.sender = setterArg;
}
/** The jid of the sender. */
private @NonNull String jid;
public @NonNull String getJid() {
return jid;
}
public void setJid(@NonNull String setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"jid\" is null.");
}
this.jid = setterArg;
}
/** The body of the message. */
private @NonNull NotificationMessageContent content;
public @NonNull NotificationMessageContent getContent() {
return content;
}
public void setContent(@NonNull NotificationMessageContent setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"content\" is null.");
}
this.content = setterArg;
}
/** Milliseconds since epoch. */
private @NonNull Long timestamp;
public @NonNull Long getTimestamp() {
return timestamp;
}
public void setTimestamp(@NonNull Long setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"timestamp\" is null.");
}
this.timestamp = setterArg;
}
/** The path to the avatar to use */
private @Nullable String avatarPath;
public @Nullable String getAvatarPath() {
return avatarPath;
}
public void setAvatarPath(@Nullable String setterArg) {
this.avatarPath = setterArg;
}
/** Constructor is non-public to enforce null safety; use Builder. */
NotificationMessage() {}
public static final class Builder {
private @Nullable String sender;
public @NonNull Builder setSender(@NonNull String setterArg) {
this.sender = setterArg;
return this;
}
private @Nullable String jid;
public @NonNull Builder setJid(@NonNull String setterArg) {
this.jid = setterArg;
return this;
}
private @Nullable NotificationMessageContent content;
public @NonNull Builder setContent(@NonNull NotificationMessageContent setterArg) {
this.content = setterArg;
return this;
}
private @Nullable Long timestamp;
public @NonNull Builder setTimestamp(@NonNull Long setterArg) {
this.timestamp = setterArg;
return this;
}
private @Nullable String avatarPath;
public @NonNull Builder setAvatarPath(@Nullable String setterArg) {
this.avatarPath = setterArg;
return this;
}
public @NonNull NotificationMessage build() {
NotificationMessage pigeonReturn = new NotificationMessage();
pigeonReturn.setSender(sender);
pigeonReturn.setJid(jid);
pigeonReturn.setContent(content);
pigeonReturn.setTimestamp(timestamp);
pigeonReturn.setAvatarPath(avatarPath);
return pigeonReturn;
}
}
@NonNull
ArrayList<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<Object>(5);
toListResult.add(sender);
toListResult.add(jid);
toListResult.add((content == null) ? null : content.toList());
toListResult.add(timestamp);
toListResult.add(avatarPath);
return toListResult;
}
static @NonNull NotificationMessage fromList(@NonNull ArrayList<Object> list) {
NotificationMessage pigeonResult = new NotificationMessage();
Object sender = list.get(0);
pigeonResult.setSender((String) sender);
Object jid = list.get(1);
pigeonResult.setJid((String) jid);
Object content = list.get(2);
pigeonResult.setContent((content == null) ? null : NotificationMessageContent.fromList((ArrayList<Object>) content));
Object timestamp = list.get(3);
pigeonResult.setTimestamp((timestamp == null) ? null : ((timestamp instanceof Integer) ? (Integer) timestamp : (Long) timestamp));
Object avatarPath = list.get(4);
pigeonResult.setAvatarPath((String) avatarPath);
return pigeonResult;
}
}
/** Generated class from Pigeon that represents data sent in messages. */
public static final class MessagingNotification {
/** The title of the conversation. */
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 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 id of the notification channel the notification should appear 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;
}
/** Messages to show. */
private @NonNull List<NotificationMessage> messages;
public @NonNull List<NotificationMessage> getMessages() {
return messages;
}
public void setMessages(@NonNull List<NotificationMessage> setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"messages\" is null.");
}
this.messages = setterArg;
}
/** Constructor is non-public to enforce null safety; use Builder. */
MessagingNotification() {}
public static final class Builder {
private @Nullable String title;
public @NonNull Builder setTitle(@NonNull String setterArg) {
this.title = setterArg;
return this;
}
private @Nullable Long id;
public @NonNull Builder setId(@NonNull Long setterArg) {
this.id = setterArg;
return this;
}
private @Nullable String channelId;
public @NonNull Builder setChannelId(@NonNull String setterArg) {
this.channelId = setterArg;
return this;
}
private @Nullable List<NotificationMessage> messages;
public @NonNull Builder setMessages(@NonNull List<NotificationMessage> setterArg) {
this.messages = setterArg;
return this;
}
public @NonNull MessagingNotification build() {
MessagingNotification pigeonReturn = new MessagingNotification();
pigeonReturn.setTitle(title);
pigeonReturn.setId(id);
pigeonReturn.setChannelId(channelId);
pigeonReturn.setMessages(messages);
return pigeonReturn;
}
}
@NonNull
ArrayList<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<Object>(4);
toListResult.add(title);
toListResult.add(id);
toListResult.add(channelId);
toListResult.add(messages);
return toListResult;
}
static @NonNull MessagingNotification fromList(@NonNull ArrayList<Object> list) {
MessagingNotification pigeonResult = new MessagingNotification();
Object title = list.get(0);
pigeonResult.setTitle((String) title);
Object id = list.get(1);
pigeonResult.setId((id == null) ? null : ((id instanceof Integer) ? (Integer) id : (Long) id));
Object channelId = list.get(2);
pigeonResult.setChannelId((String) channelId);
Object messages = list.get(3);
pigeonResult.setMessages((List<NotificationMessage>) messages);
return pigeonResult;
}
}
private static class NotificationsImplementationApiCodec extends StandardMessageCodec {
public static final NotificationsImplementationApiCodec INSTANCE = new NotificationsImplementationApiCodec();
private NotificationsImplementationApiCodec() {}
@Override
protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
switch (type) {
case (byte) 128:
return MessagingNotification.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 129:
return NotificationMessage.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 130:
return NotificationMessageContent.fromList((ArrayList<Object>) readValue(buffer));
default:
return super.readValueOfType(type, buffer);
}
}
@Override
protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
if (value instanceof MessagingNotification) {
stream.write(128);
writeValue(stream, ((MessagingNotification) value).toList());
} else if (value instanceof NotificationMessage) {
stream.write(129);
writeValue(stream, ((NotificationMessage) value).toList());
} else if (value instanceof NotificationMessageContent) {
stream.write(130);
writeValue(stream, ((NotificationMessageContent) value).toList());
} else {
super.writeValue(stream, value);
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface NotificationsImplementationApi {
void createNotificationChannel(@NonNull String title, @NonNull String id, @NonNull Boolean urgent);
void showMessagingNotification(@NonNull MessagingNotification notification);
/** The codec used by NotificationsImplementationApi. */
static @NonNull MessageCodec<Object> getCodec() {
return NotificationsImplementationApiCodec.INSTANCE;
}
/**Sets up an instance of `NotificationsImplementationApi` to handle messages through the `binaryMessenger`. */
static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable NotificationsImplementationApi api) {
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.NotificationsImplementationApi.createNotificationChannel", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
String titleArg = (String) args.get(0);
String idArg = (String) args.get(1);
Boolean urgentArg = (Boolean) args.get(2);
try {
api.createNotificationChannel(titleArg, idArg, urgentArg);
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.NotificationsImplementationApi.showMessagingNotification", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
MessagingNotification notificationArg = (MessagingNotification) args.get(0);
try {
api.showMessagingNotification(notificationArg);
wrapped.add(0, null);
}
catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
}

View File

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

View File

@ -0,0 +1,17 @@
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
class AndroidNotificationsImplementation extends NotificationsImplementation {
final NotificationsImplementationApi _api = NotificationsImplementationApi();
@override
Future<void> createNotificationChannel(String title, String id, bool urgent) async {
return _api.createNotificationChannel(title, id, urgent);
}
@override
Future<void> showMessagingNotification(MessagingNotification notification) async {
return _api.showMessagingNotification(notification);
}
}

View File

@ -2,6 +2,7 @@ import 'package:moxplatform_android/src/contacts_android.dart';
import 'package:moxplatform_android/src/crypto_android.dart'; import 'package:moxplatform_android/src/crypto_android.dart';
import 'package:moxplatform_android/src/isolate_android.dart'; import 'package:moxplatform_android/src/isolate_android.dart';
import 'package:moxplatform_android/src/media_android.dart'; import 'package:moxplatform_android/src/media_android.dart';
import 'package:moxplatform_android/src/notifications_android.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart'; import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
class MoxplatformAndroidPlugin extends MoxplatformInterface { class MoxplatformAndroidPlugin extends MoxplatformInterface {
@ -12,6 +13,7 @@ class MoxplatformAndroidPlugin extends MoxplatformInterface {
MoxplatformInterface.crypto = AndroidCryptographyImplementation(); MoxplatformInterface.crypto = AndroidCryptographyImplementation();
MoxplatformInterface.handler = AndroidIsolateHandler(); MoxplatformInterface.handler = AndroidIsolateHandler();
MoxplatformInterface.media = AndroidMediaScannerImplementation(); MoxplatformInterface.media = AndroidMediaScannerImplementation();
MoxplatformInterface.notifications = AndroidNotificationsImplementation();
} }
@override @override

View File

@ -41,4 +41,5 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
pigeon: 10.1.4
very_good_analysis: ^3.0.1 very_good_analysis: ^3.0.1

View File

@ -9,4 +9,7 @@ export 'src/isolate.dart';
export 'src/isolate_stub.dart'; export 'src/isolate_stub.dart';
export 'src/media.dart'; export 'src/media.dart';
export 'src/media_stub.dart'; export 'src/media_stub.dart';
export 'src/notifications.dart';
export 'src/notifications.g.dart';
export 'src/notifications_stub.dart';
export 'src/service.dart'; export 'src/service.dart';

View File

@ -6,6 +6,8 @@ import 'package:moxplatform_platform_interface/src/isolate.dart';
import 'package:moxplatform_platform_interface/src/isolate_stub.dart'; import 'package:moxplatform_platform_interface/src/isolate_stub.dart';
import 'package:moxplatform_platform_interface/src/media.dart'; import 'package:moxplatform_platform_interface/src/media.dart';
import 'package:moxplatform_platform_interface/src/media_stub.dart'; import 'package:moxplatform_platform_interface/src/media_stub.dart';
import 'package:moxplatform_platform_interface/src/notifications.dart';
import 'package:moxplatform_platform_interface/src/notifications_stub.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart';
abstract class MoxplatformInterface extends PlatformInterface { abstract class MoxplatformInterface extends PlatformInterface {
@ -17,6 +19,7 @@ abstract class MoxplatformInterface extends PlatformInterface {
static MediaScannerImplementation media = StubMediaScannerImplementation(); static MediaScannerImplementation media = StubMediaScannerImplementation();
static CryptographyImplementation crypto = StubCryptographyImplementation(); static CryptographyImplementation crypto = StubCryptographyImplementation();
static ContactsImplementation contacts = StubContactsImplementation(); static ContactsImplementation contacts = StubContactsImplementation();
static NotificationsImplementation notifications = StubNotificationsImplementation();
/// Return the current platform name. /// Return the current platform name.
Future<String?> getPlatformName(); Future<String?> getPlatformName();

View File

@ -0,0 +1,7 @@
import 'package:moxplatform_platform_interface/src/notifications.g.dart';
abstract class NotificationsImplementation {
Future<void> createNotificationChannel(String title, String id, bool urgent);
Future<void> showMessagingNotification(MessagingNotification notification);
}

View File

@ -0,0 +1,216 @@
// Autogenerated from Pigeon (v10.1.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
class NotificationMessageContent {
NotificationMessageContent({
this.body,
this.mime,
this.path,
});
/// The textual body of the message.
String? body;
/// The path and mime type of the media to show.
String? mime;
String? path;
Object encode() {
return <Object?>[
body,
mime,
path,
];
}
static NotificationMessageContent decode(Object result) {
result as List<Object?>;
return NotificationMessageContent(
body: result[0] as String?,
mime: result[1] as String?,
path: result[2] as String?,
);
}
}
class NotificationMessage {
NotificationMessage({
required this.sender,
required this.jid,
required this.content,
required this.timestamp,
this.avatarPath,
});
/// The sender of the message.
String sender;
/// The jid of the sender.
String jid;
/// The body of the message.
NotificationMessageContent content;
/// Milliseconds since epoch.
int timestamp;
/// The path to the avatar to use
String? avatarPath;
Object encode() {
return <Object?>[
sender,
jid,
content.encode(),
timestamp,
avatarPath,
];
}
static NotificationMessage decode(Object result) {
result as List<Object?>;
return NotificationMessage(
sender: result[0]! as String,
jid: result[1]! as String,
content: NotificationMessageContent.decode(result[2]! as List<Object?>),
timestamp: result[3]! as int,
avatarPath: result[4] as String?,
);
}
}
class MessagingNotification {
MessagingNotification({
required this.title,
required this.id,
required this.channelId,
required this.messages,
});
/// The title of the conversation.
String title;
/// The id of the notification.
int id;
/// The id of the notification channel the notification should appear on.
String channelId;
/// Messages to show.
List<NotificationMessage?> messages;
Object encode() {
return <Object?>[
title,
id,
channelId,
messages,
];
}
static MessagingNotification decode(Object result) {
result as List<Object?>;
return MessagingNotification(
title: result[0]! as String,
id: result[1]! as int,
channelId: result[2]! as String,
messages: (result[3] as List<Object?>?)!.cast<NotificationMessage?>(),
);
}
}
class _NotificationsImplementationApiCodec extends StandardMessageCodec {
const _NotificationsImplementationApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is MessagingNotification) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
} else if (value is NotificationMessage) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is NotificationMessageContent) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
}
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
return MessagingNotification.decode(readValue(buffer)!);
case 129:
return NotificationMessage.decode(readValue(buffer)!);
case 130:
return NotificationMessageContent.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
}
}
class NotificationsImplementationApi {
/// Constructor for [NotificationsImplementationApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
NotificationsImplementationApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;
static const MessageCodec<Object?> codec = _NotificationsImplementationApiCodec();
Future<void> createNotificationChannel(String arg_title, String arg_id, bool arg_urgent) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.NotificationsImplementationApi.createNotificationChannel', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_title, arg_id, arg_urgent]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<void> showMessagingNotification(MessagingNotification arg_notification) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.NotificationsImplementationApi.showMessagingNotification', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_notification]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}

View File

@ -0,0 +1,10 @@
import 'package:moxplatform_platform_interface/src/notifications.g.dart';
import 'package:moxplatform_platform_interface/src/notifications.dart';
class StubNotificationsImplementation extends NotificationsImplementation {
@override
Future<void> createNotificationChannel(String title, String id, bool urgent) async {}
@override
Future<void> showMessagingNotification(MessagingNotification notification) async {}
}

View File

@ -0,0 +1,77 @@
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'packages/moxplatform_platform_interface/lib/src/notifications.g.dart',
//kotlinOut: 'packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Notifications.g.kt',
//kotlinOptions: KotlinOptions(
// package: 'me.polynom.moxplatform_android',
//),
javaOut: 'packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Notifications.java',
javaOptions: JavaOptions(
package: 'me.polynom.moxplatform_android',
),
),
)
class NotificationMessageContent {
const NotificationMessageContent(
this.body,
this.mime,
this.path,
);
/// The textual body of the message.
final String? body;
/// The path and mime type of the media to show.
final String? mime;
final String? path;
}
class NotificationMessage {
const NotificationMessage(
this.sender,
this.content,
this.jid,
this.timestamp,
this.avatarPath,
);
/// The sender of the message.
final String sender;
/// The jid of the sender.
final String jid;
/// The body of the message.
final NotificationMessageContent content;
/// Milliseconds since epoch.
final int timestamp;
/// The path to the avatar to use
final String? avatarPath;
}
class MessagingNotification {
const MessagingNotification(this.title, this.id, this.messages, this.channelId);
/// The title of the conversation.
final String title;
/// The id of the notification.
final int id;
/// The id of the notification channel the notification should appear on.
final String channelId;
/// Messages to show.
final List<NotificationMessage?> messages;
}
@HostApi()
abstract class NotificationsImplementationApi {
void createNotificationChannel(String title, String id, bool urgent);
void showMessagingNotification(MessagingNotification notification);
}

View File

@ -4,3 +4,4 @@ environment:
sdk: '>=2.18.0 <3.0.0' sdk: '>=2.18.0 <3.0.0'
dev_dependencies: dev_dependencies:
melos: ^3.1.1 melos: ^3.1.1
pigeon: 10.1.4