Compare commits

..

No commits in common. "42155d9e31bf103dc26a668b51e7dc9d4639232d" and "7a999cf86089f6756634a4f15afef93f68747918" have entirely different histories.

74 changed files with 636 additions and 2900 deletions

View File

@ -1,14 +0,0 @@
[general]
ignore=B5,B6,B7,B8
[title-max-length]
line-length=72
[title-trailing-punctuation]
[title-hard-tab]
[title-match-regex]
regex=^(feat|fix|chore|refactor)\((android|ios|linux|windows|macos|interface|base|repo)(,(android|ios|linux|windows|macos|interface|base))*\): [A-Z0-9].*$
[body-trailing-whitespace]
[body-first-line-empty]

View File

@ -1,278 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## 2023-09-03
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`moxplatform_android` - `v0.1.22`](#moxplatform_android---v0122)
- [`moxplatform_platform_interface` - `v0.1.22`](#moxplatform_platform_interface---v0122)
- [`moxplatform` - `v0.1.17+6`](#moxplatform---v01176)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `moxplatform` - `v0.1.17+6`
---
#### `moxplatform_android` - `v0.1.22`
- **FIX**(android): Fix notification grouping with the foreground notification.
- **FIX**(android): Fix creating notifications on Android 13.
- **FEAT**(interface,android): Allow passing an initial locale to the service.
- **FEAT**(base,interface,android): Move more logic to Moxxy.
- **FEAT**(android,interface): Implement video thumbnail generation.
#### `moxplatform_platform_interface` - `v0.1.22`
- **FIX**(repo): Remove notification examples.
- **FEAT**(interface,android): Allow passing an initial locale to the service.
- **FEAT**(base,interface,android): Move more logic to Moxxy.
- **FEAT**(android,interface): Implement video thumbnail generation.
## 2023-09-03
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`moxplatform_android` - `v0.1.21`](#moxplatform_android---v0121)
- [`moxplatform_platform_interface` - `v0.1.21`](#moxplatform_platform_interface---v0121)
- [`moxplatform` - `v0.1.17+5`](#moxplatform---v01175)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `moxplatform` - `v0.1.17+5`
---
#### `moxplatform_android` - `v0.1.21`
- **FIX**(android): Fix notification grouping with the foreground notification.
- **FIX**(android): Fix creating notifications on Android 13.
- **FEAT**(base,interface,android): Move more logic to Moxxy.
- **FEAT**(android,interface): Implement video thumbnail generation.
#### `moxplatform_platform_interface` - `v0.1.21`
- **FIX**(repo): Remove notification examples.
- **FEAT**(base,interface,android): Move more logic to Moxxy.
- **FEAT**(android,interface): Implement video thumbnail generation.
## 2023-08-05
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`moxplatform_android` - `v0.1.20`](#moxplatform_android---v0120)
- [`moxplatform_platform_interface` - `v0.1.20`](#moxplatform_platform_interface---v0120)
- [`moxplatform` - `v0.1.17+4`](#moxplatform---v01174)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `moxplatform` - `v0.1.17+4`
---
#### `moxplatform_android` - `v0.1.20`
- **FIX**(android): Fix FileProvider id.
- **FEAT**(android,interface): Handle battery optimisation.
#### `moxplatform_platform_interface` - `v0.1.20`
- **FEAT**(android,interface): Handle battery optimisation.
## 2023-08-05
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`moxplatform_android` - `v0.1.19`](#moxplatform_android---v0119)
- [`moxplatform_platform_interface` - `v0.1.19`](#moxplatform_platform_interface---v0119)
- [`moxplatform` - `v0.1.17+3`](#moxplatform---v01173)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `moxplatform` - `v0.1.17+3`
---
#### `moxplatform_android` - `v0.1.19`
- **FEAT**(android,interface): Handle battery optimisation.
#### `moxplatform_platform_interface` - `v0.1.19`
- **FEAT**(android,interface): Handle battery optimisation.
## 2023-08-04
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`moxplatform` - `v0.1.17+2`](#moxplatform---v01172)
- [`moxplatform_android` - `v0.1.18`](#moxplatform_android---v0118)
- [`moxplatform_platform_interface` - `v0.1.18`](#moxplatform_platform_interface---v0118)
---
#### `moxplatform` - `v0.1.17+2`
- **FIX**: Format and lint.
#### `moxplatform_android` - `v0.1.18`
- **FIX**: Format and lint.
- **FIX**: Fix self-replies after receiving another message.
- **FIX**: Add payload to all intents.
- **FIX**: Fix images disappearing after replying.
- **FEAT**: Move recordSentMessage to pigeon.
- **FEAT**: Move the crypto APIs to pigeon.
- **FEAT**: Adjust to Moxxy changes.
- **FEAT**: Store the avatar path also in the shared preferences.
- **FEAT**: Allow the sender's data being null.
- **FEAT**: Allow attaching arbitrary data to the notification.
- **FEAT**: Allow showing regular notifications.
- **FEAT**: Make i18n data a bit more persistent.
- **FEAT**: Color in the notification silhouette.
- **FEAT**: Allow setting the self-avatar.
- **FEAT**: Take care of i18n.
#### `moxplatform_platform_interface` - `v0.1.18`
- **FIX**: Format and lint.
- **FIX**: Add payload to all intents.
- **FEAT**: Move recordSentMessage to pigeon.
- **FEAT**: Move the crypto APIs to pigeon.
- **FEAT**: Allow the sender's data being null.
- **FEAT**: Allow attaching arbitrary data to the notification.
- **FEAT**: Allow showing regular notifications.
- **FEAT**: Color in the notification silhouette.
- **FEAT**: Allow setting the self-avatar.
- **FEAT**: Take care of i18n.
## 2023-07-21
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`moxplatform_android` - `v0.1.17+1`](#moxplatform_android---v01171)
- [`moxplatform` - `v0.1.17+1`](#moxplatform---v01171)
- [`moxplatform_platform_interface` - `v0.1.17+1`](#moxplatform_platform_interface---v01171)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `moxplatform` - `v0.1.17+1`
- `moxplatform_platform_interface` - `v0.1.17+1`
---
#### `moxplatform_android` - `v0.1.17+1`
- **FIX**: Accidentally used the name as the target's key. Oops.
- **FIX**: Fix minor things.
## 2023-07-21
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`moxplatform` - `v0.1.17`](#moxplatform---v0117)
- [`moxplatform_android` - `v0.1.17`](#moxplatform_android---v0117)
- [`moxplatform_platform_interface` - `v0.1.17`](#moxplatform_platform_interface---v0117)
---
#### `moxplatform` - `v0.1.17`
- **FIX**: Fix typecasting issue.
- **FEAT**: Add an API for creating direct share shortcuts.
- **FEAT**: Migrate to moxlib 0.2.0.
- **FEAT**: I forgot to bump dependency versions.
#### `moxplatform_android` - `v0.1.17`
- **FIX**: Fix typecasting issue.
- **FEAT**: Improve code quality of the cryptography.
- **FEAT**: Rewrite recordSentMessage in Kotlin.
- **FEAT**: Add an API for creating direct share shortcuts.
- **FEAT**: Migrate to moxlib 0.2.0.
- **FEAT**: I forgot to bump dependency versions.
- **FEAT**: Also hash the file on encryption and decryption.
#### `moxplatform_platform_interface` - `v0.1.17`
- **FIX**: Fix typecasting issue.
- **FEAT**: Add an API for creating direct share shortcuts.
- **FEAT**: Migrate to moxlib 0.2.0.
- **FEAT**: I forgot to bump dependency versions.
- **FEAT**: Also hash the file on encryption and decryption.

View File

@ -9,11 +9,9 @@ This repo is based on [very_good_flutter_plugin](https://github.com/VeryGoodOpen
The development of this package is based on [melos](https://pub.dev/packages/melos). The development of this package is based on [melos](https://pub.dev/packages/melos).
To make all packages link to each other locally, begin by running `melos bootstrap`. After editing To make all packages link to each other locally, begin by running `melos bootstrap`. After editing
the code and making your changes, please format the code using `melos run format` and lint using `melos run analyze`. the code and making your changes, please run `melos run analyze` to make sure that no linter warnings
are left inside the code.
When done - and a version bump is appropriate - bump the version of all packages using `melos version` and
publish with `melos publish --no-dry-run --git-tag-version`.
## Acknowledgements ## Acknowledgements
- [ekasetiawans](https://github.com/ekasetiawans) for [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service). moxplatform_android's service implementation is basically just a copy and paste of [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service). - [ekasetiawans](https://github.com/ekasetiawans) for [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service). moxplatform_android is basically just a copy and paste of [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service).

View File

@ -1,8 +1,4 @@
include: package:very_good_analysis/analysis_options.yaml include: package:very_good_analysis/analysis_options.yaml
analyzer:
exclude:
- lib/src/api.g.dart
linter: linter:
rules: rules:
public_member_api_docs: false public_member_api_docs: false

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 26 minSdkVersion flutter.minSdkVersion
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,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.8.21' ext.kotlin_version = '1.6.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
tasks.register("clean", Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View File

@ -3,60 +3,59 @@ 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:path/path.dart'; import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
/// The id of the notification channel.
const channelId = "me.polynom.moxplatform.testing3";
const otherChannelId = "me.polynom.moxplatform.testing4";
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
} }
class Sender { class MyApp extends StatelessWidget {
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 // This widget is the root of your application.
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'Moxplatform Demo', title: 'Flutter Demo',
theme: ThemeData( theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
), ),
home: const MyHomePage(), home: const MyHomePage(title: 'Flutter Demo Home Page'),
); );
} }
} }
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const MyHomePage({super.key}); const MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override @override
MyHomePageState createState() => MyHomePageState(); State<MyHomePage> createState() => _MyHomePageState();
} }
class MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
/// List of "Message senders". int _counter = 0;
final List<Sender> senders = const [
Sender('Mash Kyrielight', 'mash@example.org'),
Sender('Rio Tsukatsuki', 'rio@millenium'),
Sender('Raiden Shogun', 'raiden@tevhat'),
];
Future<void> _cryptoTest() async { Future<void> _incrementCounter() async {
final result = await FilePicker.platform.pickFiles(); final result = await FilePicker.platform.pickFiles();
if (result == null) { if (result == null) {
return; return;
@ -66,7 +65,7 @@ class MyHomePageState extends State<MyHomePage> {
final path = result.files.single.path; final path = result.files.single.path;
final enc = await MoxplatformPlugin.crypto.encryptFile( final enc = await MoxplatformPlugin.crypto.encryptFile(
path!, path!,
'$path.enc', path + '.enc',
Uint8List.fromList(List.filled(32, 1)), Uint8List.fromList(List.filled(32, 1)),
Uint8List.fromList(List.filled(16, 2)), Uint8List.fromList(List.filled(16, 2)),
CipherAlgorithm.aes256CbcPkcs7, CipherAlgorithm.aes256CbcPkcs7,
@ -75,165 +74,75 @@ class MyHomePageState extends State<MyHomePage> {
final end = DateTime.now(); final end = DateTime.now();
final diff = end.millisecondsSinceEpoch - start.millisecondsSinceEpoch; final diff = end.millisecondsSinceEpoch - start.millisecondsSinceEpoch;
// ignore: avoid_print
print('TIME: ${diff / 1000}s'); print('TIME: ${diff / 1000}s');
// ignore: avoid_print
print('DONE (${enc != null})'); print('DONE (${enc != null})');
final lengthEnc = await File('$path.enc').length(); final lengthEnc = await File(path + ".enc").length();
final lengthOrig = await File(path).length(); final lengthOrig = await File(path).length();
// ignore: avoid_print
print('Encrypted file is $lengthEnc Bytes large (Orig $lengthOrig)'); print('Encrypted file is $lengthEnc Bytes large (Orig $lengthOrig)');
await MoxplatformPlugin.crypto.decryptFile( await MoxplatformPlugin.crypto.decryptFile(
'$path.enc', path + '.enc',
'$path.dec', path + '.dec',
Uint8List.fromList(List.filled(32, 1)), Uint8List.fromList(List.filled(32, 1)),
Uint8List.fromList(List.filled(16, 2)), Uint8List.fromList(List.filled(16, 2)),
CipherAlgorithm.aes256CbcPkcs7, CipherAlgorithm.aes256CbcPkcs7,
'SHA-256', 'SHA-256',
); );
// ignore: avoid_print
print('DONE'); print('DONE');
final lengthDec = await File('$path.dec').length(); final lengthDec = await File(path + ".dec").length();
// ignore: avoid_print
print('Decrypted file is $lengthDec Bytes large (Orig $lengthOrig)'); print('Decrypted file is $lengthDec Bytes large (Orig $lengthOrig)');
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Moxplatform Demo'), // Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
), ),
body: Center( body: Center(
child: ListView( // Center is a layout widget. It takes a single child and positions it
children: [ // in the middle of the parent.
ElevatedButton( child: Column(
onPressed: _cryptoTest, // Column is also a layout widget. It takes a list of children and
child: const Text('Test cryptography'), // arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
), ),
ElevatedButton( Text(
onPressed: () { '$_counter',
MoxplatformPlugin.contacts.recordSentMessage('Hallo', 'Welt'); style: Theme.of(context).textTheme.headline4,
},
child: const Text('Test recordSentMessage (no fallback)'),
),
ElevatedButton(
onPressed: () {
MoxplatformPlugin.contacts.recordSentMessage('Person', 'Person',
fallbackIcon: FallbackIconType.person);
},
child: const Text('Test recordSentMessage (person fallback)'),
),
ElevatedButton(
onPressed: () {
MoxplatformPlugin.contacts.recordSentMessage('Notes', 'Notes',
fallbackIcon: FallbackIconType.notes);
},
child: const Text('Test recordSentMessage (notes fallback)'),
),
ElevatedButton(
onPressed: () async {
// ignore: avoid_print
print(await MoxplatformPlugin.platform.getPersistentDataPath());
},
child: const Text('Get data directory'),
),
ElevatedButton(
onPressed: () async {
// ignore: avoid_print
print(await MoxplatformPlugin.platform.getCacheDataPath());
},
child: const Text('Get cache directory'),
),
ElevatedButton(
onPressed: () async {
// ignore: avoid_print
print(await MoxplatformPlugin.platform
.isIgnoringBatteryOptimizations());
},
child: const Text('Is battery optimised?'),
),
ElevatedButton(
onPressed: () async {
await MoxplatformPlugin.platform
.openBatteryOptimisationSettings();
},
child: const Text('Open battery optimisation page'),
),
ElevatedButton(
onPressed: () async {
final result = await FilePicker.platform.pickFiles(
type: FileType.video,
);
if (result == null) return;
final path = result.files.single.path!;
final storagePath =
await MoxplatformPlugin.platform.getPersistentDataPath();
final mediaPath = join(storagePath, 'media');
if (!Directory(mediaPath).existsSync()) {
await Directory(mediaPath).create(recursive: true);
}
final internalPath = join(mediaPath, basename(path));
// ignore: avoid_print
print('Copying file');
await File(path).copy(internalPath);
// ignore: avoid_print
print('Generating thumbnail');
final thumbResult =
await MoxplatformPlugin.platform.generateVideoThumbnail(
internalPath,
'$internalPath.thumbnail.jpg',
720,
);
// ignore: avoid_print
print('Success: $thumbResult');
// ignore: use_build_context_synchronously
await showDialog<void>(
context: context,
builder: (context) => Image.file(
File('$internalPath.thumbnail.jpg'),
),
);
},
child: const Text('Thumbnail'),
),
ElevatedButton(
onPressed: () async {
final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.image, false);
print('Picked files $result');
},
child: const Text('Pick image'),
),
ElevatedButton(
onPressed: () async {
final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.image, true);
print('Picked files $result');
},
child: const Text('Pick multiple images'),
),
ElevatedButton(
onPressed: () async {
final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.imageAndVideo, true);
print('Picked files $result');
},
child: const Text('Pick multiple images and videos'),
),
ElevatedButton(
onPressed: () async {
final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.generic, true);
print('Picked files $result');
},
child: const Text('Pick multiple generic files'),
), ),
], ],
), ),
), ),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
); );
} }
} }

View File

@ -32,15 +32,13 @@ dependencies:
moxplatform: moxplatform:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.1.17+6 version: 0.1.15
moxplatform_android: moxplatform_android:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.1.22 version: 0.1.15
file_picker: 5.2.0+1 file_picker: 5.2.0+1
path: 1.8.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.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2

View File

@ -1,66 +1,6 @@
{ {
"nodes": { "nodes": {
"android-nixpkgs": {
"inputs": {
"devshell": "devshell",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1689798050,
"narHash": "sha256-ZyFPra7N0MF803o55dYQQyX9b/BmXr6QTCyN7slRThY=",
"owner": "tadfisher",
"repo": "android-nixpkgs",
"rev": "9aa0e2990da86de8ca203af313668851dcb9ea6e",
"type": "github"
},
"original": {
"owner": "tadfisher",
"repo": "android-nixpkgs",
"type": "github"
}
},
"devshell": {
"inputs": {
"nixpkgs": [
"android-nixpkgs",
"nixpkgs"
],
"systems": "systems"
},
"locked": {
"lastModified": 1688380630,
"narHash": "sha256-8ilApWVb1mAi4439zS3iFeIT0ODlbrifm/fegWwgHjA=",
"owner": "numtide",
"repo": "devshell",
"rev": "f9238ec3d75cefbb2b42a44948c4e8fb1ae9a205",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": { "locked": {
"lastModified": 1649676176, "lastModified": 1649676176,
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
@ -77,27 +17,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1689679375, "lastModified": 1660551188,
"narHash": "sha256-LHUC52WvyVDi9PwyL1QCpaxYWBqp4ir4iL6zgOkmcb8=", "narHash": "sha256-a1LARMMYQ8DPx1BgoI/UN4bXe12hhZkCNqdxNi6uS0g=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "684c17c429c42515bafb3ad775d2a710947f3d67", "rev": "441dc5d512153039f19ef198e662e4f3dbb9fd65",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1689752456,
"narHash": "sha256-VOChdECcEI8ixz8QY+YC4JaNEFwQd1V8bA0G4B28Ki0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7f256d7da238cb627ef189d56ed590739f42f13b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -109,39 +33,8 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"android-nixpkgs": "android-nixpkgs", "flake-utils": "flake-utils",
"flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs_2"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
} }
} }
}, },

View File

@ -3,10 +3,9 @@
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
android-nixpkgs.url = "github:tadfisher/android-nixpkgs";
}; };
outputs = { self, nixpkgs, flake-utils, android-nixpkgs }: flake-utils.lib.eachDefaultSystem (system: let outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
config = { config = {
@ -14,32 +13,29 @@
allowUnfree = true; allowUnfree = true;
}; };
}; };
# Everything to make Flutter happy android = pkgs.androidenv.composeAndroidPackages {
sdk = android-nixpkgs.sdk.${system} (sdkPkgs: with sdkPkgs; [ # TODO: Find a way to pin these
cmdline-tools-latest #toolsVersion = "26.1.1";
build-tools-30-0-3 #platformToolsVersion = "31.0.3";
build-tools-33-0-2 #buildToolsVersions = [ "31.0.0" ];
build-tools-34-0-0 #includeEmulator = true;
platform-tools #emulatorVersion = "30.6.3";
emulator platformVersions = [ "28" ];
patcher-v4 includeSources = false;
platforms-android-30 includeSystemImages = true;
platforms-android-31 systemImageTypes = [ "default" ];
platforms-android-33 abiVersions = [ "x86_64" ];
]); includeNDK = false;
pinnedJDK = pkgs.jdk17; useGoogleAPIs = false;
useGoogleTVAddOns = false;
};
pinnedJDK = pkgs.jdk;
in { in {
devShell = pkgs.mkShell { devShell = pkgs.mkShell {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
# Android flutter pinnedJDK android.platform-tools dart # Flutter
pinnedJDK gitlint jq # Code hygiene
sdk ripgrep # General utilities
# Flutter
flutter dart
# Code hygiene
gitlint
# Flutter dependencies for linux desktop # Flutter dependencies for linux desktop
atk atk
@ -63,13 +59,9 @@
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include"; CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ]; LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ];
ANDROID_HOME = "${sdk}/share/android-sdk"; ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
ANDROID_SDK_ROOT = "${sdk}/share/android-sdk";
JAVA_HOME = pinnedJDK; JAVA_HOME = pinnedJDK;
ANDROID_AVD_HOME = (toString ./.) + "/.android/avd";
# Fix an issue with Flutter using an older version of aapt2, which does not know
# an used parameter.
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${sdk}/share/android-sdk/build-tools/34.0.0/aapt2";
}; };
}); });
} }

