feat: Add an API for creating direct share shortcuts
This commit is contained in:
@@ -4,4 +4,5 @@ class MoxplatformPlugin {
|
||||
static IsolateHandler get handler => MoxplatformInterface.handler;
|
||||
static MediaScannerImplementation get media => MoxplatformInterface.media;
|
||||
static CryptographyImplementation get crypto => MoxplatformInterface.crypto;
|
||||
static ContactsImplementation get contacts => MoxplatformInterface.contacts;
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ android {
|
||||
compileSdkVersion 31
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_14
|
||||
targetCompatibility JavaVersion.VERSION_14
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
@@ -36,4 +36,5 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
implementation 'androidx.core:core:1.10.1'
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -6,17 +6,28 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.Person;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
@@ -191,12 +202,79 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
|
||||
});
|
||||
hashingThread.start();
|
||||
break;
|
||||
case "recordSentMessage":
|
||||
ArrayList rargs = (ArrayList) call.arguments;
|
||||
try {
|
||||
recordSentMessage(
|
||||
(String) rargs.get(0),
|
||||
(String) rargs.get(1),
|
||||
(String) rargs.get(2),
|
||||
(int) rargs.get(3)
|
||||
);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
Log.e(TAG, "Failed to get classname");
|
||||
Log.e(TAG, ex.getMessage());
|
||||
}
|
||||
result.success(true);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void recordSentMessage(String name, String jid, String avatarPath, int fallbackIconType) throws ClassNotFoundException {
|
||||
// Very much inspired (or copied) from https://github.com/ShoutSocial/share_handler
|
||||
final String pkgName = context.getPackageName();
|
||||
final Intent intent = new Intent(context, Class.forName(pkgName + ".MainActivity"));
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
|
||||
// Compatibility with share_handler
|
||||
intent.putExtra("conversationIdentifier", jid);
|
||||
|
||||
final String shortcutTarget = pkgName + ".dynamic_share_target";
|
||||
final ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(context, name);
|
||||
builder
|
||||
.setShortLabel(name)
|
||||
.setIsConversation()
|
||||
.setCategories(Set.of(shortcutTarget))
|
||||
.setIntent(intent)
|
||||
.setLongLived(true);
|
||||
|
||||
// TODO: This is dumb. Maybe just raise the minimum Android version
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
||||
final Person.Builder personBuilder = new Person.Builder()
|
||||
.setKey(jid)
|
||||
.setName(name);
|
||||
|
||||
if (avatarPath != null) {
|
||||
final Bitmap bitmap = BitmapFactory.decodeFile(avatarPath);
|
||||
final IconCompat icon = IconCompat.createWithAdaptiveBitmap(bitmap);
|
||||
builder.setIcon(icon);
|
||||
personBuilder.setIcon(icon);
|
||||
} else {
|
||||
if (fallbackIconType == 0 || fallbackIconType == 1) {
|
||||
final int id = switch (fallbackIconType) {
|
||||
default:
|
||||
case 0: yield R.mipmap.person;
|
||||
case 1: yield R.mipmap.notes;
|
||||
};
|
||||
final IconCompat personIcon = IconCompat.createWithResource(
|
||||
context,
|
||||
id
|
||||
);
|
||||
builder.setIcon(personIcon);
|
||||
personBuilder.setIcon(personIcon);
|
||||
}
|
||||
}
|
||||
|
||||
builder.setPerson(personBuilder.build());
|
||||
}
|
||||
|
||||
final ShortcutInfoCompat shortcut = builder.build();
|
||||
ShortcutManagerCompat.addDynamicShortcuts(context, List.of(shortcut));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction() == null) return;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group android:scaleX="0.42988887"
|
||||
android:scaleY="0.42988887"
|
||||
android:translateX="6.8413334"
|
||||
android:translateY="6.8413334">
|
||||
<path
|
||||
android:pathData="M3,18h12v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h18v-2L3,11v2z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group android:scaleX="0.483625"
|
||||
android:scaleY="0.483625"
|
||||
android:translateX="6.1965"
|
||||
android:translateY="6.1965">
|
||||
<path
|
||||
android:pathData="m12,12c2.21,0 4,-1.79 4,-4C16,5.79 14.21,4 12,4 9.79,4 8,5.79 8,8c0,2.21 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"
|
||||
android:fillColor="#fffff9"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/notes_background"/>
|
||||
<foreground android:drawable="@drawable/notes_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/person_background"/>
|
||||
<foreground android:drawable="@drawable/person_foreground"/>
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 650 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="notes_background">#CF4AFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="person_background">#CF4AFF</color>
|
||||
</resources>
|
||||
32
packages/moxplatform_android/lib/src/contacts_android.dart
Normal file
32
packages/moxplatform_android/lib/src/contacts_android.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
class AndroidContactsImplementation extends ContactsImplementation {
|
||||
final _methodChannel = const MethodChannel('me.polynom.moxplatform_android');
|
||||
|
||||
@override
|
||||
Future<void> recordSentMessage(
|
||||
String name,
|
||||
String jid, {
|
||||
String? avatarPath,
|
||||
FallbackIconType fallbackIcon = FallbackIconType.none,
|
||||
}) async {
|
||||
// Ensure we always have an icon
|
||||
if (avatarPath != null) {
|
||||
assert(
|
||||
fallbackIcon != FallbackIconType.none,
|
||||
'If no avatar is specified, then a fallbackIcon must be set',
|
||||
);
|
||||
}
|
||||
|
||||
await _methodChannel.invokeMethod<void>(
|
||||
'recordSentMessage',
|
||||
[
|
||||
name,
|
||||
jid,
|
||||
avatarPath,
|
||||
fallbackIcon.id,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:moxplatform_android/src/contacts_android.dart';
|
||||
import 'package:moxplatform_android/src/crypto_android.dart';
|
||||
import 'package:moxplatform_android/src/isolate_android.dart';
|
||||
import 'package:moxplatform_android/src/media_android.dart';
|
||||
@@ -7,9 +8,10 @@ class MoxplatformAndroidPlugin extends MoxplatformInterface {
|
||||
static void registerWith() {
|
||||
// ignore: avoid_print
|
||||
print('MoxplatformAndroidPlugin: Registering implementation');
|
||||
MoxplatformInterface.contacts = AndroidContactsImplementation();
|
||||
MoxplatformInterface.crypto = AndroidCryptographyImplementation();
|
||||
MoxplatformInterface.handler = AndroidIsolateHandler();
|
||||
MoxplatformInterface.media = AndroidMediaScannerImplementation();
|
||||
MoxplatformInterface.crypto = AndroidCryptographyImplementation();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
library moxplatform_platform_interface;
|
||||
|
||||
export 'src/contacts.dart';
|
||||
export 'src/contacts_stub.dart';
|
||||
export 'src/crypto.dart';
|
||||
export 'src/crypto_stub.dart';
|
||||
export 'src/interface.dart';
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// The type of icon to use when no avatar path is provided.
|
||||
enum FallbackIconType {
|
||||
none(-1),
|
||||
person(0),
|
||||
notes(1);
|
||||
|
||||
const FallbackIconType(this.id);
|
||||
|
||||
// The ID of the fallback icon.
|
||||
final int id;
|
||||
}
|
||||
|
||||
// Wrapper around various contact APIs.
|
||||
// ignore: one_member_abstracts
|
||||
abstract class ContactsImplementation {
|
||||
Future<void> recordSentMessage(
|
||||
String name,
|
||||
String jid, {
|
||||
String? avatarPath,
|
||||
FallbackIconType fallbackIcon = FallbackIconType.none,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:moxplatform_platform_interface/src/contacts.dart';
|
||||
|
||||
class StubContactsImplementation extends ContactsImplementation {
|
||||
@override
|
||||
Future<void> recordSentMessage(
|
||||
String name,
|
||||
String jid, {
|
||||
String? avatarPath,
|
||||
FallbackIconType fallbackIcon = FallbackIconType.none,
|
||||
}) async {}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:moxplatform_platform_interface/src/contacts.dart';
|
||||
import 'package:moxplatform_platform_interface/src/contacts_stub.dart';
|
||||
import 'package:moxplatform_platform_interface/src/crypto.dart';
|
||||
import 'package:moxplatform_platform_interface/src/crypto_stub.dart';
|
||||
import 'package:moxplatform_platform_interface/src/isolate.dart';
|
||||
@@ -14,6 +16,7 @@ abstract class MoxplatformInterface extends PlatformInterface {
|
||||
static IsolateHandler handler = StubIsolateHandler();
|
||||
static MediaScannerImplementation media = StubMediaScannerImplementation();
|
||||
static CryptographyImplementation crypto = StubCryptographyImplementation();
|
||||
static ContactsImplementation contacts = StubContactsImplementation();
|
||||
|
||||
/// Return the current platform name.
|
||||
Future<String?> getPlatformName();
|
||||
|
||||
Reference in New Issue
Block a user