improve tab preview rebuilding behavior
This commit is contained in:
@@ -183,6 +183,7 @@ class TabTreeDialog extends HookConsumerWidget {
|
|||||||
return SizedBox.fromSize(
|
return SizedBox.fromSize(
|
||||||
size: childSize,
|
size: childSize,
|
||||||
child: SingleTabPreview(
|
child: SingleTabPreview(
|
||||||
|
key: ValueKey(id),
|
||||||
tabId: id,
|
tabId: id,
|
||||||
activeTabId: selectedTabId,
|
activeTabId: selectedTabId,
|
||||||
onClose: () {
|
onClose: () {
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ class AppBarTitle extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
TabIcon(state: tabState),
|
TabIcon(tabState: tabState),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -77,11 +77,13 @@ class _TabDraggable extends HookConsumerWidget {
|
|||||||
|
|
||||||
final tab = (suggestedContainerId != null)
|
final tab = (suggestedContainerId != null)
|
||||||
? SuggestedSingleTabPreview(
|
? SuggestedSingleTabPreview(
|
||||||
|
key: ValueKey(entity.tabId),
|
||||||
tabId: entity.tabId,
|
tabId: entity.tabId,
|
||||||
activeTabId: activeTab,
|
activeTabId: activeTab,
|
||||||
containerId: suggestedContainerId!,
|
containerId: suggestedContainerId!,
|
||||||
)
|
)
|
||||||
: SingleTabPreview(
|
: SingleTabPreview(
|
||||||
|
key: ValueKey(entity.tabId),
|
||||||
tabId: entity.tabId,
|
tabId: entity.tabId,
|
||||||
activeTabId: activeTab,
|
activeTabId: activeTab,
|
||||||
onClose: onClose,
|
onClose: onClose,
|
||||||
|
|||||||
@@ -27,25 +27,25 @@ import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
|||||||
import 'package:weblibre/presentation/hooks/cached_future.dart';
|
import 'package:weblibre/presentation/hooks/cached_future.dart';
|
||||||
|
|
||||||
class TabIcon extends HookConsumerWidget {
|
class TabIcon extends HookConsumerWidget {
|
||||||
final TabState state;
|
final TabState tabState;
|
||||||
|
|
||||||
final double iconSize;
|
final double iconSize;
|
||||||
|
|
||||||
const TabIcon({super.key, required this.state, this.iconSize = 16});
|
const TabIcon({super.key, required this.tabState, this.iconSize = 16});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final icon = useCachedFuture(() async {
|
final icon = useCachedFuture(() async {
|
||||||
if (state.icon != null) {
|
if (tabState.icon != null) {
|
||||||
return state.icon!.value;
|
return tabState.icon!.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
final icon = await ref
|
final icon = await ref
|
||||||
.read(genericWebsiteServiceProvider.notifier)
|
.read(genericWebsiteServiceProvider.notifier)
|
||||||
.getCachedIcon(state.url);
|
.getCachedIcon(tabState.url);
|
||||||
|
|
||||||
return icon?.image.value;
|
return icon?.image.value;
|
||||||
}, [state.icon, state.url]);
|
}, [tabState.icon, tabState.url]);
|
||||||
|
|
||||||
return Skeletonizer(
|
return Skeletonizer(
|
||||||
enabled: icon.connectionState != ConnectionState.done,
|
enabled: icon.connectionState != ConnectionState.done,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
|
||||||
@@ -104,8 +103,8 @@ class TabMiniPreview extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabPreview extends HookWidget {
|
class TabPreview extends HookConsumerWidget {
|
||||||
final TabState tab;
|
final String tabId;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
@@ -117,7 +116,7 @@ class TabPreview extends HookWidget {
|
|||||||
final Widget? trailingChild;
|
final Widget? trailingChild;
|
||||||
|
|
||||||
const TabPreview({
|
const TabPreview({
|
||||||
required this.tab,
|
required this.tabId,
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onDoubleTap,
|
this.onDoubleTap,
|
||||||
@@ -127,13 +126,19 @@ class TabPreview extends HookWidget {
|
|||||||
this.trailingChild,
|
this.trailingChild,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final tabState = ref.watch(tabStateProvider(tabId));
|
||||||
|
if (tabState == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
final extendedDeleteMenuController = useMenuController();
|
final extendedDeleteMenuController = useMenuController();
|
||||||
|
|
||||||
return TabContainer(
|
return TabContainer(
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
isPrivate: tab.isPrivate,
|
isPrivate: tabState.isPrivate,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(14.0)),
|
borderRadius: const BorderRadius.all(Radius.circular(14.0)),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
@@ -148,9 +153,9 @@ class TabPreview extends HookWidget {
|
|||||||
padding: const EdgeInsets.only(left: 6.0, top: 2.0),
|
padding: const EdgeInsets.only(left: 6.0, top: 2.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
tab.title,
|
tabState.title,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
style: tab.isPrivate
|
style: tabState.isPrivate
|
||||||
? const TextStyle(color: Colors.white)
|
? const TextStyle(color: Colors.white)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -164,13 +169,13 @@ class TabPreview extends HookWidget {
|
|||||||
},
|
},
|
||||||
menuChildren: [
|
menuChildren: [
|
||||||
?onDeleteAll.mapNotNull(
|
?onDeleteAll.mapNotNull(
|
||||||
(p0) => MenuItemButton(
|
(value) => MenuItemButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
p0(tab.url.host);
|
value(tabState.url.host);
|
||||||
},
|
},
|
||||||
|
|
||||||
leadingIcon: const Icon(MdiIcons.closeBoxMultiple),
|
leadingIcon: const Icon(MdiIcons.closeBoxMultiple),
|
||||||
child: Text('Close all from ${tab.url.host}'),
|
child: Text('Close all from ${tabState.url.host}'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -196,17 +201,17 @@ class TabPreview extends HookWidget {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 6.0),
|
const SizedBox(width: 6.0),
|
||||||
TabIcon(state: tab),
|
TabIcon(tabState: tabState),
|
||||||
const SizedBox(width: 6.0),
|
const SizedBox(width: 6.0),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
tab.url.authority,
|
tabState.url.authority,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: tab.isPrivate ? Colors.white : null,
|
color: tabState.isPrivate ? Colors.white : null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (tab.isPrivate) ...[
|
if (tabState.isPrivate) ...[
|
||||||
const SizedBox(width: 6.0),
|
const SizedBox(width: 6.0),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
@@ -229,7 +234,7 @@ class TabPreview extends HookWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
if (tab.thumbnail != null)
|
if (tabState.thumbnail != null)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
@@ -240,7 +245,7 @@ class TabPreview extends HookWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: RepaintBoundary(
|
child: RepaintBoundary(
|
||||||
child: RawImage(
|
child: RawImage(
|
||||||
image: tab.thumbnail!.value,
|
image: tabState.thumbnail!.value,
|
||||||
fit: BoxFit.fitWidth,
|
fit: BoxFit.fitWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -262,19 +267,22 @@ class SingleTabPreview extends HookConsumerWidget {
|
|||||||
|
|
||||||
final void Function() onClose;
|
final void Function() onClose;
|
||||||
|
|
||||||
SingleTabPreview({
|
const SingleTabPreview({
|
||||||
required this.tabId,
|
required this.tabId,
|
||||||
required this.activeTabId,
|
required this.activeTabId,
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
required this.sourceSearchQuery,
|
required this.sourceSearchQuery,
|
||||||
this.deleteThreshold = 100,
|
this.deleteThreshold = 100,
|
||||||
}) : super(key: ValueKey(tabId));
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tab = ref.watch(tabStateProvider(tabId));
|
final hasTabState = ref.watch(
|
||||||
|
tabStateProvider(tabId).select((value) => value != null),
|
||||||
|
);
|
||||||
|
|
||||||
if (tab == null) {
|
if (!hasTabState) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +304,7 @@ class SingleTabPreview extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
onHorizontalDragEnd: (details) async {
|
onHorizontalDragEnd: (details) async {
|
||||||
if (draggedDistance.value >= deleteThreshold) {
|
if (draggedDistance.value >= deleteThreshold) {
|
||||||
await ref.read(tabRepositoryProvider.notifier).closeTab(tab.id);
|
await ref.read(tabRepositoryProvider.notifier).closeTab(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
draggedDistance.value = 0.0;
|
draggedDistance.value = 0.0;
|
||||||
@@ -304,13 +312,13 @@ class SingleTabPreview extends HookConsumerWidget {
|
|||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: 1.0 - draggedDistance.value / deleteThreshold,
|
opacity: 1.0 - draggedDistance.value / deleteThreshold,
|
||||||
child: TabPreview(
|
child: TabPreview(
|
||||||
tab: tab,
|
tabId: tabId,
|
||||||
isActive: tabId == activeTabId,
|
isActive: tabId == activeTabId,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (tabId != activeTabId) {
|
if (tabId != activeTabId) {
|
||||||
//Close first to avoid rebuilds
|
//Close first to avoid rebuilds
|
||||||
onClose();
|
onClose();
|
||||||
await ref.read(tabRepositoryProvider.notifier).selectTab(tab.id);
|
await ref.read(tabRepositoryProvider.notifier).selectTab(tabId);
|
||||||
if (sourceSearchQuery.isNotEmpty &&
|
if (sourceSearchQuery.isNotEmpty &&
|
||||||
ref.read(findInPageControllerProvider(tabId)) ==
|
ref.read(findInPageControllerProvider(tabId)) ==
|
||||||
FindInPageState.hidden()) {
|
FindInPageState.hidden()) {
|
||||||
@@ -325,7 +333,7 @@ class SingleTabPreview extends HookConsumerWidget {
|
|||||||
onDeleteAll: (host) async {
|
onDeleteAll: (host) async {
|
||||||
final containerId = await ref
|
final containerId = await ref
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
.getContainerTabId(tab.id);
|
.getContainerTabId(tabId);
|
||||||
|
|
||||||
await ref
|
await ref
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
@@ -341,7 +349,7 @@ class SingleTabPreview extends HookConsumerWidget {
|
|||||||
// );
|
// );
|
||||||
// },
|
// },
|
||||||
onDelete: () async {
|
onDelete: () async {
|
||||||
await ref.read(tabRepositoryProvider.notifier).closeTab(tab.id);
|
await ref.read(tabRepositoryProvider.notifier).closeTab(tabId);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -355,24 +363,27 @@ class SuggestedSingleTabPreview extends HookConsumerWidget {
|
|||||||
|
|
||||||
final String? activeTabId;
|
final String? activeTabId;
|
||||||
|
|
||||||
SuggestedSingleTabPreview({
|
const SuggestedSingleTabPreview({
|
||||||
required this.tabId,
|
required this.tabId,
|
||||||
required this.containerId,
|
required this.containerId,
|
||||||
required this.activeTabId,
|
required this.activeTabId,
|
||||||
}) : super(key: ValueKey(tabId));
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tab = ref.watch(tabStateProvider(tabId));
|
final hasTabState = ref.watch(
|
||||||
|
tabStateProvider(tabId).select((value) => value != null),
|
||||||
|
);
|
||||||
|
|
||||||
if (tab == null) {
|
if (!hasTabState) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
child: TabPreview(
|
child: TabPreview(
|
||||||
tab: tab,
|
tabId: tabId,
|
||||||
isActive: tabId == activeTabId,
|
isActive: tabId == activeTabId,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final containerData = await ref
|
final containerData = await ref
|
||||||
@@ -424,9 +435,11 @@ class TabTreePreview extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tab = ref.watch(tabStateProvider(entity.tabId));
|
final isTabPrivate = ref.watch(
|
||||||
|
tabStateProvider(entity.tabId).select((value) => value?.isPrivate),
|
||||||
|
);
|
||||||
|
|
||||||
if (tab == null) {
|
if (isTabPrivate != null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,7 +453,7 @@ class TabTreePreview extends HookConsumerWidget {
|
|||||||
stackCount,
|
stackCount,
|
||||||
TabContainer(
|
TabContainer(
|
||||||
isActive: entity.tabId == activeTabId,
|
isActive: entity.tabId == activeTabId,
|
||||||
isPrivate: tab.isPrivate,
|
isPrivate: isTabPrivate == true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_addPadding(
|
_addPadding(
|
||||||
@@ -454,7 +467,7 @@ class TabTreePreview extends HookConsumerWidget {
|
|||||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
textColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
textColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
child: TabPreview(
|
child: TabPreview(
|
||||||
tab: tab,
|
tabId: entity.tabId,
|
||||||
isActive: entity.tabId == activeTabId,
|
isActive: entity.tabId == activeTabId,
|
||||||
onLongPress: () async {
|
onLongPress: () async {
|
||||||
if (entity.tabId != activeTabId) {
|
if (entity.tabId != activeTabId) {
|
||||||
@@ -462,7 +475,7 @@ class TabTreePreview extends HookConsumerWidget {
|
|||||||
onClose();
|
onClose();
|
||||||
await ref
|
await ref
|
||||||
.read(tabRepositoryProvider.notifier)
|
.read(tabRepositoryProvider.notifier)
|
||||||
.selectTab(tab.id);
|
.selectTab(entity.tabId);
|
||||||
} else {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
@@ -475,7 +488,7 @@ class TabTreePreview extends HookConsumerWidget {
|
|||||||
onClose();
|
onClose();
|
||||||
await ref
|
await ref
|
||||||
.read(tabRepositoryProvider.notifier)
|
.read(tabRepositoryProvider.notifier)
|
||||||
.selectTab(tab.id);
|
.selectTab(entity.tabId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// onDeleteAll: (host) async {
|
// onDeleteAll: (host) async {
|
||||||
|
|||||||
Reference in New Issue
Block a user