ui improvements
This commit is contained in:
+3
-1
@@ -35,7 +35,9 @@ class ExtensionBadgeIcon extends StatelessWidget {
|
|||||||
label: hasBadge ? Text(state.badgeText!) : null,
|
label: hasBadge ? Text(state.badgeText!) : null,
|
||||||
textColor: state.badgeTextColor,
|
textColor: state.badgeTextColor,
|
||||||
backgroundColor: state.badgeBackgroundColor,
|
backgroundColor: state.badgeBackgroundColor,
|
||||||
child: RawImage(image: state.icon?.value, width: 24, height: 24),
|
child: RepaintBoundary(
|
||||||
|
child: RawImage(image: state.icon?.value, width: 24, height: 24),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+216
-227
@@ -380,236 +380,225 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
return Stack(
|
return Stack(
|
||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
NestedScrollView(
|
||||||
children: [
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
DraggableScrollableHeader(
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
controller: draggableScrollableController,
|
SliverToBoxAdapter(
|
||||||
child: _TabSheetHeader(onClose: onClose, treeViewEnabled: false),
|
child: DraggableScrollableHeader(
|
||||||
),
|
controller: draggableScrollableController,
|
||||||
Expanded(
|
child: _TabSheetHeader(
|
||||||
child: HookConsumer(
|
onClose: onClose,
|
||||||
builder: (context, ref, child) {
|
treeViewEnabled: false,
|
||||||
final gridViewKey = useMemoized(() => GlobalKey());
|
),
|
||||||
|
|
||||||
final containerId = ref.watch(selectedContainerProvider);
|
|
||||||
|
|
||||||
final filteredTabEntities = ref.watch(
|
|
||||||
seamlessFilteredTabEntitiesProvider(
|
|
||||||
searchPartition: TabSearchPartition.preview,
|
|
||||||
// ignore: document_ignores using fast equatable
|
|
||||||
// ignore: provider_parameters
|
|
||||||
containerFilter: ContainerFilterById(
|
|
||||||
containerId: containerId,
|
|
||||||
),
|
|
||||||
groupTrees: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final tabSuggestionsEnabled = ref.watch(
|
|
||||||
tabSuggestionsControllerProvider,
|
|
||||||
);
|
|
||||||
final enableAiFeatures = ref.watch(
|
|
||||||
generalSettingsRepositoryProvider.select(
|
|
||||||
(settings) => settings.enableLocalAiFeatures,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final suggestedTabEntities = ref.watch(
|
|
||||||
suggestedTabEntitiesProvider(
|
|
||||||
(enableAiFeatures && tabSuggestionsEnabled)
|
|
||||||
? containerId
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final itemCount =
|
|
||||||
filteredTabEntities.value.length +
|
|
||||||
suggestedTabEntities.value.length;
|
|
||||||
|
|
||||||
final activeTab = ref.watch(selectedTabProvider);
|
|
||||||
|
|
||||||
final crossAxisCount = useMemoized(() {
|
|
||||||
final calculatedCount = _calculateCrossAxisItemCount(
|
|
||||||
screenWidth: MediaQuery.of(context).size.width,
|
|
||||||
horizontalPadding: 4.0,
|
|
||||||
crossAxisSpacing: 8.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return math.max(math.min(calculatedCount, itemCount), 2);
|
|
||||||
}, [MediaQuery.of(context).size.width, itemCount]);
|
|
||||||
|
|
||||||
final itemHeight = useMemoized(
|
|
||||||
() => _calculateItemHeight(
|
|
||||||
screenWidth: MediaQuery.of(context).size.width,
|
|
||||||
childAspectRatio: 0.75,
|
|
||||||
horizontalPadding: 4.0,
|
|
||||||
mainAxisSpacing: 8.0,
|
|
||||||
crossAxisSpacing: 8.0,
|
|
||||||
crossAxisCount: crossAxisCount,
|
|
||||||
),
|
|
||||||
[MediaQuery.of(context).size.width, crossAxisCount],
|
|
||||||
);
|
|
||||||
|
|
||||||
final lastScroll = useRef<String?>(null);
|
|
||||||
final scrollControllerIsAttached = useListenableSelector(
|
|
||||||
sheetScrollController,
|
|
||||||
() => sheetScrollController.hasClients,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() {
|
|
||||||
if (scrollControllerIsAttached) {
|
|
||||||
if (lastScroll.value != activeTab) {
|
|
||||||
final index = filteredTabEntities.value.indexWhere(
|
|
||||||
(entity) => entity.tabId == activeTab,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index > -1) {
|
|
||||||
final offset = (index ~/ 2) * itemHeight;
|
|
||||||
|
|
||||||
if (offset != sheetScrollController.offset) {
|
|
||||||
lastScroll.value = activeTab;
|
|
||||||
|
|
||||||
unawaited(
|
|
||||||
sheetScrollController.animateTo(
|
|
||||||
offset,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
[
|
|
||||||
filteredTabEntities,
|
|
||||||
activeTab,
|
|
||||||
scrollControllerIsAttached,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final tabs = useMemoized(() {
|
|
||||||
return [
|
|
||||||
...filteredTabEntities.value.map(
|
|
||||||
(entity) => CustomDraggable(
|
|
||||||
key: Key(entity.tabId),
|
|
||||||
data: TabDragData(entity.tabId),
|
|
||||||
child: _TabDraggable(
|
|
||||||
entity: entity,
|
|
||||||
onClose: onClose,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...suggestedTabEntities.value.map(
|
|
||||||
(entity) => CustomDraggable(
|
|
||||||
key: Key('suggested_${entity.tabId}'),
|
|
||||||
child: _TabDraggable(
|
|
||||||
entity: entity,
|
|
||||||
onClose: onClose,
|
|
||||||
suggestedContainerId: containerId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}, [filteredTabEntities, suggestedTabEntities]);
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: FadingScroll(
|
|
||||||
fadingSize: 5,
|
|
||||||
controller: sheetScrollController,
|
|
||||||
builder: (context, controller) {
|
|
||||||
return ReorderableBuilder.builder(
|
|
||||||
//Rebuild when cross axis count changes
|
|
||||||
key: ValueKey(crossAxisCount),
|
|
||||||
scrollController: controller,
|
|
||||||
itemCount: itemCount,
|
|
||||||
onDragStarted: (index) {
|
|
||||||
ref.read(willAcceptDropProvider.notifier).clear();
|
|
||||||
},
|
|
||||||
onReorderPositions: (positions) async {
|
|
||||||
assert(
|
|
||||||
positions.length == 1,
|
|
||||||
'Not ready for multiple reorders',
|
|
||||||
);
|
|
||||||
|
|
||||||
final oldIndex = positions.first.oldIndex;
|
|
||||||
final newIndex = positions.first.newIndex;
|
|
||||||
|
|
||||||
final containerRepository = ref.read(
|
|
||||||
containerRepositoryProvider.notifier,
|
|
||||||
);
|
|
||||||
|
|
||||||
//Suggestions are at the end and not reorderable, so skip
|
|
||||||
if (oldIndex >= filteredTabEntities.value.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final tabId =
|
|
||||||
filteredTabEntities.value[oldIndex].tabId;
|
|
||||||
final containerId = await ref
|
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
|
||||||
.getContainerTabId(tabId);
|
|
||||||
|
|
||||||
final String key;
|
|
||||||
if (newIndex <= 0) {
|
|
||||||
key = await containerRepository
|
|
||||||
.getLeadingOrderKey(containerId);
|
|
||||||
} else if (newIndex >=
|
|
||||||
filteredTabEntities.value.length - 1) {
|
|
||||||
key = await containerRepository
|
|
||||||
.getTrailingOrderKey(containerId);
|
|
||||||
} else {
|
|
||||||
if (newIndex < oldIndex) {
|
|
||||||
key = await containerRepository
|
|
||||||
.getOrderKeyAfterTab(
|
|
||||||
filteredTabEntities
|
|
||||||
.value[newIndex - 1]
|
|
||||||
.tabId,
|
|
||||||
containerId,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
key = await containerRepository
|
|
||||||
.getOrderKeyBeforeTab(
|
|
||||||
filteredTabEntities
|
|
||||||
.value[newIndex + 1]
|
|
||||||
.tabId,
|
|
||||||
containerId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await ref
|
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
|
||||||
.assignOrderKey(tabId, key);
|
|
||||||
},
|
|
||||||
childBuilder: (itemBuilder) {
|
|
||||||
return GridView.builder(
|
|
||||||
key: gridViewKey,
|
|
||||||
controller: controller,
|
|
||||||
gridDelegate:
|
|
||||||
SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
//Sync values for itemHeight calculation _calculateItemHeight
|
|
||||||
childAspectRatio: 0.75,
|
|
||||||
mainAxisSpacing: 8.0,
|
|
||||||
crossAxisSpacing: 8.0,
|
|
||||||
crossAxisCount: crossAxisCount,
|
|
||||||
),
|
|
||||||
itemCount: itemCount,
|
|
||||||
itemBuilder: (context, index) =>
|
|
||||||
itemBuilder(tabs[index], index),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
body: HookConsumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final gridViewKey = useMemoized(() => GlobalKey());
|
||||||
|
|
||||||
|
final containerId = ref.watch(selectedContainerProvider);
|
||||||
|
|
||||||
|
final filteredTabEntities = ref.watch(
|
||||||
|
seamlessFilteredTabEntitiesProvider(
|
||||||
|
searchPartition: TabSearchPartition.preview,
|
||||||
|
// ignore: document_ignores using fast equatable
|
||||||
|
// ignore: provider_parameters
|
||||||
|
containerFilter: ContainerFilterById(
|
||||||
|
containerId: containerId,
|
||||||
|
),
|
||||||
|
groupTrees: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final tabSuggestionsEnabled = ref.watch(
|
||||||
|
tabSuggestionsControllerProvider,
|
||||||
|
);
|
||||||
|
final enableAiFeatures = ref.watch(
|
||||||
|
generalSettingsRepositoryProvider.select(
|
||||||
|
(settings) => settings.enableLocalAiFeatures,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final suggestedTabEntities = ref.watch(
|
||||||
|
suggestedTabEntitiesProvider(
|
||||||
|
(enableAiFeatures && tabSuggestionsEnabled)
|
||||||
|
? containerId
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final itemCount =
|
||||||
|
filteredTabEntities.value.length +
|
||||||
|
suggestedTabEntities.value.length;
|
||||||
|
|
||||||
|
final activeTab = ref.watch(selectedTabProvider);
|
||||||
|
|
||||||
|
final crossAxisCount = useMemoized(() {
|
||||||
|
final calculatedCount = _calculateCrossAxisItemCount(
|
||||||
|
screenWidth: MediaQuery.of(context).size.width,
|
||||||
|
horizontalPadding: 4.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return math.max(math.min(calculatedCount, itemCount), 2);
|
||||||
|
}, [MediaQuery.of(context).size.width, itemCount]);
|
||||||
|
|
||||||
|
final itemHeight = useMemoized(
|
||||||
|
() => _calculateItemHeight(
|
||||||
|
screenWidth: MediaQuery.of(context).size.width,
|
||||||
|
childAspectRatio: 0.75,
|
||||||
|
horizontalPadding: 4.0,
|
||||||
|
mainAxisSpacing: 8.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
),
|
||||||
|
[MediaQuery.of(context).size.width, crossAxisCount],
|
||||||
|
);
|
||||||
|
|
||||||
|
final lastScroll = useRef<String?>(null);
|
||||||
|
final scrollControllerIsAttached = useListenableSelector(
|
||||||
|
sheetScrollController,
|
||||||
|
() => sheetScrollController.hasClients,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (scrollControllerIsAttached) {
|
||||||
|
if (lastScroll.value != activeTab) {
|
||||||
|
final index = filteredTabEntities.value.indexWhere(
|
||||||
|
(entity) => entity.tabId == activeTab,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
final offset = (index ~/ 2) * itemHeight;
|
||||||
|
|
||||||
|
if (offset != sheetScrollController.offset) {
|
||||||
|
lastScroll.value = activeTab;
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
sheetScrollController.animateTo(
|
||||||
|
offset,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [filteredTabEntities, activeTab, scrollControllerIsAttached]);
|
||||||
|
|
||||||
|
final tabs = useMemoized(() {
|
||||||
|
return [
|
||||||
|
...filteredTabEntities.value.map(
|
||||||
|
(entity) => CustomDraggable(
|
||||||
|
key: Key(entity.tabId),
|
||||||
|
data: TabDragData(entity.tabId),
|
||||||
|
child: _TabDraggable(entity: entity, onClose: onClose),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...suggestedTabEntities.value.map(
|
||||||
|
(entity) => CustomDraggable(
|
||||||
|
key: Key('suggested_${entity.tabId}'),
|
||||||
|
child: _TabDraggable(
|
||||||
|
entity: entity,
|
||||||
|
onClose: onClose,
|
||||||
|
suggestedContainerId: containerId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}, [filteredTabEntities, suggestedTabEntities]);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
|
child: FadingScroll(
|
||||||
|
fadingSize: 5,
|
||||||
|
controller: sheetScrollController,
|
||||||
|
builder: (context, controller) {
|
||||||
|
return ReorderableBuilder.builder(
|
||||||
|
//Rebuild when cross axis count changes
|
||||||
|
key: ValueKey(crossAxisCount),
|
||||||
|
scrollController: controller,
|
||||||
|
itemCount: itemCount,
|
||||||
|
onDragStarted: (index) {
|
||||||
|
ref.read(willAcceptDropProvider.notifier).clear();
|
||||||
|
},
|
||||||
|
onReorderPositions: (positions) async {
|
||||||
|
assert(
|
||||||
|
positions.length == 1,
|
||||||
|
'Not ready for multiple reorders',
|
||||||
|
);
|
||||||
|
|
||||||
|
final oldIndex = positions.first.oldIndex;
|
||||||
|
final newIndex = positions.first.newIndex;
|
||||||
|
|
||||||
|
final containerRepository = ref.read(
|
||||||
|
containerRepositoryProvider.notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
//Suggestions are at the end and not reorderable, so skip
|
||||||
|
if (oldIndex >= filteredTabEntities.value.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tabId = filteredTabEntities.value[oldIndex].tabId;
|
||||||
|
final containerId = await ref
|
||||||
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
|
.getContainerTabId(tabId);
|
||||||
|
|
||||||
|
final String key;
|
||||||
|
if (newIndex <= 0) {
|
||||||
|
key = await containerRepository.getLeadingOrderKey(
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
} else if (newIndex >=
|
||||||
|
filteredTabEntities.value.length - 1) {
|
||||||
|
key = await containerRepository.getTrailingOrderKey(
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (newIndex < oldIndex) {
|
||||||
|
key = await containerRepository.getOrderKeyAfterTab(
|
||||||
|
filteredTabEntities.value[newIndex - 1].tabId,
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
key = await containerRepository
|
||||||
|
.getOrderKeyBeforeTab(
|
||||||
|
filteredTabEntities.value[newIndex + 1].tabId,
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
|
.assignOrderKey(tabId, key);
|
||||||
|
},
|
||||||
|
childBuilder: (itemBuilder) {
|
||||||
|
return GridView.builder(
|
||||||
|
key: gridViewKey,
|
||||||
|
controller: controller,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
//Sync values for itemHeight calculation _calculateItemHeight
|
||||||
|
childAspectRatio: 0.75,
|
||||||
|
mainAxisSpacing: 8.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
),
|
||||||
|
itemCount: itemCount,
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
itemBuilder(tabs[index], index),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
|
|||||||
Reference in New Issue
Block a user