ui refactorings
This commit is contained in:
+17
-8
@@ -950,7 +950,11 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Consumer(
|
: Consumer(
|
||||||
builder: (context, ref, _) {
|
// _TabBar is passed via `child` so it is built once and
|
||||||
|
// reused across toolbar show/hide toggles; only the
|
||||||
|
// `visible` flag fed to _AnimatedToolbar depends on the
|
||||||
|
// watched provider.
|
||||||
|
builder: (context, ref, child) {
|
||||||
final toolbarState = ref.watch(
|
final toolbarState = ref.watch(
|
||||||
toolbarVisibilityControllerProvider(selectedTabId),
|
toolbarVisibilityControllerProvider(selectedTabId),
|
||||||
);
|
);
|
||||||
@@ -961,21 +965,21 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
return _AnimatedToolbar(
|
return _AnimatedToolbar(
|
||||||
position: TabBarPosition.bottom,
|
position: TabBarPosition.bottom,
|
||||||
visible: visible,
|
visible: visible,
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: _TabBar(
|
child: _TabBar(
|
||||||
tabBarPosition: TabBarPosition.bottom,
|
tabBarPosition: TabBarPosition.bottom,
|
||||||
showMainToolbar:
|
showMainToolbar:
|
||||||
tabBarPosition == TabBarPosition.bottom,
|
tabBarPosition == TabBarPosition.bottom,
|
||||||
showContextualToolbar: showContextualToolbar,
|
showContextualToolbar: showContextualToolbar,
|
||||||
showQuickTabSwitcherBar:
|
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
|
||||||
displayQuickTabSwitcherBar,
|
|
||||||
isSmallWebMode: false,
|
isSmallWebMode: false,
|
||||||
pointerMoveEvents:
|
pointerMoveEvents:
|
||||||
tabBarPosition == TabBarPosition.bottom
|
tabBarPosition == TabBarPosition.bottom
|
||||||
? pointerMoveEventsController.stream
|
? pointerMoveEventsController.stream
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -986,7 +990,11 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
right: 0,
|
right: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
child: Consumer(
|
child: Consumer(
|
||||||
builder: (context, ref, _) {
|
// _TabBar is passed via `child` so it is built once and
|
||||||
|
// reused across toolbar show/hide toggles; only the
|
||||||
|
// `visible` flag fed to _AnimatedToolbar depends on the
|
||||||
|
// watched provider.
|
||||||
|
builder: (context, ref, child) {
|
||||||
final toolbarState = ref.watch(
|
final toolbarState = ref.watch(
|
||||||
toolbarVisibilityControllerProvider(selectedTabId),
|
toolbarVisibilityControllerProvider(selectedTabId),
|
||||||
);
|
);
|
||||||
@@ -997,6 +1005,9 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
return _AnimatedToolbar(
|
return _AnimatedToolbar(
|
||||||
position: TabBarPosition.top,
|
position: TabBarPosition.top,
|
||||||
visible: visible,
|
visible: visible,
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: _TabBar(
|
child: _TabBar(
|
||||||
tabBarPosition: TabBarPosition.top,
|
tabBarPosition: TabBarPosition.top,
|
||||||
showMainToolbar: true,
|
showMainToolbar: true,
|
||||||
@@ -1008,8 +1019,6 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
? null
|
? null
|
||||||
: pointerMoveEventsController.stream,
|
: pointerMoveEventsController.stream,
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
+69
-38
@@ -501,6 +501,57 @@ class QuickTabSwitcherItem with FastEquatable {
|
|||||||
this.depth = 0,
|
this.depth = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Builds a switcher entry for an open tab. [sandboxSourceUri] is the
|
||||||
|
/// canonical source URL when the tab is a sandbox capture (otherwise null),
|
||||||
|
/// so the bar shows the real site instead of the loopback capture URL.
|
||||||
|
factory QuickTabSwitcherItem.tab(
|
||||||
|
TabStateWithContainer state, {
|
||||||
|
required String? selectedTabId,
|
||||||
|
required Set<String> pinnedTabIds,
|
||||||
|
required Map<String, int> tabDepthById,
|
||||||
|
required Uri? sandboxSourceUri,
|
||||||
|
}) {
|
||||||
|
final (tab, container) = state;
|
||||||
|
|
||||||
|
return QuickTabSwitcherItem(
|
||||||
|
color: container?.color,
|
||||||
|
useCustomColor: container?.metadata.useCustomColor ?? false,
|
||||||
|
id: tab.id,
|
||||||
|
isActive: tab.id == selectedTabId,
|
||||||
|
title: sandboxSourceUri != null && tab.title.isEmpty
|
||||||
|
? sandboxSourceUri.authority
|
||||||
|
: tab.titleOrAuthority,
|
||||||
|
tabMode: tab.tabMode,
|
||||||
|
isHistory: false,
|
||||||
|
isPinned: pinnedTabIds.contains(tab.id),
|
||||||
|
isSandbox: sandboxSourceUri != null,
|
||||||
|
depth: tabDepthById[tab.id] ?? 0,
|
||||||
|
url: sandboxSourceUri ?? tab.url,
|
||||||
|
avatar: TabIcon(tabState: tab, iconSize: 20),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a switcher entry for a history suggestion (shown only when there
|
||||||
|
/// are no open tabs in the active mode).
|
||||||
|
factory QuickTabSwitcherItem.history({
|
||||||
|
required String url,
|
||||||
|
required String? title,
|
||||||
|
}) {
|
||||||
|
final parsedUrl = Uri.parse(url);
|
||||||
|
|
||||||
|
return QuickTabSwitcherItem(
|
||||||
|
color: null,
|
||||||
|
id: url,
|
||||||
|
isActive: false,
|
||||||
|
title: title ?? parsedUrl.authority,
|
||||||
|
tabMode: TabMode.regular,
|
||||||
|
isHistory: true,
|
||||||
|
isPinned: false,
|
||||||
|
url: parsedUrl,
|
||||||
|
avatar: UrlIcon([parsedUrl], iconSize: 20),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get hashParameters => [
|
List<Object?> get hashParameters => [
|
||||||
color,
|
color,
|
||||||
@@ -587,45 +638,25 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
final reorderEnabled =
|
final reorderEnabled =
|
||||||
effectiveMode == QuickTabSwitcherMode.containerTabs && canManualReorder;
|
effectiveMode == QuickTabSwitcherMode.containerTabs && canManualReorder;
|
||||||
final tabItems = tabStates.value.map<QuickTabSwitcherItem>((state) {
|
final tabItems = tabStates.value
|
||||||
final sandboxSourceUri = parseSandboxSource(
|
.map(
|
||||||
sandboxCaptureMap[state.$1.id],
|
(state) => QuickTabSwitcherItem.tab(
|
||||||
);
|
|
||||||
final displayUrl = sandboxSourceUri ?? state.$1.url;
|
|
||||||
final displayTitle = sandboxSourceUri != null && state.$1.title.isEmpty
|
|
||||||
? sandboxSourceUri.authority
|
|
||||||
: state.$1.titleOrAuthority;
|
|
||||||
return QuickTabSwitcherItem(
|
|
||||||
color: state.$2?.color,
|
|
||||||
useCustomColor: state.$2?.metadata.useCustomColor ?? false,
|
|
||||||
id: state.$1.id,
|
|
||||||
isActive: state.$1.id == selectedTabId,
|
|
||||||
title: displayTitle,
|
|
||||||
tabMode: state.$1.tabMode,
|
|
||||||
isHistory: false,
|
|
||||||
isPinned: pinnedTabIds.contains(state.$1.id),
|
|
||||||
isSandbox: sandboxSourceUri != null,
|
|
||||||
depth: tabDepthById[state.$1.id] ?? 0,
|
|
||||||
url: displayUrl,
|
|
||||||
avatar: TabIcon(tabState: state.$1, iconSize: 20),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
final historyItems = (historySuggestions ?? []).map<QuickTabSwitcherItem>((
|
|
||||||
state,
|
state,
|
||||||
) {
|
selectedTabId: selectedTabId,
|
||||||
final url = Uri.parse(state.url);
|
pinnedTabIds: pinnedTabIds,
|
||||||
return QuickTabSwitcherItem(
|
tabDepthById: tabDepthById,
|
||||||
color: null,
|
sandboxSourceUri: parseSandboxSource(
|
||||||
id: state.url,
|
sandboxCaptureMap[state.$1.id],
|
||||||
isActive: false,
|
),
|
||||||
title: state.title ?? url.authority,
|
),
|
||||||
tabMode: TabMode.regular,
|
)
|
||||||
isHistory: true,
|
.toList();
|
||||||
isPinned: false,
|
final historyItems = (historySuggestions ?? [])
|
||||||
url: url,
|
.map(
|
||||||
avatar: UrlIcon([url], iconSize: 20),
|
(visit) =>
|
||||||
);
|
QuickTabSwitcherItem.history(url: visit.url, title: visit.title),
|
||||||
}).toList();
|
)
|
||||||
|
.toList();
|
||||||
final availableItems = [...tabItems, ...historyItems];
|
final availableItems = [...tabItems, ...historyItems];
|
||||||
|
|
||||||
final activeItem = availableItems.isEmpty
|
final activeItem = availableItems.isEmpty
|
||||||
|
|||||||
+52
-44
@@ -192,12 +192,56 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
TabViewHeader(onClose: onClose, tabsViewMode: TabsViewMode.tree),
|
TabViewHeader(onClose: onClose, tabsViewMode: TabsViewMode.tree),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: HookConsumer(
|
child: _TabTreesGrid(
|
||||||
builder: (context, ref, child) {
|
scrollController: scrollController,
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
onClose: onClose,
|
||||||
final disableAnimations = MediaQuery.disableAnimationsOf(
|
),
|
||||||
context,
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (showNewTabFab)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: TabViewHeader.headerSize + 4,
|
||||||
|
right: 4,
|
||||||
|
),
|
||||||
|
child: FloatingActionButton.small(
|
||||||
|
onPressed: () async {
|
||||||
|
final settings = ref.read(generalSettingsWithDefaultsProvider);
|
||||||
|
|
||||||
|
await SearchRoute(
|
||||||
|
tabType:
|
||||||
|
ref.read(selectedTabTypeProvider) ??
|
||||||
|
settings.effectiveDefaultCreateTabType,
|
||||||
|
).push(context);
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scrollable grid of tab-tree previews.
|
||||||
|
///
|
||||||
|
/// Extracted into its own [HookConsumerWidget] (rather than an inline
|
||||||
|
/// `HookConsumer`) so the heavy `seamlessFilteredTabEntitiesProvider` watch
|
||||||
|
/// and the layout/scroll-sync hook state live in a stable, dedicated element
|
||||||
|
/// instead of an anonymous builder closure. This keeps the surrounding
|
||||||
|
/// [TabViewHeader] and FAB out of this subtree's rebuild scope.
|
||||||
|
class _TabTreesGrid extends HookConsumerWidget {
|
||||||
|
final ScrollController scrollController;
|
||||||
|
final VoidCallback onClose;
|
||||||
|
|
||||||
|
const _TabTreesGrid({required this.scrollController, required this.onClose});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
final disableAnimations = MediaQuery.disableAnimationsOf(context);
|
||||||
|
|
||||||
final containerId = ref.watch(selectedContainerProvider);
|
final containerId = ref.watch(selectedContainerProvider);
|
||||||
|
|
||||||
@@ -206,9 +250,7 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
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: ContainerFilterById(
|
containerFilter: ContainerFilterById(containerId: containerId),
|
||||||
containerId: containerId,
|
|
||||||
),
|
|
||||||
groupTrees: true,
|
groupTrees: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -223,10 +265,7 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return math.max(
|
return math.max(
|
||||||
math.min(
|
math.min(calculatedCount, filteredTabEntities.value.length),
|
||||||
calculatedCount,
|
|
||||||
filteredTabEntities.value.length,
|
|
||||||
),
|
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
}, [screenWidth, filteredTabEntities.value.length]);
|
}, [screenWidth, filteredTabEntities.value.length]);
|
||||||
@@ -261,10 +300,7 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
scrollController.position.viewportDimension;
|
scrollController.position.viewportDimension;
|
||||||
|
|
||||||
final targetOffset =
|
final targetOffset =
|
||||||
(tabStart -
|
(tabStart - viewportDimension / 2 + itemSize.height / 2).clamp(
|
||||||
viewportDimension / 2 +
|
|
||||||
itemSize.height / 2)
|
|
||||||
.clamp(
|
|
||||||
0.0,
|
0.0,
|
||||||
scrollController.position.maxScrollExtent,
|
scrollController.position.maxScrollExtent,
|
||||||
);
|
);
|
||||||
@@ -323,33 +359,5 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (showNewTabFab)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: TabViewHeader.headerSize + 4,
|
|
||||||
right: 4,
|
|
||||||
),
|
|
||||||
child: FloatingActionButton.small(
|
|
||||||
onPressed: () async {
|
|
||||||
final settings = ref.read(generalSettingsWithDefaultsProvider);
|
|
||||||
|
|
||||||
await SearchRoute(
|
|
||||||
tabType:
|
|
||||||
ref.read(selectedTabTypeProvider) ??
|
|
||||||
settings.effectiveDefaultCreateTabType,
|
|
||||||
).push(context);
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-6
@@ -1142,12 +1142,7 @@ class TabViewHeader extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
if (showContainerUi)
|
if (showContainerUi) _TabFilters(tabsViewMode: tabsViewMode),
|
||||||
Consumer(
|
|
||||||
builder: (context, ref, child) {
|
|
||||||
return _TabFilters(tabsViewMode: tabsViewMode);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
+57
-37
@@ -52,14 +52,6 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
|
|||||||
? selectedContainerTabs.value[selectedContainer.value!]
|
? selectedContainerTabs.value[selectedContainer.value!]
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(title: const Text('Draft Containers')),
|
|
||||||
body: SafeArea(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
||||||
child: suggestionsAsync.when(
|
|
||||||
skipLoadingOnReload: true,
|
|
||||||
data: (suggestions) {
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
final crossAxisCount = useMemoized(() {
|
final crossAxisCount = useMemoized(() {
|
||||||
@@ -78,6 +70,14 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}, [screenWidth, selectedContainer.value?.tabIds.length]);
|
}, [screenWidth, selectedContainer.value?.tabIds.length]);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Draft Containers')),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: suggestionsAsync.when(
|
||||||
|
skipLoadingOnReload: true,
|
||||||
|
data: (suggestions) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -88,35 +88,10 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
|
|||||||
itemId: (container) => container,
|
itemId: (container) => container,
|
||||||
itemAvatar: (container) =>
|
itemAvatar: (container) =>
|
||||||
const Icon(MdiIcons.creation, size: 20),
|
const Icon(MdiIcons.creation, size: 20),
|
||||||
itemLabel: (container) => HookConsumer(
|
itemLabel: (container) => _SuggestedContainerLabel(
|
||||||
builder: (context, ref, child) {
|
key: ValueKey(container),
|
||||||
final items = useListenableSelector(
|
container: container,
|
||||||
selectedContainerTabs,
|
selectedContainerTabs: selectedContainerTabs,
|
||||||
() => selectedContainerTabs.value[container],
|
|
||||||
);
|
|
||||||
|
|
||||||
final topic = items.isNotEmpty
|
|
||||||
? ref.watch(
|
|
||||||
tabsTopicProvider(EquatableValue(items!)),
|
|
||||||
)
|
|
||||||
: AsyncValue.data(container.topic);
|
|
||||||
|
|
||||||
return topic.when(
|
|
||||||
skipLoadingOnReload: true,
|
|
||||||
data: (topic) => Text(topic ?? 'Untitled'),
|
|
||||||
error: (error, stackTrace) {
|
|
||||||
logger.e(
|
|
||||||
'Failed predicting selected tabs topic',
|
|
||||||
error: error,
|
|
||||||
stackTrace: stackTrace,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Text(container.topic ?? 'Untitled');
|
|
||||||
},
|
|
||||||
loading: () =>
|
|
||||||
const Skeletonizer(child: Text('Untitled')),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
itemBadgeCount: (container) => container.tabIds.length,
|
itemBadgeCount: (container) => container.tabIds.length,
|
||||||
availableItems: suggestions!,
|
availableItems: suggestions!,
|
||||||
@@ -237,3 +212,48 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Label for a single suggested-container chip.
|
||||||
|
///
|
||||||
|
/// Extracted into its own [HookConsumerWidget] (rather than an inline
|
||||||
|
/// `HookConsumer` in `itemLabel`) so its hook state is bound to a stable
|
||||||
|
/// element identity per container, and the heavy `tabsTopicProvider` watch
|
||||||
|
/// is scoped to just this chip.
|
||||||
|
class _SuggestedContainerLabel extends HookConsumerWidget {
|
||||||
|
final SuggestedContainer container;
|
||||||
|
final ValueNotifier<Map<SuggestedContainer, Set<String>>>
|
||||||
|
selectedContainerTabs;
|
||||||
|
|
||||||
|
const _SuggestedContainerLabel({
|
||||||
|
required this.container,
|
||||||
|
required this.selectedContainerTabs,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final items = useListenableSelector(
|
||||||
|
selectedContainerTabs,
|
||||||
|
() => selectedContainerTabs.value[container],
|
||||||
|
);
|
||||||
|
|
||||||
|
final topic = items.isNotEmpty
|
||||||
|
? ref.watch(tabsTopicProvider(EquatableValue(items!)))
|
||||||
|
: AsyncValue.data(container.topic);
|
||||||
|
|
||||||
|
return topic.when(
|
||||||
|
skipLoadingOnReload: true,
|
||||||
|
data: (topic) => Text(topic ?? 'Untitled'),
|
||||||
|
error: (error, stackTrace) {
|
||||||
|
logger.e(
|
||||||
|
'Failed predicting selected tabs topic',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Text(container.topic ?? 'Untitled');
|
||||||
|
},
|
||||||
|
loading: () => const Skeletonizer(child: Text('Untitled')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -145,6 +145,11 @@ class SelectableChips<T extends S, S, K> extends StatelessWidget {
|
|||||||
final GlobalKey? activeItemKey;
|
final GlobalKey? activeItemKey;
|
||||||
final double? cacheExtent;
|
final double? cacheExtent;
|
||||||
|
|
||||||
|
/// Key applied to the underlying scroll view. Supply a [PageStorageKey] to
|
||||||
|
/// preserve the scroll offset across rebuilds/remounts (e.g. when a host
|
||||||
|
/// widget is torn down and recreated by a bottom sheet open/close).
|
||||||
|
final Key? scrollKey;
|
||||||
|
|
||||||
final K Function(S item) itemId;
|
final K Function(S item) itemId;
|
||||||
final Widget Function(T item) itemLabel;
|
final Widget Function(T item) itemLabel;
|
||||||
final Widget? Function(T item)? itemAvatar;
|
final Widget? Function(T item)? itemAvatar;
|
||||||
@@ -187,6 +192,7 @@ class SelectableChips<T extends S, S, K> extends StatelessWidget {
|
|||||||
this.scrollController,
|
this.scrollController,
|
||||||
this.activeItemKey,
|
this.activeItemKey,
|
||||||
this.cacheExtent = 0,
|
this.cacheExtent = 0,
|
||||||
|
this.scrollKey,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,6 +302,7 @@ class SelectableChips<T extends S, S, K> extends StatelessWidget {
|
|||||||
builder: (context, controller) {
|
builder: (context, controller) {
|
||||||
if (onReorder == null) {
|
if (onReorder == null) {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
|
key: scrollKey,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
scrollCacheExtent: cacheExtent.mapNotNull(
|
scrollCacheExtent: cacheExtent.mapNotNull(
|
||||||
(extent) => ScrollCacheExtent.pixels(extent),
|
(extent) => ScrollCacheExtent.pixels(extent),
|
||||||
|
|||||||
Reference in New Issue
Block a user