update search box design

This commit is contained in:
Fabian Freund
2026-03-12 16:26:14 +01:00
parent df69498fbe
commit a378bc697f
2 changed files with 178 additions and 164 deletions
@@ -232,7 +232,8 @@ class SearchScreen extends HookConsumerWidget {
final measuredHeight = getTextFieldHeight(textFieldKey);
if (measuredHeight != null) {
preferredHeight.value = measuredHeight;
// Add 2px to account for SearchField container border
preferredHeight.value = measuredHeight + 2;
return;
}
}
@@ -458,7 +459,7 @@ class SearchScreen extends HookConsumerWidget {
title: isEditMode
? null
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Builder(
builder: (context) {
final tabTypeSwitcher = Focus(
@@ -529,111 +530,105 @@ class SearchScreen extends HookConsumerWidget {
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(preferredHeight.value),
child: Padding(
padding: const EdgeInsets.only(left: 16.0),
child: SearchField(
textFieldKey: textFieldKey,
showBangIcon: showBangIcon,
textEditingController: searchTextController,
focusNode: searchFocusNode,
maxLines: isEditMode ? 3 : 1,
autofocus: true,
label: const Text('Search or enter URL'),
unfocusOnTapOutside: false,
onSubmitted: (value) async {
if (value.isNotEmpty) {
final classification = classifyAddressBarInput(value);
Uri? newUrl;
String? searchQuery;
switch (classification) {
case NavigateInputClassification(:final uri):
newUrl = uri;
case SearchInputClassification(:final query):
searchQuery = query;
case InvalidInputClassification():
if (context.mounted) {
ui_helper.showErrorMessage(
context,
'Invalid address',
);
}
return;
}
if (newUrl == null && searchQuery != null) {
// Read from both providers - use site if set, otherwise global
final siteBang = isEditMode
? ref.read(
selectedBangDataProvider(
domain: existingTabState.url.host,
),
)
: null;
final globalBang = ref.read(
selectedBangDataProvider(),
);
final bang =
siteBang ??
globalBang ??
await ref.read(
defaultSearchBangDataProvider.future,
);
if (bang != null) {
newUrl = bang.getTemplateUrl(searchQuery);
if (!privateTabMode) {
await ref
.read(bangSearchProvider.notifier)
.triggerBangSearch(bang, searchQuery);
}
}
}
if (newUrl != null) {
if (isEditMode) {
// Load into existing tab
await ref
.read(
tabSessionProvider(tabId: tabId).notifier,
)
.loadUrl(url: newUrl);
} else {
// Create new tab
await ref
.read(tabRepositoryProvider.notifier)
.addTab(
url: newUrl,
tabMode: effectiveTabMode,
parentId:
(selectedTabType.value == TabType.child)
? ref.read(selectedTabProvider)
: null,
launchedFromIntent: launchedFromIntent,
selectTab: true,
containerSelection:
selectedContainer == null
? const TabContainerSelection.unassigned()
: TabContainerSelection.specific(
selectedContainer,
),
);
}
child: SearchField(
textFieldKey: textFieldKey,
showBangIcon: showBangIcon,
textEditingController: searchTextController,
focusNode: searchFocusNode,
maxLines: isEditMode ? 3 : 1,
autofocus: true,
label: const Text('Search or enter URL'),
unfocusOnTapOutside: false,
onSubmitted: (value) async {
if (value.isNotEmpty) {
final classification = classifyAddressBarInput(value);
Uri? newUrl;
String? searchQuery;
switch (classification) {
case NavigateInputClassification(:final uri):
newUrl = uri;
case SearchInputClassification(:final query):
searchQuery = query;
case InvalidInputClassification():
if (context.mounted) {
ref
.read(bottomSheetControllerProvider.notifier)
.requestDismiss();
ui_helper.showErrorMessage(
context,
'Invalid address',
);
}
return;
}
const BrowserRoute().go(context);
if (newUrl == null && searchQuery != null) {
// Read from both providers - use site if set, otherwise global
final siteBang = isEditMode
? ref.read(
selectedBangDataProvider(
domain: existingTabState.url.host,
),
)
: null;
final globalBang = ref.read(
selectedBangDataProvider(),
);
final bang =
siteBang ??
globalBang ??
await ref.read(
defaultSearchBangDataProvider.future,
);
if (bang != null) {
newUrl = bang.getTemplateUrl(searchQuery);
if (!privateTabMode) {
await ref
.read(bangSearchProvider.notifier)
.triggerBangSearch(bang, searchQuery);
}
}
}
},
activeBang: activeBang,
showSuggestions: true,
),
if (newUrl != null) {
if (isEditMode) {
// Load into existing tab
await ref
.read(tabSessionProvider(tabId: tabId).notifier)
.loadUrl(url: newUrl);
} else {
// Create new tab
await ref
.read(tabRepositoryProvider.notifier)
.addTab(
url: newUrl,
tabMode: effectiveTabMode,
parentId:
(selectedTabType.value == TabType.child)
? ref.read(selectedTabProvider)
: null,
launchedFromIntent: launchedFromIntent,
selectTab: true,
containerSelection: selectedContainer == null
? const TabContainerSelection.unassigned()
: TabContainerSelection.specific(
selectedContainer,
),
);
}
if (context.mounted) {
ref
.read(bottomSheetControllerProvider.notifier)
.requestDismiss();
const BrowserRoute().go(context);
}
}
}
},
activeBang: activeBang,
showSuggestions: true,
),
),
),
@@ -107,72 +107,91 @@ class SearchField extends HookConsumerWidget {
);
}
return AutoSuggestTextField(
controller: textEditingController,
suggestion: suggestion.value,
enableIMEPersonalizedLearning: !incognitoEnabled,
focusNode: safeFocusNode,
maxLines: maxLines,
textFieldKey: textFieldKey,
textInputAction: (maxLines == null || maxLines! > 1)
? TextInputAction.done
: null,
minLines: minLines,
autofocus: autofocus,
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: (showBangIcon && activeBang != null)
? Padding(
padding: const EdgeInsetsDirectional.all(12.0),
child: UrlIcon([activeBang!.getDefaultUrl()], iconSize: 24.0),
)
: null,
label: label,
hint: hint,
floatingLabelBehavior: FloatingLabelBehavior.always,
suffixIcon: hasText
? IconButton(
onPressed: () {
textEditingController.clear();
},
icon: const Icon(Icons.clear),
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
QrScannerButton(
onScanResult: (scanResult) {
if (scanResult?.code != null) {
textEditingController.text = scanResult!.code!;
}
},
),
SpeechToTextButton(
onTextReceived: (data) {
textEditingController.text = data;
},
),
],
),
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: colorScheme.outlineVariant),
),
onTapOutside: unfocusOnTapOutside
? (event) {
safeFocusNode.unfocus();
}
: null,
onSubmitted: onSubmitted,
onTap: () {
if (suggestion.value != null) {
unawaited(HapticFeedback.lightImpact());
child: AutoSuggestTextField(
controller: textEditingController,
suggestion: suggestion.value,
enableIMEPersonalizedLearning: !incognitoEnabled,
focusNode: safeFocusNode,
maxLines: maxLines,
textFieldKey: textFieldKey,
textInputAction: (maxLines == null || maxLines! > 1)
? TextInputAction.done
: null,
minLines: minLines,
autofocus: autofocus,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: const EdgeInsetsDirectional.fromSTEB(12, 12, 0, 12),
prefixIcon: (showBangIcon && activeBang != null)
? Padding(
padding: const EdgeInsetsDirectional.all(12.0),
child: UrlIcon([activeBang!.getDefaultUrl()], iconSize: 24.0),
)
: null,
label: label,
hint: hint,
floatingLabelBehavior: FloatingLabelBehavior.always,
suffixIconConstraints: const BoxConstraints(minHeight: 48),
suffixIcon: hasText
? Padding(
padding: const EdgeInsetsDirectional.only(end: 8.0),
child: IconButton(
onPressed: () {
textEditingController.clear();
},
icon: const Icon(Icons.clear),
),
)
: Padding(
padding: const EdgeInsetsDirectional.only(end: 8.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
QrScannerButton(
onScanResult: (scanResult) {
if (scanResult?.code != null) {
textEditingController.text = scanResult!.code!;
}
},
),
SpeechToTextButton(
onTextReceived: (data) {
textEditingController.text = data;
},
),
],
),
),
),
onTapOutside: unfocusOnTapOutside
? (event) {
safeFocusNode.unfocus();
}
: null,
onSubmitted: onSubmitted,
onTap: () {
if (suggestion.value != null) {
unawaited(HapticFeedback.lightImpact());
textEditingController.text = suggestion.value!;
textEditingController.selection = TextSelection.collapsed(
offset: suggestion.value!.length,
);
textEditingController.text = suggestion.value!;
textEditingController.selection = TextSelection.collapsed(
offset: suggestion.value!.length,
);
suggestion.value = null;
}
},
suggestion.value = null;
}
},
),
);
}
}