clipboard url handling

This commit is contained in:
Fabian Freund
2025-05-29 14:21:35 +02:00
parent 05d938309c
commit 6985e5f2d5
6 changed files with 113 additions and 35 deletions
@@ -216,12 +216,20 @@ class _BrowserViewState extends ConsumerState<BrowserView>
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;
@@ -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),
],
),
);
},
@@ -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,
@@ -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();
},
),
);
}
}
+7
View File
@@ -0,0 +1,7 @@
import 'package:flutter/services.dart';
import 'package:weblibre/utils/uri_parser.dart';
Future<Uri?> tryGetUriFromClipboard({bool eagerParsing = true}) async {
final data = await Clipboard.getData('text/plain');
return tryParseUrl(data?.text, eagerParsing: eagerParsing);
}
+31 -9
View File
@@ -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<void> 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(