clipboard url handling
This commit is contained in:
+14
-6
@@ -216,12 +216,20 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
|||||||
if (_suggestionCountTime != null &&
|
if (_suggestionCountTime != null &&
|
||||||
DateTime.now().difference(_suggestionCountTime!) >
|
DateTime.now().difference(_suggestionCountTime!) >
|
||||||
widget.suggestionTimeout) {
|
widget.suggestionTimeout) {
|
||||||
showSuggestNewTabMessage(
|
//Don't do anything if a child route is active
|
||||||
context,
|
if (GoRouterState.of(context).topRoute?.name == BrowserRoute.name) {
|
||||||
onAdd: () async {
|
unawaited(
|
||||||
await const SearchRoute(tabType: TabType.regular).push(context);
|
showSuggestNewTabMessage(
|
||||||
},
|
context,
|
||||||
);
|
onAdd: (searchText) async {
|
||||||
|
await SearchRoute(
|
||||||
|
tabType: TabType.regular,
|
||||||
|
searchText: searchText ?? SearchRoute.emptySearchText,
|
||||||
|
).push(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_suggestionCountTime = null;
|
_suggestionCountTime = null;
|
||||||
|
|||||||
+27
-20
@@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:nullability/nullability.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/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/hooks/listenable_callback.dart';
|
||||||
import 'package:weblibre/presentation/widgets/auto_suggest_text_field.dart';
|
import 'package:weblibre/presentation/widgets/auto_suggest_text_field.dart';
|
||||||
import 'package:weblibre/utils/form_validators.dart';
|
import 'package:weblibre/utils/form_validators.dart';
|
||||||
@@ -51,26 +52,32 @@ class EditUrlDialog extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Form(
|
return Form(
|
||||||
key: formKey,
|
key: formKey,
|
||||||
child: AutoSuggestTextField(
|
child: Column(
|
||||||
controller: addressTextController,
|
mainAxisSize: MainAxisSize.min,
|
||||||
focusNode: addressTextFocusNode,
|
children: [
|
||||||
suggestion: suggestion.value,
|
AutoSuggestTextField(
|
||||||
// enableIMEPersonalizedLearning: !incognitoEnabled,
|
controller: addressTextController,
|
||||||
maxLines: null,
|
focusNode: addressTextFocusNode,
|
||||||
keyboardType: TextInputType.url,
|
suggestion: suggestion.value,
|
||||||
decoration: const InputDecoration(hintText: 'Enter URL'),
|
// enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||||
onTap: () {
|
maxLines: null,
|
||||||
if (!addressTextFocusNode.hasFocus) {
|
keyboardType: TextInputType.url,
|
||||||
// Select all text when the field is tapped
|
decoration: const InputDecoration(hintText: 'Enter URL'),
|
||||||
addressTextController.selection = TextSelection(
|
onTap: () {
|
||||||
baseOffset: 0,
|
if (!addressTextFocusNode.hasFocus) {
|
||||||
extentOffset: addressTextController.text.length,
|
// Select all text when the field is tapped
|
||||||
);
|
addressTextController.selection = TextSelection(
|
||||||
}
|
baseOffset: 0,
|
||||||
},
|
extentOffset: addressTextController.text.length,
|
||||||
validator: (value) {
|
);
|
||||||
return validateUrl(value, requireAuthority: false);
|
}
|
||||||
},
|
},
|
||||||
|
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/controllers/bottom_sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.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/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_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/feed_search.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.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()),
|
const SliverToBoxAdapter(child: Divider()),
|
||||||
SearchTermSuggestions(
|
SearchTermSuggestions(
|
||||||
searchTextController: searchTextController,
|
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:flutter/material.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:weblibre/utils/clipboard.dart';
|
||||||
|
|
||||||
void showErrorMessage(BuildContext context, String message) {
|
void showErrorMessage(BuildContext context, String message) {
|
||||||
final snackBar = SnackBar(
|
final snackBar = SnackBar(
|
||||||
@@ -52,18 +53,39 @@ void showTabOpenedMessage(
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showSuggestNewTabMessage(
|
Future<void> showSuggestNewTabMessage(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required void Function() onAdd,
|
required void Function(String? searchText) onAdd,
|
||||||
Duration duration = const Duration(seconds: 2),
|
Duration duration = const Duration(seconds: 2),
|
||||||
}) {
|
}) async {
|
||||||
final snackBar = SnackBar(
|
final clipboardUrl = await tryGetUriFromClipboard();
|
||||||
content: const Text('Want to open a new tab?'),
|
|
||||||
action: SnackBarAction(label: 'New Tab', onPressed: onAdd),
|
|
||||||
duration: duration,
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
void showTabSwitchMessage(
|
||||||
|
|||||||
Reference in New Issue
Block a user