View File

@ -8,7 +8,5 @@ command:
usePubspecOverrides: true usePubspecOverrides: true
scripts: scripts:
format:
exec: dart format .
analyze: analyze:
exec: flutter analyze exec: dart analyze .

View File

@ -1,34 +1,3 @@
## 0.1.17+6
- Update a dependency to the latest release.
## 0.1.17+5
- Update a dependency to the latest release.
## 0.1.17+4
- Update a dependency to the latest release.
## 0.1.17+3
- Update a dependency to the latest release.
## 0.1.17+2
- **FIX**: Format and lint.
## 0.1.17+1
- Update a dependency to the latest release.
## 0.1.17
- **FIX**: Fix typecasting issue.
- **FEAT**: Add an API for creating direct share shortcuts.
- **FEAT**: Migrate to moxlib 0.2.0.
- **FEAT**: I forgot to bump dependency versions.
## 0.1.11+2 ## 0.1.11+2
- Update a dependency to the latest release. - Update a dependency to the latest release.

View File

@ -1,6 +1,4 @@
library moxplatform; library moxplatform;
export 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
export 'src/plugin.dart'; export 'src/plugin.dart';
export 'src/types.dart'; export 'src/types.dart';

View File

@ -2,7 +2,6 @@ import 'package:moxplatform_platform_interface/moxplatform_platform_interface.da
class MoxplatformPlugin { class MoxplatformPlugin {
static IsolateHandler get handler => MoxplatformInterface.handler; static IsolateHandler get handler => MoxplatformInterface.handler;
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 PlatformImplementation get platform => MoxplatformInterface.platform;
} }

View File

@ -1,5 +1,4 @@
import 'package:moxlib/moxlib.dart'; import 'package:moxlib/awaitabledatasender.dart';
abstract class BackgroundCommand implements JsonImplementation {} abstract class BackgroundCommand implements JsonImplementation {}
abstract class BackgroundEvent implements JsonImplementation {} abstract class BackgroundEvent implements JsonImplementation {}

View File

@ -1,11 +1,11 @@
name: moxplatform name: moxplatform
description: Moxxy platform-specific code description: Moxxy platform-specific code
version: 0.1.17+6 version: 0.1.15
publish_to: https://git.polynom.me/api/packages/Moxxy/pub publish_to: https://git.polynom.me/api/packages/Moxxy/pub
homepage: https://codeberg.org/moxxy/moxplatform homepage: https://codeberg.org/moxxy/moxplatform
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.16.0 <3.0.0"
flutter: ">=2.10.0" flutter: ">=2.10.0"
flutter: flutter:
@ -22,17 +22,17 @@ dependencies:
moxlib: moxlib:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.2.0 version: ^0.1.4
moxplatform_android: moxplatform_android:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.1.22 version: ^0.1.15
moxplatform_platform_interface: moxplatform_platform_interface:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.1.22 version: ^0.1.15
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
plugin_platform_interface: ^2.1.2 plugin_platform_interface: ^2.1.2
very_good_analysis: ^3.0.1 very_good_analysis: ^2.4.0

View File

@ -1 +0,0 @@
pubspec_overrides.yaml

View File

@ -1,60 +1,3 @@
## 0.1.22
- **FIX**(android): Fix notification grouping with the foreground notification.
- **FIX**(android): Fix creating notifications on Android 13.
- **FEAT**(interface,android): Allow passing an initial locale to the service.
- **FEAT**(base,interface,android): Move more logic to Moxxy.
- **FEAT**(android,interface): Implement video thumbnail generation.
## 0.1.21
- **FIX**(android): Fix notification grouping with the foreground notification.
- **FIX**(android): Fix creating notifications on Android 13.
- **FEAT**(base,interface,android): Move more logic to Moxxy.
- **FEAT**(android,interface): Implement video thumbnail generation.
## 0.1.20
- **FIX**(android): Fix FileProvider id.
- **FEAT**(android,interface): Handle battery optimisation.
## 0.1.19
- **FEAT**(android,interface): Handle battery optimisation.
## 0.1.18
- **FIX**: Format and lint.
- **FIX**: Fix self-replies after receiving another message.
- **FIX**: Add payload to all intents.
- **FIX**: Fix images disappearing after replying.
- **FEAT**: Move recordSentMessage to pigeon.
- **FEAT**: Move the crypto APIs to pigeon.
- **FEAT**: Adjust to Moxxy changes.
- **FEAT**: Store the avatar path also in the shared preferences.
- **FEAT**: Allow the sender's data being null.
- **FEAT**: Allow attaching arbitrary data to the notification.
- **FEAT**: Allow showing regular notifications.
- **FEAT**: Make i18n data a bit more persistent.
- **FEAT**: Color in the notification silhouette.
- **FEAT**: Allow setting the self-avatar.
- **FEAT**: Take care of i18n.
## 0.1.17+1
- **FIX**: Accidentally used the name as the target's key. Oops.
- **FIX**: Fix minor things.
## 0.1.17
- **FIX**: Fix typecasting issue.
- **FEAT**: Improve code quality of the cryptography.
- **FEAT**: Rewrite recordSentMessage in Kotlin.
- **FEAT**: Add an API for creating direct share shortcuts.
- **FEAT**: Migrate to moxlib 0.2.0.
- **FEAT**: I forgot to bump dependency versions.
- **FEAT**: Also hash the file on encryption and decryption.
## 0.1.11+2 ## 0.1.11+2
- **REFACTOR**: Make version constraints looser. - **REFACTOR**: Make version constraints looser.

