improved search ui
This commit is contained in:
@@ -45,7 +45,6 @@ class SiteSearch extends HookConsumerWidget {
|
|||||||
final List<BangData> availableBangs;
|
final List<BangData> availableBangs;
|
||||||
final TextEditingController? controller;
|
final TextEditingController? controller;
|
||||||
final bool searchInNewTab;
|
final bool searchInNewTab;
|
||||||
final Widget? label;
|
|
||||||
|
|
||||||
const SiteSearch({
|
const SiteSearch({
|
||||||
required this.domain,
|
required this.domain,
|
||||||
@@ -53,7 +52,6 @@ class SiteSearch extends HookConsumerWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.searchInNewTab = false,
|
this.searchInNewTab = false,
|
||||||
this.label,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -119,6 +117,7 @@ class SiteSearch extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: availableBangs.isNotEmpty,
|
visible: availableBangs.isNotEmpty,
|
||||||
@@ -169,7 +168,9 @@ class SiteSearch extends HookConsumerWidget {
|
|||||||
maxLines: null,
|
maxLines: null,
|
||||||
activeBang: selectedBang,
|
activeBang: selectedBang,
|
||||||
showSuggestions: true,
|
showSuggestions: true,
|
||||||
label: label,
|
label: (selectedBang != null)
|
||||||
|
? const Text('Search')
|
||||||
|
: const Text('Address / Search'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (!searchFocusNode.hasFocus) {
|
if (!searchFocusNode.hasFocus) {
|
||||||
// Select all text when the field is tapped
|
// Select all text when the field is tapped
|
||||||
@@ -188,6 +189,7 @@ class SiteSearch extends HookConsumerWidget {
|
|||||||
searchTextController: searchTextController,
|
searchTextController: searchTextController,
|
||||||
activeBang: selectedBang,
|
activeBang: selectedBang,
|
||||||
submitSearch: submitSearch,
|
submitSearch: submitSearch,
|
||||||
|
limit: 25,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ EquatableValue<List<TabEntity>> seamlessFilteredTabEntities(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
EquatableValue<List<TabPreview>> seamlessFilteredTabPreviews(
|
EquatableValue<List<TabPreview>> filteredTabPreviews(
|
||||||
Ref ref,
|
Ref ref,
|
||||||
TabSearchPartition searchPartition,
|
TabSearchPartition searchPartition,
|
||||||
ContainerFilter containerFilter,
|
ContainerFilter containerFilter,
|
||||||
@@ -307,21 +307,7 @@ EquatableValue<List<TabPreview>> seamlessFilteredTabPreviews(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (tabSearchResults == null) {
|
if (tabSearchResults == null) {
|
||||||
return EquatableValue(
|
return EquatableValue([]);
|
||||||
availableTabStates.value.values
|
|
||||||
.map(
|
|
||||||
(state) => TabPreview(
|
|
||||||
id: state.id,
|
|
||||||
title: state.title,
|
|
||||||
icon: state.icon,
|
|
||||||
url: state.url,
|
|
||||||
highlightedUrl: null,
|
|
||||||
content: null,
|
|
||||||
sourceSearchQuery: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return EquatableValue(
|
return EquatableValue(
|
||||||
@@ -330,6 +316,7 @@ EquatableValue<List<TabPreview>> seamlessFilteredTabPreviews(
|
|||||||
.map((tab) {
|
.map((tab) {
|
||||||
return TabPreview(
|
return TabPreview(
|
||||||
id: tab.id,
|
id: tab.id,
|
||||||
|
containerId: tab.containerId,
|
||||||
title: tab.title ?? availableTabStates.value[tab.id]!.title,
|
title: tab.title ?? availableTabStates.value[tab.id]!.title,
|
||||||
icon: null,
|
icon: null,
|
||||||
url: tab.cleanUrl ?? availableTabStates.value[tab.id]!.url,
|
url: tab.cleanUrl ?? availableTabStates.value[tab.id]!.url,
|
||||||
|
|||||||
@@ -640,11 +640,10 @@ final class SeamlessFilteredTabEntitiesFamily extends $Family
|
|||||||
String toString() => r'seamlessFilteredTabEntitiesProvider';
|
String toString() => r'seamlessFilteredTabEntitiesProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ProviderFor(seamlessFilteredTabPreviews)
|
@ProviderFor(filteredTabPreviews)
|
||||||
const seamlessFilteredTabPreviewsProvider =
|
const filteredTabPreviewsProvider = FilteredTabPreviewsFamily._();
|
||||||
SeamlessFilteredTabPreviewsFamily._();
|
|
||||||
|
|
||||||
final class SeamlessFilteredTabPreviewsProvider
|
final class FilteredTabPreviewsProvider
|
||||||
extends
|
extends
|
||||||
$FunctionalProvider<
|
$FunctionalProvider<
|
||||||
EquatableValue<List<TabPreview>>,
|
EquatableValue<List<TabPreview>>,
|
||||||
@@ -652,23 +651,23 @@ final class SeamlessFilteredTabPreviewsProvider
|
|||||||
EquatableValue<List<TabPreview>>
|
EquatableValue<List<TabPreview>>
|
||||||
>
|
>
|
||||||
with $Provider<EquatableValue<List<TabPreview>>> {
|
with $Provider<EquatableValue<List<TabPreview>>> {
|
||||||
const SeamlessFilteredTabPreviewsProvider._({
|
const FilteredTabPreviewsProvider._({
|
||||||
required SeamlessFilteredTabPreviewsFamily super.from,
|
required FilteredTabPreviewsFamily super.from,
|
||||||
required (TabSearchPartition, ContainerFilter) super.argument,
|
required (TabSearchPartition, ContainerFilter) super.argument,
|
||||||
}) : super(
|
}) : super(
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'seamlessFilteredTabPreviewsProvider',
|
name: r'filteredTabPreviewsProvider',
|
||||||
isAutoDispose: true,
|
isAutoDispose: true,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String debugGetCreateSourceHash() => _$seamlessFilteredTabPreviewsHash();
|
String debugGetCreateSourceHash() => _$filteredTabPreviewsHash();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return r'seamlessFilteredTabPreviewsProvider'
|
return r'filteredTabPreviewsProvider'
|
||||||
''
|
''
|
||||||
'$argument';
|
'$argument';
|
||||||
}
|
}
|
||||||
@@ -682,7 +681,7 @@ final class SeamlessFilteredTabPreviewsProvider
|
|||||||
@override
|
@override
|
||||||
EquatableValue<List<TabPreview>> create(Ref ref) {
|
EquatableValue<List<TabPreview>> create(Ref ref) {
|
||||||
final argument = this.argument as (TabSearchPartition, ContainerFilter);
|
final argument = this.argument as (TabSearchPartition, ContainerFilter);
|
||||||
return seamlessFilteredTabPreviews(ref, argument.$1, argument.$2);
|
return filteredTabPreviews(ref, argument.$1, argument.$2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@macro riverpod.override_with_value}
|
/// {@macro riverpod.override_with_value}
|
||||||
@@ -697,8 +696,7 @@ final class SeamlessFilteredTabPreviewsProvider
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is SeamlessFilteredTabPreviewsProvider &&
|
return other is FilteredTabPreviewsProvider && other.argument == argument;
|
||||||
other.argument == argument;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -707,32 +705,32 @@ final class SeamlessFilteredTabPreviewsProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$seamlessFilteredTabPreviewsHash() =>
|
String _$filteredTabPreviewsHash() =>
|
||||||
r'ee1d441ca5f5822e201de8fc0b264ea7b8db0776';
|
r'f2958369b4270f4c8165526c0ffad22f41ec0491';
|
||||||
|
|
||||||
final class SeamlessFilteredTabPreviewsFamily extends $Family
|
final class FilteredTabPreviewsFamily extends $Family
|
||||||
with
|
with
|
||||||
$FunctionalFamilyOverride<
|
$FunctionalFamilyOverride<
|
||||||
EquatableValue<List<TabPreview>>,
|
EquatableValue<List<TabPreview>>,
|
||||||
(TabSearchPartition, ContainerFilter)
|
(TabSearchPartition, ContainerFilter)
|
||||||
> {
|
> {
|
||||||
const SeamlessFilteredTabPreviewsFamily._()
|
const FilteredTabPreviewsFamily._()
|
||||||
: super(
|
: super(
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'seamlessFilteredTabPreviewsProvider',
|
name: r'filteredTabPreviewsProvider',
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
isAutoDispose: true,
|
isAutoDispose: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
SeamlessFilteredTabPreviewsProvider call(
|
FilteredTabPreviewsProvider call(
|
||||||
TabSearchPartition searchPartition,
|
TabSearchPartition searchPartition,
|
||||||
ContainerFilter containerFilter,
|
ContainerFilter containerFilter,
|
||||||
) => SeamlessFilteredTabPreviewsProvider._(
|
) => FilteredTabPreviewsProvider._(
|
||||||
argument: (searchPartition, containerFilter),
|
argument: (searchPartition, containerFilter),
|
||||||
from: this,
|
from: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => r'seamlessFilteredTabPreviewsProvider';
|
String toString() => r'filteredTabPreviewsProvider';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
domain: initialTabState.url.host,
|
domain: initialTabState.url.host,
|
||||||
availableBangs: availableBangs,
|
availableBangs: availableBangs,
|
||||||
controller: searchTextController,
|
controller: searchTextController,
|
||||||
label: const Text('Address'),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|||||||
+1
-1
@@ -266,7 +266,7 @@ class ListTabPreview extends HookConsumerWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: tabState.isPrivate ? const Color(0xFF25003E) : null,
|
color: tabState.isPrivate ? const Color(0xFF25003E) : null,
|
||||||
border: isActive ? Border.all(color: colorScheme.primary) : null,
|
border: isActive ? Border.all(color: colorScheme.primary) : null,
|
||||||
// borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import 'package:weblibre/domain/entities/equatable_image.dart';
|
|||||||
|
|
||||||
class TabPreview with FastEquatable {
|
class TabPreview with FastEquatable {
|
||||||
final String id;
|
final String id;
|
||||||
|
final String? containerId;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final EquatableImage? icon;
|
final EquatableImage? icon;
|
||||||
@@ -35,6 +36,7 @@ class TabPreview with FastEquatable {
|
|||||||
|
|
||||||
TabPreview({
|
TabPreview({
|
||||||
required this.id,
|
required this.id,
|
||||||
|
required this.containerId,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.url,
|
required this.url,
|
||||||
@@ -46,6 +48,7 @@ class TabPreview with FastEquatable {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get hashParameters => [
|
List<Object?> get hashParameters => [
|
||||||
id,
|
id,
|
||||||
|
containerId,
|
||||||
title,
|
title,
|
||||||
icon,
|
icon,
|
||||||
url,
|
url,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* 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 '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';
|
||||||
@@ -125,142 +126,152 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Form(
|
body: SafeArea(
|
||||||
key: formKey,
|
child: Form(
|
||||||
child: CustomScrollView(
|
key: formKey,
|
||||||
slivers: [
|
child: FadingScroll(
|
||||||
SliverAppBar(
|
builder: (context, controller) {
|
||||||
floating: true,
|
return CustomScrollView(
|
||||||
pinned: true,
|
controller: controller,
|
||||||
automaticallyImplyLeading: false,
|
slivers: [
|
||||||
title: Align(
|
SliverAppBar(
|
||||||
child: Focus(
|
floating: true,
|
||||||
canRequestFocus: false,
|
pinned: true,
|
||||||
child: SegmentedButton(
|
automaticallyImplyLeading: false,
|
||||||
showSelectedIcon: false,
|
title: Align(
|
||||||
segments: [
|
child: Focus(
|
||||||
const ButtonSegment(
|
canRequestFocus: false,
|
||||||
value: TabType.regular,
|
child: SegmentedButton(
|
||||||
label: Text('Regular'),
|
showSelectedIcon: false,
|
||||||
icon: Icon(MdiIcons.tab),
|
segments: [
|
||||||
),
|
const ButtonSegment(
|
||||||
const ButtonSegment(
|
value: TabType.regular,
|
||||||
value: TabType.private,
|
label: Text('Regular'),
|
||||||
label: Text('Private'),
|
icon: Icon(MdiIcons.tab),
|
||||||
icon: Icon(MdiIcons.tabUnselected),
|
),
|
||||||
),
|
const ButtonSegment(
|
||||||
if (createChildTabsOption)
|
value: TabType.private,
|
||||||
const ButtonSegment(
|
label: Text('Private'),
|
||||||
value: TabType.child,
|
icon: Icon(MdiIcons.tabUnselected),
|
||||||
label: Text('Child'),
|
),
|
||||||
icon: Icon(MdiIcons.fileTree),
|
if (createChildTabsOption)
|
||||||
|
const ButtonSegment(
|
||||||
|
value: TabType.child,
|
||||||
|
label: Text('Child'),
|
||||||
|
icon: Icon(MdiIcons.fileTree),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: {selectedTabType.value},
|
||||||
|
onSelectionChanged: (value) {
|
||||||
|
selectedTabType.value = value.first;
|
||||||
|
// Restore focus to search field after segment change
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
searchFocusNode.requestFocus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
style: switch (selectedTabType.value) {
|
||||||
|
TabType.regular => null,
|
||||||
|
TabType.private => SegmentedButton.styleFrom(
|
||||||
|
selectedBackgroundColor: const Color(0x648000D7),
|
||||||
|
),
|
||||||
|
TabType.child =>
|
||||||
|
(currentTabTabType == TabType.private)
|
||||||
|
? SegmentedButton.styleFrom(
|
||||||
|
selectedBackgroundColor: const Color(
|
||||||
|
0x648000D7,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
|
||||||
selected: {selectedTabType.value},
|
|
||||||
onSelectionChanged: (value) {
|
|
||||||
selectedTabType.value = value.first;
|
|
||||||
// Restore focus to search field after segment change
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
searchFocusNode.requestFocus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
style: switch (selectedTabType.value) {
|
|
||||||
TabType.regular => null,
|
|
||||||
TabType.private => SegmentedButton.styleFrom(
|
|
||||||
selectedBackgroundColor: const Color(0x648000D7),
|
|
||||||
),
|
),
|
||||||
TabType.child =>
|
),
|
||||||
(currentTabTabType == TabType.private)
|
bottom: PreferredSize(
|
||||||
? SegmentedButton.styleFrom(
|
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||||
selectedBackgroundColor: const Color(
|
child: Padding(
|
||||||
0x648000D7,
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
),
|
child: SearchField(
|
||||||
)
|
showBangIcon: showBangIcon.value,
|
||||||
: null,
|
textEditingController: searchTextController,
|
||||||
},
|
focusNode: searchFocusNode,
|
||||||
),
|
autofocus: true,
|
||||||
),
|
label: const Text('Address / Search'),
|
||||||
),
|
onSubmitted: (value) async {
|
||||||
bottom: PreferredSize(
|
if (value.isNotEmpty) {
|
||||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
var newUrl = uri_parser.tryParseUrl(
|
||||||
child: Padding(
|
value,
|
||||||
padding: const EdgeInsets.only(left: 16.0),
|
eagerParsing: true,
|
||||||
child: SearchField(
|
|
||||||
showBangIcon: showBangIcon.value,
|
|
||||||
textEditingController: searchTextController,
|
|
||||||
focusNode: searchFocusNode,
|
|
||||||
autofocus: true,
|
|
||||||
label: const Text('Address / Search'),
|
|
||||||
onSubmitted: (value) async {
|
|
||||||
if (value.isNotEmpty) {
|
|
||||||
var newUrl = uri_parser.tryParseUrl(
|
|
||||||
value,
|
|
||||||
eagerParsing: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newUrl == null) {
|
|
||||||
final bang =
|
|
||||||
ref.read(selectedBangDataProvider()) ??
|
|
||||||
await ref.read(
|
|
||||||
defaultSearchBangDataProvider.future,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (bang != null) {
|
if (newUrl == null) {
|
||||||
newUrl = bang.getTemplateUrl(value);
|
final bang =
|
||||||
|
ref.read(selectedBangDataProvider()) ??
|
||||||
|
await ref.read(
|
||||||
|
defaultSearchBangDataProvider.future,
|
||||||
|
);
|
||||||
|
|
||||||
if (!privateTabMode) {
|
if (bang != null) {
|
||||||
await ref
|
newUrl = bang.getTemplateUrl(value);
|
||||||
.read(bangSearchProvider.notifier)
|
|
||||||
.triggerBangSearch(bang, value);
|
if (!privateTabMode) {
|
||||||
|
await ref
|
||||||
|
.read(bangSearchProvider.notifier)
|
||||||
|
.triggerBangSearch(bang, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newUrl != null) {
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.addTab(
|
||||||
|
url: newUrl,
|
||||||
|
private: privateTabMode,
|
||||||
|
parentId:
|
||||||
|
(selectedTabType.value ==
|
||||||
|
TabType.child)
|
||||||
|
? ref.read(selectedTabProvider)
|
||||||
|
: null,
|
||||||
|
launchedFromIntent: launchedFromIntent,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
bottomSheetControllerProvider.notifier,
|
||||||
|
)
|
||||||
|
.requestDismiss();
|
||||||
|
|
||||||
|
const BrowserRoute().go(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
activeBang: activeBang,
|
||||||
|
showSuggestions: true,
|
||||||
if (newUrl != null) {
|
),
|
||||||
await ref
|
),
|
||||||
.read(tabRepositoryProvider.notifier)
|
),
|
||||||
.addTab(
|
|
||||||
url: newUrl,
|
|
||||||
private: privateTabMode,
|
|
||||||
parentId:
|
|
||||||
(selectedTabType.value == TabType.child)
|
|
||||||
? ref.read(selectedTabProvider)
|
|
||||||
: null,
|
|
||||||
launchedFromIntent: launchedFromIntent,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (context.mounted) {
|
|
||||||
ref
|
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
|
||||||
.requestDismiss();
|
|
||||||
|
|
||||||
const BrowserRoute().go(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activeBang: activeBang,
|
|
||||||
showSuggestions: true,
|
|
||||||
),
|
),
|
||||||
),
|
SliverToBoxAdapter(
|
||||||
),
|
child: ClipboardFillLink(controller: searchTextController),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
const SliverToBoxAdapter(child: Divider()),
|
||||||
child: ClipboardFillLink(controller: searchTextController),
|
FullSearchTermSuggestions(
|
||||||
),
|
searchTextController: searchTextController,
|
||||||
const SliverToBoxAdapter(child: Divider()),
|
activeBang: activeBang,
|
||||||
FullSearchTermSuggestions(
|
submitSearch: submitSearch,
|
||||||
searchTextController: searchTextController,
|
),
|
||||||
activeBang: activeBang,
|
TabSearch(searchTextListenable: sampledSearchText),
|
||||||
submitSearch: submitSearch,
|
FeedSearch(searchTextNotifier: sampledSearchText),
|
||||||
),
|
HistorySuggestions(
|
||||||
TabSearch(searchTextListenable: sampledSearchText),
|
isPrivate: privateTabMode,
|
||||||
FeedSearch(searchTextNotifier: sampledSearchText),
|
searchTextListenable: sampledSearchText,
|
||||||
HistorySuggestions(
|
),
|
||||||
isPrivate: privateTabMode,
|
],
|
||||||
searchTextListenable: sampledSearchText,
|
);
|
||||||
),
|
},
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+21
-20
@@ -32,13 +32,13 @@ class FixedSearchTermSuggestions extends HookConsumerWidget {
|
|||||||
final TextEditingController searchTextController;
|
final TextEditingController searchTextController;
|
||||||
final Future<void> Function(String query) submitSearch;
|
final Future<void> Function(String query) submitSearch;
|
||||||
final BangData? activeBang;
|
final BangData? activeBang;
|
||||||
final int count;
|
final int limit;
|
||||||
|
|
||||||
const FixedSearchTermSuggestions({
|
const FixedSearchTermSuggestions({
|
||||||
required this.searchTextController,
|
required this.searchTextController,
|
||||||
required this.submitSearch,
|
required this.submitSearch,
|
||||||
required this.activeBang,
|
required this.activeBang,
|
||||||
this.count = 3,
|
required this.limit,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,27 +76,28 @@ class FixedSearchTermSuggestions extends HookConsumerWidget {
|
|||||||
.whereNot(
|
.whereNot(
|
||||||
(suggestion) => suggestion == searchTextController.text,
|
(suggestion) => suggestion == searchTextController.text,
|
||||||
)
|
)
|
||||||
.take(count),
|
.take(limit),
|
||||||
]
|
]
|
||||||
: <String>[];
|
: <String>[];
|
||||||
|
|
||||||
return Column(
|
return Wrap(
|
||||||
mainAxisSize: MainAxisSize.min,
|
spacing: 8.0,
|
||||||
children: prioritizedSuggestions
|
children: prioritizedSuggestions.map((query) {
|
||||||
.map(
|
return InkWell(
|
||||||
(suggestion) => ListTile(
|
onLongPress: () {
|
||||||
contentPadding: EdgeInsets.zero,
|
searchTextController.text = query;
|
||||||
leading: const Icon(Icons.search),
|
},
|
||||||
title: Text(suggestion),
|
child: InputChip(
|
||||||
onLongPress: () {
|
// avatar: const Icon(Icons.search),
|
||||||
searchTextController.text = suggestion;
|
label: Text(query),
|
||||||
},
|
onSelected: (value) async {
|
||||||
onTap: () async {
|
if (value) {
|
||||||
await submitSearch(suggestion);
|
await submitSearch(query);
|
||||||
},
|
}
|
||||||
),
|
},
|
||||||
)
|
),
|
||||||
.toList(),
|
);
|
||||||
|
}).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-50
@@ -61,35 +61,45 @@ class FullSearchTermSuggestions extends HookConsumerWidget {
|
|||||||
.addQuery(searchTextController.text);
|
.addQuery(searchTextController.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget listSliver;
|
final MultiSliver listSliver;
|
||||||
|
|
||||||
if (!searchTextIsNotEmpty && (searchHistory.value.isNotEmpty)) {
|
if (!searchTextIsNotEmpty && (searchHistory.value.isNotEmpty)) {
|
||||||
final entries = searchHistory.value!;
|
final entries = searchHistory.value!;
|
||||||
|
|
||||||
listSliver = SliverList.builder(
|
listSliver = MultiSliver(
|
||||||
itemCount: entries.length,
|
children: [
|
||||||
itemBuilder: (context, index) {
|
SliverToBoxAdapter(
|
||||||
final query = entries[index].searchQuery;
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
children: entries.map((entry) {
|
||||||
|
final query = entry.searchQuery;
|
||||||
|
|
||||||
return ListTile(
|
return InkWell(
|
||||||
leading: const Icon(Icons.history),
|
onLongPress: () {
|
||||||
title: Text(query),
|
searchTextController.text = query;
|
||||||
onLongPress: () {
|
},
|
||||||
searchTextController.text = query;
|
child: InputChip(
|
||||||
},
|
avatar: const Icon(Icons.history),
|
||||||
onTap: () async {
|
label: Text(query),
|
||||||
await submitSearch(query);
|
onSelected: (value) async {
|
||||||
},
|
if (value) {
|
||||||
trailing: IconButton(
|
await submitSearch(query);
|
||||||
onPressed: () async {
|
}
|
||||||
await ref
|
},
|
||||||
.read(bangDataRepositoryProvider.notifier)
|
onDeleted: () async {
|
||||||
.removeSearchEntry(query);
|
await ref
|
||||||
},
|
.read(bangDataRepositoryProvider.notifier)
|
||||||
icon: const Icon(Icons.close),
|
.removeSearchEntry(query);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final prioritizedSuggestions = [
|
final prioritizedSuggestions = [
|
||||||
@@ -100,22 +110,33 @@ class FullSearchTermSuggestions extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
listSliver = SliverList.builder(
|
listSliver = MultiSliver(
|
||||||
itemCount: prioritizedSuggestions.length,
|
children: [
|
||||||
itemBuilder: (context, index) {
|
SliverToBoxAdapter(
|
||||||
final suggestion = prioritizedSuggestions[index];
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
return ListTile(
|
child: Wrap(
|
||||||
leading: const Icon(Icons.search),
|
spacing: 8.0,
|
||||||
title: Text(suggestion),
|
children: prioritizedSuggestions.map((query) {
|
||||||
onLongPress: () {
|
return InkWell(
|
||||||
searchTextController.text = suggestion;
|
onLongPress: () {
|
||||||
},
|
searchTextController.text = query;
|
||||||
onTap: () async {
|
},
|
||||||
await submitSearch(suggestion);
|
child: InputChip(
|
||||||
},
|
// avatar: const Icon(Icons.search),
|
||||||
);
|
label: Text(query),
|
||||||
},
|
onSelected: (value) async {
|
||||||
|
if (value) {
|
||||||
|
await submitSearch(query);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,17 +188,20 @@ class FullSearchTermSuggestions extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: ConstrainedBox(
|
child: Padding(
|
||||||
constraints: const BoxConstraints(maxHeight: 150),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: FadingScroll(
|
child: ConstrainedBox(
|
||||||
fadingSize: 25,
|
constraints: const BoxConstraints(maxHeight: 150),
|
||||||
builder: (context, controller) {
|
child: FadingScroll(
|
||||||
return CustomScrollView(
|
fadingSize: 25,
|
||||||
shrinkWrap: true,
|
builder: (context, controller) {
|
||||||
controller: controller,
|
return CustomScrollView(
|
||||||
slivers: [listSliver],
|
shrinkWrap: true,
|
||||||
);
|
controller: controller,
|
||||||
},
|
slivers: [listSliver],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+20
-21
@@ -33,6 +33,7 @@ import 'package:weblibre/features/geckoview/utils/image_helper.dart';
|
|||||||
import 'package:weblibre/presentation/hooks/cached_future.dart';
|
import 'package:weblibre/presentation/hooks/cached_future.dart';
|
||||||
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
|
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
|
||||||
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
||||||
|
import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart';
|
||||||
|
|
||||||
class HistorySuggestions extends HookConsumerWidget {
|
class HistorySuggestions extends HookConsumerWidget {
|
||||||
final bool isPrivate;
|
final bool isPrivate;
|
||||||
@@ -82,6 +83,7 @@ class HistorySuggestions extends HookConsumerWidget {
|
|||||||
itemCount: historySuggestions.length,
|
itemCount: historySuggestions.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final suggestion = historySuggestions[index];
|
final suggestion = historySuggestions[index];
|
||||||
|
final uri = suggestion.description.mapNotNull(Uri.tryParse);
|
||||||
|
|
||||||
return HookBuilder(
|
return HookBuilder(
|
||||||
key: ValueKey(suggestion.id),
|
key: ValueKey(suggestion.id),
|
||||||
@@ -107,30 +109,27 @@ class HistorySuggestions extends HookConsumerWidget {
|
|||||||
title: suggestion.title.mapNotNull(
|
title: suggestion.title.mapNotNull(
|
||||||
(title) => Text(title),
|
(title) => Text(title),
|
||||||
),
|
),
|
||||||
subtitle: suggestion.description.mapNotNull(
|
subtitle:
|
||||||
(description) => Text(
|
uri.mapNotNull((uri) => UriBreadcrumb(uri: uri)) ??
|
||||||
description,
|
suggestion.description.mapNotNull(
|
||||||
maxLines: 2,
|
(description) => Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
description,
|
||||||
),
|
maxLines: 2,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (suggestion.description != null) {
|
if (uri != null) {
|
||||||
if (Uri.tryParse(suggestion.description!)
|
await ref
|
||||||
case final Uri url) {
|
.read(tabRepositoryProvider.notifier)
|
||||||
await ref
|
.addTab(url: uri, private: isPrivate);
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.addTab(url: url, private: isPrivate);
|
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ref
|
ref
|
||||||
.read(
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
bottomSheetControllerProvider.notifier,
|
.requestDismiss();
|
||||||
)
|
|
||||||
.requestDismiss();
|
|
||||||
|
|
||||||
const BrowserRoute().go(context);
|
const BrowserRoute().go(context);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+72
-16
@@ -17,6 +17,10 @@
|
|||||||
* 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:collection/collection.dart';
|
||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.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';
|
||||||
@@ -33,6 +37,7 @@ import 'package:weblibre/features/geckoview/features/find_in_page/presentation/c
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||||
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
|
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
|
||||||
@@ -53,15 +58,53 @@ class TabSearch extends HookConsumerWidget {
|
|||||||
ref.read(selectedContainerDataProvider.select((value) => value.value)),
|
ref.read(selectedContainerDataProvider.select((value) => value.value)),
|
||||||
);
|
);
|
||||||
|
|
||||||
final tabs = ref
|
final tabSearchResults = ref.watch(
|
||||||
.watch(
|
filteredTabPreviewsProvider(
|
||||||
seamlessFilteredTabPreviewsProvider(
|
TabSearchPartition.search,
|
||||||
TabSearchPartition.search,
|
// ignore: provider_parameters
|
||||||
// ignore: provider_parameters
|
ContainerFilterDisabled(),
|
||||||
ContainerFilterById(containerId: selectedContainer.value?.id),
|
),
|
||||||
),
|
);
|
||||||
)
|
|
||||||
.value;
|
final containerIdsWithResults = useMemoized(
|
||||||
|
() => EquatableValue(
|
||||||
|
tabSearchResults.value
|
||||||
|
.map((result) => result.containerId)
|
||||||
|
.fold(
|
||||||
|
<String?, int>{},
|
||||||
|
(map, id) =>
|
||||||
|
map..update(id, (value) => ++value, ifAbsent: () => 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[tabSearchResults],
|
||||||
|
);
|
||||||
|
|
||||||
|
final filteredTabs = useMemoized(
|
||||||
|
() => tabSearchResults.value
|
||||||
|
.where((result) => result.containerId == selectedContainer.value?.id)
|
||||||
|
.toList(),
|
||||||
|
[tabSearchResults, selectedContainer.value?.id],
|
||||||
|
);
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
useValueChanged(containerIdsWithResults, (_, _) async {
|
||||||
|
if (containerIdsWithResults.value.isNotEmpty &&
|
||||||
|
!containerIdsWithResults.value.containsKey(
|
||||||
|
selectedContainer.value?.id,
|
||||||
|
)) {
|
||||||
|
selectedContainer.value = await ref
|
||||||
|
.read(containerRepositoryProvider.notifier)
|
||||||
|
.getAllContainersWithCount()
|
||||||
|
.then((containers) {
|
||||||
|
return containers.firstWhereOrNull(
|
||||||
|
(container) =>
|
||||||
|
!container.metadata.authSettings.authenticationRequired &&
|
||||||
|
containerIdsWithResults.value.containsKey(container.id),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
useListenableCallback(searchTextListenable, () async {
|
useListenableCallback(searchTextListenable, () async {
|
||||||
if (ref.exists(tabSearchRepositoryProvider(TabSearchPartition.search))) {
|
if (ref.exists(tabSearchRepositoryProvider(TabSearchPartition.search))) {
|
||||||
@@ -79,6 +122,10 @@ class TabSearch extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (tabSearchResults.value.isEmpty) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||||
|
}
|
||||||
|
|
||||||
return MultiSliver(
|
return MultiSliver(
|
||||||
children: [
|
children: [
|
||||||
const SliverToBoxAdapter(child: Divider()),
|
const SliverToBoxAdapter(child: Divider()),
|
||||||
@@ -92,6 +139,9 @@ class TabSearch extends HookConsumerWidget {
|
|||||||
ContainerChips(
|
ContainerChips(
|
||||||
displayMenu: false,
|
displayMenu: false,
|
||||||
selectedContainer: selectedContainer.value,
|
selectedContainer: selectedContainer.value,
|
||||||
|
showUnassignedChip: containerIdsWithResults.value.containsKey(
|
||||||
|
null,
|
||||||
|
),
|
||||||
onSelected: (container) async {
|
onSelected: (container) async {
|
||||||
if (container != null) {
|
if (container != null) {
|
||||||
if (await ref
|
if (await ref
|
||||||
@@ -106,7 +156,10 @@ class TabSearch extends HookConsumerWidget {
|
|||||||
onDeleted: (container) {
|
onDeleted: (container) {
|
||||||
selectedContainer.value = null;
|
selectedContainer.value = null;
|
||||||
},
|
},
|
||||||
containerFilter: (container) => (container.tabCount ?? 0) > 0,
|
containerFilter: (container) =>
|
||||||
|
containerIdsWithResults.value.containsKey(container.id),
|
||||||
|
containerBadgeCount: (container) =>
|
||||||
|
containerIdsWithResults.value[container?.id] ?? 0,
|
||||||
searchTextListenable: searchTextListenable,
|
searchTextListenable: searchTextListenable,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -114,10 +167,11 @@ class TabSearch extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverList.builder(
|
SliverList.builder(
|
||||||
itemCount: tabs.length,
|
itemCount: filteredTabs.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final result = tabs[index];
|
final result = filteredTabs[index];
|
||||||
|
|
||||||
|
final titleHasMatch = result.title.contains(_matchPrefix);
|
||||||
final urlHasMatch =
|
final urlHasMatch =
|
||||||
result.highlightedUrl?.contains(_matchPrefix) ?? false;
|
result.highlightedUrl?.contains(_matchPrefix) ?? false;
|
||||||
final bodyHasMatch =
|
final bodyHasMatch =
|
||||||
@@ -142,11 +196,13 @@ class TabSearch extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: (bodyHasMatch || urlHasMatch)
|
subtitle: (bodyHasMatch || (urlHasMatch && !titleHasMatch))
|
||||||
? MarkdownBody(
|
? MarkdownBody(
|
||||||
data: bodyHasMatch
|
data:
|
||||||
? result.content!
|
(bodyHasMatch
|
||||||
: result.highlightedUrl!,
|
? result.content!
|
||||||
|
: result.highlightedUrl!)
|
||||||
|
.replaceAll(RegExp(r'\s+'), ' '),
|
||||||
styleSheet: MarkdownStyleSheet(
|
styleSheet: MarkdownStyleSheet(
|
||||||
p: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
p: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
|||||||
+38
-29
@@ -38,10 +38,12 @@ import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
|||||||
|
|
||||||
class ContainerChips extends HookConsumerWidget {
|
class ContainerChips extends HookConsumerWidget {
|
||||||
final bool displayMenu;
|
final bool displayMenu;
|
||||||
|
final bool showUnassignedChip;
|
||||||
final bool showGroupSuggestions;
|
final bool showGroupSuggestions;
|
||||||
|
|
||||||
final ContainerData? selectedContainer;
|
final ContainerData? selectedContainer;
|
||||||
final bool Function(ContainerDataWithCount)? containerFilter;
|
final bool Function(ContainerDataWithCount)? containerFilter;
|
||||||
|
final int Function(ContainerDataWithCount?)? containerBadgeCount;
|
||||||
final void Function(ContainerDataWithCount?)? onSelected;
|
final void Function(ContainerDataWithCount?)? onSelected;
|
||||||
final void Function(ContainerDataWithCount)? onDeleted;
|
final void Function(ContainerDataWithCount)? onDeleted;
|
||||||
final void Function(ContainerDataWithCount)? onLongPress;
|
final void Function(ContainerDataWithCount)? onLongPress;
|
||||||
@@ -54,8 +56,10 @@ class ContainerChips extends HookConsumerWidget {
|
|||||||
required this.onDeleted,
|
required this.onDeleted,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
this.containerFilter,
|
this.containerFilter,
|
||||||
|
this.containerBadgeCount,
|
||||||
this.searchTextListenable,
|
this.searchTextListenable,
|
||||||
this.displayMenu = true,
|
this.displayMenu = true,
|
||||||
|
this.showUnassignedChip = true,
|
||||||
this.showGroupSuggestions = false,
|
this.showGroupSuggestions = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,7 +101,9 @@ class ContainerChips extends HookConsumerWidget {
|
|||||||
container.color.withValues(alpha: 0.33),
|
container.color.withValues(alpha: 0.33),
|
||||||
itemLabel: (container) =>
|
itemLabel: (container) =>
|
||||||
ContainerTitle(container: container),
|
ContainerTitle(container: container),
|
||||||
itemBadgeCount: (container) => container.tabCount,
|
itemBadgeCount: (container) =>
|
||||||
|
containerBadgeCount?.call(container) ??
|
||||||
|
container.tabCount,
|
||||||
itemWrap: (child, container) {
|
itemWrap: (child, container) {
|
||||||
return TabDragContainerTarget(
|
return TabDragContainerTarget(
|
||||||
container: container,
|
container: container,
|
||||||
@@ -105,36 +111,39 @@ class ContainerChips extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
prefixListItems: [
|
prefixListItems: [
|
||||||
TabDragContainerTarget(
|
if (showUnassignedChip)
|
||||||
container: null,
|
TabDragContainerTarget(
|
||||||
child: Consumer(
|
container: null,
|
||||||
builder: (context, ref, child) {
|
child: Consumer(
|
||||||
final tabCount = ref.watch(
|
builder: (context, ref, child) {
|
||||||
containerTabCountProvider(
|
final int tabCount =
|
||||||
// ignore: provider_parameters
|
containerBadgeCount?.call(null) ??
|
||||||
ContainerFilterById(containerId: null),
|
ref.watch(
|
||||||
).select((value) => value.value ?? 0),
|
containerTabCountProvider(
|
||||||
);
|
// ignore: provider_parameters
|
||||||
|
ContainerFilterById(containerId: null),
|
||||||
|
).select((value) => value.value ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
return FilterChip(
|
return FilterChip(
|
||||||
avatar: const Icon(MdiIcons.folderHidden),
|
avatar: const Icon(MdiIcons.folderHidden),
|
||||||
labelPadding: (tabCount > 0)
|
labelPadding: (tabCount > 0)
|
||||||
? null
|
? null
|
||||||
: const EdgeInsets.only(right: 2.0),
|
: const EdgeInsets.only(right: 2.0),
|
||||||
label: (tabCount > 0)
|
label: (tabCount > 0)
|
||||||
? Text(tabCount.toString())
|
? Text(tabCount.toString())
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
selected: selectedContainer == null,
|
selected: selectedContainer == null,
|
||||||
showCheckmark: false,
|
showCheckmark: false,
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
onSelected?.call(null);
|
onSelected?.call(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
if (showGroupSuggestions)
|
if (showGroupSuggestions)
|
||||||
Consumer(
|
Consumer(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
|
|||||||
Reference in New Issue
Block a user