improve app link banner handling
This commit is contained in:
+24
-1
@@ -54,10 +54,33 @@ class AppLinkPromptHost extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Native expiry is lazy — it only runs when the store is queried or consumed — and nothing
|
||||||
|
// pushes an expiry event. A prompt that outlives its deadline would keep rendering live
|
||||||
|
// buttons whose resolution is already a no-op, so drop anything already past due rather than
|
||||||
|
// offering an action that cannot happen.
|
||||||
final activeRequests = prompts
|
final activeRequests = prompts
|
||||||
.where((request) => request.tabId == activeTabId)
|
.where(
|
||||||
|
(request) => request.tabId == activeTabId && request.expiresInMs > 0,
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
// ...and re-query when the soonest deadline passes, so a prompt retires itself on time
|
||||||
|
// instead of waiting for the next event or resume. The lower clamp matters: a request that
|
||||||
|
// reports 0 would otherwise reschedule instantly and spin.
|
||||||
|
final soonestExpiry = activeRequests.isEmpty
|
||||||
|
? null
|
||||||
|
: activeRequests
|
||||||
|
.map((request) => request.expiresInMs)
|
||||||
|
.reduce((a, b) => a < b ? a : b);
|
||||||
|
useEffect(() {
|
||||||
|
if (soonestExpiry == null) return null;
|
||||||
|
final timer = Timer(
|
||||||
|
Duration(milliseconds: soonestExpiry.clamp(250, 10 * 60 * 1000)),
|
||||||
|
() => unawaited(ref.read(appLinksCoordinatorProvider.notifier).refresh()),
|
||||||
|
);
|
||||||
|
return timer.cancel;
|
||||||
|
}, [soonestExpiry]);
|
||||||
|
|
||||||
final modalRequest = activeRequests
|
final modalRequest = activeRequests
|
||||||
.where((request) => request.isModal)
|
.where((request) => request.isModal)
|
||||||
.lastOrNull;
|
.lastOrNull;
|
||||||
|
|||||||
+6
-1
@@ -134,7 +134,12 @@ class GeckoAppLinksApiImpl(
|
|||||||
try {
|
try {
|
||||||
val components = GlobalComponents.components
|
val components = GlobalComponents.components
|
||||||
val list = components
|
val list = components
|
||||||
?.let { pendingStoreFor(it).getPending(owner).map(PendingAppLinkRequest::toPigeon) }
|
?.let {
|
||||||
|
val store = pendingStoreFor(it)
|
||||||
|
store.getPending(owner).map { request ->
|
||||||
|
request.toPigeon(store.expiresInMs(request))
|
||||||
|
}
|
||||||
|
}
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
callback(Result.success(list))
|
callback(Result.success(list))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
+37
-4
@@ -56,6 +56,7 @@ class NativeAppLinkPromptFeature(
|
|||||||
private val sessionUseCases: SessionUseCases,
|
private val sessionUseCases: SessionUseCases,
|
||||||
) : LifecycleAwareFeature {
|
) : LifecycleAwareFeature {
|
||||||
private var dialog: AlertDialog? = null
|
private var dialog: AlertDialog? = null
|
||||||
|
private var shownRequestId: Long? = null
|
||||||
private val mainHandler = Handler(Looper.getMainLooper())
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
@@ -70,15 +71,33 @@ class NativeAppLinkPromptFeature(
|
|||||||
dialog?.setOnDismissListener(null)
|
dialog?.setOnDismissListener(null)
|
||||||
dialog?.dismiss()
|
dialog?.dismiss()
|
||||||
dialog = null
|
dialog = null
|
||||||
|
shownRequestId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A new pending request may have been created for this tab (interceptor, engine thread) after
|
* The tab's pending requests changed (interceptor created one on an engine thread after [start]
|
||||||
* [start] already queried. Re-check on the main thread; [showNext] is idempotent (a no-op while a
|
* already queried, or the navigation middleware invalidated one). Re-check on the main thread;
|
||||||
* dialog is up or when nothing pends).
|
* [showNext] is idempotent (a no-op while a live dialog is up or when nothing pends).
|
||||||
*/
|
*/
|
||||||
fun onPromptAvailable() {
|
fun onPromptAvailable() {
|
||||||
mainHandler.post { showNext() }
|
mainHandler.post {
|
||||||
|
dismissStaleDialog()
|
||||||
|
showNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a dialog whose request has since been invalidated — otherwise it stays on screen as a
|
||||||
|
* dud whose Open button consumes nothing. Not a user dismissal: nothing is suppressed.
|
||||||
|
*/
|
||||||
|
private fun dismissStaleDialog() {
|
||||||
|
val shown = shownRequestId ?: return
|
||||||
|
if (store.peek(shown) != null) return
|
||||||
|
dialog?.setOnCancelListener(null)
|
||||||
|
dialog?.setOnDismissListener(null)
|
||||||
|
dialog?.dismiss()
|
||||||
|
dialog = null
|
||||||
|
shownRequestId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNext() {
|
private fun showNext() {
|
||||||
@@ -107,6 +126,14 @@ class NativeAppLinkPromptFeature(
|
|||||||
}
|
}
|
||||||
.setOnDismissListener { dialog = null }
|
.setOnDismissListener { dialog = null }
|
||||||
.show()
|
.show()
|
||||||
|
shownRequestId = request.requestId
|
||||||
|
|
||||||
|
// Expiry in the store is lazy, so nothing would take this dialog down when the request
|
||||||
|
// lapses — its buttons would consume nothing. Retire it on its own deadline.
|
||||||
|
mainHandler.postDelayed(
|
||||||
|
{ dismissStaleDialog() },
|
||||||
|
store.expiresInMs(request).coerceAtLeast(MIN_EXPIRY_TICK_MS),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveOpen(request: PendingAppLinkRequest) {
|
private fun resolveOpen(request: PendingAppLinkRequest) {
|
||||||
@@ -142,6 +169,12 @@ class NativeAppLinkPromptFeature(
|
|||||||
|
|
||||||
private fun afterResolve() {
|
private fun afterResolve() {
|
||||||
dialog = null
|
dialog = null
|
||||||
|
shownRequestId = null
|
||||||
showNext()
|
showNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
/** Never schedule a zero-delay expiry tick; a lapsed request would reschedule in a spin. */
|
||||||
|
const val MIN_EXPIRY_TICK_MS = 250L
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-70
@@ -51,7 +51,7 @@ data class PendingAppLinkRequest(
|
|||||||
val scopeKey: String,
|
val scopeKey: String,
|
||||||
val createdAt: Long,
|
val createdAt: Long,
|
||||||
) {
|
) {
|
||||||
fun toPigeon(): AppLinkPromptRequest = AppLinkPromptRequest(
|
fun toPigeon(expiresInMs: Long): AppLinkPromptRequest = AppLinkPromptRequest(
|
||||||
requestId = requestId,
|
requestId = requestId,
|
||||||
owner = owner,
|
owner = owner,
|
||||||
tabId = tabId,
|
tabId = tabId,
|
||||||
@@ -72,6 +72,7 @@ data class PendingAppLinkRequest(
|
|||||||
engineSupportsScheme = engineSupportsScheme,
|
engineSupportsScheme = engineSupportsScheme,
|
||||||
scopeKey = scopeKey,
|
scopeKey = scopeKey,
|
||||||
),
|
),
|
||||||
|
expiresInMs = expiresInMs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,10 +121,32 @@ object PendingAppLinkStores {
|
|||||||
* Query + consume: requests stay until resolved, invalidated, or expired. The store
|
* Query + consume: requests stay until resolved, invalidated, or expired. The store
|
||||||
* never holds its lock across a side effect — [consume] returns the request and the
|
* never holds its lock across a side effect — [consume] returns the request and the
|
||||||
* caller performs launch/fallback after the lock is released.
|
* caller performs launch/fallback after the lock is released.
|
||||||
|
*
|
||||||
|
* **A request's lifetime is deliberately not derived from navigation.** Three attempts to infer
|
||||||
|
* "the user has left the page this prompt belongs to" from the [mozilla.components.browser.state.store.BrowserStore]
|
||||||
|
* action stream all failed the same way, because every available signal is *per document* while a
|
||||||
|
* single user-visible navigation spans several:
|
||||||
|
* - comparing the committed URL's host to the request's anchor killed a banner on its own redirect
|
||||||
|
* chain (`youtu.be` → `youtube.com`, shortener → destination) and killed a modal on the commit of
|
||||||
|
* the very load it had interrupted — leaving a denied navigation with no dialog to un-stall it;
|
||||||
|
* - counting load starts ([mozilla.components.browser.state.action.ContentAction.UpdateLoadingStateAction])
|
||||||
|
* dismissed banners seconds in, because each redirected document starts its own load;
|
||||||
|
* - settling on idle only moved that to the first `onPageStop`, which multi-document pages reach
|
||||||
|
* long before the user is done with them.
|
||||||
|
*
|
||||||
|
* So navigation is not consulted at all. A request ends when the user answers it, when its tab
|
||||||
|
* closes, when a newer banner for the same tab replaces it, or when it expires ([BANNER_EXPIRY_MS]
|
||||||
|
* for the passive banner, [REQUEST_EXPIRY_MS] for a modal that is holding a navigation). The
|
||||||
|
* residual risk — a banner outliving the page it was raised on — is bounded by that expiry and is
|
||||||
|
* strictly safer than the alternatives: a lingering banner still names its target and still opens
|
||||||
|
* exactly that link, whereas the invalidation heuristics produced prompts that silently did
|
||||||
|
* nothing. Do not reintroduce URL- or load-state-derived invalidation without a signal that is
|
||||||
|
* per *navigation* rather than per document.
|
||||||
*/
|
*/
|
||||||
class PendingAppLinkStore(
|
class PendingAppLinkStore(
|
||||||
private val clock: MonotonicClock = MonotonicClock.SYSTEM,
|
private val clock: MonotonicClock = MonotonicClock.SYSTEM,
|
||||||
private val requestExpiryMs: Long = REQUEST_EXPIRY_MS,
|
private val requestExpiryMs: Long = REQUEST_EXPIRY_MS,
|
||||||
|
private val bannerExpiryMs: Long = BANNER_EXPIRY_MS,
|
||||||
private val suppressionExpiryMs: Long = SUPPRESSION_EXPIRY_MS,
|
private val suppressionExpiryMs: Long = SUPPRESSION_EXPIRY_MS,
|
||||||
private val dedupeWindowMs: Long = DEDUPE_WINDOW_MS,
|
private val dedupeWindowMs: Long = DEDUPE_WINDOW_MS,
|
||||||
private val fallbackReentryMs: Long = FALLBACK_REENTRY_MS,
|
private val fallbackReentryMs: Long = FALLBACK_REENTRY_MS,
|
||||||
@@ -180,11 +203,26 @@ class PendingAppLinkStore(
|
|||||||
scopeKey = input.scopeKey,
|
scopeKey = input.scopeKey,
|
||||||
createdAt = clock.elapsedRealtime(),
|
createdAt = clock.elapsedRealtime(),
|
||||||
)
|
)
|
||||||
|
// At most one live banner per tab: the surface renders one anyway, and a second
|
||||||
|
// app-link site visited in the same tab should replace the offer, not stack behind it.
|
||||||
|
if (request.urlClass == AppLinkUrlClass.BANNER) {
|
||||||
|
requests.values.removeAll {
|
||||||
|
it.tabId == request.tabId && it.urlClass == AppLinkUrlClass.BANNER
|
||||||
|
}
|
||||||
|
}
|
||||||
requests[request.requestId] = request
|
requests[request.requestId] = request
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long [request] has left before [sweepExpiredLocked] drops it. Handed to the surface so it
|
||||||
|
* can retire the prompt on time — expiry is lazy (it only runs on query/consume), so a prompt
|
||||||
|
* left on screen past its deadline would still render buttons that resolve to `stale`.
|
||||||
|
*/
|
||||||
|
fun expiresInMs(request: PendingAppLinkRequest): Long =
|
||||||
|
(expiryFor(request) - (clock.elapsedRealtime() - request.createdAt)).coerceAtLeast(0L)
|
||||||
|
|
||||||
/** Non-consuming query of live requests for [owner]. */
|
/** Non-consuming query of live requests for [owner]. */
|
||||||
fun getPending(owner: AppLinkPromptOwner): List<PendingAppLinkRequest> {
|
fun getPending(owner: AppLinkPromptOwner): List<PendingAppLinkRequest> {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
@@ -212,80 +250,23 @@ class PendingAppLinkStore(
|
|||||||
synchronized(lock) { requests.remove(requestId) }
|
synchronized(lock) { requests.remove(requestId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invalidate every pending request for a tab (tab close / replacement). */
|
/**
|
||||||
fun invalidateTab(tabId: String) {
|
* Invalidate every pending request for a tab (tab close / replacement).
|
||||||
|
*
|
||||||
|
* @return the owners that had a request removed, so the caller can tell those surfaces to
|
||||||
|
* re-query instead of leaving a dead prompt on screen.
|
||||||
|
*/
|
||||||
|
fun invalidateTab(tabId: String): Set<AppLinkPromptOwner> {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
|
val owners = requests.values
|
||||||
|
.filter { it.tabId == tabId }
|
||||||
|
.mapTo(mutableSetOf()) { it.owner }
|
||||||
requests.values.removeAll { it.tabId == tabId }
|
requests.values.removeAll { it.tabId == tabId }
|
||||||
suppression.keys.removeAll { it.startsWith("$tabId\u0000") }
|
suppression.keys.removeAll { it.startsWith("$tabId\u0000") }
|
||||||
|
return owners
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A committed top-level navigation in [tabId]. A request whose own page committed
|
|
||||||
* stays alive (that commit is the page the prompt sits on); a commit to a
|
|
||||||
* *different site* invalidates the tab's pending requests (§2.6).
|
|
||||||
*
|
|
||||||
* Matching is by **normalised host**, not exact URL: the initial load a banner
|
|
||||||
* rides on almost always commits at a redirected/normalised URL (`www`, trailing
|
|
||||||
* slash, tracking params) that never equals the intercepted URL, so an exact-URL
|
|
||||||
* check would invalidate every banner on its own page load. The anchor is the
|
|
||||||
* target host for a banner (the page it loads) and the source host for a modal
|
|
||||||
* (the page it is shown over, since the modal's own navigation was denied). When
|
|
||||||
* no host can be derived, the request is kept and left to expiry/tab-close.
|
|
||||||
*/
|
|
||||||
fun onCommittedNavigation(tabId: String, committedUrl: String) {
|
|
||||||
val committedHost = siteKey(committedUrl)
|
|
||||||
synchronized(lock) {
|
|
||||||
val removed = mutableListOf<Long>()
|
|
||||||
requests.values.removeAll { request ->
|
|
||||||
if (request.tabId != tabId) return@removeAll false
|
|
||||||
val anchorHost = siteKey(if (request.isModal) request.sourceUrl else request.url)
|
|
||||||
val invalidate = anchorHost != null && committedHost != null && anchorHost != committedHost
|
|
||||||
if (invalidate) removed.add(request.requestId)
|
|
||||||
invalidate
|
|
||||||
}
|
|
||||||
if (removed.isNotEmpty()) {
|
|
||||||
logger.info(
|
|
||||||
"onCommittedNavigation tab=$tabId committedHost=$committedHost invalidated=$removed",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Normalised, subdomain-stripped host for same-site comparison; null if underivable. */
|
|
||||||
private fun siteKey(url: String?): String? {
|
|
||||||
val rawHost = extractHost(url) ?: return null
|
|
||||||
val normalized = AppLinkHostNormalizer.normalizeHost(rawHost) ?: return null
|
|
||||||
return stripCommonSubDomains(normalized)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractHost(url: String?): String? {
|
|
||||||
if (url.isNullOrEmpty()) return null
|
|
||||||
val schemeSep = url.indexOf("://")
|
|
||||||
if (schemeSep < 0) return null
|
|
||||||
val afterScheme = url.substring(schemeSep + 3)
|
|
||||||
val end = afterScheme.indexOfFirst { it == '/' || it == '?' || it == '#' }
|
|
||||||
var authority = if (end >= 0) afterScheme.substring(0, end) else afterScheme
|
|
||||||
val at = authority.lastIndexOf('@')
|
|
||||||
if (at >= 0) authority = authority.substring(at + 1)
|
|
||||||
// Preserve a bracketed IPv6 literal; AppLinkHostNormalizer canonicalises it.
|
|
||||||
if (authority.startsWith("[")) {
|
|
||||||
val close = authority.indexOf(']')
|
|
||||||
return if (close >= 0) authority.substring(0, close + 1) else null
|
|
||||||
}
|
|
||||||
val colon = authority.lastIndexOf(':')
|
|
||||||
if (colon >= 0) authority = authority.substring(0, colon)
|
|
||||||
return authority.ifEmpty { null }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stripCommonSubDomains(host: String): String = when {
|
|
||||||
host.startsWith("www.") -> host.removePrefix("www.")
|
|
||||||
host.startsWith("m.") -> host.removePrefix("m.")
|
|
||||||
host.startsWith("mobile.") -> host.removePrefix("mobile.")
|
|
||||||
host.startsWith("maps.") -> host.removePrefix("maps.")
|
|
||||||
else -> host
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Suppression (§2.6) ----
|
// ---- Suppression (§2.6) ----
|
||||||
|
|
||||||
fun recordSuppression(tabId: String, fingerprint: String) {
|
fun recordSuppression(tabId: String, fingerprint: String) {
|
||||||
@@ -326,15 +307,24 @@ class PendingAppLinkStore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A banner is a passive offer sitting over a page the user keeps reading, so it is bounded by
|
||||||
|
* time rather than by navigation (see the class KDoc). A modal blocks a navigation until it is
|
||||||
|
* answered, so it keeps the long window.
|
||||||
|
*/
|
||||||
|
private fun expiryFor(request: PendingAppLinkRequest): Long =
|
||||||
|
if (request.urlClass == AppLinkUrlClass.BANNER) bannerExpiryMs else requestExpiryMs
|
||||||
|
|
||||||
private fun sweepExpiredLocked() {
|
private fun sweepExpiredLocked() {
|
||||||
val now = clock.elapsedRealtime()
|
val now = clock.elapsedRealtime()
|
||||||
requests.values.removeAll { now > it.createdAt + requestExpiryMs }
|
requests.values.removeAll { now > it.createdAt + expiryFor(it) }
|
||||||
suppression.values.removeAll { now > it }
|
suppression.values.removeAll { now > it }
|
||||||
fallbackReentry.values.removeAll { now > it }
|
fallbackReentry.values.removeAll { now > it }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val REQUEST_EXPIRY_MS = 10 * 60 * 1000L
|
const val REQUEST_EXPIRY_MS = 10 * 60 * 1000L
|
||||||
|
const val BANNER_EXPIRY_MS = 90 * 1000L
|
||||||
const val SUPPRESSION_EXPIRY_MS = 10 * 60 * 1000L
|
const val SUPPRESSION_EXPIRY_MS = 10 * 60 * 1000L
|
||||||
const val DEDUPE_WINDOW_MS = 2000L
|
const val DEDUPE_WINDOW_MS = 2000L
|
||||||
const val FALLBACK_REENTRY_MS = 10 * 1000L
|
const val FALLBACK_REENTRY_MS = 10 * 1000L
|
||||||
|
|||||||
+38
-20
@@ -6,9 +6,12 @@
|
|||||||
|
|
||||||
package eu.weblibre.flutter_mozilla_components.middleware
|
package eu.weblibre.flutter_mozilla_components.middleware
|
||||||
|
|
||||||
|
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||||
|
import eu.weblibre.flutter_mozilla_components.applinks.NativeAppLinkPromptNotifier
|
||||||
import eu.weblibre.flutter_mozilla_components.applinks.PendingAppLinkStore
|
import eu.weblibre.flutter_mozilla_components.applinks.PendingAppLinkStore
|
||||||
|
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.AppLinkPromptOwner
|
||||||
import mozilla.components.browser.state.action.BrowserAction
|
import mozilla.components.browser.state.action.BrowserAction
|
||||||
import mozilla.components.browser.state.action.ContentAction
|
|
||||||
import mozilla.components.browser.state.action.CustomTabListAction
|
import mozilla.components.browser.state.action.CustomTabListAction
|
||||||
import mozilla.components.browser.state.action.EngineAction
|
import mozilla.components.browser.state.action.EngineAction
|
||||||
import mozilla.components.browser.state.action.TabListAction
|
import mozilla.components.browser.state.action.TabListAction
|
||||||
@@ -17,17 +20,18 @@ import mozilla.components.lib.state.Middleware
|
|||||||
import mozilla.components.lib.state.Store
|
import mozilla.components.lib.state.Store
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observes the [BrowserStore] and drives [PendingAppLinkStore] invalidation and
|
* Observes the [BrowserStore] and drives [PendingAppLinkStore] tab teardown and suppression
|
||||||
* suppression clearing (APP_LINKS_OWN_IMPLEMENTATION_PLAN.md §2.6):
|
* clearing (APP_LINKS_OWN_IMPLEMENTATION_PLAN.md §2.6):
|
||||||
*
|
*
|
||||||
* - a committed top-level navigation whose URL is not a request's own target
|
* - tab close / Custom Tab removal invalidates the tab's pending requests and suppression, and
|
||||||
* invalidates that request (a banner-class request's target committing keeps it
|
* tells the owning surface to re-query so no dead prompt is left on screen;
|
||||||
* alive — that commit is the page the banner sits on);
|
* - a new user-initiated/direct navigation (omnibar, bookmark, typed URL — which dispatch a
|
||||||
* - tab close / Custom Tab removal invalidates the tab's pending requests and
|
* `LoadUrlAction`) clears the tab's suppression. In-page redirects do not dispatch these
|
||||||
* suppression;
|
* actions, so the redirect-loop defence stays intact.
|
||||||
* - a new user-initiated/direct navigation (omnibar, bookmark, typed URL — which
|
*
|
||||||
* dispatch a `LoadUrlAction`) clears the tab's suppression. In-page redirects
|
* It deliberately does **not** invalidate prompts on navigation: see the [PendingAppLinkStore]
|
||||||
* do not dispatch these actions, so the redirect-loop defence stays intact.
|
* KDoc for the three per-document signals that were tried and why none of them can express
|
||||||
|
* "the user left this page".
|
||||||
*/
|
*/
|
||||||
class AppLinkNavigationMiddleware(
|
class AppLinkNavigationMiddleware(
|
||||||
private val store: PendingAppLinkStore,
|
private val store: PendingAppLinkStore,
|
||||||
@@ -38,13 +42,7 @@ class AppLinkNavigationMiddleware(
|
|||||||
action: BrowserAction,
|
action: BrowserAction,
|
||||||
) {
|
) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is ContentAction.UpdateUrlAction -> {
|
|
||||||
// A committed top-level navigation.
|
|
||||||
this.store.onCommittedNavigation(action.sessionId, action.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
is EngineAction.LoadUrlAction -> {
|
is EngineAction.LoadUrlAction -> {
|
||||||
// App-initiated (direct) navigation — clears suppression.
|
|
||||||
this.store.clearSuppressionForTab(action.tabId)
|
this.store.clearSuppressionForTab(action.tabId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,15 +51,17 @@ class AppLinkNavigationMiddleware(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is TabListAction.RemoveTabAction -> {
|
is TabListAction.RemoveTabAction -> {
|
||||||
this.store.invalidateTab(action.tabId)
|
notifyInvalidated(action.tabId, this.store.invalidateTab(action.tabId))
|
||||||
}
|
}
|
||||||
|
|
||||||
is TabListAction.RemoveTabsAction -> {
|
is TabListAction.RemoveTabsAction -> {
|
||||||
action.tabIds.forEach(this.store::invalidateTab)
|
action.tabIds.forEach { tabId ->
|
||||||
|
notifyInvalidated(tabId, this.store.invalidateTab(tabId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is CustomTabListAction.RemoveCustomTabAction -> {
|
is CustomTabListAction.RemoveCustomTabAction -> {
|
||||||
this.store.invalidateTab(action.tabId)
|
notifyInvalidated(action.tabId, this.store.invalidateTab(action.tabId))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
@@ -69,4 +69,22 @@ class AppLinkNavigationMiddleware(
|
|||||||
|
|
||||||
next(action)
|
next(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nudge each affected surface to re-query the pending store. The event is the same
|
||||||
|
* "prompts changed" signal the interceptor sends on creation — the query is authoritative, so
|
||||||
|
* a lost event only delays the cleanup to the next resume.
|
||||||
|
*/
|
||||||
|
private fun notifyInvalidated(tabId: String, owners: Set<AppLinkPromptOwner>) {
|
||||||
|
for (owner in owners) {
|
||||||
|
when (owner) {
|
||||||
|
AppLinkPromptOwner.NATIVE_EXTERNAL ->
|
||||||
|
NativeAppLinkPromptNotifier.notifyPromptAvailable(tabId)
|
||||||
|
|
||||||
|
AppLinkPromptOwner.FLUTTER_BROWSER ->
|
||||||
|
GlobalComponents.appLinkEvents
|
||||||
|
?.onAppLinkPromptAvailable(EventSequence.next(), owner) { _ -> }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-4
@@ -5929,7 +5929,14 @@ data class AppLinkPromptRequest (
|
|||||||
* unsupported-scheme prompt.
|
* unsupported-scheme prompt.
|
||||||
*/
|
*/
|
||||||
val isModal: Boolean,
|
val isModal: Boolean,
|
||||||
val target: AppLinkTarget
|
val target: AppLinkTarget,
|
||||||
|
/**
|
||||||
|
* Milliseconds until the native store drops this request, measured at query
|
||||||
|
* time. The surface showing it must stop offering it by then: resolving an
|
||||||
|
* expired request is a no-op, so a prompt left on screen past this becomes a
|
||||||
|
* button that silently does nothing.
|
||||||
|
*/
|
||||||
|
val expiresInMs: Long
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
companion object {
|
companion object {
|
||||||
@@ -5945,7 +5952,8 @@ data class AppLinkPromptRequest (
|
|||||||
val canRemember = pigeonVar_list[8] as Boolean
|
val canRemember = pigeonVar_list[8] as Boolean
|
||||||
val isModal = pigeonVar_list[9] as Boolean
|
val isModal = pigeonVar_list[9] as Boolean
|
||||||
val target = pigeonVar_list[10] as AppLinkTarget
|
val target = pigeonVar_list[10] as AppLinkTarget
|
||||||
return AppLinkPromptRequest(requestId, owner, tabId, contextId, sourceUrl, isPrivate, isWallet, isProtectedContext, canRemember, isModal, target)
|
val expiresInMs = pigeonVar_list[11] as Long
|
||||||
|
return AppLinkPromptRequest(requestId, owner, tabId, contextId, sourceUrl, isPrivate, isWallet, isProtectedContext, canRemember, isModal, target, expiresInMs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
@@ -5961,6 +5969,7 @@ data class AppLinkPromptRequest (
|
|||||||
canRemember,
|
canRemember,
|
||||||
isModal,
|
isModal,
|
||||||
target,
|
target,
|
||||||
|
expiresInMs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -5971,7 +5980,7 @@ data class AppLinkPromptRequest (
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
val other = other as AppLinkPromptRequest
|
val other = other as AppLinkPromptRequest
|
||||||
return GeckoPigeonUtils.deepEquals(this.requestId, other.requestId) && GeckoPigeonUtils.deepEquals(this.owner, other.owner) && GeckoPigeonUtils.deepEquals(this.tabId, other.tabId) && GeckoPigeonUtils.deepEquals(this.contextId, other.contextId) && GeckoPigeonUtils.deepEquals(this.sourceUrl, other.sourceUrl) && GeckoPigeonUtils.deepEquals(this.isPrivate, other.isPrivate) && GeckoPigeonUtils.deepEquals(this.isWallet, other.isWallet) && GeckoPigeonUtils.deepEquals(this.isProtectedContext, other.isProtectedContext) && GeckoPigeonUtils.deepEquals(this.canRemember, other.canRemember) && GeckoPigeonUtils.deepEquals(this.isModal, other.isModal) && GeckoPigeonUtils.deepEquals(this.target, other.target)
|
return GeckoPigeonUtils.deepEquals(this.requestId, other.requestId) && GeckoPigeonUtils.deepEquals(this.owner, other.owner) && GeckoPigeonUtils.deepEquals(this.tabId, other.tabId) && GeckoPigeonUtils.deepEquals(this.contextId, other.contextId) && GeckoPigeonUtils.deepEquals(this.sourceUrl, other.sourceUrl) && GeckoPigeonUtils.deepEquals(this.isPrivate, other.isPrivate) && GeckoPigeonUtils.deepEquals(this.isWallet, other.isWallet) && GeckoPigeonUtils.deepEquals(this.isProtectedContext, other.isProtectedContext) && GeckoPigeonUtils.deepEquals(this.canRemember, other.canRemember) && GeckoPigeonUtils.deepEquals(this.isModal, other.isModal) && GeckoPigeonUtils.deepEquals(this.target, other.target) && GeckoPigeonUtils.deepEquals(this.expiresInMs, other.expiresInMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
@@ -5987,10 +5996,11 @@ data class AppLinkPromptRequest (
|
|||||||
result = 31 * result + GeckoPigeonUtils.deepHash(this.canRemember)
|
result = 31 * result + GeckoPigeonUtils.deepHash(this.canRemember)
|
||||||
result = 31 * result + GeckoPigeonUtils.deepHash(this.isModal)
|
result = 31 * result + GeckoPigeonUtils.deepHash(this.isModal)
|
||||||
result = 31 * result + GeckoPigeonUtils.deepHash(this.target)
|
result = 31 * result + GeckoPigeonUtils.deepHash(this.target)
|
||||||
|
result = 31 * result + GeckoPigeonUtils.deepHash(this.expiresInMs)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "AppLinkPromptRequest(requestId=$requestId, owner=$owner, tabId=$tabId, contextId=$contextId, sourceUrl=$sourceUrl, isPrivate=$isPrivate, isWallet=$isWallet, isProtectedContext=$isProtectedContext, canRemember=$canRemember, isModal=$isModal, target=$target)"
|
return "AppLinkPromptRequest(requestId=$requestId, owner=$owner, tabId=$tabId, contextId=$contextId, sourceUrl=$sourceUrl, isPrivate=$isPrivate, isWallet=$isWallet, isProtectedContext=$isProtectedContext, canRemember=$canRemember, isModal=$isModal, target=$target, expiresInMs=$expiresInMs)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+71
-23
@@ -103,33 +103,82 @@ class PendingAppLinkStoreTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun bannerTargetCommitKeepsRequestButUnrelatedCommitInvalidates() {
|
fun aNewerBannerForTheTabReplacesTheOlderOne() {
|
||||||
val store = PendingAppLinkStore(FakeClock())
|
val clock = FakeClock()
|
||||||
val banner = store.createRequest(
|
val store = PendingAppLinkStore(clock)
|
||||||
newRequest(urlClass = AppLinkUrlClass.BANNER, url = "https://youtu.be/x"),
|
val first = store.createRequest(
|
||||||
|
newRequest(urlClass = AppLinkUrlClass.BANNER, url = "https://a.example/x"),
|
||||||
)
|
)
|
||||||
// The banner's own target committing keeps it alive.
|
clock.now = 5000L // past the dedupe window, so this is a genuinely new offer
|
||||||
store.onCommittedNavigation("tab1", "https://youtu.be/x")
|
val second = store.createRequest(
|
||||||
assertNotNull(store.peek(banner.requestId))
|
newRequest(
|
||||||
// An unrelated commit invalidates it.
|
urlClass = AppLinkUrlClass.BANNER,
|
||||||
store.onCommittedNavigation("tab1", "https://example.com/other")
|
url = "https://b.example/y",
|
||||||
assertNull(store.peek(banner.requestId))
|
fingerprint = "fp2",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// One live banner per tab: visiting a second app-link site replaces the offer rather than
|
||||||
|
// stacking behind it.
|
||||||
|
assertNull(store.peek(first.requestId))
|
||||||
|
assertNotNull(store.peek(second.requestId))
|
||||||
|
assertEquals(1, store.getPending(AppLinkPromptOwner.FLUTTER_BROWSER).size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun bannerSurvivesSameSiteRedirectAndNormalisation() {
|
fun aBannerForAnotherTabIsUntouched() {
|
||||||
val store = PendingAppLinkStore(FakeClock())
|
val clock = FakeClock()
|
||||||
// The intercepted URL is rarely byte-identical to the committed one: the initial
|
val store = PendingAppLinkStore(clock)
|
||||||
// load redirects/normalises (www stripped, tracking params added, trailing slash).
|
val other = store.createRequest(
|
||||||
val banner = store.createRequest(
|
newRequest(tabId = "tab2", urlClass = AppLinkUrlClass.BANNER, url = "https://a.example/x"),
|
||||||
newRequest(urlClass = AppLinkUrlClass.BANNER, url = "https://www.reddit.com/r/foo"),
|
|
||||||
)
|
)
|
||||||
store.onCommittedNavigation("tab1", "https://reddit.com/r/foo/?utm_source=share")
|
clock.now = 5000L
|
||||||
assertNotNull(store.peek(banner.requestId))
|
store.createRequest(
|
||||||
|
newRequest(tabId = "tab1", urlClass = AppLinkUrlClass.BANNER, url = "https://b.example/y"),
|
||||||
|
)
|
||||||
|
assertNotNull(store.peek(other.requestId))
|
||||||
|
}
|
||||||
|
|
||||||
// A commit to a genuinely different site still invalidates it.
|
@Test
|
||||||
store.onCommittedNavigation("tab1", "https://twitter.com/reddit")
|
fun bannersExpireSoonerThanModals() {
|
||||||
|
val clock = FakeClock()
|
||||||
|
val store = PendingAppLinkStore(
|
||||||
|
clock,
|
||||||
|
requestExpiryMs = 10_000L,
|
||||||
|
bannerExpiryMs = 1_000L,
|
||||||
|
)
|
||||||
|
val banner = store.createRequest(
|
||||||
|
newRequest(urlClass = AppLinkUrlClass.BANNER, url = "https://a.example/x"),
|
||||||
|
)
|
||||||
|
val modal = store.createRequest(
|
||||||
|
newRequest(urlClass = AppLinkUrlClass.MODAL, fingerprint = "fp-modal"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// The banner is a passive offer bounded by time; the modal is holding a navigation open
|
||||||
|
// and keeps the long window.
|
||||||
|
clock.now = 1001L
|
||||||
assertNull(store.peek(banner.requestId))
|
assertNull(store.peek(banner.requestId))
|
||||||
|
assertNotNull(store.peek(modal.requestId))
|
||||||
|
|
||||||
|
clock.now = 10_001L
|
||||||
|
assertNull(store.peek(modal.requestId))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun remainingTtlIsReportedSoTheSurfaceCanRetireThePromptOnTime() {
|
||||||
|
val clock = FakeClock()
|
||||||
|
val store = PendingAppLinkStore(clock, bannerExpiryMs = 1_000L)
|
||||||
|
val banner = store.createRequest(
|
||||||
|
newRequest(urlClass = AppLinkUrlClass.BANNER, url = "https://a.example/x"),
|
||||||
|
)
|
||||||
|
assertEquals(1_000L, store.expiresInMs(banner))
|
||||||
|
|
||||||
|
clock.now = 400L
|
||||||
|
assertEquals(600L, store.expiresInMs(banner))
|
||||||
|
|
||||||
|
// Never negative: a surface schedules on this value, and expiry itself is lazy.
|
||||||
|
clock.now = 5_000L
|
||||||
|
assertEquals(0L, store.expiresInMs(banner))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -137,7 +186,7 @@ class PendingAppLinkStoreTest {
|
|||||||
val store = PendingAppLinkStore(FakeClock())
|
val store = PendingAppLinkStore(FakeClock())
|
||||||
val request = store.createRequest(newRequest())
|
val request = store.createRequest(newRequest())
|
||||||
store.recordSuppression("tab1", "fp1")
|
store.recordSuppression("tab1", "fp1")
|
||||||
store.invalidateTab("tab1")
|
assertEquals(setOf(AppLinkPromptOwner.FLUTTER_BROWSER), store.invalidateTab("tab1"))
|
||||||
assertNull(store.peek(request.requestId))
|
assertNull(store.peek(request.requestId))
|
||||||
assertFalse(store.isSuppressed("tab1", "fp1"))
|
assertFalse(store.isSuppressed("tab1", "fp1"))
|
||||||
}
|
}
|
||||||
@@ -148,8 +197,7 @@ class PendingAppLinkStoreTest {
|
|||||||
val store = PendingAppLinkStore(clock, suppressionExpiryMs = 1000L)
|
val store = PendingAppLinkStore(clock, suppressionExpiryMs = 1000L)
|
||||||
store.recordSuppression("tab1", "fp1")
|
store.recordSuppression("tab1", "fp1")
|
||||||
assertTrue(store.isSuppressed("tab1", "fp1"))
|
assertTrue(store.isSuppressed("tab1", "fp1"))
|
||||||
// Ordinary committed navigation does not clear it.
|
// A redirect within the current load does not clear it (no load start is dispatched).
|
||||||
store.onCommittedNavigation("tab1", "https://redirect.example")
|
|
||||||
assertTrue(store.isSuppressed("tab1", "fp1"))
|
assertTrue(store.isSuppressed("tab1", "fp1"))
|
||||||
// Direct navigation clears it.
|
// Direct navigation clears it.
|
||||||
store.clearSuppressionForTab("tab1")
|
store.clearSuppressionForTab("tab1")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3050,6 +3050,12 @@ class AppLinkPromptRequest {
|
|||||||
final bool isModal;
|
final bool isModal;
|
||||||
final AppLinkTarget target;
|
final AppLinkTarget target;
|
||||||
|
|
||||||
|
/// Milliseconds until the native store drops this request, measured at query
|
||||||
|
/// time. The surface showing it must stop offering it by then: resolving an
|
||||||
|
/// expired request is a no-op, so a prompt left on screen past this becomes a
|
||||||
|
/// button that silently does nothing.
|
||||||
|
final int expiresInMs;
|
||||||
|
|
||||||
const AppLinkPromptRequest({
|
const AppLinkPromptRequest({
|
||||||
required this.requestId,
|
required this.requestId,
|
||||||
required this.owner,
|
required this.owner,
|
||||||
@@ -3062,6 +3068,7 @@ class AppLinkPromptRequest {
|
|||||||
required this.canRemember,
|
required this.canRemember,
|
||||||
required this.isModal,
|
required this.isModal,
|
||||||
required this.target,
|
required this.target,
|
||||||
|
required this.expiresInMs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
||||||
|
|
||||||
Object? _extractReplyValueOrThrow(
|
Object? _extractReplyValueOrThrow(
|
||||||
List<Object?>? replyList,
|
List<Object?>? replyList,
|
||||||
String channelName, {
|
String channelName, {
|
||||||
required bool isNullValid,
|
required bool isNullValid,
|
||||||
}) {
|
}) {
|
||||||
if (replyList == null) {
|
if (replyList == null) {
|
||||||
throw PlatformException(
|
throw PlatformException(
|
||||||
@@ -34,11 +34,8 @@ Object? _extractReplyValueOrThrow(
|
|||||||
return replyList.firstOrNull;
|
return replyList.firstOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object?> wrapResponse({
|
|
||||||
Object? result,
|
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||||
PlatformException? error,
|
|
||||||
bool empty = false,
|
|
||||||
}) {
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -47,7 +44,6 @@ List<Object?> wrapResponse({
|
|||||||
}
|
}
|
||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (identical(a, b)) {
|
if (identical(a, b)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -60,9 +56,8 @@ bool _deepEquals(Object? a, Object? b) {
|
|||||||
}
|
}
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed.every(
|
a.indexed
|
||||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
if (a.length != b.length) {
|
if (a.length != b.length) {
|
||||||
@@ -111,6 +106,7 @@ int _deepHash(Object? value) {
|
|||||||
return value.hashCode;
|
return value.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum SingboxProxyProfileType {
|
enum SingboxProxyProfileType {
|
||||||
socks,
|
socks,
|
||||||
http,
|
http,
|
||||||
@@ -129,7 +125,13 @@ enum SingboxProxyProfileType {
|
|||||||
customOutbound,
|
customOutbound,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SingboxProxyRuntimeStatus { stopped, starting, running, stopping, error }
|
enum SingboxProxyRuntimeStatus {
|
||||||
|
stopped,
|
||||||
|
starting,
|
||||||
|
running,
|
||||||
|
stopping,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
class SingboxProxyProfile {
|
class SingboxProxyProfile {
|
||||||
SingboxProxyProfile({
|
SingboxProxyProfile({
|
||||||
@@ -155,12 +157,17 @@ class SingboxProxyProfile {
|
|||||||
String? secretJson;
|
String? secretJson;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[id, name, type, configJson, secretJson];
|
return <Object?>[
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
configJson,
|
||||||
|
secretJson,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static SingboxProxyProfile decode(Object result) {
|
static SingboxProxyProfile decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -182,11 +189,7 @@ class SingboxProxyProfile {
|
|||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(id, other.id) &&
|
return _deepEquals(id, other.id) && _deepEquals(name, other.name) && _deepEquals(type, other.type) && _deepEquals(configJson, other.configJson) && _deepEquals(secretJson, other.secretJson);
|
||||||
_deepEquals(name, other.name) &&
|
|
||||||
_deepEquals(type, other.type) &&
|
|
||||||
_deepEquals(configJson, other.configJson) &&
|
|
||||||
_deepEquals(secretJson, other.secretJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -236,8 +239,7 @@ class SingboxProxyRuntimeOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static SingboxProxyRuntimeOptions decode(Object result) {
|
static SingboxProxyRuntimeOptions decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -252,17 +254,13 @@ class SingboxProxyRuntimeOptions {
|
|||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! SingboxProxyRuntimeOptions ||
|
if (other is! SingboxProxyRuntimeOptions || other.runtimeType != runtimeType) {
|
||||||
other.runtimeType != runtimeType) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(preferredBasePort, other.preferredBasePort) &&
|
return _deepEquals(preferredBasePort, other.preferredBasePort) && _deepEquals(blockUnmatchedTraffic, other.blockUnmatchedTraffic) && _deepEquals(dnsConfig, other.dnsConfig) && _deepEquals(bootstrapDohUrl, other.bootstrapDohUrl);
|
||||||
_deepEquals(blockUnmatchedTraffic, other.blockUnmatchedTraffic) &&
|
|
||||||
_deepEquals(dnsConfig, other.dnsConfig) &&
|
|
||||||
_deepEquals(bootstrapDohUrl, other.bootstrapDohUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -333,8 +331,7 @@ class SingboxProxyDnsServerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static SingboxProxyDnsServerConfig decode(Object result) {
|
static SingboxProxyDnsServerConfig decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -352,20 +349,13 @@ class SingboxProxyDnsServerConfig {
|
|||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! SingboxProxyDnsServerConfig ||
|
if (other is! SingboxProxyDnsServerConfig || other.runtimeType != runtimeType) {
|
||||||
other.runtimeType != runtimeType) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(tag, other.tag) &&
|
return _deepEquals(tag, other.tag) && _deepEquals(address, other.address) && _deepEquals(detourTag, other.detourTag) && _deepEquals(matchDomainSuffixes, other.matchDomainSuffixes) && _deepEquals(matchGeosites, other.matchGeosites) && _deepEquals(matchOutbounds, other.matchOutbounds) && _deepEquals(matchInbounds, other.matchInbounds);
|
||||||
_deepEquals(address, other.address) &&
|
|
||||||
_deepEquals(detourTag, other.detourTag) &&
|
|
||||||
_deepEquals(matchDomainSuffixes, other.matchDomainSuffixes) &&
|
|
||||||
_deepEquals(matchGeosites, other.matchGeosites) &&
|
|
||||||
_deepEquals(matchOutbounds, other.matchOutbounds) &&
|
|
||||||
_deepEquals(matchInbounds, other.matchInbounds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -395,18 +385,20 @@ class SingboxProxyDnsConfig {
|
|||||||
String domainStrategy;
|
String domainStrategy;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[servers, finalServerTag, domainStrategy];
|
return <Object?>[
|
||||||
|
servers,
|
||||||
|
finalServerTag,
|
||||||
|
domainStrategy,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static SingboxProxyDnsConfig decode(Object result) {
|
static SingboxProxyDnsConfig decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
return SingboxProxyDnsConfig(
|
return SingboxProxyDnsConfig(
|
||||||
servers: (result[0]! as List<Object?>)
|
servers: (result[0]! as List<Object?>).cast<SingboxProxyDnsServerConfig>(),
|
||||||
.cast<SingboxProxyDnsServerConfig>(),
|
|
||||||
finalServerTag: result[1] as String?,
|
finalServerTag: result[1] as String?,
|
||||||
domainStrategy: result[2]! as String,
|
domainStrategy: result[2]! as String,
|
||||||
);
|
);
|
||||||
@@ -421,9 +413,7 @@ class SingboxProxyDnsConfig {
|
|||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(servers, other.servers) &&
|
return _deepEquals(servers, other.servers) && _deepEquals(finalServerTag, other.finalServerTag) && _deepEquals(domainStrategy, other.domainStrategy);
|
||||||
_deepEquals(finalServerTag, other.finalServerTag) &&
|
|
||||||
_deepEquals(domainStrategy, other.domainStrategy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -456,12 +446,17 @@ class SingboxProxyRuntimeEndpoint {
|
|||||||
String password;
|
String password;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[profileId, host, port, username, password];
|
return <Object?>[
|
||||||
|
profileId,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static SingboxProxyRuntimeEndpoint decode(Object result) {
|
static SingboxProxyRuntimeEndpoint decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -477,18 +472,13 @@ class SingboxProxyRuntimeEndpoint {
|
|||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! SingboxProxyRuntimeEndpoint ||
|
if (other is! SingboxProxyRuntimeEndpoint || other.runtimeType != runtimeType) {
|
||||||
other.runtimeType != runtimeType) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(profileId, other.profileId) &&
|
return _deepEquals(profileId, other.profileId) && _deepEquals(host, other.host) && _deepEquals(port, other.port) && _deepEquals(username, other.username) && _deepEquals(password, other.password);
|
||||||
_deepEquals(host, other.host) &&
|
|
||||||
_deepEquals(port, other.port) &&
|
|
||||||
_deepEquals(username, other.username) &&
|
|
||||||
_deepEquals(password, other.password);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -515,19 +505,21 @@ class SingboxProxyRuntimeState {
|
|||||||
String? message;
|
String? message;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[status, endpoints, message];
|
return <Object?>[
|
||||||
|
status,
|
||||||
|
endpoints,
|
||||||
|
message,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static SingboxProxyRuntimeState decode(Object result) {
|
static SingboxProxyRuntimeState decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
return SingboxProxyRuntimeState(
|
return SingboxProxyRuntimeState(
|
||||||
status: result[0]! as SingboxProxyRuntimeStatus,
|
status: result[0]! as SingboxProxyRuntimeStatus,
|
||||||
endpoints: (result[1]! as List<Object?>)
|
endpoints: (result[1]! as List<Object?>).cast<SingboxProxyRuntimeEndpoint>(),
|
||||||
.cast<SingboxProxyRuntimeEndpoint>(),
|
|
||||||
message: result[2] as String?,
|
message: result[2] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -535,16 +527,13 @@ class SingboxProxyRuntimeState {
|
|||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! SingboxProxyRuntimeState ||
|
if (other is! SingboxProxyRuntimeState || other.runtimeType != runtimeType) {
|
||||||
other.runtimeType != runtimeType) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(status, other.status) &&
|
return _deepEquals(status, other.status) && _deepEquals(endpoints, other.endpoints) && _deepEquals(message, other.message);
|
||||||
_deepEquals(endpoints, other.endpoints) &&
|
|
||||||
_deepEquals(message, other.message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -558,41 +547,43 @@ class SingboxProxyRuntimeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SingboxProxyConfigResult {
|
class SingboxProxyConfigResult {
|
||||||
SingboxProxyConfigResult({required this.configJson, required this.endpoints});
|
SingboxProxyConfigResult({
|
||||||
|
required this.configJson,
|
||||||
|
required this.endpoints,
|
||||||
|
});
|
||||||
|
|
||||||
String configJson;
|
String configJson;
|
||||||
|
|
||||||
List<SingboxProxyRuntimeEndpoint> endpoints;
|
List<SingboxProxyRuntimeEndpoint> endpoints;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[configJson, endpoints];
|
return <Object?>[
|
||||||
|
configJson,
|
||||||
|
endpoints,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static SingboxProxyConfigResult decode(Object result) {
|
static SingboxProxyConfigResult decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
return SingboxProxyConfigResult(
|
return SingboxProxyConfigResult(
|
||||||
configJson: result[0]! as String,
|
configJson: result[0]! as String,
|
||||||
endpoints: (result[1]! as List<Object?>)
|
endpoints: (result[1]! as List<Object?>).cast<SingboxProxyRuntimeEndpoint>(),
|
||||||
.cast<SingboxProxyRuntimeEndpoint>(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! SingboxProxyConfigResult ||
|
if (other is! SingboxProxyConfigResult || other.runtimeType != runtimeType) {
|
||||||
other.runtimeType != runtimeType) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(configJson, other.configJson) &&
|
return _deepEquals(configJson, other.configJson) && _deepEquals(endpoints, other.endpoints);
|
||||||
_deepEquals(endpoints, other.endpoints);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -622,12 +613,16 @@ class SingboxProxyLogMessage {
|
|||||||
String? profileId;
|
String? profileId;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[level, message, timestamp, profileId];
|
return <Object?>[
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
timestamp,
|
||||||
|
profileId,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static SingboxProxyLogMessage decode(Object result) {
|
static SingboxProxyLogMessage decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -648,10 +643,7 @@ class SingboxProxyLogMessage {
|
|||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(level, other.level) &&
|
return _deepEquals(level, other.level) && _deepEquals(message, other.message) && _deepEquals(timestamp, other.timestamp) && _deepEquals(profileId, other.profileId);
|
||||||
_deepEquals(message, other.message) &&
|
|
||||||
_deepEquals(timestamp, other.timestamp) &&
|
|
||||||
_deepEquals(profileId, other.profileId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -664,6 +656,7 @@ class SingboxProxyLogMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -671,34 +664,34 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is SingboxProxyProfileType) {
|
} else if (value is SingboxProxyProfileType) {
|
||||||
buffer.putUint8(129);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.index);
|
writeValue(buffer, value.index);
|
||||||
} else if (value is SingboxProxyRuntimeStatus) {
|
} else if (value is SingboxProxyRuntimeStatus) {
|
||||||
buffer.putUint8(130);
|
buffer.putUint8(130);
|
||||||
writeValue(buffer, value.index);
|
writeValue(buffer, value.index);
|
||||||
} else if (value is SingboxProxyProfile) {
|
} else if (value is SingboxProxyProfile) {
|
||||||
buffer.putUint8(131);
|
buffer.putUint8(131);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SingboxProxyRuntimeOptions) {
|
} else if (value is SingboxProxyRuntimeOptions) {
|
||||||
buffer.putUint8(132);
|
buffer.putUint8(132);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SingboxProxyDnsServerConfig) {
|
} else if (value is SingboxProxyDnsServerConfig) {
|
||||||
buffer.putUint8(133);
|
buffer.putUint8(133);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SingboxProxyDnsConfig) {
|
} else if (value is SingboxProxyDnsConfig) {
|
||||||
buffer.putUint8(134);
|
buffer.putUint8(134);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SingboxProxyRuntimeEndpoint) {
|
} else if (value is SingboxProxyRuntimeEndpoint) {
|
||||||
buffer.putUint8(135);
|
buffer.putUint8(135);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SingboxProxyRuntimeState) {
|
} else if (value is SingboxProxyRuntimeState) {
|
||||||
buffer.putUint8(136);
|
buffer.putUint8(136);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SingboxProxyConfigResult) {
|
} else if (value is SingboxProxyConfigResult) {
|
||||||
buffer.putUint8(137);
|
buffer.putUint8(137);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SingboxProxyLogMessage) {
|
} else if (value is SingboxProxyLogMessage) {
|
||||||
buffer.putUint8(138);
|
buffer.putUint8(138);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
@@ -741,13 +734,9 @@ class SingboxProxyApi {
|
|||||||
/// Constructor for [SingboxProxyApi]. The [binaryMessenger] named argument is
|
/// Constructor for [SingboxProxyApi]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
SingboxProxyApi({
|
SingboxProxyApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -755,97 +744,82 @@ class SingboxProxyApi {
|
|||||||
final String pigeonVar_messageChannelSuffix;
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
Future<String?> validateProfile(SingboxProxyProfile profile) async {
|
Future<String?> validateProfile(SingboxProxyProfile profile) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.validateProfile$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.validateProfile$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[profile]);
|
||||||
<Object?>[profile],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue as String?;
|
return pigeonVar_replyValue as String?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SingboxProxyConfigResult> buildConfig(
|
Future<SingboxProxyConfigResult> buildConfig(List<SingboxProxyProfile> profiles, SingboxProxyRuntimeOptions options) async {
|
||||||
List<SingboxProxyProfile> profiles,
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.buildConfig$pigeonVar_messageChannelSuffix';
|
||||||
SingboxProxyRuntimeOptions options,
|
|
||||||
) async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.buildConfig$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[profiles, options]);
|
||||||
<Object?>[profiles, options],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue! as SingboxProxyConfigResult;
|
return pigeonVar_replyValue! as SingboxProxyConfigResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SingboxProxyRuntimeState> start(
|
Future<SingboxProxyRuntimeState> start(List<SingboxProxyProfile> profiles, SingboxProxyRuntimeOptions options) async {
|
||||||
List<SingboxProxyProfile> profiles,
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.start$pigeonVar_messageChannelSuffix';
|
||||||
SingboxProxyRuntimeOptions options,
|
|
||||||
) async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.start$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[profiles, options]);
|
||||||
<Object?>[profiles, options],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue! as SingboxProxyRuntimeState;
|
return pigeonVar_replyValue! as SingboxProxyRuntimeState;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stop(List<String> profileIds) async {
|
Future<void> stop(List<String> profileIds) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.stop$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.stop$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[profileIds]);
|
||||||
<Object?>[profileIds],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
_extractReplyValueOrThrow(
|
_extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stopAll() async {
|
Future<void> stopAll() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.stopAll$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.stopAll$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -855,15 +829,15 @@ class SingboxProxyApi {
|
|||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
_extractReplyValueOrThrow(
|
_extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SingboxProxyRuntimeState> getState() async {
|
Future<SingboxProxyRuntimeState> getState() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.getState$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.getState$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -873,10 +847,11 @@ class SingboxProxyApi {
|
|||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue! as SingboxProxyRuntimeState;
|
return pigeonVar_replyValue! as SingboxProxyRuntimeState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -888,62 +863,46 @@ abstract class SingboxProxyEventsApi {
|
|||||||
|
|
||||||
void onLogMessage(SingboxProxyLogMessage message);
|
void onLogMessage(SingboxProxyLogMessage message);
|
||||||
|
|
||||||
static void setUp(
|
static void setUp(SingboxProxyEventsApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||||
SingboxProxyEventsApi? api, {
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
BinaryMessenger? binaryMessenger,
|
|
||||||
String messageChannelSuffix = '',
|
|
||||||
}) {
|
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyEventsApi.onStateChanged$messageChannelSuffix',
|
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyEventsApi.onStateChanged$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
final List<Object?> args = message! as List<Object?>;
|
final List<Object?> args = message! as List<Object?>;
|
||||||
final SingboxProxyRuntimeState arg_state =
|
final SingboxProxyRuntimeState arg_state = args[0]! as SingboxProxyRuntimeState;
|
||||||
args[0]! as SingboxProxyRuntimeState;
|
|
||||||
try {
|
try {
|
||||||
api.onStateChanged(arg_state);
|
api.onStateChanged(arg_state);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyEventsApi.onLogMessage$messageChannelSuffix',
|
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyEventsApi.onLogMessage$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
final List<Object?> args = message! as List<Object?>;
|
final List<Object?> args = message! as List<Object?>;
|
||||||
final SingboxProxyLogMessage arg_message =
|
final SingboxProxyLogMessage arg_message = args[0]! as SingboxProxyLogMessage;
|
||||||
args[0]! as SingboxProxyLogMessage;
|
|
||||||
try {
|
try {
|
||||||
api.onLogMessage(arg_message);
|
api.onLogMessage(arg_message);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
||||||
|
|
||||||
Object? _extractReplyValueOrThrow(
|
Object? _extractReplyValueOrThrow(
|
||||||
List<Object?>? replyList,
|
List<Object?>? replyList,
|
||||||
String channelName, {
|
String channelName, {
|
||||||
required bool isNullValid,
|
required bool isNullValid,
|
||||||
}) {
|
}) {
|
||||||
if (replyList == null) {
|
if (replyList == null) {
|
||||||
throw PlatformException(
|
throw PlatformException(
|
||||||
@@ -34,11 +34,8 @@ Object? _extractReplyValueOrThrow(
|
|||||||
return replyList.firstOrNull;
|
return replyList.firstOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object?> wrapResponse({
|
|
||||||
Object? result,
|
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||||
PlatformException? error,
|
|
||||||
bool empty = false,
|
|
||||||
}) {
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -47,7 +44,6 @@ List<Object?> wrapResponse({
|
|||||||
}
|
}
|
||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (identical(a, b)) {
|
if (identical(a, b)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -60,9 +56,8 @@ bool _deepEquals(Object? a, Object? b) {
|
|||||||
}
|
}
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed.every(
|
a.indexed
|
||||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
if (a.length != b.length) {
|
if (a.length != b.length) {
|
||||||
@@ -111,29 +106,23 @@ int _deepHash(Object? value) {
|
|||||||
return value.hashCode;
|
return value.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Transport types for Tor connections
|
/// Transport types for Tor connections
|
||||||
enum TransportType {
|
enum TransportType {
|
||||||
/// Direct Tor connection (no bridges)
|
/// Direct Tor connection (no bridges)
|
||||||
none,
|
none,
|
||||||
|
|
||||||
/// obfs4 pluggable transport
|
/// obfs4 pluggable transport
|
||||||
obfs4,
|
obfs4,
|
||||||
|
|
||||||
/// Snowflake pluggable transport (default broker)
|
/// Snowflake pluggable transport (default broker)
|
||||||
snowflake,
|
snowflake,
|
||||||
|
|
||||||
/// Snowflake via AMP cache
|
/// Snowflake via AMP cache
|
||||||
snowflakeAmp,
|
snowflakeAmp,
|
||||||
|
|
||||||
/// Meek pluggable transport
|
/// Meek pluggable transport
|
||||||
meek,
|
meek,
|
||||||
|
|
||||||
/// Meek via Azure CDN
|
/// Meek via Azure CDN
|
||||||
meekAzure,
|
meekAzure,
|
||||||
|
|
||||||
/// WebTunnel pluggable transport
|
/// WebTunnel pluggable transport
|
||||||
webtunnel,
|
webtunnel,
|
||||||
|
|
||||||
/// Custom bridge lines (passthrough)
|
/// Custom bridge lines (passthrough)
|
||||||
custom,
|
custom,
|
||||||
}
|
}
|
||||||
@@ -174,8 +163,7 @@ class TorConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static TorConfiguration decode(Object result) {
|
static TorConfiguration decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -197,11 +185,7 @@ class TorConfiguration {
|
|||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(transport, other.transport) &&
|
return _deepEquals(transport, other.transport) && _deepEquals(bridgeLines, other.bridgeLines) && _deepEquals(entryNodeCountries, other.entryNodeCountries) && _deepEquals(exitNodeCountries, other.exitNodeCountries) && _deepEquals(strictNodes, other.strictNodes);
|
||||||
_deepEquals(bridgeLines, other.bridgeLines) &&
|
|
||||||
_deepEquals(entryNodeCountries, other.entryNodeCountries) &&
|
|
||||||
_deepEquals(exitNodeCountries, other.exitNodeCountries) &&
|
|
||||||
_deepEquals(strictNodes, other.strictNodes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -250,8 +234,7 @@ class TorStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static TorStatus decode(Object result) {
|
static TorStatus decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -273,11 +256,7 @@ class TorStatus {
|
|||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(isRunning, other.isRunning) &&
|
return _deepEquals(isRunning, other.isRunning) && _deepEquals(socksPort, other.socksPort) && _deepEquals(bootstrapProgress, other.bootstrapProgress) && _deepEquals(currentCircuit, other.currentCircuit) && _deepEquals(exitNodeCountry, other.exitNodeCountry);
|
||||||
_deepEquals(socksPort, other.socksPort) &&
|
|
||||||
_deepEquals(bootstrapProgress, other.bootstrapProgress) &&
|
|
||||||
_deepEquals(currentCircuit, other.currentCircuit) &&
|
|
||||||
_deepEquals(exitNodeCountry, other.exitNodeCountry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -308,12 +287,15 @@ class TorLogMessage {
|
|||||||
int timestamp;
|
int timestamp;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[severity, message, timestamp];
|
return <Object?>[
|
||||||
|
severity,
|
||||||
|
message,
|
||||||
|
timestamp,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static TorLogMessage decode(Object result) {
|
static TorLogMessage decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -333,9 +315,7 @@ class TorLogMessage {
|
|||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(severity, other.severity) &&
|
return _deepEquals(severity, other.severity) && _deepEquals(message, other.message) && _deepEquals(timestamp, other.timestamp);
|
||||||
_deepEquals(message, other.message) &&
|
|
||||||
_deepEquals(timestamp, other.timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -348,6 +328,7 @@ class TorLogMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -355,16 +336,16 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is TransportType) {
|
} else if (value is TransportType) {
|
||||||
buffer.putUint8(129);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.index);
|
writeValue(buffer, value.index);
|
||||||
} else if (value is TorConfiguration) {
|
} else if (value is TorConfiguration) {
|
||||||
buffer.putUint8(130);
|
buffer.putUint8(130);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is TorStatus) {
|
} else if (value is TorStatus) {
|
||||||
buffer.putUint8(131);
|
buffer.putUint8(131);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is TorLogMessage) {
|
} else if (value is TorLogMessage) {
|
||||||
buffer.putUint8(132);
|
buffer.putUint8(132);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
@@ -396,10 +377,8 @@ class TorApi {
|
|||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -409,30 +388,27 @@ class TorApi {
|
|||||||
/// Start Tor with the given configuration
|
/// Start Tor with the given configuration
|
||||||
/// Returns a Future to avoid blocking the main thread
|
/// Returns a Future to avoid blocking the main thread
|
||||||
Future<int> startTor(TorConfiguration config) async {
|
Future<int> startTor(TorConfiguration config) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.startTor$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.TorApi.startTor$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[config]);
|
||||||
<Object?>[config],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue! as int;
|
return pigeonVar_replyValue! as int;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop Tor
|
/// Stop Tor
|
||||||
Future<void> stopTor() async {
|
Future<void> stopTor() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.stopTor$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.TorApi.stopTor$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -442,16 +418,16 @@ class TorApi {
|
|||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
_extractReplyValueOrThrow(
|
_extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current status
|
/// Get current status
|
||||||
Future<TorStatus> getStatus() async {
|
Future<TorStatus> getStatus() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.getStatus$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.TorApi.getStatus$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -461,17 +437,17 @@ class TorApi {
|
|||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue! as TorStatus;
|
return pigeonVar_replyValue! as TorStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request a new Tor identity (new circuit)
|
/// Request a new Tor identity (new circuit)
|
||||||
Future<void> requestNewIdentity() async {
|
Future<void> requestNewIdentity() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.requestNewIdentity$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.TorApi.requestNewIdentity$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -481,10 +457,11 @@ class TorApi {
|
|||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
_extractReplyValueOrThrow(
|
_extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,20 +475,12 @@ abstract class TorLogApi {
|
|||||||
/// Called when status changes
|
/// Called when status changes
|
||||||
void onStatusChanged(TorStatus status);
|
void onStatusChanged(TorStatus status);
|
||||||
|
|
||||||
static void setUp(
|
static void setUp(TorLogApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||||
TorLogApi? api, {
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
BinaryMessenger? binaryMessenger,
|
|
||||||
String messageChannelSuffix = '',
|
|
||||||
}) {
|
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix',
|
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
@@ -523,20 +492,16 @@ abstract class TorLogApi {
|
|||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix',
|
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
@@ -548,10 +513,8 @@ abstract class TorLogApi {
|
|||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -563,13 +526,9 @@ class IPtProxyController {
|
|||||||
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
|
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
IPtProxyController({
|
IPtProxyController({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -577,43 +536,39 @@ class IPtProxyController {
|
|||||||
final String pigeonVar_messageChannelSuffix;
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
Future<int> start(TransportType proxyType, String proxy) async {
|
Future<int> start(TransportType proxyType, String proxy) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.IPtProxyController.start$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.IPtProxyController.start$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType, proxy]);
|
||||||
<Object?>[proxyType, proxy],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue! as int;
|
return pigeonVar_replyValue! as int;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stop(TransportType proxyType) async {
|
Future<void> stop(TransportType proxyType) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType]);
|
||||||
<Object?>[proxyType],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
_extractReplyValueOrThrow(
|
_extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
||||||
|
|
||||||
Object? _extractReplyValueOrThrow(
|
Object? _extractReplyValueOrThrow(
|
||||||
List<Object?>? replyList,
|
List<Object?>? replyList,
|
||||||
String channelName, {
|
String channelName, {
|
||||||
required bool isNullValid,
|
required bool isNullValid,
|
||||||
}) {
|
}) {
|
||||||
if (replyList == null) {
|
if (replyList == null) {
|
||||||
throw PlatformException(
|
throw PlatformException(
|
||||||
@@ -46,9 +46,8 @@ bool _deepEquals(Object? a, Object? b) {
|
|||||||
}
|
}
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed.every(
|
a.indexed
|
||||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
if (a.length != b.length) {
|
if (a.length != b.length) {
|
||||||
@@ -97,20 +96,26 @@ int _deepHash(Object? value) {
|
|||||||
return value.hashCode;
|
return value.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LocalizedResult {
|
class LocalizedResult {
|
||||||
LocalizedResult({required this.languageName, this.countryName});
|
LocalizedResult({
|
||||||
|
required this.languageName,
|
||||||
|
this.countryName,
|
||||||
|
});
|
||||||
|
|
||||||
String languageName;
|
String languageName;
|
||||||
|
|
||||||
String? countryName;
|
String? countryName;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[languageName, countryName];
|
return <Object?>[
|
||||||
|
languageName,
|
||||||
|
countryName,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static LocalizedResult decode(Object result) {
|
static LocalizedResult decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -129,8 +134,7 @@ class LocalizedResult {
|
|||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(languageName, other.languageName) &&
|
return _deepEquals(languageName, other.languageName) && _deepEquals(countryName, other.countryName);
|
||||||
_deepEquals(countryName, other.countryName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -143,6 +147,7 @@ class LocalizedResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -150,7 +155,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is LocalizedResult) {
|
} else if (value is LocalizedResult) {
|
||||||
buffer.putUint8(129);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
@@ -173,40 +178,31 @@ class LocaleResolver {
|
|||||||
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
|
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
LocaleResolver({
|
LocaleResolver({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
|
|
||||||
final String pigeonVar_messageChannelSuffix;
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
Future<LocalizedResult> resolve(
|
Future<LocalizedResult> resolve(String languageTag, String targetLangouageTag) async {
|
||||||
String languageTag,
|
final pigeonVar_channelName = 'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
|
||||||
String targetLangouageTag,
|
|
||||||
) async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[languageTag, targetLangouageTag]);
|
||||||
<Object?>[languageTag, targetLangouageTag],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue! as LocalizedResult;
|
return pigeonVar_replyValue! as LocalizedResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
||||||
|
|
||||||
Object? _extractReplyValueOrThrow(
|
Object? _extractReplyValueOrThrow(
|
||||||
List<Object?>? replyList,
|
List<Object?>? replyList,
|
||||||
String channelName, {
|
String channelName, {
|
||||||
required bool isNullValid,
|
required bool isNullValid,
|
||||||
}) {
|
}) {
|
||||||
if (replyList == null) {
|
if (replyList == null) {
|
||||||
throw PlatformException(
|
throw PlatformException(
|
||||||
@@ -34,11 +34,8 @@ Object? _extractReplyValueOrThrow(
|
|||||||
return replyList.firstOrNull;
|
return replyList.firstOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object?> wrapResponse({
|
|
||||||
Object? result,
|
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||||
PlatformException? error,
|
|
||||||
bool empty = false,
|
|
||||||
}) {
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -47,7 +44,6 @@ List<Object?> wrapResponse({
|
|||||||
}
|
}
|
||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (identical(a, b)) {
|
if (identical(a, b)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -60,9 +56,8 @@ bool _deepEquals(Object? a, Object? b) {
|
|||||||
}
|
}
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed.every(
|
a.indexed
|
||||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
if (a.length != b.length) {
|
if (a.length != b.length) {
|
||||||
@@ -111,6 +106,7 @@ int _deepHash(Object? value) {
|
|||||||
return value.hashCode;
|
return value.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Intent {
|
class Intent {
|
||||||
Intent({
|
Intent({
|
||||||
this.fromPackageName,
|
this.fromPackageName,
|
||||||
@@ -145,8 +141,7 @@ class Intent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static Intent decode(Object result) {
|
static Intent decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -169,12 +164,7 @@ class Intent {
|
|||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _deepEquals(fromPackageName, other.fromPackageName) &&
|
return _deepEquals(fromPackageName, other.fromPackageName) && _deepEquals(action, other.action) && _deepEquals(data, other.data) && _deepEquals(categories, other.categories) && _deepEquals(mimeType, other.mimeType) && _deepEquals(extra, other.extra);
|
||||||
_deepEquals(action, other.action) &&
|
|
||||||
_deepEquals(data, other.data) &&
|
|
||||||
_deepEquals(categories, other.categories) &&
|
|
||||||
_deepEquals(mimeType, other.mimeType) &&
|
|
||||||
_deepEquals(extra, other.extra);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -187,6 +177,7 @@ class Intent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -194,7 +185,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is Intent) {
|
} else if (value is Intent) {
|
||||||
buffer.putUint8(129);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
@@ -217,13 +208,9 @@ class IntentHost {
|
|||||||
/// Constructor for [IntentHost]. The [binaryMessenger] named argument is
|
/// Constructor for [IntentHost]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
IntentHost({
|
IntentHost({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -235,8 +222,7 @@ class IntentHost {
|
|||||||
/// IntentEvents.setUp() was called (cold-start deep links).
|
/// IntentEvents.setUp() was called (cold-start deep links).
|
||||||
/// Returns null if no launch intent is pending.
|
/// Returns null if no launch intent is pending.
|
||||||
Future<Intent?> getInitialIntent() async {
|
Future<Intent?> getInitialIntent() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.simple_intent_receiver.IntentHost.getInitialIntent$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentHost.getInitialIntent$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -246,10 +232,11 @@ class IntentHost {
|
|||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue as Intent?;
|
return pigeonVar_replyValue as Intent?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,20 +246,12 @@ abstract class IntentEvents {
|
|||||||
|
|
||||||
void onIntentReceived(int sequence, Intent intent);
|
void onIntentReceived(int sequence, Intent intent);
|
||||||
|
|
||||||
static void setUp(
|
static void setUp(IntentEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||||
IntentEvents? api, {
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
BinaryMessenger? binaryMessenger,
|
|
||||||
String messageChannelSuffix = '',
|
|
||||||
}) {
|
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix',
|
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
@@ -285,10 +264,8 @@ abstract class IntentEvents {
|
|||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -300,13 +277,9 @@ class IntentGatekeeperHostApi {
|
|||||||
/// Constructor for [IntentGatekeeperHostApi]. The [binaryMessenger] named argument is
|
/// Constructor for [IntentGatekeeperHostApi]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
IntentGatekeeperHostApi({
|
IntentGatekeeperHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -316,23 +289,21 @@ class IntentGatekeeperHostApi {
|
|||||||
/// Replicates the blocked-packages policy to the native side so the
|
/// Replicates the blocked-packages policy to the native side so the
|
||||||
/// [IntentReceiverActivity] can reject intents without launching Flutter.
|
/// [IntentReceiverActivity] can reject intents without launching Flutter.
|
||||||
Future<void> setConfig(bool enabled, List<String> blockedPackages) async {
|
Future<void> setConfig(bool enabled, List<String> blockedPackages) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.setConfig$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.setConfig$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[enabled, blockedPackages]);
|
||||||
<Object?>[enabled, blockedPackages],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
_extractReplyValueOrThrow(
|
_extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replicates whether the Custom Tabs feature is enabled to the native side.
|
/// Replicates whether the Custom Tabs feature is enabled to the native side.
|
||||||
@@ -340,46 +311,42 @@ class IntentGatekeeperHostApi {
|
|||||||
/// share-with-URL intents to the main browser instead of launching the
|
/// share-with-URL intents to the main browser instead of launching the
|
||||||
/// stripped-down custom-tab activity. Defaults to enabled on the native side.
|
/// stripped-down custom-tab activity. Defaults to enabled on the native side.
|
||||||
Future<void> setCustomTabsEnabled(bool enabled) async {
|
Future<void> setCustomTabsEnabled(bool enabled) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.setCustomTabsEnabled$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.setCustomTabsEnabled$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[enabled]);
|
||||||
<Object?>[enabled],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
_extractReplyValueOrThrow(
|
_extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves a package name to its user-visible application label via
|
/// Resolves a package name to its user-visible application label via
|
||||||
/// [PackageManager]. Returns `null` if the package is not installed or the
|
/// [PackageManager]. Returns `null` if the package is not installed or the
|
||||||
/// label cannot be resolved.
|
/// label cannot be resolved.
|
||||||
Future<String?> resolvePackageLabel(String packageName) async {
|
Future<String?> resolvePackageLabel(String packageName) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.resolvePackageLabel$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.resolvePackageLabel$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[packageName]);
|
||||||
<Object?>[packageName],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue as String?;
|
return pigeonVar_replyValue as String?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,8 +356,7 @@ class IntentGatekeeperHostApi {
|
|||||||
/// [ackPendingAlwaysAllows] after Flutter settings were updated
|
/// [ackPendingAlwaysAllows] after Flutter settings were updated
|
||||||
/// successfully.
|
/// successfully.
|
||||||
Future<List<String>> getPendingAlwaysAllows() async {
|
Future<List<String>> getPendingAlwaysAllows() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.getPendingAlwaysAllows$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.getPendingAlwaysAllows$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -400,32 +366,31 @@ class IntentGatekeeperHostApi {
|
|||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return (pigeonVar_replyValue! as List<Object?>).cast<String>();
|
return (pigeonVar_replyValue! as List<Object?>).cast<String>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the given packages from the pending "Always allow" set after
|
/// Removes the given packages from the pending "Always allow" set after
|
||||||
/// Flutter has successfully persisted them into its own policy store.
|
/// Flutter has successfully persisted them into its own policy store.
|
||||||
Future<void> ackPendingAlwaysAllows(List<String> packageNames) async {
|
Future<void> ackPendingAlwaysAllows(List<String> packageNames) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.ackPendingAlwaysAllows$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentGatekeeperHostApi.ackPendingAlwaysAllows$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[packageNames]);
|
||||||
<Object?>[packageNames],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
_extractReplyValueOrThrow(
|
_extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: true,
|
isNullValid: true,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
||||||
|
|
||||||
Object? _extractReplyValueOrThrow(
|
Object? _extractReplyValueOrThrow(
|
||||||
List<Object?>? replyList,
|
List<Object?>? replyList,
|
||||||
String channelName, {
|
String channelName, {
|
||||||
required bool isNullValid,
|
required bool isNullValid,
|
||||||
}) {
|
}) {
|
||||||
if (replyList == null) {
|
if (replyList == null) {
|
||||||
throw PlatformException(
|
throw PlatformException(
|
||||||
@@ -34,11 +34,8 @@ Object? _extractReplyValueOrThrow(
|
|||||||
return replyList.firstOrNull;
|
return replyList.firstOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object?> wrapResponse({
|
|
||||||
Object? result,
|
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||||
PlatformException? error,
|
|
||||||
bool empty = false,
|
|
||||||
}) {
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -48,6 +45,7 @@ List<Object?> wrapResponse({
|
|||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -74,13 +72,9 @@ class SpeechToTextApi {
|
|||||||
/// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is
|
/// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
SpeechToTextApi({
|
SpeechToTextApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -95,23 +89,21 @@ class SpeechToTextApi {
|
|||||||
/// The [locale] parameter specifies the language locale for recognition
|
/// The [locale] parameter specifies the language locale for recognition
|
||||||
/// (e.g., 'en-US', 'de-DE'). If null, uses the device default.
|
/// (e.g., 'en-US', 'de-DE'). If null, uses the device default.
|
||||||
Future<bool> showDialog({String? locale}) async {
|
Future<bool> showDialog({String? locale}) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextApi.showDialog$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextApi.showDialog$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[locale]);
|
||||||
<Object?>[locale],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
pigeonVar_replyList,
|
pigeonVar_replyList,
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
isNullValid: false,
|
isNullValid: false,
|
||||||
);
|
)
|
||||||
|
;
|
||||||
return pigeonVar_replyValue! as bool;
|
return pigeonVar_replyValue! as bool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,20 +118,12 @@ abstract class SpeechToTextEvents {
|
|||||||
/// recognition failed or was cancelled.
|
/// recognition failed or was cancelled.
|
||||||
void onTextReceived(String text);
|
void onTextReceived(String text);
|
||||||
|
|
||||||
static void setUp(
|
static void setUp(SpeechToTextEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||||
SpeechToTextEvents? api, {
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
BinaryMessenger? binaryMessenger,
|
|
||||||
String messageChannelSuffix = '',
|
|
||||||
}) {
|
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix',
|
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
@@ -151,10 +135,8 @@ abstract class SpeechToTextEvents {
|
|||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user