View File

@ -2,17 +2,13 @@ group 'me.polynom.moxplatform_android'
version '1.0' version '1.0'
buildscript { buildscript {
ext {
kotlin_version = '1.8.21'
}
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
@ -24,10 +20,9 @@ rootProject.allprojects {
} }
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
android { android {
compileSdkVersion 33 compileSdkVersion 31
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -35,14 +30,10 @@ android {
} }
defaultConfig { defaultConfig {
// What Moxxy currently uses minSdkVersion 16
minSdkVersion 26
targetSdkVersion 33
} }
} }
dependencies { dependencies {
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
implementation 'androidx.core:core:1.10.1'
implementation 'androidx.activity:activity:1.7.2'
} }

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -1,10 +1,8 @@
<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" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application> <application>
@ -28,5 +26,6 @@
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
</application> </application>
</manifest> </manifest>

View File

@ -1,507 +0,0 @@
// 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 Api {
/** 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;
}
public enum CipherAlgorithm {
AES128GCM_NO_PADDING(0),
AES256GCM_NO_PADDING(1),
AES256CBC_PKCS7(2);
final int index;
private CipherAlgorithm(final int index) {
this.index = index;
}
}
public enum FallbackIconType {
NONE(0),
PERSON(1),
NOTES(2);
final int index;
private FallbackIconType(final int index) {
this.index = index;
}
}
public enum FilePickerType {
IMAGE(0),
VIDEO(1),
IMAGE_AND_VIDEO(2),
GENERIC(3);
final int index;
private FilePickerType(final int index) {
this.index = index;
}
}
/** Generated class from Pigeon that represents data sent in messages. */
public static final class CryptographyResult {
private @NonNull byte[] plaintextHash;
public @NonNull byte[] getPlaintextHash() {
return plaintextHash;
}
public void setPlaintextHash(@NonNull byte[] setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"plaintextHash\" is null.");
}
this.plaintextHash = setterArg;
}
private @NonNull byte[] ciphertextHash;
public @NonNull byte[] getCiphertextHash() {
return ciphertextHash;
}
public void setCiphertextHash(@NonNull byte[] setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"ciphertextHash\" is null.");
}
this.ciphertextHash = setterArg;
}
/** Constructor is non-public to enforce null safety; use Builder. */
CryptographyResult() {}
public static final class Builder {
private @Nullable byte[] plaintextHash;
public @NonNull Builder setPlaintextHash(@NonNull byte[] setterArg) {
this.plaintextHash = setterArg;
return this;
}
private @Nullable byte[] ciphertextHash;
public @NonNull Builder setCiphertextHash(@NonNull byte[] setterArg) {
this.ciphertextHash = setterArg;
return this;
}
public @NonNull CryptographyResult build() {
CryptographyResult pigeonReturn = new CryptographyResult();
pigeonReturn.setPlaintextHash(plaintextHash);
pigeonReturn.setCiphertextHash(ciphertextHash);
return pigeonReturn;
}
}
@NonNull
ArrayList<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<Object>(2);
toListResult.add(plaintextHash);
toListResult.add(ciphertextHash);
return toListResult;
}
static @NonNull CryptographyResult fromList(@NonNull ArrayList<Object> list) {
CryptographyResult pigeonResult = new CryptographyResult();
Object plaintextHash = list.get(0);
pigeonResult.setPlaintextHash((byte[]) plaintextHash);
Object ciphertextHash = list.get(1);
pigeonResult.setCiphertextHash((byte[]) ciphertextHash);
return pigeonResult;
}
}
public interface Result<T> {
@SuppressWarnings("UnknownNullness")
void success(T result);
void error(@NonNull Throwable error);
}
private static class MoxplatformApiCodec extends StandardMessageCodec {
public static final MoxplatformApiCodec INSTANCE = new MoxplatformApiCodec();
private MoxplatformApiCodec() {}
@Override
protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
switch (type) {
case (byte) 128:
return CryptographyResult.fromList((ArrayList<Object>) readValue(buffer));
default:
return super.readValueOfType(type, buffer);
}
}
@Override
protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
if (value instanceof CryptographyResult) {
stream.write(128);
writeValue(stream, ((CryptographyResult) value).toList());
} else {
super.writeValue(stream, value);
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface MoxplatformApi {
/** Platform APIs */
@NonNull
String getPersistentDataPath();
@NonNull
String getCacheDataPath();
void openBatteryOptimisationSettings();
@NonNull
Boolean isIgnoringBatteryOptimizations();
/** Contacts APIs */
void recordSentMessage(@NonNull String name, @NonNull String jid, @Nullable String avatarPath, @NonNull FallbackIconType fallbackIcon);
/** Cryptography APIs */
void encryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Result<CryptographyResult> result);
void decryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Result<CryptographyResult> result);
void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Result<byte[]> result);
/** Media APIs */
@NonNull
Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth);
/** Picker */
void pickFiles(@NonNull FilePickerType type, @NonNull Boolean pickMultiple, @NonNull Result<List<String>> result);
/** The codec used by MoxplatformApi. */
static @NonNull MessageCodec<Object> getCodec() {
return MoxplatformApiCodec.INSTANCE;
}
/**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.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;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.openBatteryOptimisationSettings", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
api.openBatteryOptimisationSettings();
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.isIgnoringBatteryOptimizations", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
Boolean output = api.isIgnoringBatteryOptimizations();
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.recordSentMessage", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
String nameArg = (String) args.get(0);
String jidArg = (String) args.get(1);
String avatarPathArg = (String) args.get(2);
FallbackIconType fallbackIconArg = args.get(3) == null ? null : FallbackIconType.values()[(int) args.get(3)];
try {
api.recordSentMessage(nameArg, jidArg, avatarPathArg, fallbackIconArg);
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.encryptFile", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
String sourcePathArg = (String) args.get(0);
String destPathArg = (String) args.get(1);
byte[] keyArg = (byte[]) args.get(2);
byte[] ivArg = (byte[]) args.get(3);
CipherAlgorithm algorithmArg = args.get(4) == null ? null : CipherAlgorithm.values()[(int) args.get(4)];
String hashSpecArg = (String) args.get(5);
Result<CryptographyResult> resultCallback =
new Result<CryptographyResult>() {
public void success(CryptographyResult result) {
wrapped.add(0, result);
reply.reply(wrapped);
}
public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};
api.encryptFile(sourcePathArg, destPathArg, keyArg, ivArg, algorithmArg, hashSpecArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
String sourcePathArg = (String) args.get(0);
String destPathArg = (String) args.get(1);
byte[] keyArg = (byte[]) args.get(2);
byte[] ivArg = (byte[]) args.get(3);
CipherAlgorithm algorithmArg = args.get(4) == null ? null : CipherAlgorithm.values()[(int) args.get(4)];
String hashSpecArg = (String) args.get(5);
Result<CryptographyResult> resultCallback =
new Result<CryptographyResult>() {
public void success(CryptographyResult result) {
wrapped.add(0, result);
reply.reply(wrapped);
}
public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};
api.decryptFile(sourcePathArg, destPathArg, keyArg, ivArg, algorithmArg, hashSpecArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
String sourcePathArg = (String) args.get(0);
String hashSpecArg = (String) args.get(1);
Result<byte[]> resultCallback =
new Result<byte[]>() {
public void success(byte[] result) {
wrapped.add(0, result);
reply.reply(wrapped);
}
public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};
api.hashFile(sourcePathArg, hashSpecArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
String srcArg = (String) args.get(0);
String destArg = (String) args.get(1);
Number maxWidthArg = (Number) args.get(2);
try {
Boolean output = api.generateVideoThumbnail(srcArg, destArg, (maxWidthArg == null) ? null : maxWidthArg.longValue());
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.pickFiles", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
FilePickerType typeArg = args.get(0) == null ? null : FilePickerType.values()[(int) args.get(0)];
Boolean pickMultipleArg = (Boolean) args.get(1);
Result<List<String>> resultCallback =
new Result<List<String>>() {
public void success(List<String> result) {
wrapped.add(0, result);
reply.reply(wrapped);
}
public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};
api.pickFiles(typeArg, pickMultipleArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
}

View File

@ -1,13 +1,7 @@
package me.polynom.moxplatform_android; package me.polynom.moxplatform_android;
import static me.polynom.moxplatform_android.ConstantsKt.GROUP_KEY_FOREGROUND;
import static me.polynom.moxplatform_android.ConstantsKt.GROUP_KEY_MESSAGES;
import static me.polynom.moxplatform_android.ConstantsKt.GROUP_KEY_OTHER;
import static me.polynom.moxplatform_android.ConstantsKt.SHARED_PREFERENCES_KEY;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@ -91,15 +85,29 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa
} }
public static boolean isManuallyStopped(Context context) { public static boolean isManuallyStopped(Context context) {
return context.getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE).getBoolean(manuallyStoppedKey, false); return context.getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE).getBoolean(manuallyStoppedKey, false);
} }
public void setManuallyStopped(Context context, boolean value) { public void setManuallyStopped(Context context, boolean value) {
context.getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE) context.getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE)
.edit() .edit()
.putBoolean(manuallyStoppedKey, value) .putBoolean(manuallyStoppedKey, value)
.apply(); .apply();
} }
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Moxxy Background Service";
String description = "Executing Moxxy in the background";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel("FOREGROUND_DEFAULT", name, importance);
channel.setDescription(description);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
protected void updateNotificationInfo() { protected void updateNotificationInfo() {
String packageName = getApplicationContext().getPackageName(); String packageName = getApplicationContext().getPackageName();
Intent i = getPackageManager().getLaunchIntentForPackage(packageName); Intent i = getPackageManager().getLaunchIntentForPackage(packageName);
@ -114,7 +122,7 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa
| (PendingIntent.FLAG_MUTABLE & (aboveS ? PendingIntent.FLAG_MUTABLE : 0)) | (PendingIntent.FLAG_MUTABLE & (aboveS ? PendingIntent.FLAG_MUTABLE : 0))
); );
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, "foreground_service") NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, "FOREGROUND_DEFAULT")
.setSmallIcon(R.drawable.ic_service_icon) .setSmallIcon(R.drawable.ic_service_icon)
.setAutoCancel(true) .setAutoCancel(true)
.setOngoing(true) .setOngoing(true)
@ -143,7 +151,7 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa
FlutterInjector.instance().flutterLoader().startInitialization(getApplicationContext()); FlutterInjector.instance().flutterLoader().startInitialization(getApplicationContext());
} }
long entrypointHandle = getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE) long entrypointHandle = getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE)
.getLong(MoxplatformAndroidPlugin.entrypointKey, 0); .getLong(MoxplatformAndroidPlugin.entrypointKey, 0);
FlutterInjector.instance().flutterLoader().ensureInitializationComplete(getApplicationContext(), null); FlutterInjector.instance().flutterLoader().ensureInitializationComplete(getApplicationContext(), null);
FlutterCallbackInformation callback = FlutterCallbackInformation.lookupCallbackInformation(entrypointHandle); FlutterCallbackInformation callback = FlutterCallbackInformation.lookupCallbackInformation(entrypointHandle);
@ -229,6 +237,7 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
createNotificationChannel();
notificationBody = "Preparing..."; notificationBody = "Preparing...";
updateNotificationInfo(); updateNotificationInfo();
} }

View File

@ -1,58 +0,0 @@
package me.polynom.moxplatform_android
// The tag we use for logging.
const val TAG = "Moxplatform"
// The size of the buffer to hashing, encryption, and decryption in bytes.
const val BUFFER_SIZE = 8096
const val GROUP_KEY_FOREGROUND = "foreground"
const val GROUP_KEY_MESSAGES = "messages"
const val GROUP_KEY_OTHER = "other"
// The data key for text entered in the notification's reply field
const val REPLY_TEXT_KEY = "key_reply_text"
// The key for the notification id to mark as read
const val MARK_AS_READ_ID_KEY = "notification_id"
// Values for actions performed through the notification
const val REPLY_ACTION = "reply"
const val MARK_AS_READ_ACTION = "mark_as_read"
const val TAP_ACTION = "tap"
// Extra data keys for the intents that reach the NotificationReceiver
const val NOTIFICATION_EXTRA_JID_KEY = "jid"
const val NOTIFICATION_EXTRA_ID_KEY = "notification_id"
// Extra data keys for messages embedded inside the notification style
const val NOTIFICATION_MESSAGE_EXTRA_MIME = "mime"
const val NOTIFICATION_MESSAGE_EXTRA_PATH = "path"
const val MOXPLATFORM_FILEPROVIDER_ID = "me.polynom.moxplatform_android.fileprovider"
// Shared preferences keys
const val SHARED_PREFERENCES_KEY = "me.polynom.moxplatform_android"
const val SHARED_PREFERENCES_YOU_KEY = "you"
const val SHARED_PREFERENCES_MARK_AS_READ_KEY = "mark_as_read"
const val SHARED_PREFERENCES_REPLY_KEY = "reply"
const val SHARED_PREFERENCES_AVATAR_KEY = "avatar_path"
// TODO: Maybe try again to rewrite the entire plugin in Kotlin
//const val METHOD_CHANNEL_KEY = "me.polynom.moxplatform_android"
//const val BACKGROUND_METHOD_CHANNEL_KEY = METHOD_CHANNEL_KEY + "_bg"
// https://github.com/ekasetiawans/flutter_background_service/blob/e427f3b70138ec26f9671c2617f9061f25eade6f/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/BootReceiver.java#L20
//const val WAKELOCK_DURATION = 10*60*1000L;
// The name of the wakelock the background service manager holds.
//const val SERVICE_WAKELOCK_NAME = "BackgroundService.Lock"
//const val DATA_RECEIVER_METHOD_NAME = "dataReceived"
// Shared preferences keys
//const val SHARED_PREFERENCES_KEY = "me.polynom.moxplatform_android"
//const val SP_MANUALLY_STOPPED_KEY = "manually_stopped"
//const val SP_ENTRYPOINT_KEY = "entrypoint_handle"
//const val SP_EXTRA_DATA_KEY = "extra_data"
//const val SP_AUTO_START_AT_BOOT_KEY = "auto_start_at_boot"

View File

@ -1,158 +0,0 @@
package me.polynom.moxplatform_android
import android.util.Log
import me.polynom.moxplatform_android.Api.CipherAlgorithm
import me.polynom.moxplatform_android.Api.CryptographyResult
import java.io.FileInputStream
import java.io.FileOutputStream
import java.lang.Exception
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.concurrent.thread
// A FileOutputStream that continuously hashes whatever it writes to the file.
private class HashedFileOutputStream(name: String, hashAlgorithm: String) : FileOutputStream(name) {
private val digest: MessageDigest
init {
this.digest = MessageDigest.getInstance(hashAlgorithm)
}
override fun write(buffer: ByteArray, offset: Int, length: Int) {
super.write(buffer, offset, length)
digest.update(buffer, offset, length)
}
fun digest() : ByteArray {
return digest.digest()
}
}
fun getCipherSpecFromInteger(algorithm: CipherAlgorithm): String {
return when (algorithm) {
CipherAlgorithm.AES128GCM_NO_PADDING -> "AES_128/GCM/NoPadding"
CipherAlgorithm.AES256GCM_NO_PADDING -> "AES_256/GCM/NoPadding"
CipherAlgorithm.AES256CBC_PKCS7 -> "AES_256/CBC/PKCS7PADDING"
}
}
// Compute the hash, specified by @algorithm, of the file at path @srcFile. If an exception
// occurs, returns null. If everything went well, returns the raw hash of @srcFile.
fun hashFile(srcFile: String, algorithm: String, result: Api.Result<ByteArray?>) {
thread(start = true) {
val buffer = ByteArray(BUFFER_SIZE)
try {
val digest = MessageDigest.getInstance(algorithm)
val fInputStream = FileInputStream(srcFile)
var length: Int
while (true) {
length = fInputStream.read()
if (length <= 0) break
// Only update the digest if we read more than 0 bytes
digest.update(buffer, 0, length)
}
fInputStream.close()
result.success(digest.digest())
} catch (e: Exception) {
Log.e(TAG, "[hashFile]: " + e.stackTraceToString())
result.success(null)
}
}
}
// Encrypt the plaintext file at @src to @dest using the secret key @key and the IV @iv. The algorithm is chosen using @cipherAlgorithm. The file is additionally
// hashed before and after encryption using the hash algorithm specified by @hashAlgorithm.
fun encryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: CipherAlgorithm, hashAlgorithm: String, result: Api.Result<CryptographyResult?>) {
thread(start = true) {
val cipherSpec = getCipherSpecFromInteger(cipherAlgorithm)
val buffer = ByteArray(BUFFER_SIZE)
val secretKey = SecretKeySpec(key, cipherSpec)
try {
val digest = MessageDigest.getInstance(hashAlgorithm)
val cipher = Cipher.getInstance(cipherSpec)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
val fileInputStream = FileInputStream(src)
val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm)
val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher)
var length: Int
while (true) {
length = fileInputStream.read(buffer)
if (length <= 0) break
digest.update(buffer, 0, length)
cipherOutputStream.write(buffer, 0, length)
}
// Flush and close
cipherOutputStream.flush()
cipherOutputStream.close()
fileInputStream.close()
result.success(
CryptographyResult().apply {
plaintextHash = digest.digest()
ciphertextHash = fileOutputStream.digest()
}
)
} catch (e: Exception) {
Log.e(TAG, "[encryptAndHash]: " + e.stackTraceToString())
result.success(null)
}
}
}
// Decrypt the ciphertext file at @src to @dest using the secret key @key and the IV @iv. The algorithm is chosen using @cipherAlgorithm. The file is additionally
// hashed before and after decryption using the hash algorithm specified by @hashAlgorithm.
fun decryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: CipherAlgorithm, hashAlgorithm: String, result: Api.Result<CryptographyResult?>) {
thread(start = true) {
val cipherSpec = getCipherSpecFromInteger(cipherAlgorithm)
// Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3
val buffer = ByteArray(BUFFER_SIZE)
val secretKey = SecretKeySpec(key, cipherSpec)
try {
val digest = MessageDigest.getInstance(hashAlgorithm)
val cipher = Cipher.getInstance(cipherSpec)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
val fileInputStream = FileInputStream(src)
val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm)
val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher)
// Read, decrypt, and hash until we read 0 bytes
var length: Int
while (true) {
length = fileInputStream.read(buffer)
if (length <= 0) break
digest.update(buffer, 0, length)
cipherOutputStream.write(buffer, 0, length)
}
// Flush
cipherOutputStream.flush()
cipherOutputStream.close()
fileInputStream.close()
result.success(
CryptographyResult().apply {
plaintextHash = digest.digest()
ciphertextHash = fileOutputStream.digest()
}
)
} catch (e: Exception) {
Log.e(TAG, "[hashAndDecrypt]: " + e.stackTraceToString())
result.success(null)
}
}
}

View File

@ -0,0 +1,34 @@
package me.polynom.moxplatform_android;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashedFileOutputStream extends FileOutputStream {
public MessageDigest digest;
public HashedFileOutputStream(String name, String hashSpec) throws FileNotFoundException, NoSuchAlgorithmException {
super(name);
digest = MessageDigest.getInstance(hashSpec);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
digest.update(b, off, len);
}
public String getHexHash() {
StringBuffer result = new StringBuffer();
for (byte b : digest.digest()) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
public byte[] getHash() {
return digest.digest();
}
}

View File

@ -1,79 +1,43 @@
package me.polynom.moxplatform_android; package me.polynom.moxplatform_android;
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
import static androidx.core.content.ContextCompat.getSystemService;
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.PickerKt.filePickerRequest;
import static me.polynom.moxplatform_android.PickerKt.onActivityResultImpl;
import static me.polynom.moxplatform_android.RecordSentMessageKt.*;
import static me.polynom.moxplatform_android.ThumbnailsKt.generateVideoThumbnailImplementation;
import me.polynom.moxplatform_android.Api.*;
import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentResolver;
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.Bitmap;
import android.media.MediaMetadataRetriever;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.MediaStore;
import android.util.Log; import android.util.Log;
import android.util.Size;
import androidx.activity.result.PickVisualMediaRequest;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.ByteArrayOutputStream; import java.io.FileInputStream;
import java.io.File; import java.security.MessageDigest;
import java.io.FileOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; 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;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.EventChannel.EventSink;
import io.flutter.plugin.common.EventChannel.StreamHandler;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 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.PluginRegistry;
import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.JSONMethodCodec;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware, ActivityAware, PluginRegistry.ActivityResultListener, MoxplatformApi { public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware {
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";
public static final String sharedPrefKey = "me.polynom.moxplatform_android";
private static final String TAG = "moxplatform_android"; private static final String TAG = "moxplatform_android";
public static final String methodChannelKey = "me.polynom.moxplatform_android"; public static final String methodChannelKey = "me.polynom.moxplatform_android";
public static final String dataReceivedMethodName = "dataReceived"; public static final String dataReceivedMethodName = "dataReceived";
@ -81,19 +45,12 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
private static final List<MoxplatformAndroidPlugin> _instances = new ArrayList<>(); private static final List<MoxplatformAndroidPlugin> _instances = new ArrayList<>();
private BackgroundService service; private BackgroundService service;
private MethodChannel channel; private MethodChannel channel;
public static Activity activity;
private Context context; private Context context;
public MoxplatformAndroidPlugin() { public MoxplatformAndroidPlugin() {
_instances.add(this); _instances.add(this);
} }
@Override
public boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
return onActivityResultImpl(context, requestCode, resultCode, data);
}
@Override @Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), methodChannelKey); channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), methodChannelKey);
@ -103,8 +60,6 @@ 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));
MoxplatformApi.setup(flutterPluginBinding.getBinaryMessenger(), this);
Log.d(TAG, "Attached to engine"); Log.d(TAG, "Attached to engine");
} }
@ -112,7 +67,6 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context()); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context());
final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin(); final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin();
localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey)); localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey));
activity = registrar.activity();
final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE); final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE);
channel.setMethodCallHandler(plugin); channel.setMethodCallHandler(plugin);
@ -123,24 +77,29 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
/// Store the entrypoint handle and extra data for the background service. /// Store the entrypoint handle and extra data for the background service.
private void configure(long entrypointHandle, String extraData) { private void configure(long entrypointHandle, String extraData) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(sharedPrefKey, 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) { public static long getHandle(Context c) {
return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getLong(entrypointKey, 0); return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getLong(entrypointKey, 0);
} }
public static String getExtraData(Context c) { public static String getExtraData(Context c) {
return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getString(extraDataKey, ""); return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getString(extraDataKey, "");
} }
public static void setStartAtBoot(Context c, boolean value) { public static void setStartAtBoot(Context c, boolean value) {
c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).edit().putBoolean(autoStartAtBootKey, value).apply(); c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE)
.edit()
.putBoolean(autoStartAtBootKey, value)
.apply();
} }
public static boolean getStartAtBoot(Context c) { public static boolean getStartAtBoot(Context c) {
return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getBoolean(autoStartAtBootKey, false); return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getBoolean(autoStartAtBootKey, false);
} }
private boolean isRunning() { private boolean isRunning() {
@ -155,7 +114,7 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
} }
@Override @Override
public void onMethodCall(@NonNull MethodCall call, @NonNull io.flutter.plugin.common.MethodChannel.Result result) { public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
switch (call.method) { switch (call.method) {
case "configure": case "configure":
ArrayList args = (ArrayList) call.arguments; ArrayList args = (ArrayList) call.arguments;
@ -185,6 +144,53 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
} }
result.success(true); result.success(true);
break; break;
case "encryptFile":
Thread encryptionThread = new Thread(new Runnable() {
@Override
public void run() {
ArrayList args = (ArrayList) call.arguments;
String src = (String) args.get(0);
String dest = (String) args.get(1);
byte[] key = (byte[]) args.get(2);
byte[] iv = (byte[]) args.get(3);
int algorithm = (int) args.get(4);
String hashSpec = (String) args.get(5);
result.success(encryptFile(src, dest, key, iv, algorithm, hashSpec));
}
});
encryptionThread.start();
break;
case "decryptFile":
Thread decryptionThread = new Thread(new Runnable() {
@Override
public void run() {
ArrayList args = (ArrayList) call.arguments;
String src = (String) args.get(0);
String dest = (String) args.get(1);
byte[] key = (byte[]) args.get(2);
byte[] iv = (byte[]) args.get(3);
int algorithm = (int) args.get(4);
String hashSpec = (String) args.get(5);
result.success(decryptFile(src, dest, key, iv, algorithm, hashSpec));
}
});
decryptionThread.start();
break;
case "hashFile":
Thread hashingThread = new Thread(new Runnable() {
@Override
public void run() {
ArrayList args = (ArrayList) call.arguments;
String src = (String) args.get(0);
String hashSpec = (String) args.get(1);
result.success(hashFile(src, hashSpec));
}
});
hashingThread.start();
break;
default: default:
result.notImplemented(); result.notImplemented();
break; break;
@ -225,94 +231,118 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
this.service = null; this.service = null;
} }
@NonNull private String getCipherSpecFromInteger(int algorithm) {
@Override switch (algorithm) {
public String getPersistentDataPath() { case 0: return "AES_128/GCM/NoPadding";
return context.getFilesDir().getPath(); case 1: return "AES_256/GCM/NoPadding";
case 2: return "AES_256/CBC/PKCS7PADDING";
default:
Log.d(TAG, "INVALID ALGORITHM");
return "";
}
} }
@NonNull public HashMap<String, byte[]> encryptFile(String src, String dest, byte[] key, byte[] iv, int algorithm, String hashSpec) {
@Override String spec = getCipherSpecFromInteger(algorithm);
public String getCacheDataPath() { if (spec.isEmpty()) {
return context.getCacheDir().getPath(); return null;
} }
@Override // Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3
public void openBatteryOptimisationSettings() { byte[] buffer = new byte[8096];
final Uri packageUri = Uri.parse("package:" + context.getPackageName()); SecretKeySpec sk = new SecretKeySpec(key, spec);
Log.d(TAG, packageUri.toString()); try {
final Intent intent = new Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, packageUri); MessageDigest md = MessageDigest.getInstance(hashSpec);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Cipher cipher = Cipher.getInstance(spec);
context.startActivity(intent); cipher.init(Cipher.ENCRYPT_MODE, sk, new IvParameterSpec(iv));
FileInputStream fin = new FileInputStream(src);
HashedFileOutputStream fout = new HashedFileOutputStream(dest, hashSpec);
CipherOutputStream cout = new CipherOutputStream(fout, cipher);
int len = 0;
int bufLen = 0;
while (true) {
len = fin.read(buffer);
if (len != 0 && len > 0) {
md.update(buffer, 0, len);
cout.write(buffer, 0, len);
} else {
break;
}
}
cout.flush();
cout.close();
fin.close();
return new HashMap<String, byte[]>() {{
put("plaintext_hash", md.digest());
put("ciphertext_hash", fout.getHash());
}};
} catch (Exception ex) {
Log.d(TAG, "ENC: " + ex.getMessage());
return null;
}
} }
@NonNull public HashMap<String, byte[]> decryptFile(String src, String dest, byte[] key, byte[] iv, int algorithm, String hashSpec) {
@Override String spec = getCipherSpecFromInteger(algorithm);
public Boolean isIgnoringBatteryOptimizations() { if (spec.isEmpty()) {
final PowerManager pm = context.getSystemService(PowerManager.class); return null;
return pm.isIgnoringBatteryOptimizations(context.getPackageName());
} }
@Override // Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3
public void recordSentMessage(@NonNull String name, @NonNull String jid, @Nullable String avatarPath, @NonNull FallbackIconType fallbackIcon) { byte[] buffer = new byte[8096];
systemRecordSentMessage(context, name, jid, avatarPath, fallbackIcon); SecretKeySpec sk = new SecretKeySpec(key, spec);
try {
Cipher cipher = Cipher.getInstance(spec);
cipher.init(Cipher.DECRYPT_MODE, sk, new IvParameterSpec(iv));
FileInputStream fin = new FileInputStream(src);
HashedFileOutputStream fout = new HashedFileOutputStream(dest, hashSpec);
CipherOutputStream cout = new CipherOutputStream(fout, cipher);
MessageDigest md = MessageDigest.getInstance(hashSpec);
Log.d(TAG, "Reading from " + src + ", writing to " + dest);
int len = 0;
while (true) {
len = fin.read(buffer);
if (len != 0 && len > 0) {
cout.write(buffer, 0, len);
md.update(buffer, 0, len);
} else {
break;
}
}
cout.flush();
cout.close();
fin.close();
return new HashMap<String, byte[]>() {{
put("plaintext_hash", md.digest());
put("ciphertext_hash", fout.getHash());
}};
} catch (Exception ex) {
Log.d(TAG, "DEC: " + ex.getMessage());
return null;
}
} }
@Override public byte[] hashFile(String src, String algorithm) {
public void encryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Api.Result<CryptographyResult> result) { byte[] buffer = new byte[8096];
CryptoKt.encryptAndHash( try {
sourcePath, MessageDigest md = MessageDigest.getInstance(algorithm);
destPath, FileInputStream fin = new FileInputStream(src);
key, int len = 0;
iv, while (true) {
algorithm, len = fin.read(buffer);
hashSpec, if (len != 0 && len > 0) {
result md.update(buffer, 0, len);
); } else {
break;
}
} }
@Override return md.digest();
public void decryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Api.Result<CryptographyResult> result) { } catch (Exception ex) {
CryptoKt.decryptAndHash( Log.d(TAG, "Hash: " + ex.getMessage());
sourcePath, return null;
destPath,
key,
iv,
algorithm,
hashSpec,
result
);
} }
@Override
public void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Api.Result<byte[]> result) {
CryptoKt.hashFile(sourcePath, hashSpec, result);
} }
@NonNull
@Override
public Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth) {
return generateVideoThumbnailImplementation(src, dest, maxWidth);
}
@Override
public void pickFiles(@NonNull FilePickerType type, @NonNull Boolean pickMultiple, @NonNull Api.Result<List<String>> result) {
filePickerRequest(context, activity, type, pickMultiple, result);
}
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
activity = binding.getActivity();
binding.addActivityResultListener(this);
Log.d(TAG, "Activity attached");
}
@Override
public void onDetachedFromActivity() {}
@Override
public void onDetachedFromActivityForConfigChanges() {}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {}
} }

View File

@ -1,226 +0,0 @@
package me.polynom.moxplatform_android
import android.app.Activity
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import android.webkit.MimeTypeMap
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.UUID
object RequestTracker {
val requests: MutableMap<Int, Api.Result<Any>> = mutableMapOf()
}
const val PICK_FILE_REQUEST = 41;
const val PICK_FILES_REQUEST = 42;
fun genericFilePickerRequest(activity: Activity?, pickMultiple: Boolean, result: Api.Result<List<String>>) {
val pickIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, pickMultiple);
}
RequestTracker.requests[PICK_FILE_REQUEST] = result as Api.Result<Any>;
activity?.startActivityForResult(pickIntent, PICK_FILE_REQUEST)
}
fun filePickerRequest(
context: Context,
activity: Activity?,
type: Api.FilePickerType,
pickMultiple: Boolean,
result: Api.Result<List<String>>
) {
if (type == Api.FilePickerType.GENERIC) {
return genericFilePickerRequest(activity, pickMultiple, result)
}
val pickerType = when (type) {
Api.FilePickerType.IMAGE -> ActivityResultContracts.PickVisualMedia.ImageOnly
Api.FilePickerType.VIDEO -> ActivityResultContracts.PickVisualMedia.VideoOnly
Api.FilePickerType.IMAGE_AND_VIDEO -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
// TODO
Api.FilePickerType.GENERIC -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
}
val pick = when (pickMultiple) {
false -> ActivityResultContracts.PickVisualMedia()
true -> ActivityResultContracts.PickMultipleVisualMedia()
}
val requestCode = if (pickMultiple) PICK_FILES_REQUEST else PICK_FILE_REQUEST
val pickIntent = pick.createIntent(context, PickVisualMediaRequest(pickerType))
RequestTracker.requests[requestCode] = result as Api.Result<Any>
Log.d(TAG, "Tracked size ${RequestTracker.requests.size}")
if (activity == null) {
Log.w(TAG, "Activity is null")
}
activity?.startActivityForResult(pickIntent, requestCode);
}
/**
* Copies the file from the given content URI to a temporary directory, retaining the original
* file name if possible.
*
*
* Each file is placed in its own directory to avoid conflicts according to the following
* scheme: {cacheDir}/{randomUuid}/{fileName}
*
*
* File extension is changed to match MIME type of the file, if known. Otherwise, the extension
* is left unchanged.
*
*
* If the original file name is unknown, a predefined "image_picker" filename is used and the
* file extension is deduced from the mime type (with fallback to ".jpg" in case of failure).
*/
fun getPathFromUri(context: Context, uri: Uri): String? {
try {
context.contentResolver.openInputStream(uri).use { inputStream ->
val uuid = UUID.randomUUID().toString()
val targetDirectory = File(context.cacheDir, uuid)
targetDirectory.mkdir()
// TODO(SynSzakala) according to the docs, `deleteOnExit` does not work reliably on Android; we should preferably
// just clear the picked files after the app startup.
targetDirectory.deleteOnExit()
var fileName = getImageName(context, uri)
var extension = getImageExtension(context, uri)
if (fileName == null) {
Log.w("FileUtils", "Cannot get file name for $uri")
if (extension == null) extension = ".jpg"
fileName = "image_picker$extension"
} else if (extension != null) {
fileName = getBaseName(fileName) + extension
}
val file = File(targetDirectory, fileName)
FileOutputStream(file).use { outputStream ->
copy(inputStream!!, outputStream)
return file.path
}
}
} catch (e: IOException) {
// If closing the output stream fails, we cannot be sure that the
// target file was written in full. Flushing the stream merely moves
// the bytes into the OS, not necessarily to the file.
return null
} catch (e: SecurityException) {
// Calling `ContentResolver#openInputStream()` has been reported to throw a
// `SecurityException` on some devices in certain circumstances. Instead of crashing, we
// return `null`.
//
// See https://github.com/flutter/flutter/issues/100025 for more details.
return null
}
}
/** @return extension of image with dot, or null if it's empty.
*/
private fun getImageExtension(context: Context, uriImage: Uri): String? {
val extension: String?
extension = try {
if (uriImage.scheme == ContentResolver.SCHEME_CONTENT) {
val mime = MimeTypeMap.getSingleton()
mime.getExtensionFromMimeType(context.contentResolver.getType(uriImage))
} else {
MimeTypeMap.getFileExtensionFromUrl(
Uri.fromFile(File(uriImage.path)).toString()
)
}
} catch (e: Exception) {
return null
}
return if (extension == null || extension.isEmpty()) {
null
} else ".$extension"
}
/** @return name of the image provided by ContentResolver; this may be null.
*/
private fun getImageName(context: Context, uriImage: Uri): String? {
queryImageName(context, uriImage).use { cursor ->
return if (cursor == null || !cursor.moveToFirst() || (cursor.columnCount < 1)) null else cursor.getString(
0
)
}
}
private fun queryImageName(context: Context, uriImage: Uri): Cursor? {
return context
.contentResolver
.query(uriImage, arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null)
}
@Throws(IOException::class)
private fun copy(`in`: InputStream, out: OutputStream) {
val buffer = ByteArray(4 * 1024)
var bytesRead: Int
while (`in`.read(buffer).also { bytesRead = it } != -1) {
out.write(buffer, 0, bytesRead)
}
out.flush()
}
private fun getBaseName(fileName: String): String {
val lastDotIndex = fileName.lastIndexOf('.')
return if (lastDotIndex < 0) {
fileName
} else fileName.substring(0, lastDotIndex)
// Basename is everything before the last '.'.
}
fun onActivityResultImpl(context: Context, requestCode: Int, resultCode: Int, data: Intent?): Boolean {
Log.d(TAG, "Got result for $requestCode with result $resultCode (${data?.action})")
if (requestCode == PICK_FILE_REQUEST || requestCode == PICK_FILES_REQUEST) {
Log.d(TAG, "Extra data ${data?.data}")
val result = RequestTracker.requests.remove(requestCode);
if (result == null) {
Log.w(TAG, "Untracked response.")
return false;
}
if (resultCode != Activity.RESULT_OK) {
// No files picked
result!!.success(listOf<String>())
return true;
}
val pickedMultiple = requestCode == PICK_FILES_REQUEST
val pickedFiles = mutableListOf<String>()
if (pickedMultiple) {
val intentUris = data!!.clipData
if (data!!.clipData != null) {
for (i in 0 until data!!.clipData!!.itemCount) {
val path = getPathFromUri(context, data!!.clipData!!.getItemAt(i).uri)
if (path != null) {
pickedFiles.add(path )
}
}
}
} else {
val path = getPathFromUri(context, data!!.data!!)
if (path != null) {
pickedFiles.add(path )
}
}
result!!.success(pickedFiles)
return true;
}
return false;
}

