merge address bar into search screen
This commit is contained in:
@@ -594,7 +594,7 @@ class _SheetContainer extends HookConsumerWidget {
|
|||||||
final SiteSettingsSheet parameter =>
|
final SiteSettingsSheet parameter =>
|
||||||
NotificationListener<DraggableScrollableNotification>(
|
NotificationListener<DraggableScrollableNotification>(
|
||||||
onNotification: dismissOnThreshold,
|
onNotification: dismissOnThreshold,
|
||||||
child: _ViewUrlSheet(
|
child: _SiteSettingsSheet(
|
||||||
initialTabState: parameter.tabState,
|
initialTabState: parameter.tabState,
|
||||||
maxChildSize: stableMaxChildSize,
|
maxChildSize: stableMaxChildSize,
|
||||||
bottomAppBarHeight: bottomAppBarHeight,
|
bottomAppBarHeight: bottomAppBarHeight,
|
||||||
@@ -855,12 +855,14 @@ class _BrowserView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ViewUrlSheet extends HookConsumerWidget {
|
class _SiteSettingsSheet extends HookConsumerWidget {
|
||||||
final double maxChildSize;
|
final double maxChildSize;
|
||||||
final TabState initialTabState;
|
final TabState initialTabState;
|
||||||
final double bottomAppBarHeight;
|
final double bottomAppBarHeight;
|
||||||
|
|
||||||
const _ViewUrlSheet({
|
static const initialHeight = 0.8;
|
||||||
|
|
||||||
|
const _SiteSettingsSheet({
|
||||||
required this.initialTabState,
|
required this.initialTabState,
|
||||||
required this.bottomAppBarHeight,
|
required this.bottomAppBarHeight,
|
||||||
this.maxChildSize = 1.0,
|
this.maxChildSize = 1.0,
|
||||||
@@ -870,7 +872,17 @@ class _ViewUrlSheet extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final draggableScrollableController = useDraggableScrollableController();
|
final draggableScrollableController = useDraggableScrollableController();
|
||||||
|
|
||||||
final initialHeight = 172.0 / MediaQuery.of(context).size.height;
|
void handleClearSiteDataExpansion(bool isExpanded) {
|
||||||
|
if (isExpanded) {
|
||||||
|
unawaited(
|
||||||
|
draggableScrollableController.animateTo(
|
||||||
|
1.0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.decelerate,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
controller: draggableScrollableController,
|
controller: draggableScrollableController,
|
||||||
@@ -904,6 +916,7 @@ class _ViewUrlSheet extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
initialHeight: initialHeight,
|
initialHeight: initialHeight,
|
||||||
bottomAppBarHeight: bottomAppBarHeight,
|
bottomAppBarHeight: bottomAppBarHeight,
|
||||||
|
onClearSiteDataExpandedChanged: handleClearSiteDataExpansion,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class CertificateTile extends HookConsumerWidget {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Skeleton.keep(child: Icon(MdiIcons.timerSand)),
|
leading: const Skeleton.keep(child: Icon(MdiIcons.timerSand)),
|
||||||
title: Text(BoneMock.title),
|
title: Text(BoneMock.title),
|
||||||
|
subtitle: Text(BoneMock.subtitle),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -28,8 +28,13 @@ import 'package:weblibre/utils/ui_helper.dart';
|
|||||||
/// Section widget for clearing site data
|
/// Section widget for clearing site data
|
||||||
class ClearSiteDataSection extends HookConsumerWidget {
|
class ClearSiteDataSection extends HookConsumerWidget {
|
||||||
final Uri url;
|
final Uri url;
|
||||||
|
final ValueChanged<bool>? onExpandedChanged;
|
||||||
|
|
||||||
const ClearSiteDataSection({required this.url, super.key});
|
const ClearSiteDataSection({
|
||||||
|
required this.url,
|
||||||
|
this.onExpandedChanged,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -65,7 +70,11 @@ class ClearSiteDataSection extends HookConsumerWidget {
|
|||||||
trailing: Icon(
|
trailing: Icon(
|
||||||
isExpanded.value ? Icons.expand_less : Icons.expand_more,
|
isExpanded.value ? Icons.expand_less : Icons.expand_more,
|
||||||
),
|
),
|
||||||
onTap: () => isExpanded.value = !isExpanded.value,
|
onTap: () {
|
||||||
|
final newValue = !isExpanded.value;
|
||||||
|
isExpanded.value = newValue;
|
||||||
|
onExpandedChanged?.call(newValue);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (isExpanded.value) ...[
|
if (isExpanded.value) ...[
|
||||||
_DataTypeCheckbox(
|
_DataTypeCheckbox(
|
||||||
|
|||||||
+10
-1
@@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/repositories/tracking_protection.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/repositories/tracking_protection.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/tracking_protection_provider.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/tracking_protection_provider.dart';
|
||||||
@@ -41,7 +42,15 @@ class TrackingProtectionSection extends HookConsumerWidget {
|
|||||||
tabId: tabId,
|
tabId: tabId,
|
||||||
isEnabled: !hasException, // ETP enabled when NOT in exceptions
|
isEnabled: !hasException, // ETP enabled when NOT in exceptions
|
||||||
),
|
),
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => Skeletonizer(
|
||||||
|
child: SwitchListTile.adaptive(
|
||||||
|
value: false,
|
||||||
|
onChanged: null,
|
||||||
|
title: Text(BoneMock.title),
|
||||||
|
subtitle: Text(BoneMock.subtitle),
|
||||||
|
secondary: const Icon(Icons.shield_outlined),
|
||||||
|
),
|
||||||
|
),
|
||||||
error: (error, stack) => const SizedBox.shrink(),
|
error: (error, stack) => const SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -50,6 +50,7 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
final VoidCallback onClose;
|
final VoidCallback onClose;
|
||||||
final double initialHeight;
|
final double initialHeight;
|
||||||
final double bottomAppBarHeight;
|
final double bottomAppBarHeight;
|
||||||
|
final ValueChanged<bool>? onClearSiteDataExpandedChanged;
|
||||||
|
|
||||||
const ViewTabSheetWidget({
|
const ViewTabSheetWidget({
|
||||||
required this.initialTabState,
|
required this.initialTabState,
|
||||||
@@ -58,6 +59,7 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
required this.onClose,
|
required this.onClose,
|
||||||
required this.initialHeight,
|
required this.initialHeight,
|
||||||
required this.bottomAppBarHeight,
|
required this.bottomAppBarHeight,
|
||||||
|
this.onClearSiteDataExpandedChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -152,7 +154,10 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
// Clear Site Data Section
|
// Clear Site Data Section
|
||||||
ClearSiteDataSection(url: initialTabState.url),
|
ClearSiteDataSection(
|
||||||
|
url: initialTabState.url,
|
||||||
|
onExpandedChanged: onClearSiteDataExpandedChanged,
|
||||||
|
),
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,13 +17,17 @@
|
|||||||
* 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:fading_scroll/fading_scroll.dart';
|
import 'package:fading_scroll/fading_scroll.dart';
|
||||||
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:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:weblibre/core/design/app_colors.dart';
|
import 'package:weblibre/core/design/app_colors.dart';
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/bangs/data/models/bang_data.dart';
|
import 'package:weblibre/features/bangs/data/models/bang_data.dart';
|
||||||
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
|
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
|
||||||
import 'package:weblibre/features/bangs/domain/providers/search.dart';
|
import 'package:weblibre/features/bangs/domain/providers/search.dart';
|
||||||
@@ -40,9 +44,11 @@ import 'package:weblibre/features/geckoview/features/search/presentation/widgets
|
|||||||
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';
|
||||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart';
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.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/on_listenable_change_selector.dart';
|
||||||
import 'package:weblibre/presentation/hooks/sampled_value_notifier.dart';
|
import 'package:weblibre/presentation/hooks/sampled_value_notifier.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/text_field_line_count.dart';
|
||||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||||
|
|
||||||
class SearchScreen extends HookConsumerWidget {
|
class SearchScreen extends HookConsumerWidget {
|
||||||
@@ -101,6 +107,74 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
sampleDuration: const Duration(milliseconds: 150),
|
sampleDuration: const Duration(milliseconds: 150),
|
||||||
);
|
);
|
||||||
final searchFocusNode = useFocusNode();
|
final searchFocusNode = useFocusNode();
|
||||||
|
final textFieldKey = useMemoized(() => GlobalKey());
|
||||||
|
final preferredHeight = useState<double>(kToolbarHeight);
|
||||||
|
|
||||||
|
useOnListenableChangeSelector(
|
||||||
|
searchFocusNode,
|
||||||
|
() => searchFocusNode.hasFocus,
|
||||||
|
() {
|
||||||
|
if (searchFocusNode.hasFocus && isEditMode) {
|
||||||
|
// Select all text when the field is focused
|
||||||
|
searchTextController.selection = TextSelection(
|
||||||
|
baseOffset: 0,
|
||||||
|
extentOffset: searchTextController.text.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (isEditMode) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
searchTextController.selection = TextSelection(
|
||||||
|
baseOffset: 0,
|
||||||
|
extentOffset: searchTextController.text.length,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//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;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
void measureHeight() {
|
||||||
|
final measuredHeight = getTextFieldHeight(textFieldKey);
|
||||||
|
|
||||||
|
if (measuredHeight != null) {
|
||||||
|
preferredHeight.value = measuredHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
measureHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [textFieldKey, isEditMode]);
|
||||||
|
|
||||||
|
useOnListenableChange(isEditMode ? searchTextController : null, () {
|
||||||
|
final measuredHeight = getTextFieldHeight(textFieldKey);
|
||||||
|
|
||||||
|
if (measuredHeight != null && preferredHeight.value != measuredHeight) {
|
||||||
|
preferredHeight.value = measuredHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useOnAppLifecycleStateChange((previous, current) {
|
useOnAppLifecycleStateChange((previous, current) {
|
||||||
switch (current) {
|
switch (current) {
|
||||||
@@ -126,7 +200,9 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
final showBangIcon = useState(false);
|
final showBangIcon = useState(false);
|
||||||
|
|
||||||
ref.listen(
|
ref.listen(
|
||||||
selectedBangDataProvider(domain: isEditMode ? existingTabState.url.host : null),
|
selectedBangDataProvider(
|
||||||
|
domain: isEditMode ? existingTabState.url.host : null,
|
||||||
|
),
|
||||||
(previous, next) {
|
(previous, next) {
|
||||||
if (previous != next) {
|
if (previous != next) {
|
||||||
showBangIcon.value = true;
|
showBangIcon.value = true;
|
||||||
@@ -188,20 +264,10 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
pinned: true,
|
pinned: true,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
title: isEditMode
|
title: isEditMode
|
||||||
? Column(
|
? _SiteBangsSelector(
|
||||||
mainAxisSize: MainAxisSize.min,
|
tabId: tabId!,
|
||||||
children: [
|
domain: existingTabState.url.host,
|
||||||
_SiteBangsSelector(
|
searchTextController: searchTextController,
|
||||||
tabId: tabId!,
|
|
||||||
domain: existingTabState.url.host,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Editing current tab',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
: Align(
|
: Align(
|
||||||
child: Focus(
|
child: Focus(
|
||||||
@@ -230,7 +296,9 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
onSelectionChanged: (value) {
|
onSelectionChanged: (value) {
|
||||||
selectedTabType.value = value.first;
|
selectedTabType.value = value.first;
|
||||||
// Restore focus to search field after segment change
|
// Restore focus to search field after segment change
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((
|
||||||
|
_,
|
||||||
|
) {
|
||||||
searchFocusNode.requestFocus();
|
searchFocusNode.requestFocus();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -243,8 +311,8 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
TabType.child =>
|
TabType.child =>
|
||||||
(currentTabTabType == TabType.private)
|
(currentTabTabType == TabType.private)
|
||||||
? SegmentedButton.styleFrom(
|
? SegmentedButton.styleFrom(
|
||||||
selectedBackgroundColor:
|
selectedBackgroundColor: appColors
|
||||||
appColors.privateSelectionOverlay,
|
.privateSelectionOverlay,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
@@ -252,15 +320,20 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
preferredSize: Size.fromHeight(preferredHeight.value),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 16.0),
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
child: SearchField(
|
child: SearchField(
|
||||||
|
textFieldKey: textFieldKey,
|
||||||
showBangIcon: showBangIcon.value,
|
showBangIcon: showBangIcon.value,
|
||||||
textEditingController: searchTextController,
|
textEditingController: searchTextController,
|
||||||
focusNode: searchFocusNode,
|
focusNode: searchFocusNode,
|
||||||
|
maxLines: isEditMode ? 3 : 1,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
label: const Text('Address / Search'),
|
label: (activeBang != null)
|
||||||
|
? const Text('Search')
|
||||||
|
: const Text('Address / Search'),
|
||||||
|
unfocusOnTapOutside: !isEditMode,
|
||||||
onSubmitted: (value) async {
|
onSubmitted: (value) async {
|
||||||
if (value.isNotEmpty) {
|
if (value.isNotEmpty) {
|
||||||
var newUrl = uri_parser.tryParseUrl(
|
var newUrl = uri_parser.tryParseUrl(
|
||||||
@@ -270,9 +343,13 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (newUrl == null) {
|
if (newUrl == null) {
|
||||||
final bang =
|
final bang =
|
||||||
ref.read(selectedBangDataProvider(
|
ref.read(
|
||||||
domain: isEditMode ? existingTabState.url.host : null,
|
selectedBangDataProvider(
|
||||||
)) ??
|
domain: isEditMode
|
||||||
|
? existingTabState.url.host
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
) ??
|
||||||
await ref.read(
|
await ref.read(
|
||||||
defaultSearchBangDataProvider.future,
|
defaultSearchBangDataProvider.future,
|
||||||
);
|
);
|
||||||
@@ -292,7 +369,11 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
if (isEditMode) {
|
if (isEditMode) {
|
||||||
// Load into existing tab
|
// Load into existing tab
|
||||||
await ref
|
await ref
|
||||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
.read(
|
||||||
|
tabSessionProvider(
|
||||||
|
tabId: tabId,
|
||||||
|
).notifier,
|
||||||
|
)
|
||||||
.loadUrl(url: newUrl);
|
.loadUrl(url: newUrl);
|
||||||
} else {
|
} else {
|
||||||
// Create new tab
|
// Create new tab
|
||||||
@@ -357,17 +438,17 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
class _SiteBangsSelector extends HookConsumerWidget {
|
class _SiteBangsSelector extends HookConsumerWidget {
|
||||||
final String tabId;
|
final String tabId;
|
||||||
final String domain;
|
final String domain;
|
||||||
|
final TextEditingController searchTextController;
|
||||||
|
|
||||||
const _SiteBangsSelector({
|
const _SiteBangsSelector({
|
||||||
required this.tabId,
|
required this.tabId,
|
||||||
required this.domain,
|
required this.domain,
|
||||||
|
required this.searchTextController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final selectedBang = ref.watch(
|
final selectedBang = ref.watch(selectedBangDataProvider(domain: domain));
|
||||||
selectedBangDataProvider(domain: domain),
|
|
||||||
);
|
|
||||||
final availableBangs = ref.watch(
|
final availableBangs = ref.watch(
|
||||||
bangListProvider(
|
bangListProvider(
|
||||||
domain: domain,
|
domain: domain,
|
||||||
@@ -390,6 +471,18 @@ class _SiteBangsSelector extends HookConsumerWidget {
|
|||||||
availableItems: availableBangs,
|
availableItems: availableBangs,
|
||||||
selectedItem: selectedBang,
|
selectedItem: selectedBang,
|
||||||
onSelected: (bang) {
|
onSelected: (bang) {
|
||||||
|
if (selectedBang == null) {
|
||||||
|
final hasSupportedScheme =
|
||||||
|
uri_parser
|
||||||
|
.tryParseUrl(searchTextController.text)
|
||||||
|
.mapNotNull((uri) => uri.hasSupportedScheme) ??
|
||||||
|
false;
|
||||||
|
|
||||||
|
if (hasSupportedScheme) {
|
||||||
|
searchTextController.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ref
|
ref
|
||||||
.read(selectedBangTriggerProvider(domain: domain).notifier)
|
.read(selectedBangTriggerProvider(domain: domain).notifier)
|
||||||
.setTrigger(bang.toKey());
|
.setTrigger(bang.toKey());
|
||||||
|
|||||||
@@ -37,8 +37,10 @@ class SearchField extends HookConsumerWidget {
|
|||||||
final void Function(String)? onSubmitted;
|
final void Function(String)? onSubmitted;
|
||||||
final bool showSuggestions;
|
final bool showSuggestions;
|
||||||
final int? maxLines;
|
final int? maxLines;
|
||||||
|
final int? minLines;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final bool unfocusOnTapOutside;
|
final bool unfocusOnTapOutside;
|
||||||
|
final GlobalKey? textFieldKey;
|
||||||
|
|
||||||
final BangData? activeBang;
|
final BangData? activeBang;
|
||||||
final bool showBangIcon;
|
final bool showBangIcon;
|
||||||
@@ -52,10 +54,12 @@ class SearchField extends HookConsumerWidget {
|
|||||||
this.label,
|
this.label,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.maxLines = 1,
|
this.maxLines = 1,
|
||||||
|
this.minLines = 1,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.unfocusOnTapOutside = true,
|
this.unfocusOnTapOutside = true,
|
||||||
this.showBangIcon = true,
|
this.showBangIcon = true,
|
||||||
this.autofocus = false,
|
this.autofocus = false,
|
||||||
|
this.textFieldKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -100,10 +104,11 @@ class SearchField extends HookConsumerWidget {
|
|||||||
enableIMEPersonalizedLearning: !incognitoEnabled,
|
enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||||
focusNode: safeFocusNode,
|
focusNode: safeFocusNode,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
//Submit isntead of newline
|
textFieldKey: textFieldKey,
|
||||||
textInputAction: (maxLines == null || maxLines! > 1)
|
textInputAction: (maxLines == null || maxLines! > 1)
|
||||||
? TextInputAction.done
|
? TextInputAction.done
|
||||||
: null,
|
: null,
|
||||||
|
minLines: minLines,
|
||||||
autofocus: autofocus,
|
autofocus: autofocus,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
final TapRegionCallback? onTapOutside;
|
final TapRegionCallback? onTapOutside;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final bool autocorrect;
|
final bool autocorrect;
|
||||||
|
final GlobalKey? textFieldKey;
|
||||||
|
|
||||||
const AutoSuggestTextField({
|
const AutoSuggestTextField({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -79,6 +80,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
this.onTapOutside,
|
this.onTapOutside,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.autocorrect = false,
|
this.autocorrect = false,
|
||||||
|
this.textFieldKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool _suggestionHasMatch() =>
|
bool _suggestionHasMatch() =>
|
||||||
@@ -88,7 +90,8 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textFieldKey = useMemoized(() => GlobalKey());
|
final textFieldKey =
|
||||||
|
this.textFieldKey ?? useMemoized<GlobalKey>(() => GlobalKey());
|
||||||
|
|
||||||
final showSuggestion = useListenableSelector(controller, () {
|
final showSuggestion = useListenableSelector(controller, () {
|
||||||
if (maxLines != 1) {
|
if (maxLines != 1) {
|
||||||
|
|||||||
@@ -47,3 +47,12 @@ int? getTextFieldLineCount(GlobalKey key, String text, TextStyle style) {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double? getTextFieldHeight(GlobalKey key) {
|
||||||
|
final box = key.currentContext?.findRenderObject();
|
||||||
|
if (box case final RenderBox box) {
|
||||||
|
return box.size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user