migrate to new graph view

This commit is contained in:
Fabian Freund
2025-10-03 13:33:08 +02:00
parent 2e45d88272
commit 5cc442aed9
@@ -25,62 +25,14 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:graphview/GraphView.dart'; import 'package:graphview/GraphView.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart'; import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_preview.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/utils/ui_helper.dart'; import 'package:weblibre/utils/ui_helper.dart';
class BuchheimWalkerAlgorithmWithFocus extends BuchheimWalkerAlgorithm {
bool _hasScrolled = false;
final Size childSize;
final TransformationController transforamtionController;
final dynamic initialNodeId;
BuchheimWalkerAlgorithmWithFocus(
super.configuration,
super.renderer, {
required this.childSize,
required this.transforamtionController,
this.initialNodeId,
});
@override
void setFocusedNode(Node node) {
final scale = transforamtionController.value.getMaxScaleOnAxis();
final newPosition =
(node.position - Offset(childSize.width / 2, childSize.height / 2)) *
scale;
transforamtionController.value = transforamtionController.value.clone()
..setTranslation(Vector3(-newPosition.dx, -newPosition.dy, 0));
}
@override
Size run(Graph? graph, double shiftX, double shiftY) {
final size = super.run(graph, shiftX, shiftY);
if (initialNodeId != null && !_hasScrolled) {
final node = graph?.nodes.firstWhereOrNull(
(element) => element.key?.value == initialNodeId,
);
if (node != null && node.position != Offset.zero) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setFocusedNode(node);
});
_hasScrolled = true;
}
}
return size;
}
}
class TabTreeDialog extends HookConsumerWidget { class TabTreeDialog extends HookConsumerWidget {
final String tabId; final String tabId;
final Size childSize; final Size childSize;
@@ -93,8 +45,6 @@ class TabTreeDialog extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final transforamtionController = useTransformationController();
final selectedTabId = ref.watch(selectedTabProvider); final selectedTabId = ref.watch(selectedTabProvider);
final tabs = ref.watch(tabDescendantsProvider(tabId)); final tabs = ref.watch(tabDescendantsProvider(tabId));
@@ -115,6 +65,8 @@ class TabTreeDialog extends HookConsumerWidget {
return graph; return graph;
}, [EquatableValue(tabs.value), selectedTabId]); }, [EquatableValue(tabs.value), selectedTabId]);
final graphViewController = useMemoized(() => GraphViewController());
final graphAlgo = useMemoized(() { final graphAlgo = useMemoized(() {
final config = BuchheimWalkerConfiguration() final config = BuchheimWalkerConfiguration()
..siblingSeparation = 100 ..siblingSeparation = 100
@@ -122,13 +74,7 @@ class TabTreeDialog extends HookConsumerWidget {
..subtreeSeparation = 150 ..subtreeSeparation = 150
..orientation = BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM; ..orientation = BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM;
return BuchheimWalkerAlgorithmWithFocus( return BuchheimWalkerAlgorithm(config, TreeEdgeRenderer(config));
config,
TreeEdgeRenderer(config),
childSize: childSize,
transforamtionController: transforamtionController,
initialNodeId: selectedTabId,
);
}, [selectedTabId]); }, [selectedTabId]);
return Dialog.fullscreen( return Dialog.fullscreen(
@@ -146,57 +92,54 @@ class TabTreeDialog extends HookConsumerWidget {
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(MdiIcons.target), child: const Icon(MdiIcons.target),
onPressed: () { onPressed: () {
final node = graph.nodes.firstWhereOrNull( if (selectedTabId != null) {
(element) => element.key?.value == selectedTabId, final node = graph.nodes.firstWhereOrNull(
); (element) => element.key?.value == selectedTabId,
if (node != null) {
graphAlgo.setFocusedNode(node);
} else {
showErrorMessage(
context,
'The current tab is not part of this tree',
); );
if (node != null) {
graphViewController.animateToNode(ValueKey(selectedTabId));
} else {
showErrorMessage(
context,
'The current tab is not part of this tree',
);
}
} }
}, },
), ),
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
body: InteractiveViewer( body: Skeletonizer(
transformationController: transforamtionController, enabled: !tabs.hasValue || tabs.value?.isEmpty == true,
constrained: false, child: Skeleton.replace(
boundaryMargin: const EdgeInsets.all(250), replacement: const Bone.square(),
minScale: 0.1, child: GraphView.builder(
maxScale: 5.0, graph: graph,
child: Skeletonizer( algorithm: graphAlgo,
enabled: !tabs.hasValue || tabs.value?.isEmpty == true, controller: graphViewController,
child: Skeleton.replace( initialNode: selectedTabId.mapNotNull((tabId) => ValueKey(tabId)),
replacement: const Bone.square(), paint: Paint()
child: GraphView( ..color = Theme.of(context).colorScheme.outline
graph: graph, ..strokeWidth = 1
algorithm: graphAlgo, ..style = PaintingStyle.stroke,
paint: Paint() builder: (Node node) {
..color = Theme.of(context).colorScheme.outline final id = node.key!.value as String;
..strokeWidth = 1 return SizedBox.fromSize(
..style = PaintingStyle.stroke, size: childSize,
builder: (Node node) { child: SingleTabPreview(
final id = node.key!.value as String; key: ValueKey(id),
return SizedBox.fromSize( tabId: id,
size: childSize, activeTabId: selectedTabId,
child: SingleTabPreview( onClose: () {
key: ValueKey(id), context.pop();
tabId: id, ref
activeTabId: selectedTabId, .read(bottomSheetControllerProvider.notifier)
onClose: () { .requestDismiss();
context.pop(); },
ref sourceSearchQuery: null,
.read(bottomSheetControllerProvider.notifier) ),
.requestDismiss(); );
}, },
sourceSearchQuery: null,
),
);
},
),
), ),
), ),
), ),