View File

@ -1,65 +0,0 @@
package me.polynom.moxplatform_android
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import androidx.core.app.Person
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import me.polynom.moxplatform_android.Api.FallbackIconType
/*
* Uses Android's direct share API to create dynamic share targets that are compatible
* with share_handler's media handling.
* NOTE: The "system" prefix is to prevent confusion between pigeon's abstract recordSentMessage
* method and this one.
* */
fun systemRecordSentMessage(context: Context, name: String, jid: String, avatarPath: String?, fallbackIcon: FallbackIconType) {
val pkgName = context.packageName
val intent = Intent(context, Class.forName("$pkgName.MainActivity")).apply {
action = Intent.ACTION_SEND
// Compatibility with share_handler
putExtra("conversationIdentifier", jid)
}
val shortcutTarget = "$pkgName.dynamic_share_target"
val shortcutBuilder = ShortcutInfoCompat.Builder(context, jid).apply {
setShortLabel(name)
setIsConversation()
setCategories(setOf(shortcutTarget))
setIntent(intent)
setLongLived(true)
}
val personBuilder = Person.Builder().apply {
setKey(jid)
setName(name)
}
// Either set an avatar image OR a fallback icon
if (avatarPath != null) {
val icon = IconCompat.createWithAdaptiveBitmap(
BitmapFactory.decodeFile(avatarPath),
)
shortcutBuilder.setIcon(icon)
personBuilder.setIcon(icon)
} else {
val resourceId = when(fallbackIcon) {
FallbackIconType.PERSON -> R.mipmap.person
FallbackIconType.NOTES -> R.mipmap.notes
// "Fallthrough"
else -> R.mipmap.person
}
val icon = IconCompat.createWithResource(context, resourceId)
shortcutBuilder.setIcon(icon)
personBuilder.setIcon(icon)
}
shortcutBuilder.setPerson(personBuilder.build())
ShortcutManagerCompat.addDynamicShortcuts(
context,
listOf(shortcutBuilder.build()),
)
}

