implement open in app feature
This commit is contained in:
+1
-1
@@ -600,7 +600,7 @@ class ShareMenuButton extends HookConsumerWidget {
|
|||||||
controller: menuController,
|
controller: menuController,
|
||||||
menuChildren: [
|
menuChildren: [
|
||||||
CopyAddressMenuItemButton(selectedTabId: selectedTabId),
|
CopyAddressMenuItemButton(selectedTabId: selectedTabId),
|
||||||
LaunchExternalMenuItemButton(selectedTabId: selectedTabId),
|
OpenInAppMenuItemButton(selectedTabId: selectedTabId),
|
||||||
ShareScreenshotMenuItemButton(selectedTabId: selectedTabId),
|
ShareScreenshotMenuItemButton(selectedTabId: selectedTabId),
|
||||||
ShareMenuItemButton(selectedTabId: selectedTabId),
|
ShareMenuItemButton(selectedTabId: selectedTabId),
|
||||||
ShowQrCodeMenuItemButton(selectedTabId: selectedTabId),
|
ShowQrCodeMenuItemButton(selectedTabId: selectedTabId),
|
||||||
|
|||||||
+23
-8
@@ -22,6 +22,7 @@ import 'dart:ui' as ui;
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
@@ -31,7 +32,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/dialog
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/definitions.drift.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/database/definitions.drift.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
import 'package:weblibre/presentation/hooks/cached_future.dart';
|
||||||
|
|
||||||
class ShareMenuItemButton extends HookConsumerWidget {
|
class ShareMenuItemButton extends HookConsumerWidget {
|
||||||
const ShareMenuItemButton({super.key, required this.selectedTabId});
|
const ShareMenuItemButton({super.key, required this.selectedTabId});
|
||||||
@@ -225,23 +226,37 @@ class ShareScreenshotMenuItemButton extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LaunchExternalMenuItemButton extends HookConsumerWidget {
|
class OpenInAppMenuItemButton extends HookConsumerWidget {
|
||||||
const LaunchExternalMenuItemButton({super.key, required this.selectedTabId});
|
const OpenInAppMenuItemButton({super.key, required this.selectedTabId});
|
||||||
|
|
||||||
|
static final _service = GeckoAppLinksService();
|
||||||
|
|
||||||
final String? selectedTabId;
|
final String? selectedTabId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final tabState = ref.watch(tabStateProvider(selectedTabId));
|
||||||
|
final url = tabState?.url;
|
||||||
|
final hasExternalApp = useCachedFuture(
|
||||||
|
// ignore: discarded_futures useFuture
|
||||||
|
() => url != null ? _service.hasExternalApp(url) : Future.value(false),
|
||||||
|
[url],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasExternalApp.data != true) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
return MenuItemButton(
|
return MenuItemButton(
|
||||||
leadingIcon: const Icon(Icons.open_in_browser),
|
leadingIcon: const Icon(Icons.open_in_new),
|
||||||
closeOnActivate: false,
|
closeOnActivate: false,
|
||||||
child: const Text('Launch External'),
|
child: const Text('Open in App'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
if (url == null) return;
|
||||||
|
|
||||||
await ui_helper.launchUrlFeedback(context, tabState.url);
|
final success = await _service.openAppLink(url);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (success && context.mounted) {
|
||||||
MenuController.maybeOf(context)?.close();
|
MenuController.maybeOf(context)?.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ class TabMenu extends HookConsumerWidget {
|
|||||||
SubmenuButton(
|
SubmenuButton(
|
||||||
menuChildren: [
|
menuChildren: [
|
||||||
CopyAddressMenuItemButton(selectedTabId: selectedTabId),
|
CopyAddressMenuItemButton(selectedTabId: selectedTabId),
|
||||||
LaunchExternalMenuItemButton(selectedTabId: selectedTabId),
|
OpenInAppMenuItemButton(selectedTabId: selectedTabId),
|
||||||
ShareScreenshotMenuItemButton(selectedTabId: selectedTabId),
|
ShareScreenshotMenuItemButton(selectedTabId: selectedTabId),
|
||||||
ShareMenuItemButton(selectedTabId: selectedTabId),
|
ShareMenuItemButton(selectedTabId: selectedTabId),
|
||||||
ShowQrCodeMenuItemButton(selectedTabId: selectedTabId),
|
ShowQrCodeMenuItemButton(selectedTabId: selectedTabId),
|
||||||
|
|||||||
+9
-7
@@ -22,30 +22,32 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
||||||
import 'package:weblibre/utils/ui_helper.dart';
|
|
||||||
|
|
||||||
class LaunchExternal extends HookConsumerWidget {
|
class LaunchExternal extends HookConsumerWidget {
|
||||||
final HitResult hitResult;
|
final HitResult hitResult;
|
||||||
|
|
||||||
const LaunchExternal({super.key, required this.hitResult});
|
const LaunchExternal({super.key, required this.hitResult});
|
||||||
|
|
||||||
|
static final _service = GeckoAppLinksService();
|
||||||
|
|
||||||
static Future<bool> isSupported(HitResult hitResult) async {
|
static Future<bool> isSupported(HitResult hitResult) async {
|
||||||
return hitResult.tryGetLink().mapNotNull((url) => canLaunchUrl(url)) ??
|
return hitResult.tryGetLink().mapNotNull(
|
||||||
|
(url) => _service.hasExternalApp(url),
|
||||||
|
) ??
|
||||||
false;
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: const Icon(Icons.open_in_browser),
|
leading: const Icon(Icons.open_in_new),
|
||||||
title: const Text('Launch External'),
|
title: const Text('Open in App'),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await hitResult.tryGetLink().mapNotNull((url) async {
|
await hitResult.tryGetLink().mapNotNull((url) async {
|
||||||
await launchUrlFeedback(context, url);
|
final success = await _service.openAppLink(url);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (success && context.mounted) {
|
||||||
context.pop();
|
context.pop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class ContextMenuDialog extends HookConsumerWidget {
|
|||||||
final isSupported = useCachedFuture(
|
final isSupported = useCachedFuture(
|
||||||
// ignore: discarded_futures useFuture
|
// ignore: discarded_futures useFuture
|
||||||
() => LaunchExternal.isSupported(hitResult),
|
() => LaunchExternal.isSupported(hitResult),
|
||||||
|
[hitResult],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isSupported.data == false) {
|
if (isSupported.data == false) {
|
||||||
|
|||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.weblibre.flutter_mozilla_components.api
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAppLinksApi
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of GeckoAppLinksApi that detects and launches external applications
|
||||||
|
* that can handle URLs.
|
||||||
|
*
|
||||||
|
* This uses Mozilla Android Components' AppLinksUseCases to properly detect if a native
|
||||||
|
* app is available to handle a URL, matching the behavior in Firefox/Fenix.
|
||||||
|
*/
|
||||||
|
class GeckoAppLinksApiImpl(
|
||||||
|
private val context: Context
|
||||||
|
) : GeckoAppLinksApi {
|
||||||
|
companion object {
|
||||||
|
private val coroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val components by lazy {
|
||||||
|
requireNotNull(GlobalComponents.components) { "Components not initialized" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasExternalApp(url: String, callback: (Result<Boolean>) -> Unit) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
val redirect = components.useCases.appLinksUseCases.appLinkRedirect(url)
|
||||||
|
callback(Result.success(redirect.hasExternalApp()))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
callback(Result.success(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openAppLink(url: String, callback: (Result<Boolean>) -> Unit) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
val redirect = components.useCases.appLinksUseCases.appLinkRedirect(url)
|
||||||
|
|
||||||
|
if (!redirect.hasExternalApp()) {
|
||||||
|
callback(Result.success(false))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set FLAG_ACTIVITY_NEW_TASK to launch in new task
|
||||||
|
// This prevents issues with app task stacks
|
||||||
|
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
|
||||||
|
components.useCases.appLinksUseCases.openAppLink.invoke(redirect.appIntent)
|
||||||
|
callback(Result.success(true))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
callback(Result.success(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -21,6 +21,7 @@ import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents
|
|||||||
import eu.weblibre.flutter_mozilla_components.pigeons.ContentBlocking
|
import eu.weblibre.flutter_mozilla_components.pigeons.ContentBlocking
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonsApi
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonsApi
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAppLinksApi
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoBookmarksApi
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoBookmarksApi
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoBrowserApi
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoBrowserApi
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoBrowserExtensionApi
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoBrowserExtensionApi
|
||||||
@@ -272,6 +273,7 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
|
|||||||
GeckoSitePermissionsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoSitePermissionsApiImpl())
|
GeckoSitePermissionsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoSitePermissionsApiImpl())
|
||||||
GeckoPublicSuffixListApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoPublicSuffixListApiImpl(profileApplicationContext))
|
GeckoPublicSuffixListApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoPublicSuffixListApiImpl(profileApplicationContext))
|
||||||
GeckoTrackingProtectionApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoTrackingProtectionApiImpl())
|
GeckoTrackingProtectionApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoTrackingProtectionApiImpl())
|
||||||
|
GeckoAppLinksApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoAppLinksApiImpl(profileApplicationContext))
|
||||||
|
|
||||||
// Viewport API for dynamic toolbar and keyboard handling
|
// Viewport API for dynamic toolbar and keyboard handling
|
||||||
val viewportEvents = GeckoViewportEvents(_flutterPluginBinding.binaryMessenger)
|
val viewportEvents = GeckoViewportEvents(_flutterPluginBinding.binaryMessenger)
|
||||||
|
|||||||
+82
@@ -7112,3 +7112,85 @@ interface GeckoTrackingProtectionApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* API for detecting and launching external applications that can handle URLs.
|
||||||
|
*
|
||||||
|
* This API wraps Mozilla Android Components' AppLinksUseCases to allow Flutter
|
||||||
|
* code to check if native apps can handle URLs and launch them directly.
|
||||||
|
*
|
||||||
|
* Generated interface from Pigeon that represents a handler of messages from Flutter.
|
||||||
|
*/
|
||||||
|
interface GeckoAppLinksApi {
|
||||||
|
/**
|
||||||
|
* Checks if an external application is available to handle the given URL.
|
||||||
|
*
|
||||||
|
* This method uses mozilla-components AppLinksUseCases to determine if
|
||||||
|
* a native app can handle the URL (e.g., YouTube app for youtube.com links).
|
||||||
|
*
|
||||||
|
* Returns true if an external app is available, false otherwise.
|
||||||
|
*/
|
||||||
|
fun hasExternalApp(url: String, callback: (Result<Boolean>) -> Unit)
|
||||||
|
/**
|
||||||
|
* Opens the URL in an external application if available.
|
||||||
|
*
|
||||||
|
* This method will:
|
||||||
|
* 1. Check if an external app can handle the URL
|
||||||
|
* 2. If available, launch the app directly with Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
* 3. Return true if successfully launched, false otherwise
|
||||||
|
*
|
||||||
|
* Returns true if URL was opened in external app, false if no app available.
|
||||||
|
*/
|
||||||
|
fun openAppLink(url: String, callback: (Result<Boolean>) -> Unit)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by GeckoAppLinksApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
GeckoPigeonCodec()
|
||||||
|
}
|
||||||
|
/** Sets up an instance of `GeckoAppLinksApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@JvmOverloads
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: GeckoAppLinksApi?, messageChannelSuffix: String = "") {
|
||||||
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoAppLinksApi.hasExternalApp$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val urlArg = args[0] as String
|
||||||
|
api.hasExternalApp(urlArg) { result: Result<Boolean> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(GeckoPigeonUtils.wrapError(error))
|
||||||
|
} else {
|
||||||
|
val data = result.getOrNull()
|
||||||
|
reply.reply(GeckoPigeonUtils.wrapResult(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoAppLinksApi.openAppLink$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val urlArg = args[0] as String
|
||||||
|
api.openAppLink(urlArg) { result: Result<Boolean> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(GeckoPigeonUtils.wrapError(error))
|
||||||
|
} else {
|
||||||
|
val data = result.getOrNull()
|
||||||
|
reply.reply(GeckoPigeonUtils.wrapResult(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export 'src/data/models/load_url_flags.dart';
|
|||||||
export 'src/data/models/source.dart';
|
export 'src/data/models/source.dart';
|
||||||
export 'src/domain/entities/default_selection_actions.dart';
|
export 'src/domain/entities/default_selection_actions.dart';
|
||||||
export 'src/domain/services/gecko_addon.dart';
|
export 'src/domain/services/gecko_addon.dart';
|
||||||
|
export 'src/domain/services/gecko_app_links.dart';
|
||||||
export 'src/domain/services/gecko_bookmarks.dart';
|
export 'src/domain/services/gecko_bookmarks.dart';
|
||||||
export 'src/domain/services/gecko_browser.dart';
|
export 'src/domain/services/gecko_browser.dart';
|
||||||
export 'src/domain/services/gecko_browser_extension.dart';
|
export 'src/domain/services/gecko_browser_extension.dart';
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
|
||||||
|
|
||||||
|
final _api = GeckoAppLinksApi();
|
||||||
|
|
||||||
|
/// Service for detecting and launching external applications that can handle URLs.
|
||||||
|
///
|
||||||
|
/// This service wraps Mozilla Android Components' AppLinksUseCases to allow
|
||||||
|
/// checking if native apps can handle URLs and launching them directly.
|
||||||
|
/// This matches the behavior in Firefox/Fenix for "Open in App" functionality.
|
||||||
|
class GeckoAppLinksService {
|
||||||
|
/// Checks if an external application is available to handle the given URL.
|
||||||
|
///
|
||||||
|
/// This method uses mozilla-components AppLinksUseCases to determine if
|
||||||
|
/// a native app can handle the URL (e.g., YouTube app for youtube.com links).
|
||||||
|
///
|
||||||
|
/// @param url The URL to check.
|
||||||
|
/// @return true if an external app is available, false otherwise.
|
||||||
|
Future<bool> hasExternalApp(Uri url) {
|
||||||
|
return _api.hasExternalApp(url.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the URL in an external application if available.
|
||||||
|
///
|
||||||
|
/// This method will:
|
||||||
|
/// 1. Check if an external app can handle the URL
|
||||||
|
/// 2. If available, launch the app directly with Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
/// 3. Return true if successfully launched, false otherwise
|
||||||
|
///
|
||||||
|
/// @param url The URL to open in external app.
|
||||||
|
/// @return true if URL was opened in external app, false if no app available.
|
||||||
|
Future<bool> openAppLink(Uri url) {
|
||||||
|
return _api.openAppLink(url.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8272,3 +8272,89 @@ class GeckoTrackingProtectionApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// API for detecting and launching external applications that can handle URLs.
|
||||||
|
///
|
||||||
|
/// This API wraps Mozilla Android Components' AppLinksUseCases to allow Flutter
|
||||||
|
/// code to check if native apps can handle URLs and launch them directly.
|
||||||
|
class GeckoAppLinksApi {
|
||||||
|
/// Constructor for [GeckoAppLinksApi]. 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.
|
||||||
|
GeckoAppLinksApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
|
|
||||||
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
|
/// Checks if an external application is available to handle the given URL.
|
||||||
|
///
|
||||||
|
/// This method uses mozilla-components AppLinksUseCases to determine if
|
||||||
|
/// a native app can handle the URL (e.g., YouTube app for youtube.com links).
|
||||||
|
///
|
||||||
|
/// Returns true if an external app is available, false otherwise.
|
||||||
|
Future<bool> hasExternalApp(String url) async {
|
||||||
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoAppLinksApi.hasExternalApp$pigeonVar_messageChannelSuffix';
|
||||||
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[url]);
|
||||||
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
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 if (pigeonVar_replyList[0] == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'null-error',
|
||||||
|
message: 'Host platform returned null value for non-null return value.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (pigeonVar_replyList[0] as bool?)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the URL in an external application if available.
|
||||||
|
///
|
||||||
|
/// This method will:
|
||||||
|
/// 1. Check if an external app can handle the URL
|
||||||
|
/// 2. If available, launch the app directly with Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
/// 3. Return true if successfully launched, false otherwise
|
||||||
|
///
|
||||||
|
/// Returns true if URL was opened in external app, false if no app available.
|
||||||
|
Future<bool> openAppLink(String url) async {
|
||||||
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoAppLinksApi.openAppLink$pigeonVar_messageChannelSuffix';
|
||||||
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[url]);
|
||||||
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
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 if (pigeonVar_replyList[0] == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'null-error',
|
||||||
|
message: 'Host platform returned null value for non-null return value.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (pigeonVar_replyList[0] as bool?)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1977,3 +1977,34 @@ abstract class GeckoTrackingProtectionApi {
|
|||||||
@async
|
@async
|
||||||
void removeAllExceptions();
|
void removeAllExceptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// App Links API
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// API for detecting and launching external applications that can handle URLs.
|
||||||
|
///
|
||||||
|
/// This API wraps Mozilla Android Components' AppLinksUseCases to allow Flutter
|
||||||
|
/// code to check if native apps can handle URLs and launch them directly.
|
||||||
|
@HostApi()
|
||||||
|
abstract class GeckoAppLinksApi {
|
||||||
|
/// Checks if an external application is available to handle the given URL.
|
||||||
|
///
|
||||||
|
/// This method uses mozilla-components AppLinksUseCases to determine if
|
||||||
|
/// a native app can handle the URL (e.g., YouTube app for youtube.com links).
|
||||||
|
///
|
||||||
|
/// Returns true if an external app is available, false otherwise.
|
||||||
|
@async
|
||||||
|
bool hasExternalApp(String url);
|
||||||
|
|
||||||
|
/// Opens the URL in an external application if available.
|
||||||
|
///
|
||||||
|
/// This method will:
|
||||||
|
/// 1. Check if an external app can handle the URL
|
||||||
|
/// 2. If available, launch the app directly with Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
/// 3. Return true if successfully launched, false otherwise
|
||||||
|
///
|
||||||
|
/// Returns true if URL was opened in external app, false if no app available.
|
||||||
|
@async
|
||||||
|
bool openAppLink(String url);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user