feat: enable drag and drop in row document (#6513)

* feat: enable drag and drop in row document

* refactor: cleanup drop handling to one widget

* fix: clean up code
This commit is contained in:
Mathias Mogensen 2024-10-09 15:23:42 +02:00 committed by GitHub
parent 8cf683eb50
commit 8d6c67206c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 203 additions and 207 deletions

View file

@ -1,22 +1,19 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart';
import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart';
import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_file.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
import 'package:appflowy/shared/patterns/file_type_patterns.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
@ -24,20 +21,10 @@ import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:cross_file/cross_file.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:universal_platform/universal_platform.dart';
const _excludeFromDropTarget = [
ImageBlockKeys.type,
CustomImageBlockKeys.type,
MultiImageBlockKeys.type,
FileBlockKeys.type,
];
class DocumentPage extends StatefulWidget {
const DocumentPage({
super.key,
@ -96,55 +83,39 @@ class _DocumentPageState extends State<DocumentPage>
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
// Due to how DropTarget works, there is no way to differentiate if an overlay is
// blocking the target visibly, so when we have an overlay with a drop target,
// we should disable the drop target for the Editor, until it is closed.
//
// See FileBlockComponent for sample use.
//
// Relates to:
// - https://github.com/MixinNetwork/flutter-plugins/issues/2
// - https://github.com/MixinNetwork/flutter-plugins/issues/331
//
create: (_) => EditorDropManagerState(),
child: MultiBlocProvider(
providers: [
BlocProvider.value(value: getIt<ActionNavigationBloc>()),
BlocProvider.value(value: documentBloc),
],
child: BlocBuilder<DocumentBloc, DocumentState>(
buildWhen: shouldRebuildDocument,
builder: (context, state) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator.adaptive());
}
return MultiBlocProvider(
providers: [
BlocProvider.value(value: getIt<ActionNavigationBloc>()),
BlocProvider.value(value: documentBloc),
],
child: BlocBuilder<DocumentBloc, DocumentState>(
buildWhen: shouldRebuildDocument,
builder: (context, state) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator.adaptive());
}
final editorState = state.editorState;
this.editorState = editorState;
final error = state.error;
if (error != null || editorState == null) {
Log.error(error);
return Center(child: AppFlowyErrorPage(error: error));
}
final editorState = state.editorState;
this.editorState = editorState;
final error = state.error;
if (error != null || editorState == null) {
Log.error(error);
return Center(child: AppFlowyErrorPage(error: error));
}
if (state.forceClose) {
widget.onDeleted();
return const SizedBox.shrink();
}
if (state.forceClose) {
widget.onDeleted();
return const SizedBox.shrink();
}
editorState.transactionStream.listen(onEditorTransaction);
editorState.transactionStream.listen(onEditorTransaction);
return BlocListener<ActionNavigationBloc, ActionNavigationState>(
listenWhen: (_, curr) => curr.action != null,
listener: onNotificationAction,
child: Consumer<EditorDropManagerState>(
builder: (context, dropState, _) =>
buildEditorPage(context, state, dropState),
),
);
},
),
return BlocListener<ActionNavigationBloc, ActionNavigationState>(
listenWhen: (_, curr) => curr.action != null,
listener: onNotificationAction,
child: buildEditorPage(context, state),
);
},
),
);
}
@ -152,7 +123,6 @@ class _DocumentPageState extends State<DocumentPage>
Widget buildEditorPage(
BuildContext context,
DocumentState state,
EditorDropManagerState dropState,
) {
final width = context.read<DocumentAppearanceCubit>().state.width;
@ -173,83 +143,10 @@ class _DocumentPageState extends State<DocumentPage>
),
);
} else {
child = DropTarget(
enable: dropState.isDropEnabled,
onDragExited: (_) =>
state.editorState!.selectionService.removeDropTarget(),
onDragUpdated: (details) {
final data = state.editorState!.selectionService
.getDropTargetRenderData(details.globalPosition);
if (data != null &&
data.dropPath != null &&
// We implement custom Drop logic for image blocks, this is
// how we can exclude them from the Drop Target
!_excludeFromDropTarget.contains(data.cursorNode?.type)) {
// Render the drop target
state.editorState!.selectionService
.renderDropTargetForOffset(details.globalPosition);
} else {
state.editorState!.selectionService.removeDropTarget();
}
},
onDragDone: (details) async {
final editorState = state.editorState;
if (editorState == null) {
return;
}
editorState.selectionService.removeDropTarget();
final data = editorState.selectionService
.getDropTargetRenderData(details.globalPosition);
if (data != null) {
final cursorNode = data.cursorNode;
final dropPath = data.dropPath;
if (cursorNode != null && dropPath != null) {
if (_excludeFromDropTarget.contains(cursorNode.type)) {
return;
}
final node = editorState.getNodeAtPath(dropPath);
if (node == null) {
return;
}
final isLocalMode = context.read<DocumentBloc>().isLocalMode;
final List<XFile> imageFiles = [];
final List<XFile> otherFiles = [];
for (final file in details.files) {
final fileName = file.name.toLowerCase();
if (file.mimeType?.startsWith('image/') ??
false || imgExtensionRegex.hasMatch(fileName)) {
imageFiles.add(file);
} else {
otherFiles.add(file);
}
}
await editorState.dropImages(
node,
imageFiles,
widget.view.id,
isLocalMode,
);
await editorState.dropFiles(
node,
otherFiles,
widget.view.id,
isLocalMode,
);
}
}
},
child = EditorDropHandler(
viewId: widget.view.id,
editorState: state.editorState!,
isLocalMode: context.read<DocumentBloc>().isLocalMode,
child: AppFlowyEditorPage(
editorState: state.editorState!,
// if the view's name is empty, focus on the title