View File

@ -1,43 +0,0 @@
package me.polynom.moxplatform_android
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.util.Log
import java.io.FileOutputStream
/*
* Generate a video thumbnail using the first frame of the video at @src. Afterwards, scale it
* down such that its width is equal to @maxWidth (while keeping the aspect ratio) and write it to
* @dest.
*
* If everything went well, returns true. If we're unable to generate the thumbnail, returns false.
* */
fun generateVideoThumbnailImplementation(src: String, dest: String, maxWidth: Long): Boolean {
try {
val mmr = MediaMetadataRetriever().apply {
setDataSource(src)
}
val unscaledThumbnail = mmr.getFrameAtTime(0) ?: return false
// Scale down the thumbnail while keeping the aspect ratio
val scalingFactor = maxWidth.toDouble() / unscaledThumbnail.width;
Log.d(TAG, "Scaling to $maxWidth from ${unscaledThumbnail.width} with scalingFactor $scalingFactor");
val thumbnail = Bitmap.createScaledBitmap(
unscaledThumbnail,
(unscaledThumbnail.width * scalingFactor).toInt(),
(unscaledThumbnail.height * scalingFactor).toInt(),
false,
)
// Write it to the destination file
val fos = FileOutputStream(dest)
thumbnail.compress(Bitmap.CompressFormat.JPEG, 75, fos)
fos.flush()
fos.close()
return true;
} catch (ex: Exception) {
Log.e(TAG, "Failed to create thumbnail for $src: ${ex.message}")
ex.printStackTrace()
return false;
}
}

