container filter for tree view
This commit is contained in:
@@ -253,11 +253,20 @@ EquatableValue<List<TabEntity>> seamlessFilteredTabEntities(
|
|||||||
(value) => EquatableValue(
|
(value) => EquatableValue(
|
||||||
value.value
|
value.value
|
||||||
?.map(
|
?.map(
|
||||||
(tree) => TabTreeEntity(
|
(tree) {
|
||||||
tabId: tree.latestTabId,
|
// Find the container ID for the latest tab
|
||||||
rootId: tree.rootTabId,
|
final containerForTab = availableTabs.value
|
||||||
totalTabs: tree.totalTabs,
|
.where((t) => t.tabId == tree.latestTabId)
|
||||||
),
|
.firstOrNull
|
||||||
|
?.containerId;
|
||||||
|
|
||||||
|
return TabTreeEntity(
|
||||||
|
tabId: tree.latestTabId,
|
||||||
|
containerId: containerForTab,
|
||||||
|
rootId: tree.rootTabId,
|
||||||
|
totalTabs: tree.totalTabs,
|
||||||
|
);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
|
|||||||
@@ -600,7 +600,7 @@ final class SeamlessFilteredTabEntitiesProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$seamlessFilteredTabEntitiesHash() =>
|
String _$seamlessFilteredTabEntitiesHash() =>
|
||||||
r'f2bf14737bc03f71fc61252359979b4e901e5df7';
|
r'590288855aa93162a8f70d37f2b3cd94eabc85e5';
|
||||||
|
|
||||||
final class SeamlessFilteredTabEntitiesFamily extends $Family
|
final class SeamlessFilteredTabEntitiesFamily extends $Family
|
||||||
with
|
with
|
||||||
|
|||||||
+4
-1
@@ -15,6 +15,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/utils/grid_calculations.dart';
|
import 'package:weblibre/features/geckoview/features/browser/utils/grid_calculations.dart';
|
||||||
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/entities/tab_entity.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_entity.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.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/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
@@ -176,12 +177,14 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
final containerId = ref.watch(selectedContainerProvider);
|
||||||
|
|
||||||
final filteredTabEntities = ref.watch(
|
final filteredTabEntities = ref.watch(
|
||||||
seamlessFilteredTabEntitiesProvider(
|
seamlessFilteredTabEntitiesProvider(
|
||||||
searchPartition: TabSearchPartition.preview,
|
searchPartition: TabSearchPartition.preview,
|
||||||
// ignore: document_ignores using fast equatable
|
// ignore: document_ignores using fast equatable
|
||||||
// ignore: provider_parameters
|
// ignore: provider_parameters
|
||||||
containerFilter: ContainerFilterDisabled(),
|
containerFilter: ContainerFilterById(containerId: containerId),
|
||||||
groupTrees: true,
|
groupTrees: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+43
-40
@@ -529,52 +529,55 @@ class TabViewHeader extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (switch (tabsViewMode) {
|
Consumer(
|
||||||
TabsViewMode.list || TabsViewMode.grid => true,
|
builder: (context, ref, child) {
|
||||||
TabsViewMode.tree => false,
|
final selectedContainer = ref.watch(
|
||||||
})
|
selectedContainerDataProvider.select(
|
||||||
Consumer(
|
(value) => value.value,
|
||||||
builder: (context, ref, child) {
|
),
|
||||||
final selectedContainer = ref.watch(
|
);
|
||||||
selectedContainerDataProvider.select(
|
|
||||||
(value) => value.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return ContainerChips(
|
return ContainerChips(
|
||||||
showGroupSuggestions: true,
|
showGroupSuggestions: switch (tabsViewMode) {
|
||||||
selectedContainer: selectedContainer,
|
TabsViewMode.list || TabsViewMode.grid => true,
|
||||||
onSelected: (container) async {
|
TabsViewMode.tree => false,
|
||||||
if (container != null) {
|
},
|
||||||
final result = await ref
|
enableDragAndDrop: switch (tabsViewMode) {
|
||||||
.read(selectedContainerProvider.notifier)
|
TabsViewMode.list || TabsViewMode.grid => true,
|
||||||
.setContainerId(container.id);
|
TabsViewMode.tree => false,
|
||||||
|
},
|
||||||
|
selectedContainer: selectedContainer,
|
||||||
|
onSelected: (container) async {
|
||||||
|
if (container != null) {
|
||||||
|
final result = await ref
|
||||||
|
.read(selectedContainerProvider.notifier)
|
||||||
|
.setContainerId(container.id);
|
||||||
|
|
||||||
if (context.mounted &&
|
if (context.mounted &&
|
||||||
result == SetContainerResult.successHasProxy) {
|
result == SetContainerResult.successHasProxy) {
|
||||||
await ref
|
await ref
|
||||||
.read(startProxyControllerProvider.notifier)
|
.read(startProxyControllerProvider.notifier)
|
||||||
.maybeStartProxy(context);
|
.maybeStartProxy(context);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ref
|
|
||||||
.read(selectedContainerProvider.notifier)
|
|
||||||
.clearContainer();
|
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
onDeleted: (container) {
|
|
||||||
ref
|
ref
|
||||||
.read(selectedContainerProvider.notifier)
|
.read(selectedContainerProvider.notifier)
|
||||||
.clearContainer();
|
.clearContainer();
|
||||||
},
|
}
|
||||||
onLongPress: (container) async {
|
},
|
||||||
await ContainerEditRoute(
|
onDeleted: (container) {
|
||||||
containerData: jsonEncode(container.toJson()),
|
ref
|
||||||
).push(context);
|
.read(selectedContainerProvider.notifier)
|
||||||
},
|
.clearContainer();
|
||||||
);
|
},
|
||||||
},
|
onLongPress: (container) async {
|
||||||
),
|
await ContainerEditRoute(
|
||||||
|
containerData: jsonEncode(container.toJson()),
|
||||||
|
).push(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -55,16 +55,19 @@ class TabTreeEntity extends TabEntity {
|
|||||||
@override
|
@override
|
||||||
final String tabId;
|
final String tabId;
|
||||||
|
|
||||||
|
final String? containerId;
|
||||||
|
|
||||||
final String rootId;
|
final String rootId;
|
||||||
|
|
||||||
final int totalTabs;
|
final int totalTabs;
|
||||||
|
|
||||||
TabTreeEntity({
|
TabTreeEntity({
|
||||||
required this.tabId,
|
required this.tabId,
|
||||||
|
required this.containerId,
|
||||||
required this.rootId,
|
required this.rootId,
|
||||||
required this.totalTabs,
|
required this.totalTabs,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get hashParameters => [tabId, rootId, totalTabs];
|
List<Object?> get hashParameters => [tabId, containerId, rootId, totalTabs];
|
||||||
}
|
}
|
||||||
|
|||||||
+147
-114
@@ -37,10 +37,104 @@ import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors
|
|||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||||
|
|
||||||
|
class _UnassignedContainerChip extends ConsumerWidget {
|
||||||
|
final int? Function()? containerBadgeCount;
|
||||||
|
final ContainerData? selectedContainer;
|
||||||
|
final void Function(ContainerDataWithCount?)? onSelected;
|
||||||
|
|
||||||
|
const _UnassignedContainerChip({
|
||||||
|
required this.containerBadgeCount,
|
||||||
|
required this.selectedContainer,
|
||||||
|
required this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final int tabCount =
|
||||||
|
containerBadgeCount?.call() ??
|
||||||
|
ref.watch(
|
||||||
|
containerTabCountProvider(
|
||||||
|
// ignore: provider_parameters
|
||||||
|
ContainerFilterById(containerId: null),
|
||||||
|
).select((value) => value.value ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
return FilterChip(
|
||||||
|
avatar: const Icon(MdiIcons.folderHidden),
|
||||||
|
labelPadding: (tabCount > 0) ? null : const EdgeInsets.only(right: 2.0),
|
||||||
|
label: (tabCount > 0)
|
||||||
|
? Text(tabCount.toString())
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
selected: selectedContainer == null,
|
||||||
|
showCheckmark: false,
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value) {
|
||||||
|
onSelected?.call(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContainerSuggestionsChip extends ConsumerWidget {
|
||||||
|
const _ContainerSuggestionsChip();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final tabSuggestionsEnabled = ref.watch(tabSuggestionsControllerProvider);
|
||||||
|
final enableAiFeatures = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(settings) => settings.enableLocalAiFeatures,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!enableAiFeatures || !tabSuggestionsEnabled) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final suggestions = ref.watch(suggestClustersProvider);
|
||||||
|
|
||||||
|
return suggestions.when(
|
||||||
|
skipLoadingOnReload: true,
|
||||||
|
data: (data) {
|
||||||
|
if (data.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return FilterChip(
|
||||||
|
avatar: const Icon(MdiIcons.autoFix),
|
||||||
|
label: Text(data!.length.toString()),
|
||||||
|
showCheckmark: false,
|
||||||
|
onSelected: (_) async {
|
||||||
|
await const ContainerDraftRoute().push(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
error: (error, stackTrace) {
|
||||||
|
logger.e(
|
||||||
|
'Error suggesting containers',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
loading: () {
|
||||||
|
return const FilterChip(
|
||||||
|
avatar: Icon(MdiIcons.autoFix),
|
||||||
|
label: Skeletonizer(child: Text('0')),
|
||||||
|
showCheckmark: false,
|
||||||
|
onSelected: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ContainerChips extends HookConsumerWidget {
|
class ContainerChips extends HookConsumerWidget {
|
||||||
final bool displayMenu;
|
final bool displayMenu;
|
||||||
final bool showUnassignedChip;
|
final bool showUnassignedChip;
|
||||||
final bool showGroupSuggestions;
|
final bool showGroupSuggestions;
|
||||||
|
final bool enableDragAndDrop;
|
||||||
|
|
||||||
final ContainerData? selectedContainer;
|
final ContainerData? selectedContainer;
|
||||||
final bool Function(ContainerDataWithCount)? containerFilter;
|
final bool Function(ContainerDataWithCount)? containerFilter;
|
||||||
@@ -62,6 +156,7 @@ class ContainerChips extends HookConsumerWidget {
|
|||||||
this.displayMenu = true,
|
this.displayMenu = true,
|
||||||
this.showUnassignedChip = true,
|
this.showUnassignedChip = true,
|
||||||
this.showGroupSuggestions = false,
|
this.showGroupSuggestions = false,
|
||||||
|
this.enableDragAndDrop = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -95,121 +190,59 @@ class ContainerChips extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SelectableChips(
|
child:
|
||||||
enableDelete: false,
|
SelectableChips<
|
||||||
itemId: (container) => container.id,
|
ContainerDataWithCount,
|
||||||
itemBackgroundColor: (container) =>
|
ContainerData,
|
||||||
ContainerColors.forChip(container.color),
|
String
|
||||||
selectedBorderColor: Theme.of(context).colorScheme.primary,
|
>(
|
||||||
itemLabel: (container) =>
|
enableDelete: false,
|
||||||
ContainerTitle(container: container),
|
itemId: (container) => container.id,
|
||||||
itemBadgeCount: (container) =>
|
itemBackgroundColor: (container) =>
|
||||||
containerBadgeCount?.call(container) ??
|
ContainerColors.forChip(container.color),
|
||||||
container.tabCount,
|
selectedBorderColor: Theme.of(
|
||||||
itemWrap: (child, container) {
|
context,
|
||||||
return TabDragContainerTarget(
|
).colorScheme.primary,
|
||||||
container: container,
|
itemLabel: (container) =>
|
||||||
child: child,
|
ContainerTitle(container: container),
|
||||||
);
|
itemBadgeCount: (container) =>
|
||||||
},
|
containerBadgeCount?.call(container) ??
|
||||||
prefixListItems: [
|
container.tabCount,
|
||||||
if (showUnassignedChip)
|
itemWrap: enableDragAndDrop
|
||||||
TabDragContainerTarget(
|
? (child, container) {
|
||||||
container: null,
|
return TabDragContainerTarget(
|
||||||
child: Consumer(
|
container: container,
|
||||||
builder: (context, ref, child) {
|
child: child,
|
||||||
final int tabCount =
|
|
||||||
containerBadgeCount?.call(null) ??
|
|
||||||
ref.watch(
|
|
||||||
containerTabCountProvider(
|
|
||||||
// ignore: provider_parameters
|
|
||||||
ContainerFilterById(containerId: null),
|
|
||||||
).select((value) => value.value ?? 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
return FilterChip(
|
|
||||||
avatar: const Icon(MdiIcons.folderHidden),
|
|
||||||
labelPadding: (tabCount > 0)
|
|
||||||
? null
|
|
||||||
: const EdgeInsets.only(right: 2.0),
|
|
||||||
label: (tabCount > 0)
|
|
||||||
? Text(tabCount.toString())
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
selected: selectedContainer == null,
|
|
||||||
showCheckmark: false,
|
|
||||||
onSelected: (value) {
|
|
||||||
if (value) {
|
|
||||||
onSelected?.call(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (showGroupSuggestions)
|
|
||||||
Consumer(
|
|
||||||
builder: (context, ref, child) {
|
|
||||||
final tabSuggestionsEnabled = ref.watch(
|
|
||||||
tabSuggestionsControllerProvider,
|
|
||||||
);
|
|
||||||
final enableAiFeatures = ref.watch(
|
|
||||||
generalSettingsWithDefaultsProvider.select(
|
|
||||||
(settings) => settings.enableLocalAiFeatures,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!enableAiFeatures || !tabSuggestionsEnabled) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
final suggestions = ref.watch(
|
|
||||||
suggestClustersProvider,
|
|
||||||
);
|
|
||||||
|
|
||||||
return suggestions.when(
|
|
||||||
skipLoadingOnReload: true,
|
|
||||||
data: (data) {
|
|
||||||
if (data.isEmpty) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
return FilterChip(
|
|
||||||
avatar: const Icon(MdiIcons.autoFix),
|
|
||||||
label: Text(data!.length.toString()),
|
|
||||||
showCheckmark: false,
|
|
||||||
onSelected: (_) async {
|
|
||||||
await const ContainerDraftRoute().push(
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
error: (error, stackTrace) {
|
: null,
|
||||||
logger.e(
|
prefixListItems: [
|
||||||
'Error suggesting containers',
|
if (showUnassignedChip)
|
||||||
error: error,
|
enableDragAndDrop
|
||||||
stackTrace: stackTrace,
|
? TabDragContainerTarget(
|
||||||
);
|
container: null,
|
||||||
return const SizedBox.shrink();
|
child: _UnassignedContainerChip(
|
||||||
},
|
containerBadgeCount: () =>
|
||||||
loading: () {
|
containerBadgeCount?.call(null),
|
||||||
return const FilterChip(
|
selectedContainer: selectedContainer,
|
||||||
avatar: Icon(MdiIcons.autoFix),
|
onSelected: onSelected,
|
||||||
label: Skeletonizer(child: Text('0')),
|
),
|
||||||
showCheckmark: false,
|
)
|
||||||
onSelected: null,
|
: _UnassignedContainerChip(
|
||||||
);
|
containerBadgeCount: () =>
|
||||||
},
|
containerBadgeCount?.call(null),
|
||||||
);
|
selectedContainer: selectedContainer,
|
||||||
},
|
onSelected: onSelected,
|
||||||
),
|
),
|
||||||
],
|
if (showGroupSuggestions)
|
||||||
availableItems: availableContainers,
|
const _ContainerSuggestionsChip(),
|
||||||
selectedItem: selectedContainer,
|
],
|
||||||
onSelected: onSelected,
|
availableItems: availableContainers,
|
||||||
onDeleted: onDeleted,
|
selectedItem: selectedContainer,
|
||||||
onLongPress: onLongPress,
|
onSelected: onSelected,
|
||||||
),
|
onDeleted: onDeleted,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (displayMenu)
|
if (displayMenu)
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
Reference in New Issue
Block a user