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 c191597d..7eff6ec0 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 @@ -216,12 +216,20 @@ class _BrowserViewState extends ConsumerState if (_suggestionCountTime != null && DateTime.now().difference(_suggestionCountTime!) > widget.suggestionTimeout) { - showSuggestNewTabMessage( - context, - onAdd: () async { - await const SearchRoute(tabType: TabType.regular).push(context); - }, - ); + //Don't do anything if a child route is active + if (GoRouterState.of(context).topRoute?.name == BrowserRoute.name) { + unawaited( + showSuggestNewTabMessage( + context, + onAdd: (searchText) async { + await SearchRoute( + tabType: TabType.regular, + searchText: searchText ?? SearchRoute.emptySearchText, + ).push(context); + }, + ), + ); + } } _suggestionCountTime = null; diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/edit_url_dialog.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/edit_url_dialog.dart index c49f1a17..c8ad0528 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/edit_url_dialog.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/edit_url_dialog.dart @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nullability/nullability.dart'; import 'package:weblibre/features/geckoview/features/search/domain/providers/engine_suggestions.dart'; +import 'package:weblibre/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart'; import 'package:weblibre/presentation/hooks/listenable_callback.dart'; import 'package:weblibre/presentation/widgets/auto_suggest_text_field.dart'; import 'package:weblibre/utils/form_validators.dart'; @@ -51,26 +52,32 @@ class EditUrlDialog extends HookConsumerWidget { return Form( key: formKey, - child: AutoSuggestTextField( - controller: addressTextController, - focusNode: addressTextFocusNode, - suggestion: suggestion.value, - // enableIMEPersonalizedLearning: !incognitoEnabled, - maxLines: null, - keyboardType: TextInputType.url, - decoration: const InputDecoration(hintText: 'Enter URL'), - onTap: () { - if (!addressTextFocusNode.hasFocus) { - // Select all text when the field is tapped - addressTextController.selection = TextSelection( - baseOffset: 0, - extentOffset: addressTextController.text.length, - ); - } - }, - validator: (value) { - return validateUrl(value, requireAuthority: false); - }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AutoSuggestTextField( + controller: addressTextController, + focusNode: addressTextFocusNode, + suggestion: suggestion.value, + // enableIMEPersonalizedLearning: !incognitoEnabled, + maxLines: null, + keyboardType: TextInputType.url, + decoration: const InputDecoration(hintText: 'Enter URL'), + onTap: () { + if (!addressTextFocusNode.hasFocus) { + // Select all text when the field is tapped + addressTextController.selection = TextSelection( + baseOffset: 0, + extentOffset: addressTextController.text.length, + ); + } + }, + validator: (value) { + return validateUrl(value, requireAuthority: false); + }, + ), + ClipboardFillLink(controller: addressTextController), + ], ), ); }, diff --git a/app/lib/features/geckoview/features/search/presentation/screens/search.dart b/app/lib/features/geckoview/features/search/presentation/screens/search.dart index 0fdafe63..927a2e93 100644 --- a/app/lib/features/geckoview/features/search/presentation/screens/search.dart +++ b/app/lib/features/geckoview/features/search/presentation/screens/search.dart @@ -10,6 +10,7 @@ import 'package:weblibre/features/bangs/domain/providers/search.dart'; import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; +import 'package:weblibre/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_field.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart'; @@ -164,6 +165,9 @@ class SearchScreen extends HookConsumerWidget { ), ), ), + SliverToBoxAdapter( + child: ClipboardFillLink(controller: searchTextController), + ), const SliverToBoxAdapter(child: Divider()), SearchTermSuggestions( searchTextController: searchTextController, diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart b/app/lib/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart new file mode 100644 index 00000000..c4ba36b3 --- /dev/null +++ b/app/lib/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; +import 'package:weblibre/presentation/hooks/cached_future.dart'; +import 'package:weblibre/utils/clipboard.dart'; + +class ClipboardFillLink extends HookWidget { + final TextEditingController controller; + + const ClipboardFillLink({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + final clipboardUrl = useCachedFuture(() => tryGetUriFromClipboard()); + final currentText = useValueListenable(controller); + + return Visibility( + visible: + clipboardUrl.hasData && + clipboardUrl.data.toString() != currentText.text, + child: ListTile( + leading: const Icon(MdiIcons.linkPlus), + title: const Text('Fill link from clipboard'), + onTap: () { + controller.text = clipboardUrl.data.toString(); + }, + ), + ); + } +} diff --git a/app/lib/utils/clipboard.dart b/app/lib/utils/clipboard.dart new file mode 100644 index 00000000..e76a7323 --- /dev/null +++ b/app/lib/utils/clipboard.dart @@ -0,0 +1,7 @@ +import 'package:flutter/services.dart'; +import 'package:weblibre/utils/uri_parser.dart'; + +Future tryGetUriFromClipboard({bool eagerParsing = true}) async { + final data = await Clipboard.getData('text/plain'); + return tryParseUrl(data?.text, eagerParsing: eagerParsing); +} diff --git a/app/lib/utils/ui_helper.dart b/app/lib/utils/ui_helper.dart index 867a01f4..05bb8adc 100644 --- a/app/lib/utils/ui_helper.dart +++ b/app/lib/utils/ui_helper.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:nullability/nullability.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:weblibre/utils/clipboard.dart'; void showErrorMessage(BuildContext context, String message) { final snackBar = SnackBar( @@ -52,18 +53,39 @@ void showTabOpenedMessage( ScaffoldMessenger.of(context).showSnackBar(snackBar); } -void showSuggestNewTabMessage( +Future showSuggestNewTabMessage( BuildContext context, { - required void Function() onAdd, + required void Function(String? searchText) onAdd, Duration duration = const Duration(seconds: 2), -}) { - final snackBar = SnackBar( - content: const Text('Want to open a new tab?'), - action: SnackBarAction(label: 'New Tab', onPressed: onAdd), - duration: duration, - ); +}) async { + final clipboardUrl = await tryGetUriFromClipboard(); - ScaffoldMessenger.of(context).showSnackBar(snackBar); + final snackBar = + (clipboardUrl != null) + ? SnackBar( + content: const Text('Want to open link from clipboard?'), + action: SnackBarAction( + label: 'Open', + onPressed: () { + onAdd(clipboardUrl.toString()); + }, + ), + duration: duration, + ) + : SnackBar( + content: const Text('Want to open a new tab?'), + action: SnackBarAction( + label: 'New Tab', + onPressed: () { + onAdd(null); + }, + ), + duration: duration, + ); + + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } } void showTabSwitchMessage(