From 3a6fd58b5d717060c37f2a8defe39aae7ba062a6 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Sun, 27 Jul 2025 10:38:04 +0200 Subject: [PATCH] refactored container tab with unassign --- .../browser/presentation/screens/browser.dart | 1 + .../presentation/widgets/container_chips.dart | 130 +++++------------- .../widgets/tab_drag_container_target.dart | 78 +++++++++++ 3 files changed, 111 insertions(+), 98 deletions(-) create mode 100644 app/lib/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.dart diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart index 81bcf2a9..1f3ee4ea 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -121,6 +121,7 @@ class BrowserScreen extends HookConsumerWidget { ref.read(willAcceptDropProvider.notifier).clear(); }, onAcceptWithDetails: (details) async { + ref.read(willAcceptDropProvider.notifier).clear(); await ref .read(tabRepositoryProvider.notifier) .closeTab(details.data.tabId); diff --git a/app/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart b/app/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart index ed63639b..1f5030a0 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart @@ -23,14 +23,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nullability/nullability.dart'; -import 'package:weblibre/core/providers/global_drop.dart'; import 'package:weblibre/core/routing/routes.dart'; -import 'package:weblibre/data/models/drag_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; -import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_title.dart'; +import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.dart'; import 'package:weblibre/presentation/widgets/selectable_chips.dart'; class ContainerChips extends HookConsumerWidget { @@ -63,8 +61,6 @@ class ContainerChips extends HookConsumerWidget { matchSortedContainersWithCountProvider(searchText), ); - final dragTargetTabId = useValueNotifier(null); - return containersAsync.when( data: (containers) { final availableContainers = @@ -100,103 +96,41 @@ class ContainerChips extends HookConsumerWidget { ContainerTitle(container: container), itemBadgeCount: (container) => container.tabCount, itemWrap: (child, container) { - return HookBuilder( - builder: (context) { - final overlayController = - useOverlayPortalController(); - - return DragTarget( - onMove: (details) { - ref - .read(willAcceptDropProvider.notifier) - .setData( - ContainerDropData(details.data.tabId), - ); - - dragTargetTabId.value = details.data.tabId; - overlayController.show(); - }, - onLeave: (data) { - ref.read(willAcceptDropProvider.notifier).clear(); - - dragTargetTabId.value = null; - overlayController.hide(); - }, - onAcceptWithDetails: (details) async { - await ref - .read(tabDataRepositoryProvider.notifier) - .assignContainer( - details.data.tabId, - container, - ); - - dragTargetTabId.value = null; - overlayController.hide(); - }, - builder: (context, candidateData, rejectedData) { - final renderBox = - context.findRenderObject() as RenderBox?; - final position = - renderBox?.localToGlobal(Offset.zero) ?? - Offset.zero; - - return OverlayPortal( - controller: overlayController, - overlayChildBuilder: (context) => Positioned( - top: position.dy, - left: position.dx, - child: IgnorePointer( - child: Transform.scale( - scale: 1.1, - child: child, - ), - ), - ), - child: HookBuilder( - builder: (context) { - final dragTabId = useValueListenable( - dragTargetTabId, - ); - - return Opacity( - opacity: (dragTabId == null) ? 1.0 : 0.0, - child: child, - ); - }, - ), - ); - }, - ); - }, + return TabDragContainerTarget( + container: container, + child: child, ); }, prefixListItems: [ - Consumer( - builder: (context, ref, child) { - final tabCount = ref.watch( - containerTabCountProvider( - // ignore: provider_parameters - ContainerFilterById(containerId: null), - ).select((value) => value.valueOrNull ?? 0), - ); + TabDragContainerTarget( + container: null, + child: Consumer( + builder: (context, ref, child) { + final tabCount = ref.watch( + containerTabCountProvider( + // ignore: provider_parameters + ContainerFilterById(containerId: null), + ).select((value) => value.valueOrNull ?? 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); - } - }, - ); - }, + 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); + } + }, + ); + }, + ), ), ], availableItems: availableContainers, diff --git a/app/lib/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.dart b/app/lib/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.dart new file mode 100644 index 00000000..5bc37f59 --- /dev/null +++ b/app/lib/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/core/providers/global_drop.dart'; +import 'package:weblibre/data/models/drag_data.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; +import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; + +class TabDragContainerTarget extends HookConsumerWidget { + final ContainerData? container; + final Widget child; + + const TabDragContainerTarget({ + super.key, + required this.container, + required this.child, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final overlayController = useOverlayPortalController(); + + return DragTarget( + onMove: (details) { + ref + .read(willAcceptDropProvider.notifier) + .setData(ContainerDropData(details.data.tabId)); + + overlayController.show(); + }, + onLeave: (data) { + ref.read(willAcceptDropProvider.notifier).clear(); + + overlayController.hide(); + }, + onAcceptWithDetails: (details) async { + ref.read(willAcceptDropProvider.notifier).clear(); + overlayController.hide(); + + if (container != null) { + await ref + .read(tabDataRepositoryProvider.notifier) + .assignContainer(details.data.tabId, container!); + } else { + await ref + .read(tabDataRepositoryProvider.notifier) + .unassignContainer(details.data.tabId); + } + }, + builder: (context, candidateData, rejectedData) { + final renderBox = context.findRenderObject() as RenderBox?; + final position = renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + + return OverlayPortal( + controller: overlayController, + overlayChildBuilder: (context) => Positioned( + top: position.dy, + left: position.dx, + child: IgnorePointer( + child: Transform.scale(scale: 1.1, child: child), + ), + ), + child: Consumer( + child: child, + builder: (context, ref, child) { + final dragTabId = ref.watch(willAcceptDropProvider); + + return Opacity( + opacity: (dragTabId == null) ? 1.0 : 0.0, + child: child, + ); + }, + ), + ); + }, + ); + } +}