diff --git a/app/lib/event/native_event.dart b/app/lib/event/native_event.dart index b78ab066..282a7c0f 100644 --- a/app/lib/event/native_event.dart +++ b/app/lib/event/native_event.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:logging/logging.dart'; import 'package:nc_photos/stream_extension.dart'; -import 'package:nc_photos_plugin/nc_photos_plugin.dart'; +import 'package:np_platform_message_relay/np_platform_message_relay.dart'; class NativeEventListener { NativeEventListener(this.listener); @@ -26,7 +26,7 @@ class NativeEventListener { } static final _mappedStream = - NativeEvent.stream.whereType().map((ev) { + MessageRelay.stream.whereType().map((ev) { switch (ev.event) { case FileExifUpdatedEvent._id: return FileExifUpdatedEvent.fromEvent(ev); @@ -39,20 +39,21 @@ class NativeEventListener { final void Function(T) listener; StreamSubscription? _subscription; - final _log = Logger("event.native_event.NativeEventListener<${T.runtimeType}>"); + final _log = + Logger("event.native_event.NativeEventListener<${T.runtimeType}>"); } class FileExifUpdatedEvent { const FileExifUpdatedEvent(this.fileIds); - factory FileExifUpdatedEvent.fromEvent(NativeEventObject ev) { + factory FileExifUpdatedEvent.fromEvent(Message ev) { assert(ev.event == _id); assert(ev.data != null); final dataJson = jsonDecode(ev.data!) as Map; return FileExifUpdatedEvent((dataJson["fileIds"] as List).cast()); } - NativeEventObject toEvent() => NativeEventObject( + Message toEvent() => Message( _id, jsonEncode({ "fileIds": fileIds, diff --git a/app/lib/service.dart b/app/lib/service.dart index 3d1b2a56..8f93aaef 100644 --- a/app/lib/service.dart +++ b/app/lib/service.dart @@ -24,6 +24,7 @@ import 'package:nc_photos_plugin/nc_photos_plugin.dart'; import 'package:np_async/np_async.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:np_geocoder/np_geocoder.dart'; +import 'package:np_platform_message_relay/np_platform_message_relay.dart'; part 'service.g.dart'; @@ -237,7 +238,7 @@ class _MetadataTask { } if (_processedIds.isNotEmpty) { unawaited( - NativeEvent.fire(FileExifUpdatedEvent(_processedIds).toEvent()), + MessageRelay.broadcast(FileExifUpdatedEvent(_processedIds).toEvent()), ); _processedIds = []; } @@ -319,7 +320,7 @@ class _MetadataTask { _processedIds.add(file.fileId!); if (_processedIds.length >= 10) { - NativeEvent.fire(FileExifUpdatedEvent(_processedIds).toEvent()); + MessageRelay.broadcast(FileExifUpdatedEvent(_processedIds).toEvent()); _processedIds = []; } } diff --git a/app/pubspec.lock b/app/pubspec.lock index 11a24710..7908712f 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1032,6 +1032,13 @@ packages: relative: true source: path version: "0.0.1" + np_platform_message_relay: + dependency: "direct main" + description: + path: "../np_platform_message_relay" + relative: true + source: path + version: "0.0.1" np_platform_permission: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 56ef3ace..5059ec3f 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -115,6 +115,8 @@ dependencies: path: ../np_platform_lock np_platform_log: path: ../np_platform_log + np_platform_message_relay: + path: ../np_platform_message_relay np_platform_permission: path: ../np_platform_permission np_platform_raw_image: diff --git a/np_platform_message_relay/.gitignore b/np_platform_message_relay/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/np_platform_message_relay/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/np_platform_message_relay/.metadata b/np_platform_message_relay/.metadata new file mode 100644 index 00000000..b0ed2b59 --- /dev/null +++ b/np_platform_message_relay/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + channel: stable + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + - platform: android + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/np_platform_message_relay/analysis_options.yaml b/np_platform_message_relay/analysis_options.yaml new file mode 100644 index 00000000..f92d2567 --- /dev/null +++ b/np_platform_message_relay/analysis_options.yaml @@ -0,0 +1 @@ +include: package:np_lints/np.yaml diff --git a/np_platform_message_relay/android/.gitignore b/np_platform_message_relay/android/.gitignore new file mode 100644 index 00000000..161bdcda --- /dev/null +++ b/np_platform_message_relay/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/np_platform_message_relay/android/build.gradle b/np_platform_message_relay/android/build.gradle new file mode 100644 index 00000000..64ddc418 --- /dev/null +++ b/np_platform_message_relay/android/build.gradle @@ -0,0 +1,52 @@ +group 'com.nkming.nc_photos.np_platform_message_relay' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.8.20' + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace 'com.nkming.nc_photos.np_platform_message_relay' + compileSdk 31 + + defaultConfig { + minSdk 21 + } + + buildTypes { + release { + minifyEnabled false + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation "androidx.annotation:annotation:1.6.0" +} diff --git a/np_platform_message_relay/android/gradle/wrapper/gradle-wrapper.properties b/np_platform_message_relay/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..98debb84 --- /dev/null +++ b/np_platform_message_relay/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/np_platform_message_relay/android/settings.gradle b/np_platform_message_relay/android/settings.gradle new file mode 100644 index 00000000..ba82f93e --- /dev/null +++ b/np_platform_message_relay/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'np_platform_message_relay' diff --git a/np_platform_message_relay/android/src/main/AndroidManifest.xml b/np_platform_message_relay/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..1f080a31 --- /dev/null +++ b/np_platform_message_relay/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/K.kt b/np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/K.kt new file mode 100644 index 00000000..b7560a63 --- /dev/null +++ b/np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/K.kt @@ -0,0 +1,7 @@ +package com.nkming.nc_photos.np_platform_message_relay + +internal interface K { + companion object { + const val LIB_ID = "com.nkming.nc_photos.np_platform_message_relay" + } +} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEventChannelHandler.kt b/np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/MessageRelayChannelHandler.kt similarity index 66% rename from plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEventChannelHandler.kt rename to np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/MessageRelayChannelHandler.kt index d97790f8..1caaed61 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEventChannelHandler.kt +++ b/np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/MessageRelayChannelHandler.kt @@ -1,28 +1,14 @@ -package com.nkming.nc_photos.plugin +package com.nkming.nc_photos.np_platform_message_relay import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler, +internal class MessageRelayChannelHandler : MethodChannel.MethodCallHandler, EventChannel.StreamHandler { companion object { - const val EVENT_CHANNEL = "${K.LIB_ID}/native_event" - const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method" - - /** - * Fire native events on the native side - */ - fun fire(eventObj: NativeEvent) { - synchronized(eventSinks) { - for (s in eventSinks.values) { - s.success(buildMap { - put("event", eventObj.getId()) - eventObj.getData()?.also { put("data", it) } - }) - } - } - } + const val EVENT_CHANNEL = "${K.LIB_ID}/message_relay_event" + const val METHOD_CHANNEL = "${K.LIB_ID}/message_relay_method" private val eventSinks = mutableMapOf() private var nextId = 0 @@ -30,9 +16,9 @@ internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler, override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { - "fire" -> { + "broadcast" -> { try { - fire( + broadcast( call.argument("event")!!, call.argument("data"), result ) } catch (e: Throwable) { @@ -54,7 +40,7 @@ internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler, } } - private fun fire( + private fun broadcast( event: String, data: String?, result: MethodChannel.Result ) { synchronized(eventSinks) { diff --git a/np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/NpPlatformMessageRelayPlugin.kt b/np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/NpPlatformMessageRelayPlugin.kt new file mode 100644 index 00000000..7b381616 --- /dev/null +++ b/np_platform_message_relay/android/src/main/kotlin/com/nkming/nc_photos/np_platform_message_relay/NpPlatformMessageRelayPlugin.kt @@ -0,0 +1,34 @@ +package com.nkming.nc_photos.np_platform_message_relay + +import androidx.annotation.NonNull +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodChannel + +class NpPlatformMessageRelayPlugin : FlutterPlugin { + override fun onAttachedToEngine( + @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding + ) { + val messageRelayHandler = MessageRelayChannelHandler() + eventChannel = EventChannel( + flutterPluginBinding.binaryMessenger, + MessageRelayChannelHandler.EVENT_CHANNEL + ) + eventChannel.setStreamHandler(messageRelayHandler) + methodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + MessageRelayChannelHandler.METHOD_CHANNEL + ) + methodChannel.setMethodCallHandler(messageRelayHandler) + } + + override fun onDetachedFromEngine( + @NonNull binding: FlutterPlugin.FlutterPluginBinding + ) { + eventChannel.setStreamHandler(null) + methodChannel.setMethodCallHandler(null) + } + + private lateinit var eventChannel: EventChannel + private lateinit var methodChannel: MethodChannel +} diff --git a/np_platform_message_relay/lib/np_platform_message_relay.dart b/np_platform_message_relay/lib/np_platform_message_relay.dart new file mode 100644 index 00000000..a6712f0f --- /dev/null +++ b/np_platform_message_relay/lib/np_platform_message_relay.dart @@ -0,0 +1,3 @@ +library np_platform_message_relay; + +export 'src/message_relay.dart'; diff --git a/np_platform_message_relay/lib/src/k.dart b/np_platform_message_relay/lib/src/k.dart new file mode 100644 index 00000000..28586043 --- /dev/null +++ b/np_platform_message_relay/lib/src/k.dart @@ -0,0 +1 @@ +const libId = "com.nkming.nc_photos.np_platform_message_relay"; diff --git a/np_platform_message_relay/lib/src/message_relay.dart b/np_platform_message_relay/lib/src/message_relay.dart new file mode 100644 index 00000000..12275c9a --- /dev/null +++ b/np_platform_message_relay/lib/src/message_relay.dart @@ -0,0 +1,59 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:np_platform_message_relay/src/k.dart' as k; + +part 'message_relay.g.dart'; + +class Message { + const Message(this.event, this.data); + + static Message fromJson(Map json) { + return Message(json["event"], json["data"]); + } + + Map toJson() => { + "event": event, + if (data != null) "data": data, + }; + + final String event; + final String? data; +} + +/// Relay messages via native side +/// +/// Typically used to broadcast messages across different Flutter engines (e.g., +/// main isolate <-> background isolates) +/// +/// Beware that the isolate that broadcasted the message will also receive the +/// message if subscribed +@npLog +class MessageRelay { + static Future broadcast(Message msg) => + _methodChannel.invokeMethod("broadcast", msg.toJson()); + + static Stream get stream => + _eventChannel.receiveBroadcastStream().map(_toEvent).whereNotNull(); + + static Message? _toEvent(dynamic ev) { + try { + return Message.fromJson((ev as Map).cast()); + } catch (e, stackTrace) { + _log.severe("Failed while parsing native events", e, stackTrace); + return null; + } + } + + static const _eventChannel = EventChannel("${k.libId}/message_relay_event"); + static const _methodChannel = + MethodChannel("${k.libId}/message_relay_method"); + + static final _log = _$MessageRelayNpLog.log; +} + +extension on Stream { + Stream whereNotNull() => where((e) => e != null).cast(); +} diff --git a/np_platform_message_relay/lib/src/message_relay.g.dart b/np_platform_message_relay/lib/src/message_relay.g.dart new file mode 100644 index 00000000..5b0e201a --- /dev/null +++ b/np_platform_message_relay/lib/src/message_relay.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'message_relay.dart'; + +// ************************************************************************** +// NpLogGenerator +// ************************************************************************** + +extension _$MessageRelayNpLog on MessageRelay { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("src.message_relay.MessageRelay"); +} diff --git a/np_platform_message_relay/pubspec.yaml b/np_platform_message_relay/pubspec.yaml new file mode 100644 index 00000000..2522c67b --- /dev/null +++ b/np_platform_message_relay/pubspec.yaml @@ -0,0 +1,30 @@ +name: np_platform_message_relay +description: A new Flutter plugin project. +version: 0.0.1 +homepage: +publish_to: none + +environment: + sdk: '>=2.19.6 <3.0.0' + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + logging: ^1.1.1 + np_codegen: + path: ../codegen + +dev_dependencies: + build_runner: ^2.2.1 + np_codegen_build: + path: ../codegen_build + np_lints: + path: ../np_lints + +flutter: + plugin: + platforms: + android: + package: com.nkming.nc_photos.np_platform_message_relay + pluginClass: NpPlatformMessageRelayPlugin diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEvent.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEvent.kt deleted file mode 100644 index adc6d2f8..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEvent.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.nkming.nc_photos.plugin - -internal interface NativeEvent { - fun getId(): String - fun getData(): String? = null -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NcPhotosPlugin.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NcPhotosPlugin.kt index 460679f6..4d554ebf 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NcPhotosPlugin.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NcPhotosPlugin.kt @@ -60,18 +60,6 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware, PreferenceChannelHandler.METHOD_CHANNEL ) preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler) - - val nativeEventHandler = NativeEventChannelHandler() - nativeEventChannel = EventChannel( - flutterPluginBinding.binaryMessenger, - NativeEventChannelHandler.EVENT_CHANNEL - ) - nativeEventChannel.setStreamHandler(nativeEventHandler) - nativeEventMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - NativeEventChannelHandler.METHOD_CHANNEL - ) - nativeEventMethodChannel.setMethodCallHandler(nativeEventHandler) } override fun onDetachedFromEngine( @@ -82,8 +70,6 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware, mediaStoreMethodChannel.setMethodCallHandler(null) contentUriMethodChannel.setMethodCallHandler(null) preferenceMethodChannel.setMethodCallHandler(null) - nativeEventChannel.setStreamHandler(null) - nativeEventMethodChannel.setMethodCallHandler(null) } override fun onAttachedToActivity(binding: ActivityPluginBinding) { @@ -136,8 +122,6 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware, private lateinit var mediaStoreMethodChannel: MethodChannel private lateinit var contentUriMethodChannel: MethodChannel private lateinit var preferenceMethodChannel: MethodChannel - private lateinit var nativeEventChannel: EventChannel - private lateinit var nativeEventMethodChannel: MethodChannel private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler } diff --git a/plugin/lib/nc_photos_plugin.dart b/plugin/lib/nc_photos_plugin.dart index 425aa4ec..6a42ac27 100644 --- a/plugin/lib/nc_photos_plugin.dart +++ b/plugin/lib/nc_photos_plugin.dart @@ -3,6 +3,5 @@ library nc_photos_plugin; export 'src/content_uri.dart'; export 'src/exception.dart'; export 'src/media_store.dart'; -export 'src/native_event.dart'; export 'src/notification.dart'; export 'src/preference.dart'; diff --git a/plugin/lib/src/native_event.dart b/plugin/lib/src/native_event.dart deleted file mode 100644 index 4b62e87c..00000000 --- a/plugin/lib/src/native_event.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/services.dart'; -import 'package:nc_photos_plugin/src/k.dart' as k; - -class NativeEventObject { - NativeEventObject(this.event, this.data); - - final String event; - final String? data; -} - -class NativeEvent { - static Future fire(NativeEventObject ev) => - _methodChannel.invokeMethod("fire", { - "event": ev.event, - if (ev.data != null) "data": ev.data, - }); - - static Stream get stream => _eventStream; - - static const _eventChannel = EventChannel("${k.libId}/native_event"); - static const _methodChannel = MethodChannel("${k.libId}/native_event_method"); - - static final _eventStream = _eventChannel - .receiveBroadcastStream() - .map((event) { - if (event is Map) { - return NativeEventObject(event["event"], event["data"]); - } else { - return event; - } - }); -}