From c45f44632c0d53c4ac7c7dfca818a77635f94d6a Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Sat, 19 Apr 2025 01:28:41 +0200 Subject: [PATCH] bettern intent and navigation handling --- .../geckoview/domain/repositories/tab.dart | 15 +++- .../browser/presentation/screens/browser.dart | 10 +++ .../widgets/browser_modules/browser_view.dart | 2 + .../domain/services/sharing_intent.dart | 69 ++++++++----------- .../api/GeckoBrowserApiImpl.kt | 4 +- 5 files changed, 55 insertions(+), 45 deletions(-) diff --git a/app/lib/features/geckoview/domain/repositories/tab.dart b/app/lib/features/geckoview/domain/repositories/tab.dart index 25521991..7243746a 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.dart @@ -28,6 +28,15 @@ class TabRepository extends _$TabRepository { final _tabsService = GeckoTabService(); String? _previousTabId; + final _tabFromIntent = {}; + + bool hasLaunchedFromIntent(String? tabId) { + if (tabId == null) { + return false; + } + + return _tabFromIntent.contains(tabId); + } Future addTab({ Uri? url, @@ -272,13 +281,15 @@ class TabRepository extends _$TabRepository { next.whenData((value) async { switch (value) { case SharedUrl(): - await addTab(url: value.url); + _tabFromIntent.add(await addTab(url: value.url)); case SharedText(): final defaultSearchBang = ref.read(selectedBangDataProvider()) ?? await ref.read(defaultSearchBangDataProvider.future); - await addTab(url: defaultSearchBang?.getTemplateUrl(value.text)); + _tabFromIntent.add( + await addTab(url: defaultSearchBang?.getTemplateUrl(value.text)), + ); } }); }); diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart index b845f87c..f81fbef0 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -187,6 +188,14 @@ class BrowserScreen extends HookConsumerWidget { return true; } + if (ref + .read(tabRepositoryProvider.notifier) + .hasLaunchedFromIntent(selectedTabId)) { + //Mark back as unhandled and navigator will pop + await SystemNavigator.pop(); + return false; + } + if (lastBackButtonPress.value != null && DateTime.now().difference( lastBackButtonPress.value!, @@ -201,6 +210,7 @@ class BrowserScreen extends HookConsumerWidget { return true; } else { //Mark back as unhandled and navigator will pop + await SystemNavigator.pop(); return false; } } else { diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart index 0be157bf..8e5676a8 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart @@ -85,6 +85,8 @@ class _BrowserViewState extends ConsumerState _periodicScreenshotUpdate?.cancel(); _periodicScreenshotUpdate = null; _timerPaused = false; + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); } if (next.isLoading == false && diff --git a/app/lib/features/share_intent/domain/services/sharing_intent.dart b/app/lib/features/share_intent/domain/services/sharing_intent.dart index 5fcd69d1..d8b5359b 100644 --- a/app/lib/features/share_intent/domain/services/sharing_intent.dart +++ b/app/lib/features/share_intent/domain/services/sharing_intent.dart @@ -1,11 +1,9 @@ import 'dart:async'; -import 'package:collection/collection.dart'; -import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; -import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:lensai/core/logger.dart'; import 'package:lensai/data/models/received_intent_parameter.dart'; import 'package:mime/mime.dart' as mime; +import 'package:receive_intent/receive_intent.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:rxdart/rxdart.dart'; @@ -14,31 +12,30 @@ import 'package:uri_to_file/uri_to_file.dart' as uri_to_file; part 'sharing_intent.g.dart'; final _sharingIntentTransformer = - StreamTransformer, ReceivedIntentParameter>.fromHandlers( - handleData: (files, sink) async { - //For now only one file is supported to share with the app - final data = files.firstOrNull; + StreamTransformer.fromHandlers( + handleData: (intent, sink) async { + final data = switch (intent.action) { + 'android.intent.action.WEB_SEARCH' => + intent.extra?['query'] as String?, + 'android.intent.action.VIEW' => intent.data, + 'android.intent.action.SEND' => + intent.extra?['android.intent.extra.STREAM'] as String? ?? + intent.extra?['android.intent.extra.TEXT'] as String?, + _ => null, + }; - if (data != null && data.value != null) { - switch (data.type) { - case SharedMediaType.TEXT: - case SharedMediaType.URL: - case SharedMediaType.WEB_SEARCH: - if (uri_to_file.isUriSupported(data.value!)) { - final file = await uri_to_file.toFile(data.value!); - final mimeType = mime.lookupMimeType(file.path); - switch (mimeType) { - case 'application/pdf': - sink.add(ReceivedIntentParameter(data.value, null)); - default: - logger.w('Unhandled mime type: $mimeType'); - } - } else { - sink.add(ReceivedIntentParameter(data.value, null)); - } - - default: - logger.w('Unhandled media type: $data'); + if (data != null) { + if (uri_to_file.isUriSupported(data)) { + final file = await uri_to_file.toFile(data); + final mimeType = mime.lookupMimeType(file.path); + switch (mimeType) { + case 'application/pdf': + sink.add(ReceivedIntentParameter(data, null)); + default: + logger.w('Unhandled mime type: $mimeType'); + } + } else { + sink.add(ReceivedIntentParameter(data, null)); } } }, @@ -46,19 +43,9 @@ final _sharingIntentTransformer = @Riverpod() Raw> sharingIntentStream(Ref ref) { - final initialStream = - FlutterSharingIntent.instance - // ignore: discarded_futures is used as stream - .getInitialSharing() - // ignore: discarded_futures is used as stream - .then((event) { - FlutterSharingIntent.instance.reset(); - return event; - }) - .asStream(); - return ConcatStream([ - initialStream, - FlutterSharingIntent.instance.getMediaStream(), - ]).transform(_sharingIntentTransformer); + // ignore: discarded_futures + ReceiveIntent.getInitialIntent().asStream(), + ReceiveIntent.receivedIntentStream, + ]).whereNotNull().transform(_sharingIntentTransformer); } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt index 17915064..dd7925c3 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt @@ -87,10 +87,10 @@ class GeckoBrowserApiImpl : GeckoBrowserApi { override fun initialize() { synchronized(this) { + setupGeckoEngine() + if(!isGeckoInitialized) { Log.addSink(AndroidLogSink()) - - setupGeckoEngine() isGeckoInitialized = true } }