add addres bar auto focus
This commit is contained in:
@@ -17,6 +17,8 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -35,7 +37,7 @@ import 'package:weblibre/features/geckoview/features/search/domain/providers/sea
|
|||||||
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/fixed_search_suggestions.dart';
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/fixed_search_suggestions.dart';
|
||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
|
import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart';
|
||||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||||
@@ -59,7 +61,52 @@ class SiteSearch extends HookConsumerWidget {
|
|||||||
final searchTextController = controller ?? useTextEditingController();
|
final searchTextController = controller ?? useTextEditingController();
|
||||||
final searchFocusNode = useFocusNode();
|
final searchFocusNode = useFocusNode();
|
||||||
|
|
||||||
useListenableCallback(searchTextController, () {
|
useOnListenableChangeSelector(
|
||||||
|
searchFocusNode,
|
||||||
|
() => searchFocusNode.hasFocus,
|
||||||
|
() {
|
||||||
|
if (searchFocusNode.hasFocus) {
|
||||||
|
// Select all text when the field is focused
|
||||||
|
searchTextController.selection = TextSelection(
|
||||||
|
baseOffset: 0,
|
||||||
|
extentOffset: searchTextController.text.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
//Request initial focus in a way our useOnListenableChangeSelector is triggered
|
||||||
|
useEffect(() {
|
||||||
|
//Wait for first frame then request focus
|
||||||
|
unawaited(
|
||||||
|
Future.delayed(const Duration(milliseconds: 1000 ~/ 60)).whenComplete(
|
||||||
|
() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
searchFocusNode.requestFocus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useOnAppLifecycleStateChange((previous, current) {
|
||||||
|
switch (current) {
|
||||||
|
case AppLifecycleState.detached:
|
||||||
|
case AppLifecycleState.inactive:
|
||||||
|
case AppLifecycleState.hidden:
|
||||||
|
case AppLifecycleState.paused:
|
||||||
|
//Fixes issue with disappearing keyboard after resume (even we request focus)
|
||||||
|
searchFocusNode.unfocus();
|
||||||
|
case AppLifecycleState.resumed:
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
searchFocusNode.requestFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useOnListenableChange(searchTextController, () {
|
||||||
if (ref.exists(searchSuggestionsProvider())) {
|
if (ref.exists(searchSuggestionsProvider())) {
|
||||||
ref
|
ref
|
||||||
.read(searchSuggestionsProvider().notifier)
|
.read(searchSuggestionsProvider().notifier)
|
||||||
@@ -171,15 +218,6 @@ class SiteSearch extends HookConsumerWidget {
|
|||||||
label: (selectedBang != null)
|
label: (selectedBang != null)
|
||||||
? const Text('Search')
|
? const Text('Search')
|
||||||
: const Text('Address / Search'),
|
: const Text('Address / Search'),
|
||||||
onTap: () {
|
|
||||||
if (!searchFocusNode.hasFocus) {
|
|
||||||
// Select all text when the field is tapped
|
|
||||||
searchTextController.selection = TextSelection(
|
|
||||||
baseOffset: 0,
|
|
||||||
extentOffset: searchTextController.text.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unfocusOnTapOutside: false,
|
unfocusOnTapOutside: false,
|
||||||
onSubmitted: (value) async {
|
onSubmitted: (value) async {
|
||||||
await submitSearch(value);
|
await submitSearch(value);
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2024-2025 Fabian Freund.
|
|
||||||
*
|
|
||||||
* This file is part of WebLibre
|
|
||||||
* (see https://weblibre.eu).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
|
|
||||||
void useListenableCallback(
|
|
||||||
Listenable? listenable,
|
|
||||||
void Function() callback, [
|
|
||||||
List<Object?>? keys,
|
|
||||||
]) {
|
|
||||||
useEffect(() {
|
|
||||||
listenable?.addListener(callback);
|
|
||||||
return () => listenable?.removeListener(callback);
|
|
||||||
}, keys ?? [listenable]);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025 Fabian Freund.
|
||||||
|
*
|
||||||
|
* This file is part of WebLibre
|
||||||
|
* (see https://weblibre.eu).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
|
/// Adds a given [listener] to a [Listenable] and removes it when the hook is
|
||||||
|
/// disposed. The listener is only called when the [selector] result changes.
|
||||||
|
///
|
||||||
|
/// As opposed to `useListenable`, this hook does not mark the widget as needing
|
||||||
|
/// build when the listener is called. Use this for side effects that do not
|
||||||
|
/// require a rebuild.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [Listenable]
|
||||||
|
/// * [ValueListenable]
|
||||||
|
/// * [useOnListenableChange]
|
||||||
|
void useOnListenableChangeSelector<R>(
|
||||||
|
Listenable? listenable,
|
||||||
|
R Function() selector,
|
||||||
|
VoidCallback listener,
|
||||||
|
) {
|
||||||
|
return use(_OnListenableChangeSelectorHook(listenable, selector, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OnListenableChangeSelectorHook<R> extends Hook<void> {
|
||||||
|
const _OnListenableChangeSelectorHook(
|
||||||
|
this.listenable,
|
||||||
|
this.selector,
|
||||||
|
this.listener,
|
||||||
|
);
|
||||||
|
|
||||||
|
final Listenable? listenable;
|
||||||
|
final R Function() selector;
|
||||||
|
final VoidCallback listener;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_OnListenableChangeSelectorHookState<R> createState() =>
|
||||||
|
_OnListenableChangeSelectorHookState<R>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OnListenableChangeSelectorHookState<R>
|
||||||
|
extends HookState<void, _OnListenableChangeSelectorHook<R>> {
|
||||||
|
late R _selectorResult = hook.selector();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initHook() {
|
||||||
|
super.initHook();
|
||||||
|
hook.listenable?.addListener(_listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateHook(_OnListenableChangeSelectorHook<R> oldHook) {
|
||||||
|
super.didUpdateHook(oldHook);
|
||||||
|
|
||||||
|
if (hook.selector != oldHook.selector) {
|
||||||
|
_selectorResult = hook.selector();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hook.listenable != oldHook.listenable) {
|
||||||
|
oldHook.listenable?.removeListener(_listener);
|
||||||
|
hook.listenable?.addListener(_listener);
|
||||||
|
_selectorResult = hook.selector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build(BuildContext context) {}
|
||||||
|
|
||||||
|
void _listener() {
|
||||||
|
final latestSelectorResult = hook.selector();
|
||||||
|
if (_selectorResult != latestSelectorResult) {
|
||||||
|
_selectorResult = latestSelectorResult;
|
||||||
|
hook.listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
hook.listenable?.removeListener(_listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugLabel => 'useOnListenableChangeSelector<$R>';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? get debugValue => hook.listenable;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user