integrate reparenting into drag n drop flow
This commit is contained in:
+109
-24
@@ -20,6 +20,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
import 'package:weblibre/data/models/drag_data.dart';
|
import 'package:weblibre/data/models/drag_data.dart';
|
||||||
@@ -28,14 +29,15 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/co
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/utils/ui_helper.dart';
|
import 'package:weblibre/utils/ui_helper.dart';
|
||||||
|
|
||||||
/// A widget that wraps a tab and enables creating a new container
|
enum _TabDropAction { createContainer, assignParent }
|
||||||
/// when another tab is dropped onto it.
|
|
||||||
|
/// A widget that wraps a tab and offers tab relationship actions when another
|
||||||
|
/// tab is dropped onto it.
|
||||||
///
|
///
|
||||||
/// When a tab is dragged and dropped onto this widget:
|
/// When a tab is dragged and dropped onto this widget:
|
||||||
/// 1. Both tab titles are collected
|
/// 1. The user chooses whether to create a container or assign a parent.
|
||||||
/// 2. An AI-suggested container name is generated
|
/// 2. Container creation redirects to the container creation screen.
|
||||||
/// 3. User is redirected to container creation screen to confirm/modify settings
|
/// 3. Parent assignment makes the target tab the parent of the dragged tab.
|
||||||
/// 4. Both tabs are assigned to the new container if user confirms
|
|
||||||
class TabDropTarget extends HookConsumerWidget {
|
class TabDropTarget extends HookConsumerWidget {
|
||||||
/// The tab entity that serves as the drop target
|
/// The tab entity that serves as the drop target
|
||||||
final String targetTabId;
|
final String targetTabId;
|
||||||
@@ -67,27 +69,29 @@ class TabDropTarget extends HookConsumerWidget {
|
|||||||
onAcceptWithDetails: (details) async {
|
onAcceptWithDetails: (details) async {
|
||||||
final draggedTabId = details.data.tabId;
|
final draggedTabId = details.data.tabId;
|
||||||
|
|
||||||
final containerRepo = ref.read(containerRepositoryProvider.notifier);
|
final action = await showModalBottomSheet<_TabDropAction>(
|
||||||
final newContainer = await containerRepo.createNewContainer();
|
context: context,
|
||||||
|
showDragHandle: true,
|
||||||
|
builder: (context) => const _TabDropActionSheet(),
|
||||||
|
);
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (action == null || !context.mounted) return;
|
||||||
|
|
||||||
final result = await ContainerCreateRoute(
|
switch (action) {
|
||||||
containerData: jsonEncode(newContainer.toJson()),
|
case _TabDropAction.createContainer:
|
||||||
tabIds: jsonEncode([draggedTabId, targetTabId]),
|
await _createContainerForTabs(
|
||||||
).push<ContainerData?>(context);
|
context: context,
|
||||||
|
ref: ref,
|
||||||
if (result != null) {
|
draggedTabId: draggedTabId,
|
||||||
final tabRepo = ref.read(tabDataRepositoryProvider.notifier);
|
targetTabId: targetTabId,
|
||||||
await tabRepo.assignContainer(draggedTabId, result);
|
);
|
||||||
await tabRepo.assignContainer(targetTabId, result);
|
case _TabDropAction.assignParent:
|
||||||
|
await _assignParentTab(
|
||||||
if (context.mounted) {
|
context: context,
|
||||||
showInfoMessage(
|
ref: ref,
|
||||||
context,
|
draggedTabId: draggedTabId,
|
||||||
'Created container "${result.name ?? 'New Container'}"',
|
targetTabId: targetTabId,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, candidateData, rejectedData) {
|
builder: (context, candidateData, rejectedData) {
|
||||||
@@ -106,4 +110,85 @@ class TabDropTarget extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _createContainerForTabs({
|
||||||
|
required BuildContext context,
|
||||||
|
required WidgetRef ref,
|
||||||
|
required String draggedTabId,
|
||||||
|
required String targetTabId,
|
||||||
|
}) async {
|
||||||
|
final containerRepo = ref.read(containerRepositoryProvider.notifier);
|
||||||
|
final tabRepo = ref.read(tabDataRepositoryProvider.notifier);
|
||||||
|
final newContainer = await containerRepo.createNewContainer();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
final result = await ContainerCreateRoute(
|
||||||
|
containerData: jsonEncode(newContainer.toJson()),
|
||||||
|
tabIds: jsonEncode([draggedTabId, targetTabId]),
|
||||||
|
).push<ContainerData?>(context);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
await tabRepo.assignContainer(draggedTabId, result);
|
||||||
|
await tabRepo.assignContainer(targetTabId, result);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showInfoMessage(
|
||||||
|
context,
|
||||||
|
'Created container "${result.name ?? 'New Container'}"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _assignParentTab({
|
||||||
|
required BuildContext context,
|
||||||
|
required WidgetRef ref,
|
||||||
|
required String draggedTabId,
|
||||||
|
required String targetTabId,
|
||||||
|
}) async {
|
||||||
|
final didAssign = await ref
|
||||||
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
|
.setTabParent(tabId: draggedTabId, newParentId: targetTabId);
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
if (didAssign) {
|
||||||
|
showInfoMessage(context, 'Assigned parent tab');
|
||||||
|
} else {
|
||||||
|
showErrorMessage(context, 'Could not assign parent tab');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TabDropActionSheet extends StatelessWidget {
|
||||||
|
const _TabDropActionSheet();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const ListTile(
|
||||||
|
title: Text('Drop tab onto tab'),
|
||||||
|
subtitle: Text('Choose how these tabs should be related.'),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(MdiIcons.folderPlus),
|
||||||
|
title: const Text('Create container'),
|
||||||
|
subtitle: const Text('Create a new container with both tabs.'),
|
||||||
|
onTap: () =>
|
||||||
|
Navigator.of(context).pop(_TabDropAction.createContainer),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(MdiIcons.fileTree),
|
||||||
|
title: const Text('Assign new parent'),
|
||||||
|
subtitle: const Text('Make the dropped-on tab the parent.'),
|
||||||
|
onTap: () => Navigator.of(context).pop(_TabDropAction.assignParent),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user