implement open in app feature
This commit is contained in:
+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.GeckoAddonEvents
|
||||
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.GeckoBrowserApi
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoBrowserExtensionApi
|
||||
@@ -272,6 +273,7 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
|
||||
GeckoSitePermissionsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoSitePermissionsApiImpl())
|
||||
GeckoPublicSuffixListApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoPublicSuffixListApiImpl(profileApplicationContext))
|
||||
GeckoTrackingProtectionApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoTrackingProtectionApiImpl())
|
||||
GeckoAppLinksApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoAppLinksApiImpl(profileApplicationContext))
|
||||
|
||||
// Viewport API for dynamic toolbar and keyboard handling
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user