View File

@ -1,5 +0,0 @@
<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

@ -1,5 +0,0 @@
<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="M17.34,20l-3.54,-3.54l1.41,-1.41l2.12,2.12l4.24,-4.24L23,14.34L17.34,20zM12,17c0,-3.87 3.13,-7 7,-7c1.08,0 2.09,0.25 3,0.68V4c0,-1.1 -0.9,-2 -2,-2H4C2.9,2 2,2.9 2,4v18l4,-4h6v0c0,-0.17 0.01,-0.33 0.03,-0.5C12.01,17.34 12,17.17 12,17z"/>
</vector>

View File

@ -1,14 +0,0 @@
<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>

View File

@ -1,14 +0,0 @@
<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>

View File

@ -1,5 +0,0 @@
<vector android:autoMirrored="true" 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="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
</vector>

View File

@ -1,5 +0,0 @@
<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

@ -1,5 +0,0 @@
<?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>

View File

@ -1,5 +0,0 @@
<?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.

Before

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="notes_background">#CF4AFF</color>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="person_background">#CF4AFF</color>
</resources>

View File

@ -1,5 +1,6 @@
library moxplatform_android; library moxplatform_android;
export 'src/isolate_android.dart'; export 'src/isolate_android.dart';
export 'src/media_android.dart';
export 'src/plugin_android.dart'; export 'src/plugin_android.dart';
export 'src/service_android.dart'; export 'src/service_android.dart';

View File

@ -1,28 +0,0 @@
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
class AndroidContactsImplementation extends ContactsImplementation {
final MoxplatformApi _api = MoxplatformApi();
@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',
);
}
return _api.recordSentMessage(
name,
jid,
avatarPath,
fallbackIcon,
);
}
}

View File

@ -1,49 +1,59 @@
import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart'; import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
class AndroidCryptographyImplementation extends CryptographyImplementation { class AndroidCryptographyImplementation extends CryptographyImplementation {
final MoxplatformApi _api = MoxplatformApi(); final _methodChannel = const MethodChannel('me.polynom.moxplatform_android');
@override @override
Future<CryptographyResult?> encryptFile( Future<CryptographyResult?> encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec) async {
String sourcePath, final dynamic resultRaw = await _methodChannel.invokeMethod<dynamic>('encryptFile', [
String destPath,
Uint8List key,
Uint8List iv,
CipherAlgorithm algorithm,
String hashSpec,
) async {
return _api.encryptFile(
sourcePath, sourcePath,
destPath, destPath,
key, key,
iv, iv,
algorithm, algorithm.toInt(),
hashSpec, hashSpec,
]);
if (resultRaw == null) return null;
// ignore: argument_type_not_assignable
final result = Map<String, dynamic>.from(resultRaw);
return CryptographyResult(
result['plaintext_hash']! as Uint8List,
result['ciphertext_hash']! as Uint8List,
); );
} }
@override @override
Future<CryptographyResult?> decryptFile( Future<CryptographyResult?> decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec) async {
String sourcePath, final dynamic resultRaw = await _methodChannel.invokeMethod<dynamic>('decryptFile', [
String destPath,
Uint8List key,
Uint8List iv,
CipherAlgorithm algorithm,
String hashSpec,
) async {
return _api.decryptFile(
sourcePath, sourcePath,
destPath, destPath,
key, key,
iv, iv,
algorithm, algorithm.toInt(),
hashSpec, hashSpec,
]);
if (resultRaw == null) return null;
// ignore: argument_type_not_assignable
final result = Map<String, dynamic>.from(resultRaw);
return CryptographyResult(
result['plaintext_hash']! as Uint8List,
result['ciphertext_hash']! as Uint8List,
); );
} }
@override @override
Future<Uint8List?> hashFile(String sourcePath, String hashSpec) async { Future<Uint8List?> hashFile(String path, String hashSpec) async {
return _api.hashFile(sourcePath, hashSpec); final dynamic resultsRaw = await _methodChannel.invokeMethod<dynamic>('hashFile', [
path,
hashSpec,
]);
if (resultsRaw == null) return null;
return resultsRaw as Uint8List;
} }
} }

View File

