clipboard url handling
This commit is contained in:
+14
-6
@@ -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;
|
||||
|
||||
+27
-20
@@ -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();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user