diff --git a/app/lib/features/geckoview/domain/providers.dart b/app/lib/features/geckoview/domain/providers.dart index 3f6131e5..24a2bd37 100644 --- a/app/lib/features/geckoview/domain/providers.dart +++ b/app/lib/features/geckoview/domain/providers.dart @@ -2,11 +2,77 @@ import 'dart:async'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:lensai/core/logger.dart'; +import 'package:lensai/features/bangs/domain/providers.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_session.dart'; +import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; part 'providers.g.dart'; +@Riverpod(keepAlive: true) +GeckoSelectionActionService selectionActionService( + SelectionActionServiceRef ref, +) { + final service = GeckoSelectionActionService.setUp(); + + unawaited( + service.setActions([ + SearchAction((text) async { + final defaultSearchBang = + await ref.read(kagiSearchBangDataProvider.future); + + if (defaultSearchBang != null) { + await ref + .read(tabRepositoryProvider.notifier) + .addTab(url: defaultSearchBang.getUrl(text)); + } else { + logger.e('No default search bang found'); + } + }), + PrivateSearchAction((text) async { + final defaultSearchBang = + await ref.read(kagiSearchBangDataProvider.future); + + if (defaultSearchBang != null) { + await ref.read(tabRepositoryProvider.notifier).addTab( + url: defaultSearchBang.getUrl(text), + private: true, + ); + } else { + logger.e('No default search bang found'); + } + }), + ShareAction((text) async { + await Share.share(text); + }), + CallAction((text) async { + final uri = Uri.tryParse('tel:${text.replaceAll(' ', '')}'); + + if (uri != null) { + final canLaunch = await canLaunchUrl(uri); + if (canLaunch) { + await launchUrl(uri); + } + } + }), + EmailAction((text) async { + final uri = Uri.tryParse('mailto:$text'); + + if (uri != null) { + final canLaunch = await canLaunchUrl(uri); + if (canLaunch) { + await launchUrl(uri); + } + } + }), + ]), + ); + + return service; +} + @Riverpod(keepAlive: true) GeckoEventService eventService(EventServiceRef ref) { final service = GeckoEventService.setUp(); @@ -54,6 +120,6 @@ class EngineReadyState extends _$EngineReadyState { } @Riverpod() -TabSession selectedTabSessionNotifier(SelectedTabSessionNotifierRef ref) { +Raw selectedTabSessionNotifier(SelectedTabSessionNotifierRef ref) { return ref.watch(tabSessionProvider(null).notifier); } diff --git a/app/lib/features/geckoview/domain/providers.g.dart b/app/lib/features/geckoview/domain/providers.g.dart index d0d49cf3..0fc66e1a 100644 --- a/app/lib/features/geckoview/domain/providers.g.dart +++ b/app/lib/features/geckoview/domain/providers.g.dart @@ -6,6 +6,23 @@ part of 'providers.dart'; // RiverpodGenerator // ************************************************************************** +String _$selectionActionServiceHash() => + r'c150f6003dcea80b18fc063dd998ab45ce27d1a1'; + +/// See also [selectionActionService]. +@ProviderFor(selectionActionService) +final selectionActionServiceProvider = + Provider.internal( + selectionActionService, + name: r'selectionActionServiceProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$selectionActionServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef SelectionActionServiceRef = ProviderRef; String _$eventServiceHash() => r'5aa357fdf0d217677a9a66ecb50417ac18929cad'; /// See also [eventService]. @@ -21,12 +38,12 @@ final eventServiceProvider = Provider.internal( typedef EventServiceRef = ProviderRef; String _$selectedTabSessionNotifierHash() => - r'1b0fe1afa87c0b09fc8407ceb91946fffaef5214'; + r'ea9959e871dd0c3a8b80152eafe726c925094106'; /// See also [selectedTabSessionNotifier]. @ProviderFor(selectedTabSessionNotifier) final selectedTabSessionNotifierProvider = - AutoDisposeProvider.internal( + AutoDisposeProvider>.internal( selectedTabSessionNotifier, name: r'selectedTabSessionNotifierProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') @@ -36,7 +53,7 @@ final selectedTabSessionNotifierProvider = allTransitiveDependencies: null, ); -typedef SelectedTabSessionNotifierRef = AutoDisposeProviderRef; +typedef SelectedTabSessionNotifierRef = AutoDisposeProviderRef>; String _$engineReadyStateHash() => r'c682333e2e07cf0635aa7ae793a2088ca648c950'; /// See also [EngineReadyState]. diff --git a/app/lib/features/geckoview/features/browser/screens/browser.dart b/app/lib/features/geckoview/features/browser/screens/browser.dart index 59d0219a..b393cd55 100644 --- a/app/lib/features/geckoview/features/browser/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/screens/browser.dart @@ -607,19 +607,26 @@ class BrowserScreen extends HookConsumerWidget { SafeArea( child: Stack( children: [ - GeckoView( - preInitializationStep: () async { - await ref - .read(eventServiceProvider) - .viewReadyStateEvents - .firstWhere((state) => state == true) - .timeout( - const Duration(seconds: 3), - onTimeout: () { - logger.e( - 'Browser fragement not reported ready, trying to intitialize anyways', + Consumer( + builder: (context, ref, child) { + //Initialize dependencies + ref.watch(selectionActionServiceProvider); + + return GeckoView( + preInitializationStep: () async { + await ref + .read(eventServiceProvider) + .viewReadyStateEvents + .firstWhere((state) => state == true) + .timeout( + const Duration(seconds: 3), + onTimeout: () { + logger.e( + 'Browser fragement not reported ready, trying to intitialize anyways', + ); + return true; + }, ); - return true; }, ); }, diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BrowserFragment.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BrowserFragment.kt index 70d1fab2..fc00dbc4 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BrowserFragment.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BrowserFragment.kt @@ -8,7 +8,6 @@ import android.view.View import android.view.ViewGroup import eu.lensai.flutter_mozilla_components.feature.DefaultSelectionActionDelegate import eu.lensai.flutter_mozilla_components.integration.ReaderViewIntegration -import eu.lensai.flutter_mozilla_components.pigeons.SelectionAction import mozilla.components.browser.thumbnails.BrowserThumbnails import mozilla.components.concept.engine.EngineView import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature @@ -31,9 +30,7 @@ class BrowserFragment(private val context: Context) : BaseBrowserFragment(), Use override fun createEngine(components: Components): EngineView { return components.engine.createView(context).apply { -// selectionActionDelegate = DefaultSelectionActionDelegate( -// components.selectionAction -// ) + selectionActionDelegate = components.selectionAction } } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt index 8447b55e..aacd5571 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt @@ -15,7 +15,6 @@ import eu.lensai.flutter_mozilla_components.pigeons.HistoryState import eu.lensai.flutter_mozilla_components.pigeons.ReaderViewController import eu.lensai.flutter_mozilla_components.pigeons.ReaderableState import eu.lensai.flutter_mozilla_components.pigeons.SecurityInfoState -import eu.lensai.flutter_mozilla_components.pigeons.SelectionAction import eu.lensai.flutter_mozilla_components.pigeons.TabContentState import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce @@ -35,6 +34,7 @@ import mozilla.components.concept.engine.DefaultSettings import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.mediaquery.PreferredColorScheme +import mozilla.components.concept.engine.selection.SelectionActionDelegate import mozilla.components.concept.fetch.Client import mozilla.components.feature.addons.AddonManager import mozilla.components.feature.addons.amo.AMOAddonsProvider @@ -79,7 +79,7 @@ open class DefaultComponents( private val applicationContext: Context, val flutterEvents: GeckoStateEvents, val readerViewController: ReaderViewController, - val selectionAction: SelectionAction, + val selectionAction: SelectionActionDelegate, ) { companion object { const val SAMPLE_BROWSER_PREFERENCES = "sample_browser_preferences" diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt index a8886183..c961d4e0 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt @@ -9,20 +9,23 @@ import eu.lensai.flutter_mozilla_components.api.GeckoCookieApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoEngineSettingsApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoFindApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoIconsApiImpl +import eu.lensai.flutter_mozilla_components.api.GeckoSelectionActionControllerImpl import eu.lensai.flutter_mozilla_components.api.GeckoSessionApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoTabsApiImpl import eu.lensai.flutter_mozilla_components.feature.CookieManagerFeature +import eu.lensai.flutter_mozilla_components.feature.DefaultSelectionActionDelegate import eu.lensai.flutter_mozilla_components.pigeons.GeckoBrowserApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoCookieApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoEngineSettingsApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoFindApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoIconsApi +import eu.lensai.flutter_mozilla_components.pigeons.GeckoSelectionActionController +import eu.lensai.flutter_mozilla_components.pigeons.GeckoSelectionActionEvents import eu.lensai.flutter_mozilla_components.pigeons.GeckoSessionApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabsApi import eu.lensai.flutter_mozilla_components.pigeons.ReaderViewController import eu.lensai.flutter_mozilla_components.pigeons.ReaderViewEvents -import eu.lensai.flutter_mozilla_components.pigeons.SelectionAction import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware @@ -30,6 +33,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import mozilla.components.browser.engine.gecko.GeckoEngine import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient import mozilla.components.concept.engine.Engine +import mozilla.components.concept.engine.selection.SelectionActionDelegate import mozilla.components.experiment.NimbusExperimentDelegate import mozilla.components.feature.webcompat.WebCompatFeature import mozilla.components.lib.crash.handler.CrashHandlerService @@ -45,7 +49,7 @@ class Components( private val applicationContext: Context, flutterEvents: GeckoStateEvents, readerViewController: ReaderViewController, - selectionAction: SelectionAction, + selectionAction: SelectionActionDelegate, ) : DefaultComponents( applicationContext, flutterEvents, @@ -97,10 +101,12 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware { _flutterPluginBinding = flutterPluginBinding _flutterEvents = GeckoStateEvents(_flutterPluginBinding.binaryMessenger) + val selectionActionEvents = GeckoSelectionActionEvents(_flutterPluginBinding.binaryMessenger) + + val selectionActionDelegate = DefaultSelectionActionDelegate(selectionActionEvents) val readerViewController = ReaderViewController(_flutterPluginBinding.binaryMessenger) - val selectionActionDelegate = SelectionAction(_flutterPluginBinding.binaryMessenger) GlobalComponents.setUp( flutterPluginBinding.applicationContext, @@ -109,10 +115,6 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware { selectionActionDelegate ) - val intent = Intent(flutterPluginBinding.applicationContext, NotificationActivity::class.java) - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - flutterPluginBinding.applicationContext.startActivity(intent) - GeckoBrowserApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoBrowserApiImpl { showNativeFragment() }) @@ -123,11 +125,18 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware { GeckoIconsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoIconsApiImpl()) GeckoCookieApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoCookieApiImpl()) GeckoFindApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoFindApiImpl()) + GeckoSelectionActionController.setUp(_flutterPluginBinding.binaryMessenger, GeckoSelectionActionControllerImpl( + selectionActionDelegate + )) ReaderViewEvents.setUp( _flutterPluginBinding.binaryMessenger, GlobalComponents.components!!.readerViewEvents ) + + val intent = Intent(flutterPluginBinding.applicationContext, NotificationActivity::class.java) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + flutterPluginBinding.applicationContext.startActivity(intent) } private fun showNativeFragment() { diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GlobalComponents.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GlobalComponents.kt index 9e8490df..720cbe1a 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GlobalComponents.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GlobalComponents.kt @@ -3,11 +3,11 @@ package eu.lensai.flutter_mozilla_components import android.content.Context import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.lensai.flutter_mozilla_components.pigeons.ReaderViewController -import eu.lensai.flutter_mozilla_components.pigeons.SelectionAction import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import mozilla.components.concept.engine.selection.SelectionActionDelegate import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider import mozilla.components.support.base.facts.Facts import mozilla.components.support.base.facts.processor.LogFactProcessor @@ -35,7 +35,7 @@ object GlobalComponents { applicationContext: Context, flutterEvents: GeckoStateEvents, readerViewController: ReaderViewController, - selectionAction: SelectionAction + selectionAction: SelectionActionDelegate ) { val newComponents = Components( applicationContext, diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSelectionActionControllerImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSelectionActionControllerImpl.kt new file mode 100644 index 00000000..adf7770f --- /dev/null +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSelectionActionControllerImpl.kt @@ -0,0 +1,13 @@ +package eu.lensai.flutter_mozilla_components.api + +import eu.lensai.flutter_mozilla_components.feature.DefaultSelectionActionDelegate +import eu.lensai.flutter_mozilla_components.pigeons.CustomSelectionAction +import eu.lensai.flutter_mozilla_components.pigeons.GeckoSelectionActionController + +class GeckoSelectionActionControllerImpl( + private val selectionActionDelegate: DefaultSelectionActionDelegate +) : GeckoSelectionActionController { + override fun setActions(actions: List) { + selectionActionDelegate.actions = actions.associateBy { it.id } + } +} \ No newline at end of file diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/feature/DefaultSelectionActionDelegate.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/feature/DefaultSelectionActionDelegate.kt index cd61a07d..cdc8a7c5 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/feature/DefaultSelectionActionDelegate.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/feature/DefaultSelectionActionDelegate.kt @@ -1,51 +1,49 @@ package eu.lensai.flutter_mozilla_components.feature -import eu.lensai.flutter_mozilla_components.pigeons.SelectionAction -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.suspendCancellableCoroutine +import android.util.Patterns +import eu.lensai.flutter_mozilla_components.pigeons.CustomSelectionAction +import eu.lensai.flutter_mozilla_components.pigeons.GeckoSelectionActionEvents +import eu.lensai.flutter_mozilla_components.pigeons.SelectionPattern import mozilla.components.concept.engine.selection.SelectionActionDelegate -import kotlin.coroutines.resume class DefaultSelectionActionDelegate( - private val selectionAction: SelectionAction + private val selectionActionEvents: GeckoSelectionActionEvents, + private val actionSorter: ((Array) -> Array)? = null, ) : SelectionActionDelegate { - override fun getActionTitle(id: String): CharSequence? = runBlocking { - suspendCancellableCoroutine { continuation -> - selectionAction.getActionTitle(id) { result -> - continuation.resume(result.getOrNull()) - } - } + var actions: Map = emptyMap(); + + override fun getActionTitle(id: String): CharSequence? { + return actions[id]?.title; } - override fun getAllActions(): Array = runBlocking { - suspendCancellableCoroutine { continuation -> - selectionAction.getAllActions() { result -> - continuation.resume(result.getOrNull()!!.toTypedArray()) - } - } + override fun getAllActions(): Array { + return actions.values.map { x -> x.id }.toTypedArray(); } - override fun isActionAvailable(id: String, selectedText: String): Boolean = runBlocking { - suspendCancellableCoroutine { continuation -> - selectionAction.isActionAvailable(id, selectedText) { result -> - continuation.resume(result.getOrNull()!!) + override fun isActionAvailable(id: String, selectedText: String): Boolean { + val action = actions[id]; + if (action != null) { + return when(action.pattern) { + SelectionPattern.PHONE -> Patterns.PHONE.matcher(selectedText.trim()).matches() + SelectionPattern.EMAIL -> Patterns.EMAIL_ADDRESS.matcher(selectedText.trim()).matches() + null -> true } } + + return false } - override fun performAction(id: String, selectedText: String): Boolean = runBlocking { - suspendCancellableCoroutine { continuation -> - selectionAction.performAction(id, selectedText) { result -> - continuation.resume(result.getOrNull()!!) - } + override fun performAction(id: String, selectedText: String): Boolean { + val action = actions[id]; + if (action != null) { + selectionActionEvents.performSelectionAction(id, selectedText) { _ -> } + return true } + + return false } - override fun sortedActions(actions: Array): Array = runBlocking { - suspendCancellableCoroutine { continuation -> - selectionAction.sortedActions(actions.toList()) { result -> - continuation.resume(result.getOrNull()!!.toTypedArray()) - } - } + override fun sortedActions(actions: Array): Array { + return actionSorter?.invoke(actions) ?: actions } -} \ No newline at end of file +} diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt index 08e3f6fb..612c02aa 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt @@ -132,6 +132,17 @@ enum class CookieSameSiteStatus(val raw: Int) { } } +enum class SelectionPattern(val raw: Int) { + PHONE(0), + EMAIL(1); + + companion object { + fun ofRaw(raw: Int): SelectionPattern? { + return values().firstOrNull { it.raw == raw } + } + } +} + /** * Translation options that map to the Gecko Translations Options. * @@ -889,6 +900,30 @@ data class FindResultState ( ) } } + +/** Generated class from Pigeon that represents data sent in messages. */ +data class CustomSelectionAction ( + val id: String, + val title: String, + val pattern: SelectionPattern? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): CustomSelectionAction { + val id = pigeonVar_list[0] as String + val title = pigeonVar_list[1] as String + val pattern = pigeonVar_list[2] as SelectionPattern? + return CustomSelectionAction(id, title, pattern) + } + } + fun toList(): List { + return listOf( + id, + title, + pattern, + ) + } +} private open class GeckoPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { @@ -918,120 +953,130 @@ private open class GeckoPigeonCodec : StandardMessageCodec() { } } 134.toByte() -> { - return (readValue(buffer) as? List)?.let { - TranslationOptions.fromList(it) + return (readValue(buffer) as Long?)?.let { + SelectionPattern.ofRaw(it.toInt()) } } 135.toByte() -> { return (readValue(buffer) as? List)?.let { - ReaderState.fromList(it) + TranslationOptions.fromList(it) } } 136.toByte() -> { return (readValue(buffer) as? List)?.let { - LastMediaAccessState.fromList(it) + ReaderState.fromList(it) } } 137.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryMetadataKey.fromList(it) + LastMediaAccessState.fromList(it) } } 138.toByte() -> { return (readValue(buffer) as? List)?.let { - PackageCategoryValue.fromList(it) + HistoryMetadataKey.fromList(it) } } 139.toByte() -> { return (readValue(buffer) as? List)?.let { - ExternalPackage.fromList(it) + PackageCategoryValue.fromList(it) } } 140.toByte() -> { return (readValue(buffer) as? List)?.let { - LoadUrlFlagsValue.fromList(it) + ExternalPackage.fromList(it) } } 141.toByte() -> { return (readValue(buffer) as? List)?.let { - SourceValue.fromList(it) + LoadUrlFlagsValue.fromList(it) } } 142.toByte() -> { return (readValue(buffer) as? List)?.let { - TabState.fromList(it) + SourceValue.fromList(it) } } 143.toByte() -> { return (readValue(buffer) as? List)?.let { - RecoverableTab.fromList(it) + TabState.fromList(it) } } 144.toByte() -> { return (readValue(buffer) as? List)?.let { - RecoverableBrowserState.fromList(it) + RecoverableTab.fromList(it) } } 145.toByte() -> { return (readValue(buffer) as? List)?.let { - IconRequest.fromList(it) + RecoverableBrowserState.fromList(it) } } 146.toByte() -> { return (readValue(buffer) as? List)?.let { - ResourceSize.fromList(it) + IconRequest.fromList(it) } } 147.toByte() -> { return (readValue(buffer) as? List)?.let { - Resource.fromList(it) + ResourceSize.fromList(it) } } 148.toByte() -> { return (readValue(buffer) as? List)?.let { - IconResult.fromList(it) + Resource.fromList(it) } } 149.toByte() -> { return (readValue(buffer) as? List)?.let { - CookiePartitionKey.fromList(it) + IconResult.fromList(it) } } 150.toByte() -> { return (readValue(buffer) as? List)?.let { - Cookie.fromList(it) + CookiePartitionKey.fromList(it) } } 151.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryItem.fromList(it) + Cookie.fromList(it) } } 152.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryState.fromList(it) + HistoryItem.fromList(it) } } 153.toByte() -> { return (readValue(buffer) as? List)?.let { - ReaderableState.fromList(it) + HistoryState.fromList(it) } } 154.toByte() -> { return (readValue(buffer) as? List)?.let { - SecurityInfoState.fromList(it) + ReaderableState.fromList(it) } } 155.toByte() -> { return (readValue(buffer) as? List)?.let { - TabContentState.fromList(it) + SecurityInfoState.fromList(it) } } 156.toByte() -> { + return (readValue(buffer) as? List)?.let { + TabContentState.fromList(it) + } + } + 157.toByte() -> { return (readValue(buffer) as? List)?.let { FindResultState.fromList(it) } } + 158.toByte() -> { + return (readValue(buffer) as? List)?.let { + CustomSelectionAction.fromList(it) + } + } else -> super.readValueOfType(type, buffer) } } @@ -1057,98 +1102,106 @@ private open class GeckoPigeonCodec : StandardMessageCodec() { stream.write(133) writeValue(stream, value.raw) } - is TranslationOptions -> { + is SelectionPattern -> { stream.write(134) - writeValue(stream, value.toList()) + writeValue(stream, value.raw) } - is ReaderState -> { + is TranslationOptions -> { stream.write(135) writeValue(stream, value.toList()) } - is LastMediaAccessState -> { + is ReaderState -> { stream.write(136) writeValue(stream, value.toList()) } - is HistoryMetadataKey -> { + is LastMediaAccessState -> { stream.write(137) writeValue(stream, value.toList()) } - is PackageCategoryValue -> { + is HistoryMetadataKey -> { stream.write(138) writeValue(stream, value.toList()) } - is ExternalPackage -> { + is PackageCategoryValue -> { stream.write(139) writeValue(stream, value.toList()) } - is LoadUrlFlagsValue -> { + is ExternalPackage -> { stream.write(140) writeValue(stream, value.toList()) } - is SourceValue -> { + is LoadUrlFlagsValue -> { stream.write(141) writeValue(stream, value.toList()) } - is TabState -> { + is SourceValue -> { stream.write(142) writeValue(stream, value.toList()) } - is RecoverableTab -> { + is TabState -> { stream.write(143) writeValue(stream, value.toList()) } - is RecoverableBrowserState -> { + is RecoverableTab -> { stream.write(144) writeValue(stream, value.toList()) } - is IconRequest -> { + is RecoverableBrowserState -> { stream.write(145) writeValue(stream, value.toList()) } - is ResourceSize -> { + is IconRequest -> { stream.write(146) writeValue(stream, value.toList()) } - is Resource -> { + is ResourceSize -> { stream.write(147) writeValue(stream, value.toList()) } - is IconResult -> { + is Resource -> { stream.write(148) writeValue(stream, value.toList()) } - is CookiePartitionKey -> { + is IconResult -> { stream.write(149) writeValue(stream, value.toList()) } - is Cookie -> { + is CookiePartitionKey -> { stream.write(150) writeValue(stream, value.toList()) } - is HistoryItem -> { + is Cookie -> { stream.write(151) writeValue(stream, value.toList()) } - is HistoryState -> { + is HistoryItem -> { stream.write(152) writeValue(stream, value.toList()) } - is ReaderableState -> { + is HistoryState -> { stream.write(153) writeValue(stream, value.toList()) } - is SecurityInfoState -> { + is ReaderableState -> { stream.write(154) writeValue(stream, value.toList()) } - is TabContentState -> { + is SecurityInfoState -> { stream.write(155) writeValue(stream, value.toList()) } - is FindResultState -> { + is TabContentState -> { stream.write(156) writeValue(stream, value.toList()) } + is FindResultState -> { + stream.write(157) + writeValue(stream, value.toList()) + } + is CustomSelectionAction -> { + stream.write(158) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -2454,133 +2507,59 @@ class ReaderViewController(private val binaryMessenger: BinaryMessenger, private } } } -/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ -class SelectionAction(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface GeckoSelectionActionController { + fun setActions(actions: List) + companion object { - /** The codec used by SelectionAction. */ + /** The codec used by GeckoSelectionActionController. */ + val codec: MessageCodec by lazy { + GeckoPigeonCodec() + } + /** Sets up an instance of `GeckoSelectionActionController` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: GeckoSelectionActionController?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSelectionActionController.setActions$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val actionsArg = args[0] as List + val wrapped: List = try { + api.setActions(actionsArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} +/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ +class GeckoSelectionActionEvents(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { + companion object { + /** The codec used by GeckoSelectionActionEvents. */ val codec: MessageCodec by lazy { GeckoPigeonCodec() } } - /** - * Gets Strings representing all possible selection actions. - * - * @returns String IDs for each action that could possibly be shown in the context menu. This - * array must include all actions, available or not, and must not change over the class lifetime. - */ - fun getAllActions(callback: (Result>) -> Unit) + fun performSelectionAction(idArg: String, selectedTextArg: String, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" - val channelName = "dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.getAllActions$separatedMessageChannelSuffix" - val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(null) { - if (it is List<*>) { - if (it.size > 1) { - callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) - } else if (it[0] == null) { - callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", ""))) - } else { - val output = it[0] as List - callback(Result.success(output)) - } - } else { - callback(Result.failure(createConnectionError(channelName))) - } - } - } - /** - * Checks if an action can be shown on a new selection context menu. - * - * @returns whether or not the the custom action with the id of [id] is currently available - * which may be informed by [selectedText]. - */ - fun isActionAvailable(idArg: String, selectedTextArg: String, callback: (Result) -> Unit) -{ - val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" - val channelName = "dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.isActionAvailable$separatedMessageChannelSuffix" + val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoSelectionActionEvents.performSelectionAction$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) channel.send(listOf(idArg, selectedTextArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) - } else if (it[0] == null) { - callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", ""))) } else { - val output = it[0] as Boolean - callback(Result.success(output)) - } - } else { - callback(Result.failure(createConnectionError(channelName))) - } - } - } - /** - * Gets a title to be shown in the selection context menu. - * - * @returns the text that should be shown on the action. - */ - fun getActionTitle(idArg: String, callback: (Result) -> Unit) -{ - val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" - val channelName = "dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.getActionTitle$separatedMessageChannelSuffix" - val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(idArg)) { - if (it is List<*>) { - if (it.size > 1) { - callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) - } else { - val output = it[0] as String? - callback(Result.success(output)) - } - } else { - callback(Result.failure(createConnectionError(channelName))) - } - } - } - /** - * Should perform the action with the id of [id]. - * - * @returns [true] if the action was consumed. - */ - fun performAction(idArg: String, selectedTextArg: String, callback: (Result) -> Unit) -{ - val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" - val channelName = "dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.performAction$separatedMessageChannelSuffix" - val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(idArg, selectedTextArg)) { - if (it is List<*>) { - if (it.size > 1) { - callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) - } else if (it[0] == null) { - callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", ""))) - } else { - val output = it[0] as Boolean - callback(Result.success(output)) - } - } else { - callback(Result.failure(createConnectionError(channelName))) - } - } - } - /** - * Takes in a list of actions and sorts them. - * - * @returns the sorted list. - */ - fun sortedActions(actionsArg: List, callback: (Result>) -> Unit) -{ - val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" - val channelName = "dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.sortedActions$separatedMessageChannelSuffix" - val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(actionsArg)) { - if (it is List<*>) { - if (it.size > 1) { - callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) - } else if (it[0] == null) { - callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", ""))) - } else { - val output = it[0] as List - callback(Result.success(output)) + callback(Result.success(Unit)) } } else { callback(Result.failure(createConnectionError(channelName))) diff --git a/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart b/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart index 41682535..beec373a 100644 --- a/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart +++ b/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart @@ -1,12 +1,14 @@ //export 'src/domain/services/gecko_browser.dart'; export 'src/data/models/load_url_flags.dart'; export 'src/data/models/source.dart'; +export 'src/domain/entities/default_selection_actions.dart'; export 'src/domain/services/gecko_cookie.dart'; export 'src/domain/services/gecko_engine_settings.dart'; export 'src/domain/services/gecko_event.dart'; export 'src/domain/services/gecko_find_in_page.dart'; export 'src/domain/services/gecko_icon.dart'; export 'src/domain/services/gecko_readerable.dart'; +export 'src/domain/services/gecko_selection_action.dart'; export 'src/domain/services/gecko_session.dart'; export 'src/domain/services/gecko_tab.dart'; export 'src/geckoview_widget.dart'; diff --git a/packages/flutter_mozilla_components/lib/src/domain/entities/default_selection_actions.dart b/packages/flutter_mozilla_components/lib/src/domain/entities/default_selection_actions.dart new file mode 100644 index 00000000..9387631c --- /dev/null +++ b/packages/flutter_mozilla_components/lib/src/domain/entities/default_selection_actions.dart @@ -0,0 +1,61 @@ +import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart'; + +typedef PerformAction = void Function(String selectedText); + +class BaseSelectionAction extends CustomSelectionAction { + final PerformAction performAction; + + BaseSelectionAction({ + required super.id, + required super.title, + required this.performAction, + super.pattern, + }); +} + +class CallAction extends BaseSelectionAction { + CallAction(PerformAction action) + : super( + id: 'CUSTOM_CONTEXT_MENU_CALL', + title: 'Call', + pattern: SelectionPattern.phone, + performAction: action, + ); +} + +class EmailAction extends BaseSelectionAction { + EmailAction(PerformAction action) + : super( + id: 'CUSTOM_CONTEXT_MENU_EMAIL', + title: 'Email', + pattern: SelectionPattern.email, + performAction: action, + ); +} + +class SearchAction extends BaseSelectionAction { + SearchAction(PerformAction action) + : super( + id: 'CUSTOM_CONTEXT_MENU_SEARCH', + title: 'Search', + performAction: action, + ); +} + +class PrivateSearchAction extends BaseSelectionAction { + PrivateSearchAction(PerformAction action) + : super( + id: 'CUSTOM_CONTEXT_MENU_SEARCH_PRIVATELY', + title: 'Private Search', + performAction: action, + ); +} + +class ShareAction extends BaseSelectionAction { + ShareAction(PerformAction action) + : super( + id: 'CUSTOM_CONTEXT_MENU_SHARE', + title: 'Share', + performAction: action, + ); +} diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_selection_action.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_selection_action.dart new file mode 100644 index 00000000..151a3eb1 --- /dev/null +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_selection_action.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_mozilla_components/src/domain/entities/default_selection_actions.dart'; +import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart'; + +final _apiInstance = GeckoSelectionActionController(); + +class GeckoSelectionActionService extends GeckoSelectionActionEvents { + final GeckoSelectionActionController _api; + + List _actions; + + List get actions => _actions; + + Future setActions(List value) async { + _actions = value; + await _api.setActions(actions); + } + + GeckoSelectionActionService.setUp({ + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + GeckoSelectionActionController? api, + }) : _api = api ?? _apiInstance, + _actions = [] { + GeckoSelectionActionEvents.setUp( + this, + binaryMessenger: binaryMessenger, + messageChannelSuffix: messageChannelSuffix, + ); + } + + @override + void performSelectionAction(String id, String selectedText) { + _actions.firstWhere((x) => x.id == id).performAction(selectedText); + } +} diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart index 2f63b1ea..ce27e8ef 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -78,6 +78,11 @@ enum CookieSameSiteStatus { unspecified, } +enum SelectionPattern { + phone, + email, +} + /// Translation options that map to the Gecko Translations Options. /// /// @property downloadModel If the necessary models should be downloaded on request. If false, then @@ -986,6 +991,37 @@ class FindResultState { } } +class CustomSelectionAction { + CustomSelectionAction({ + required this.id, + required this.title, + this.pattern, + }); + + String id; + + String title; + + SelectionPattern? pattern; + + Object encode() { + return [ + id, + title, + pattern, + ]; + } + + static CustomSelectionAction decode(Object result) { + result as List; + return CustomSelectionAction( + id: result[0]! as String, + title: result[1]! as String, + pattern: result[2] as SelectionPattern?, + ); + } +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @@ -1009,75 +1045,81 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is CookieSameSiteStatus) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is TranslationOptions) { + } else if (value is SelectionPattern) { buffer.putUint8(134); - writeValue(buffer, value.encode()); - } else if (value is ReaderState) { + writeValue(buffer, value.index); + } else if (value is TranslationOptions) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is LastMediaAccessState) { + } else if (value is ReaderState) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is HistoryMetadataKey) { + } else if (value is LastMediaAccessState) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is PackageCategoryValue) { + } else if (value is HistoryMetadataKey) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is ExternalPackage) { + } else if (value is PackageCategoryValue) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is LoadUrlFlagsValue) { + } else if (value is ExternalPackage) { buffer.putUint8(140); writeValue(buffer, value.encode()); - } else if (value is SourceValue) { + } else if (value is LoadUrlFlagsValue) { buffer.putUint8(141); writeValue(buffer, value.encode()); - } else if (value is TabState) { + } else if (value is SourceValue) { buffer.putUint8(142); writeValue(buffer, value.encode()); - } else if (value is RecoverableTab) { + } else if (value is TabState) { buffer.putUint8(143); writeValue(buffer, value.encode()); - } else if (value is RecoverableBrowserState) { + } else if (value is RecoverableTab) { buffer.putUint8(144); writeValue(buffer, value.encode()); - } else if (value is IconRequest) { + } else if (value is RecoverableBrowserState) { buffer.putUint8(145); writeValue(buffer, value.encode()); - } else if (value is ResourceSize) { + } else if (value is IconRequest) { buffer.putUint8(146); writeValue(buffer, value.encode()); - } else if (value is Resource) { + } else if (value is ResourceSize) { buffer.putUint8(147); writeValue(buffer, value.encode()); - } else if (value is IconResult) { + } else if (value is Resource) { buffer.putUint8(148); writeValue(buffer, value.encode()); - } else if (value is CookiePartitionKey) { + } else if (value is IconResult) { buffer.putUint8(149); writeValue(buffer, value.encode()); - } else if (value is Cookie) { + } else if (value is CookiePartitionKey) { buffer.putUint8(150); writeValue(buffer, value.encode()); - } else if (value is HistoryItem) { + } else if (value is Cookie) { buffer.putUint8(151); writeValue(buffer, value.encode()); - } else if (value is HistoryState) { + } else if (value is HistoryItem) { buffer.putUint8(152); writeValue(buffer, value.encode()); - } else if (value is ReaderableState) { + } else if (value is HistoryState) { buffer.putUint8(153); writeValue(buffer, value.encode()); - } else if (value is SecurityInfoState) { + } else if (value is ReaderableState) { buffer.putUint8(154); writeValue(buffer, value.encode()); - } else if (value is TabContentState) { + } else if (value is SecurityInfoState) { buffer.putUint8(155); writeValue(buffer, value.encode()); - } else if (value is FindResultState) { + } else if (value is TabContentState) { buffer.putUint8(156); writeValue(buffer, value.encode()); + } else if (value is FindResultState) { + buffer.putUint8(157); + writeValue(buffer, value.encode()); + } else if (value is CustomSelectionAction) { + buffer.putUint8(158); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -1102,51 +1144,56 @@ class _PigeonCodec extends StandardMessageCodec { final int? value = readValue(buffer) as int?; return value == null ? null : CookieSameSiteStatus.values[value]; case 134: - return TranslationOptions.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : SelectionPattern.values[value]; case 135: - return ReaderState.decode(readValue(buffer)!); + return TranslationOptions.decode(readValue(buffer)!); case 136: - return LastMediaAccessState.decode(readValue(buffer)!); + return ReaderState.decode(readValue(buffer)!); case 137: - return HistoryMetadataKey.decode(readValue(buffer)!); + return LastMediaAccessState.decode(readValue(buffer)!); case 138: - return PackageCategoryValue.decode(readValue(buffer)!); + return HistoryMetadataKey.decode(readValue(buffer)!); case 139: - return ExternalPackage.decode(readValue(buffer)!); + return PackageCategoryValue.decode(readValue(buffer)!); case 140: - return LoadUrlFlagsValue.decode(readValue(buffer)!); + return ExternalPackage.decode(readValue(buffer)!); case 141: - return SourceValue.decode(readValue(buffer)!); + return LoadUrlFlagsValue.decode(readValue(buffer)!); case 142: - return TabState.decode(readValue(buffer)!); + return SourceValue.decode(readValue(buffer)!); case 143: - return RecoverableTab.decode(readValue(buffer)!); + return TabState.decode(readValue(buffer)!); case 144: - return RecoverableBrowserState.decode(readValue(buffer)!); + return RecoverableTab.decode(readValue(buffer)!); case 145: - return IconRequest.decode(readValue(buffer)!); + return RecoverableBrowserState.decode(readValue(buffer)!); case 146: - return ResourceSize.decode(readValue(buffer)!); + return IconRequest.decode(readValue(buffer)!); case 147: - return Resource.decode(readValue(buffer)!); + return ResourceSize.decode(readValue(buffer)!); case 148: - return IconResult.decode(readValue(buffer)!); + return Resource.decode(readValue(buffer)!); case 149: - return CookiePartitionKey.decode(readValue(buffer)!); + return IconResult.decode(readValue(buffer)!); case 150: - return Cookie.decode(readValue(buffer)!); + return CookiePartitionKey.decode(readValue(buffer)!); case 151: - return HistoryItem.decode(readValue(buffer)!); + return Cookie.decode(readValue(buffer)!); case 152: - return HistoryState.decode(readValue(buffer)!); + return HistoryItem.decode(readValue(buffer)!); case 153: - return ReaderableState.decode(readValue(buffer)!); + return HistoryState.decode(readValue(buffer)!); case 154: - return SecurityInfoState.decode(readValue(buffer)!); + return ReaderableState.decode(readValue(buffer)!); case 155: - return TabContentState.decode(readValue(buffer)!); + return SecurityInfoState.decode(readValue(buffer)!); case 156: + return TabContentState.decode(readValue(buffer)!); + case 157: return FindResultState.decode(readValue(buffer)!); + case 158: + return CustomSelectionAction.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -2735,155 +2782,69 @@ abstract class ReaderViewController { } } -abstract class SelectionAction { +class GeckoSelectionActionController { + /// Constructor for [GeckoSelectionActionController]. 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. + GeckoSelectionActionController({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - /// Gets Strings representing all possible selection actions. - /// - /// @returns String IDs for each action that could possibly be shown in the context menu. This - /// array must include all actions, available or not, and must not change over the class lifetime. - List getAllActions(); + final String pigeonVar_messageChannelSuffix; - /// Checks if an action can be shown on a new selection context menu. - /// - /// @returns whether or not the the custom action with the id of [id] is currently available - /// which may be informed by [selectedText]. - bool isActionAvailable(String id, String selectedText); + Future setActions(List actions) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSelectionActionController.setActions$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([actions]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} - /// Gets a title to be shown in the selection context menu. - /// - /// @returns the text that should be shown on the action. - String? getActionTitle(String id); +abstract class GeckoSelectionActionEvents { + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - /// Should perform the action with the id of [id]. - /// - /// @returns [true] if the action was consumed. - bool performAction(String id, String selectedText); + void performSelectionAction(String id, String selectedText); - /// Takes in a list of actions and sorts them. - /// - /// @returns the sorted list. - List sortedActions(List actions); - - static void setUp(SelectionAction? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + static void setUp(GeckoSelectionActionEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.getAllActions$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - pigeonVar_channel.setMessageHandler(null); - } else { - pigeonVar_channel.setMessageHandler((Object? message) async { - try { - final List output = api.getAllActions(); - return wrapResponse(result: output); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.isActionAvailable$messageChannelSuffix', pigeonChannelCodec, + 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSelectionActionEvents.performSelectionAction$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.isActionAvailable was null.'); + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoSelectionActionEvents.performSelectionAction was null.'); final List args = (message as List?)!; final String? arg_id = (args[0] as String?); assert(arg_id != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.isActionAvailable was null, expected non-null String.'); + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoSelectionActionEvents.performSelectionAction was null, expected non-null String.'); final String? arg_selectedText = (args[1] as String?); assert(arg_selectedText != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.isActionAvailable was null, expected non-null String.'); + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoSelectionActionEvents.performSelectionAction was null, expected non-null String.'); try { - final bool output = api.isActionAvailable(arg_id!, arg_selectedText!); - return wrapResponse(result: output); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.getActionTitle$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - pigeonVar_channel.setMessageHandler(null); - } else { - pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.getActionTitle was null.'); - final List args = (message as List?)!; - final String? arg_id = (args[0] as String?); - assert(arg_id != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.getActionTitle was null, expected non-null String.'); - try { - final String? output = api.getActionTitle(arg_id!); - return wrapResponse(result: output); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.performAction$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - pigeonVar_channel.setMessageHandler(null); - } else { - pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.performAction was null.'); - final List args = (message as List?)!; - final String? arg_id = (args[0] as String?); - assert(arg_id != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.performAction was null, expected non-null String.'); - final String? arg_selectedText = (args[1] as String?); - assert(arg_selectedText != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.performAction was null, expected non-null String.'); - try { - final bool output = api.performAction(arg_id!, arg_selectedText!); - return wrapResponse(result: output); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.sortedActions$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - pigeonVar_channel.setMessageHandler(null); - } else { - pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.sortedActions was null.'); - final List args = (message as List?)!; - final List? arg_actions = (args[0] as List?)?.cast(); - assert(arg_actions != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.SelectionAction.sortedActions was null, expected non-null List.'); - try { - final List output = api.sortedActions(arg_actions!); - return wrapResponse(result: output); + api.performSelectionAction(arg_id!, arg_selectedText!); + return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index a9d9299d..4237e932 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -469,6 +469,16 @@ class FindResultState { ); } +enum SelectionPattern { phone, email } + +class CustomSelectionAction { + final String id; + final String title; + final SelectionPattern? pattern; + + CustomSelectionAction(this.id, this.title, this.pattern); +} + // /// Represents all the different supported types of data that can be found from long clicking // /// an element. // sealed class HitResult { @@ -816,32 +826,12 @@ abstract class ReaderViewController { void appearanceButtonVisibility(bool visible); } -@FlutterApi() -abstract class SelectionAction { - /// Gets Strings representing all possible selection actions. - /// - /// @returns String IDs for each action that could possibly be shown in the context menu. This - /// array must include all actions, available or not, and must not change over the class lifetime. - List getAllActions(); - - /// Checks if an action can be shown on a new selection context menu. - /// - /// @returns whether or not the the custom action with the id of [id] is currently available - /// which may be informed by [selectedText]. - bool isActionAvailable(String id, String selectedText); - - /// Gets a title to be shown in the selection context menu. - /// - /// @returns the text that should be shown on the action. - String? getActionTitle(String id); - - /// Should perform the action with the id of [id]. - /// - /// @returns [true] if the action was consumed. - bool performAction(String id, String selectedText); - - /// Takes in a list of actions and sorts them. - /// - /// @returns the sorted list. - List sortedActions(List actions); +@HostApi() +abstract class GeckoSelectionActionController { + void setActions(List actions); +} + +@FlutterApi() +abstract class GeckoSelectionActionEvents { + void performSelectionAction(String id, String selectedText); }