@ -4,16 +4,16 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:moxlib/moxlib.dart'; import 'package:moxlib/awaitabledatasender.dart';
import 'package:moxplatform/moxplatform.dart'; import 'package:moxplatform/moxplatform.dart';
import 'package:moxplatform_android/src/service_android.dart'; import 'package:moxplatform_android/src/service_android.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
/// An [AwaitableDataSender] that uses flutter_background_service. /// An [AwaitableDataSender] that uses flutter_background_service.
class BackgroundServiceDataSender class BackgroundServiceDataSender extends AwaitableDataSender<BackgroundCommand, BackgroundEvent> {
extends AwaitableDataSender<BackgroundCommand, BackgroundEvent> {
BackgroundServiceDataSender() BackgroundServiceDataSender()
: _channel = const MethodChannel('me.polynom.moxplatform_android'), : _channel = const MethodChannel('me.polynom.moxplatform_android'), super();
super();
final MethodChannel _channel; final MethodChannel _channel;
@override @override
@ -39,27 +39,23 @@ Future<void> androidEntrypoint() async {
); );
final data = jsonDecode(result!) as Map<String, dynamic>; final data = jsonDecode(result!) as Map<String, dynamic>;
final entrypointHandle = data['genericEntrypoint']! as int; final entrypointHandle = data['genericEntrypoint']! as int;
final entrypointCallbackHandle = final entrypointCallbackHandle = CallbackHandle.fromRawHandle(entrypointHandle);
CallbackHandle.fromRawHandle(entrypointHandle); final entrypoint = PluginUtilities.getCallbackFromHandle(entrypointCallbackHandle);
final entrypoint =
PluginUtilities.getCallbackFromHandle(entrypointCallbackHandle);
final handleUIEventHandle = data['eventHandle']! as int; final handleUIEventHandle = data['eventHandle']! as int;
final handleUIEventCallbackHandle = final handleUIEventCallbackHandle = CallbackHandle.fromRawHandle(handleUIEventHandle);
CallbackHandle.fromRawHandle(handleUIEventHandle); final handleUIEvent = PluginUtilities.getCallbackFromHandle(handleUIEventCallbackHandle);
final handleUIEvent =
PluginUtilities.getCallbackFromHandle(handleUIEventCallbackHandle);
final srv = AndroidBackgroundService(); final srv = AndroidBackgroundService();
GetIt.I.registerSingleton<BackgroundService>(srv); GetIt.I.registerSingleton<BackgroundService>(srv);
srv.init( srv.init(
entrypoint! as Future<void> Function(String), entrypoint! as Future<void> Function(),
handleUIEvent! as Future<void> Function(Map<String, dynamic>? data), handleUIEvent! as Future<void> Function(Map<String, dynamic>? data),
data['initialLocale']! as String,
); );
} }
/// The Android specific implementation of the [IsolateHandler]. /// The Android specific implementation of the [IsolateHandler].
class AndroidIsolateHandler extends IsolateHandler { class AndroidIsolateHandler extends IsolateHandler {
AndroidIsolateHandler() AndroidIsolateHandler()
: _channel = const MethodChannel('me.polynom.moxplatform_android'), : _channel = const MethodChannel('me.polynom.moxplatform_android'),
_dataSender = BackgroundServiceDataSender(), _dataSender = BackgroundServiceDataSender(),
@ -70,9 +66,7 @@ class AndroidIsolateHandler extends IsolateHandler {
final Logger _log; final Logger _log;
@override @override
Future<void> attach( Future<void> attach(Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent) async {
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
) async {
_channel.setMethodCallHandler((MethodCall call) async { _channel.setMethodCallHandler((MethodCall call) async {
final args = call.arguments as String; final args = call.arguments as String;
await handleIsolateEvent(jsonDecode(args) as Map<String, dynamic>); await handleIsolateEvent(jsonDecode(args) as Map<String, dynamic>);
@ -81,25 +75,20 @@ class AndroidIsolateHandler extends IsolateHandler {
@override @override
Future<void> start( Future<void> start(
Future<void> Function(String initialLocale) entrypoint, Future<void> Function() entrypoint,
Future<void> Function(Map<String, dynamic>? data) handleUIEvent, Future<void> Function(Map<String, dynamic>? data) handleUIEvent,
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent, Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
String initialLocale,
) async { ) async {
_log.finest('Called start'); _log.finest('Called start');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
final androidEntrypointHandle = final androidEntrypointHandle = PluginUtilities.getCallbackHandle(androidEntrypoint)!.toRawHandle();
PluginUtilities.getCallbackHandle(androidEntrypoint)!.toRawHandle();
_log.finest('androidEntrypointHandle: $androidEntrypointHandle'); _log.finest('androidEntrypointHandle: $androidEntrypointHandle');
await _channel.invokeMethod<void>('configure', <dynamic>[ await _channel.invokeMethod<void>('configure', <dynamic>[
androidEntrypointHandle, androidEntrypointHandle,
jsonEncode({ jsonEncode({
'genericEntrypoint': 'genericEntrypoint': PluginUtilities.getCallbackHandle(entrypoint)!.toRawHandle(),
PluginUtilities.getCallbackHandle(entrypoint)!.toRawHandle(), 'eventHandle': PluginUtilities.getCallbackHandle(handleUIEvent)!.toRawHandle()
'eventHandle':
PluginUtilities.getCallbackHandle(handleUIEvent)!.toRawHandle(),
'initialLocale': initialLocale,
}), }),
]); ]);

View File

@ -0,0 +1,9 @@
import 'package:media_scanner/media_scanner.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
class AndroidMediaScannerImplementation extends MediaScannerImplementation {
@override
void scanFile(String path) {
MediaScanner.loadMedia(path: path);
}
}

View File

@ -1,39 +0,0 @@
import 'package:moxplatform/moxplatform.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
class AndroidPlatformImplementation extends PlatformImplementation {
@override
Future<String> getCacheDataPath() {
return MoxplatformInterface.api.getCacheDataPath();
}
@override
Future<String> getPersistentDataPath() {
return MoxplatformInterface.api.getPersistentDataPath();
}
@override
Future<bool> isIgnoringBatteryOptimizations() {
return MoxplatformInterface.api.isIgnoringBatteryOptimizations();
}
@override
Future<void> openBatteryOptimisationSettings() {
return MoxplatformInterface.api.openBatteryOptimisationSettings();
}
@override
Future<bool> generateVideoThumbnail(
String src,
String dest,
int width,
) async {
return MoxplatformInterface.api.generateVideoThumbnail(src, dest, width);
}
@override
Future<List<String>> pickFiles(FilePickerType type, bool pickMultiple) async {
final result = await MoxplatformInterface.api.pickFiles(type, pickMultiple);
return result.cast<String>();
}
}

View File

@ -1,17 +1,15 @@
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/platform_android.dart'; import 'package:moxplatform_android/src/media_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 {
static void registerWith() { static void registerWith() {
// ignore: avoid_print // ignore: avoid_print
print('MoxplatformAndroidPlugin: Registering implementation'); print('MoxplatformAndroidPlugin: Registering implementation');
MoxplatformInterface.contacts = AndroidContactsImplementation();
MoxplatformInterface.crypto = AndroidCryptographyImplementation();
MoxplatformInterface.handler = AndroidIsolateHandler(); MoxplatformInterface.handler = AndroidIsolateHandler();
MoxplatformInterface.platform = AndroidPlatformImplementation(); MoxplatformInterface.media = AndroidMediaScannerImplementation();
MoxplatformInterface.crypto = AndroidCryptographyImplementation();
} }
@override @override

View File

@ -4,18 +4,19 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:moxlib/moxlib.dart'; import 'package:moxlib/awaitabledatasender.dart';
import 'package:moxplatform/moxplatform.dart'; import 'package:moxplatform/moxplatform.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class AndroidBackgroundService extends BackgroundService { class AndroidBackgroundService extends BackgroundService {
AndroidBackgroundService() AndroidBackgroundService()
: _log = Logger('AndroidBackgroundService'), : _log = Logger('AndroidBackgroundService'),
super(); super();
@internal @internal
static const MethodChannel channel = static const MethodChannel channel = MethodChannel('me.polynom.moxplatform_android_bg');
MethodChannel('me.polynom.moxplatform_android_bg');
final Logger _log; final Logger _log;
@override @override
@ -27,21 +28,20 @@ class AndroidBackgroundService extends BackgroundService {
} }
@override @override
void sendEvent(BackgroundEvent event, {String? id}) { void sendEvent(BackgroundEvent event, { String? id }) {
final data = DataWrapper( final data = DataWrapper(
id ?? const Uuid().v4(), id ?? const Uuid().v4(),
event, event,
); );
// NOTE: *S*erver to *F*oreground // NOTE: *S*erver to *F*oreground
_log.fine('S2F: ${data.toJson()}'); _log.fine('S2F: ${data.toJson().toString()}');
channel.invokeMethod<void>('sendData', jsonEncode(data.toJson())); channel.invokeMethod<void>('sendData', jsonEncode(data.toJson()));
} }
@override @override
void init( void init(
Future<void> Function(String initialLocale) entrypoint, Future<void> Function() entrypoint,
Future<void> Function(Map<String, dynamic>? data) handleEvent, Future<void> Function(Map<String, dynamic>? data) handleEvent,
String initialLocale,
) { ) {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -59,6 +59,6 @@ class AndroidBackgroundService extends BackgroundService {
_log.finest('Running...'); _log.finest('Running...');
entrypoint(initialLocale); entrypoint();
} }
} }

View File

@ -1,11 +1,11 @@
name: moxplatform_android name: moxplatform_android
description: Android implementation of moxplatform description: Android implementation of moxplatform
version: 0.1.22 version: 0.1.15
homepage: https://codeberg.org/moxxy/moxplatform homepage: https://codeberg.org/moxxy/moxplatform
publish_to: https://git.polynom.me/api/packages/Moxxy/pub publish_to: https://git.polynom.me/api/packages/Moxxy/pub
environment: environment:
sdk: ">=2.17.0 <4.0.0" sdk: ">=2.16.0 <3.0.0"
flutter: ">=2.10.0" flutter: ">=2.10.0"
flutter: flutter:
@ -22,17 +22,18 @@ dependencies:
sdk: flutter sdk: flutter
get_it: ^7.2.0 get_it: ^7.2.0
logging: ^1.0.2 logging: ^1.0.2
media_scanner: ^2.0.0
meta: ^1.7.0 meta: ^1.7.0
moxlib: moxlib:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.2.0 version: ^0.1.4
moxplatform: moxplatform:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.1.17+6 version: ^0.1.15
moxplatform_platform_interface: moxplatform_platform_interface:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.1.22 version: ^0.1.15
plugin_platform_interface: ^2.1.2 plugin_platform_interface: ^2.1.2
uuid: ^3.0.5 uuid: ^3.0.5
@ -40,5 +41,4 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
pigeon: 10.1.4 very_good_analysis: ^2.4.0
very_good_analysis: ^3.0.1

View File

@ -1,49 +1,3 @@
## 0.1.22
- **FIX**(repo): Remove notification examples.
- **FEAT**(interface,android): Allow passing an initial locale to the service.
- **FEAT**(base,interface,android): Move more logic to Moxxy.
- **FEAT**(android,interface): Implement video thumbnail generation.
## 0.1.21
- **FIX**(repo): Remove notification examples.
- **FEAT**(base,interface,android): Move more logic to Moxxy.
- **FEAT**(android,interface): Implement video thumbnail generation.
## 0.1.20
- **FEAT**(android,interface): Handle battery optimisation.
## 0.1.19
- **FEAT**(android,interface): Handle battery optimisation.
## 0.1.18
- **FIX**: Format and lint.
- **FIX**: Add payload to all intents.
- **FEAT**: Move recordSentMessage to pigeon.
- **FEAT**: Move the crypto APIs to pigeon.
- **FEAT**: Allow the sender's data being null.
- **FEAT**: Allow attaching arbitrary data to the notification.
- **FEAT**: Allow showing regular notifications.
- **FEAT**: Color in the notification silhouette.
- **FEAT**: Allow setting the self-avatar.
- **FEAT**: Take care of i18n.
## 0.1.17+1
- Update a dependency to the latest release.
## 0.1.17
- **FIX**: Fix typecasting issue.
- **FEAT**: Add an API for creating direct share shortcuts.
- **FEAT**: Migrate to moxlib 0.2.0.
- **FEAT**: I forgot to bump dependency versions.
- **FEAT**: Also hash the file on encryption and decryption.
## 0.1.11+2 ## 0.1.11+2
- **REFACTOR**: Make version constraints looser. - **REFACTOR**: Make version constraints looser.

View File

@ -1,13 +1,10 @@
library moxplatform_platform_interface; library moxplatform_platform_interface;
export 'src/api.g.dart';
export 'src/contacts.dart';
export 'src/contacts_stub.dart';
export 'src/crypto.dart'; export 'src/crypto.dart';
export 'src/crypto_stub.dart'; export 'src/crypto_stub.dart';
export 'src/interface.dart'; export 'src/interface.dart';
export 'src/isolate.dart'; export 'src/isolate.dart';
export 'src/isolate_stub.dart'; export 'src/isolate_stub.dart';
export 'src/platform.dart'; export 'src/media.dart';
export 'src/platform_stub.dart'; export 'src/media_stub.dart';
export 'src/service.dart'; export 'src/service.dart';

View File

@ -1,338 +0,0 @@
// 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';
enum CipherAlgorithm {
aes128GcmNoPadding,
aes256GcmNoPadding,
aes256CbcPkcs7,
}
enum FallbackIconType {
none,
person,
notes,
}
enum FilePickerType {
image,
video,
imageAndVideo,
generic,
}
class CryptographyResult {
CryptographyResult({
required this.plaintextHash,
required this.ciphertextHash,
});
Uint8List plaintextHash;
Uint8List ciphertextHash;
Object encode() {
return <Object?>[
plaintextHash,
ciphertextHash,
];
}
static CryptographyResult decode(Object result) {
result as List<Object?>;
return CryptographyResult(
plaintextHash: result[0]! as Uint8List,
ciphertextHash: result[1]! as Uint8List,
);
}
}
class _MoxplatformApiCodec extends StandardMessageCodec {
const _MoxplatformApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is CryptographyResult) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
}
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
return CryptographyResult.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
}
}
class MoxplatformApi {
/// Constructor for [MoxplatformApi]. 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.
MoxplatformApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;
static const MessageCodec<Object?> codec = _MoxplatformApiCodec();
/// Platform APIs
Future<String> getPersistentDataPath() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) 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 if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as String?)!;
}
}
Future<String> getCacheDataPath() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getCacheDataPath', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) 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 if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as String?)!;
}
}
Future<void> openBatteryOptimisationSettings() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.openBatteryOptimisationSettings', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) 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<bool> isIgnoringBatteryOptimizations() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.isIgnoringBatteryOptimizations', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) 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 if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as bool?)!;
}
}
/// Contacts APIs
Future<void> recordSentMessage(String arg_name, String arg_jid, String? arg_avatarPath, FallbackIconType arg_fallbackIcon) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.recordSentMessage', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_name, arg_jid, arg_avatarPath, arg_fallbackIcon.index]) 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;
}
}
/// Cryptography APIs
Future<CryptographyResult?> encryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) 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 (replyList[0] as CryptographyResult?);
}
}
Future<CryptographyResult?> decryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) 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 (replyList[0] as CryptographyResult?);
}
}
Future<Uint8List?> hashFile(String arg_sourcePath, String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_sourcePath, arg_hashSpec]) 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 (replyList[0] as Uint8List?);
}
}
/// Media APIs
Future<bool> generateVideoThumbnail(String arg_src, String arg_dest, int arg_maxWidth) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_src, arg_dest, arg_maxWidth]) 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 if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as bool?)!;
}
}
/// Picker
Future<List<String?>> pickFiles(FilePickerType arg_type, bool arg_pickMultiple) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.pickFiles', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_type.index, arg_pickMultiple]) 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 if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as List<Object?>?)!.cast<String?>();
}
}
}

View File

@ -1,12 +0,0 @@
import 'package:moxplatform_platform_interface/src/api.g.dart';
// 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,
});
}

View File

@ -1,12 +0,0 @@
import 'package:moxplatform_platform_interface/src/api.g.dart';
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 {}
}

View File

@ -1,5 +1,27 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:moxplatform_platform_interface/src/api.g.dart';
enum CipherAlgorithm {
aes128GcmNoPadding,
aes256GcmNoPadding,
aes256CbcPkcs7,
}
extension CipherAlgorithmToIntExtension on CipherAlgorithm {
int toInt() {
switch (this) {
case CipherAlgorithm.aes128GcmNoPadding: return 0;
case CipherAlgorithm.aes256GcmNoPadding: return 1;
case CipherAlgorithm.aes256CbcPkcs7: return 2;
}
}
}
class CryptographyResult {
const CryptographyResult(this.plaintextHash, this.ciphertextHash);
final Uint8List plaintextHash;
final Uint8List ciphertextHash;
}
/// Wrapper around platform-native cryptography APIs /// Wrapper around platform-native cryptography APIs
abstract class CryptographyImplementation { abstract class CryptographyImplementation {
@ -8,32 +30,18 @@ abstract class CryptographyImplementation {
/// Note that this function runs off-thread as to not block the UI thread. /// Note that this function runs off-thread as to not block the UI thread.
/// ///
/// Resolves to true if the encryption was successful. Resolves to fale on failure. /// Resolves to true if the encryption was successful. Resolves to fale on failure.
Future<CryptographyResult?> encryptFile( Future<CryptographyResult?> encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec);
String sourcePath,
String destPath,
Uint8List key,
Uint8List iv,
CipherAlgorithm algorithm,
String hashSpec,
);
/// Decrypt the file at [sourcePath] using [algorithm] and write the result back to /// Decrypt the file at [sourcePath] using [algorithm] and write the result back to
/// [destPath]. [hashSpec] is the name of the Hash function to use, i.e. "SHA-256". /// [destPath]. [hashSpec] is the name of the Hash function to use, i.e. "SHA-256".
/// Note that this function runs off-thread as to not block the UI thread. /// Note that this function runs off-thread as to not block the UI thread.
/// ///
/// Resolves to true if the encryption was successful. Resolves to fale on failure. /// Resolves to true if the encryption was successful. Resolves to fale on failure.
Future<CryptographyResult?> decryptFile( Future<CryptographyResult?> decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec);
String sourcePath,
String destPath,
Uint8List key,
Uint8List iv,
CipherAlgorithm algorithm,
String hashSpec,
);
/// Hashes the file at [sourcePath] using the Hash function with name [hashSpec]. /// Hashes the file at [path] using the Hash function with name [hashSpec].
/// Note that this function runs off-thread as to not block the UI thread. /// Note that this function runs off-thread as to not block the UI thread.
/// ///
/// Returns the hash of the file. /// Returns the hash of the file.
Future<Uint8List?> hashFile(String sourcePath, String hashSpec); Future<Uint8List?> hashFile(String path, String hashSpec);
} }

View File

@ -1,34 +1,19 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:moxplatform_platform_interface/src/api.g.dart';
import 'package:moxplatform_platform_interface/src/crypto.dart'; import 'package:moxplatform_platform_interface/src/crypto.dart';
class StubCryptographyImplementation extends CryptographyImplementation { class StubCryptographyImplementation extends CryptographyImplementation {
@override @override
Future<CryptographyResult?> encryptFile( Future<CryptographyResult?> encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec) async {
String sourcePath,
String destPath,
Uint8List key,
Uint8List iv,
CipherAlgorithm algorithm,
String hashSpec,
) async {
return null; return null;
} }
@override @override
Future<CryptographyResult?> decryptFile( Future<CryptographyResult?> decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec) async {
String sourcePath,
String destPath,
Uint8List key,
Uint8List iv,
CipherAlgorithm algorithm,
String hashSpec,
) async {
return null; return null;
} }
@override @override
Future<Uint8List?> hashFile(String sourcePath, String hashSpec) async { Future<Uint8List?> hashFile(String path, String hashSpec) async {
return null; return null;
} }
} }

View File

@ -1,12 +1,9 @@
import 'package:moxplatform_platform_interface/src/api.g.dart';
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.dart';
import 'package:moxplatform_platform_interface/src/crypto_stub.dart'; import 'package:moxplatform_platform_interface/src/crypto_stub.dart';
import 'package:moxplatform_platform_interface/src/isolate.dart'; 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/platform.dart'; import 'package:moxplatform_platform_interface/src/media.dart';
import 'package:moxplatform_platform_interface/src/platform_stub.dart'; import 'package:moxplatform_platform_interface/src/media_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 {
@ -14,12 +11,9 @@ abstract class MoxplatformInterface extends PlatformInterface {
static final Object _token = Object(); static final Object _token = Object();
static MoxplatformApi api = MoxplatformApi();
static IsolateHandler handler = StubIsolateHandler(); static IsolateHandler handler = StubIsolateHandler();
static MediaScannerImplementation media = StubMediaScannerImplementation();
static CryptographyImplementation crypto = StubCryptographyImplementation(); static CryptographyImplementation crypto = StubCryptographyImplementation();
static ContactsImplementation contacts = StubContactsImplementation();
static PlatformImplementation platform = StubPlatformImplementation();
/// Return the current platform name. /// Return the current platform name.
Future<String?> getPlatformName(); Future<String?> getPlatformName();

View File

@ -1,4 +1,4 @@
import 'package:moxlib/moxlib.dart'; import 'package:moxlib/awaitabledatasender.dart';
/// A class abstracting the interaction between the UI isolate and the background /// A class abstracting the interaction between the UI isolate and the background
/// service, which is either a regular isolate or an Android foreground service. /// service, which is either a regular isolate or an Android foreground service.
@ -8,12 +8,10 @@ abstract class IsolateHandler {
/// [entrypoint] is the entrypoint that is run inside the new isolate. /// [entrypoint] is the entrypoint that is run inside the new isolate.
/// [handleUIEvent] is a handler function that is called when the isolate receives data from the UI. /// [handleUIEvent] is a handler function that is called when the isolate receives data from the UI.
/// [handleIsolateEvent] is a handler function that is called when the UI receives data from the service. /// [handleIsolateEvent] is a handler function that is called when the UI receives data from the service.
/// [initialLocale] the locale to pass to the background service when first starting.
Future<void> start( Future<void> start(
Future<void> Function(String initialLocale) entrypoint, Future<void> Function() entrypoint,
Future<void> Function(Map<String, dynamic>? data) handleUIEvent, Future<void> Function(Map<String, dynamic>? data) handleUIEvent,
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent, Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
String initialLocale,
); );
/// Make sure that the UI event handler is registered without starting the isolate. /// Make sure that the UI event handler is registered without starting the isolate.

View File

@ -1,4 +1,4 @@
import 'package:moxlib/moxlib.dart'; import 'package:moxlib/awaitabledatasender.dart';
import 'package:moxplatform_platform_interface/src/isolate.dart'; import 'package:moxplatform_platform_interface/src/isolate.dart';
class StubDataSender extends AwaitableDataSender { class StubDataSender extends AwaitableDataSender {
@ -22,10 +22,9 @@ class StubIsolateHandler extends IsolateHandler {
@override @override
Future<void> start( Future<void> start(
Future<void> Function(String initialLocale) entrypoint, Future<void> Function() entrypoint,
Future<void> Function(Map<String, dynamic>? data) handleUIEvent, Future<void> Function(Map<String, dynamic>? data) handleUIEvent,
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent, Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
String initialLocale,
) async { ) async {
// ignore: avoid_print // ignore: avoid_print
print('STUB STARTED!!!!!!'); print('STUB STARTED!!!!!!');

View File

@ -0,0 +1,6 @@
/// Wrapper around platform-specific media scanning
// ignore: one_member_abstracts
abstract class MediaScannerImplementation {
/// Let the platform-specific media scanner scan the file at [path].
void scanFile(String path);
}

View File

@ -0,0 +1,6 @@
import 'package:moxplatform_platform_interface/src/media.dart';
class StubMediaScannerImplementation extends MediaScannerImplementation {
@override
void scanFile(String path) {}
}

View File

@ -1,24 +0,0 @@
import 'package:moxplatform_platform_interface/src/api.g.dart';
abstract class PlatformImplementation {
/// Returns the path where persistent data should be stored.
Future<String> getPersistentDataPath();
/// Returns the path where cache data should be stored.
Future<String> getCacheDataPath();
/// Returns whether the app is battery-optimised (false) or
/// excluded from battery savings (true).
Future<bool> isIgnoringBatteryOptimizations();
/// Opens the page for battery optimisations. If not supported on the
/// platform, does nothing.
Future<void> openBatteryOptimisationSettings();
/// Attempt to generate a thumbnail for the video file at [src], scale it, while keeping the
/// aspect ratio in tact to [width], and write it to [dest]. If we were successful, returns true.
/// If no thumbnail was generated, returns false.
Future<bool> generateVideoThumbnail(String src, String dest, int width);
Future<List<String>> pickFiles(FilePickerType type, bool pickMultiple);
}

View File

@ -1,29 +0,0 @@
import 'package:moxplatform_platform_interface/src/api.g.dart';
import 'package:moxplatform_platform_interface/src/platform.dart';
class StubPlatformImplementation extends PlatformImplementation {
/// Returns the path where persistent data should be stored.
@override
Future<String> getPersistentDataPath() async => '';
/// Returns the path where cache data should be stored.
@override
Future<String> getCacheDataPath() async => '';
@override
Future<bool> isIgnoringBatteryOptimizations() async => false;
@override
Future<void> openBatteryOptimisationSettings() async {}
@override
Future<bool> generateVideoThumbnail(
String src,
String dest,
int width,
) async =>
false;
@override
Future<List<String>> pickFiles(FilePickerType type, bool pickMultiple) async => [];
}

View File

@ -5,14 +5,13 @@ abstract class BackgroundService {
void setNotification(String title, String body); void setNotification(String title, String body);
/// Send data from the background service to the UI. /// Send data from the background service to the UI.
void sendEvent(BackgroundEvent event, {String? id}); void sendEvent(BackgroundEvent event, { String? id });
/// Called before [entrypoint]. Sets up whatever it needs to set up. /// Called before [entrypoint]. Sets up whatever it needs to set up.
/// [handleEvent] is a function that is called whenever the service receives /// [handleEvent] is a function that is called whenever the service receives
/// data. /// data.
void init( void init(
Future<void> Function(String initialLocale) entrypoint, Future<void> Function() entrypoint,
Future<void> Function(Map<String, dynamic>? data) handleEvent, Future<void> Function(Map<String, dynamic>? data) handleEvent,
String initialLocale,
); );
} }

View File

@ -1,11 +1,11 @@
name: moxplatform_platform_interface name: moxplatform_platform_interface
description: A common platform interface for the my_plugin plugin. description: A common platform interface for the my_plugin plugin.
version: 0.1.22 version: 0.1.15
homepage: https://codeberg.org/moxxy/moxplatform homepage: https://codeberg.org/moxxy/moxplatform
publish_to: https://git.polynom.me/api/packages/Moxxy/pub publish_to: https://git.polynom.me/api/packages/Moxxy/pub
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.16.0 <3.0.0"
flutter: ">=2.10.0" flutter: ">=2.10.0"
dependencies: dependencies:
@ -14,14 +14,14 @@ dependencies:
moxlib: moxlib:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.2.0 version: ^0.1.4
moxplatform: moxplatform:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.1.17+6 version: ^0.1.15
plugin_platform_interface: ^2.1.2 plugin_platform_interface: ^2.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
very_good_analysis: ^3.0.1 very_good_analysis: ^2.4.0

View File

@ -1,64 +0,0 @@
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'packages/moxplatform_platform_interface/lib/src/api.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/Api.java',
javaOptions: JavaOptions(
package: 'me.polynom.moxplatform_android',
),
),
)
enum CipherAlgorithm {
aes128GcmNoPadding,
aes256GcmNoPadding,
aes256CbcPkcs7;
}
class CryptographyResult {
const CryptographyResult(this.plaintextHash, this.ciphertextHash);
final Uint8List plaintextHash;
final Uint8List ciphertextHash;
}
// The type of icon to use when no avatar path is provided.
enum FallbackIconType {
none,
person,
notes;
}
enum FilePickerType {
image,
video,
imageAndVideo,
generic,
}
@HostApi()
abstract class MoxplatformApi {
/// Platform APIs
String getPersistentDataPath();
String getCacheDataPath();
void openBatteryOptimisationSettings();
bool isIgnoringBatteryOptimizations();
/// Contacts APIs
void recordSentMessage(String name, String jid, String? avatarPath, FallbackIconType fallbackIcon);
/// Cryptography APIs
@async CryptographyResult? encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec);
@async CryptographyResult? decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec);
@async Uint8List? hashFile(String sourcePath, String hashSpec);
/// Media APIs
bool generateVideoThumbnail(String src, String dest, int maxWidth);
/// Picker
@async
List<String> pickFiles(FilePickerType type, bool pickMultiple);
}

View File

@ -1,8 +0,0 @@
name: moxplatform_workspace
environment:
sdk: '>=2.18.0 <3.0.0'
dev_dependencies:
melos: ^3.1.1
pigeon: 10.1.4
very_good_analysis: ^3.0.1