mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-12-24 13:44:19 +00:00
feat: support dragging the block to reorder (#6285)
* feat: support dragging the block to reorder * feat: render feedback widget * feat: add drag to move translation * fix: the feedback widget doesn't update after node changed * feat: render table placeholder * feat: implement auto scroll when dragging to edge * chore: add back the drop images/files feature * chore: code refactor * feat: exclude image and file block * feat: exclude image gallery and database block
This commit is contained in:
parent
129db6925c
commit
d67d77f442
@ -171,7 +171,7 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
.getDropTargetRenderData(details.globalPosition);
|
||||
|
||||
if (data != null &&
|
||||
data.dropTarget != null &&
|
||||
data.dropPath != null &&
|
||||
|
||||
// We implement custom Drop logic for image blocks, this is
|
||||
// how we can exclude them from the Drop Target
|
||||
@ -184,14 +184,28 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
}
|
||||
},
|
||||
onDragDone: (details) async {
|
||||
state.editorState!.selectionService.removeDropTarget();
|
||||
final editorState = state.editorState;
|
||||
if (editorState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final data = state.editorState!.selectionService
|
||||
editorState.selectionService.removeDropTarget();
|
||||
|
||||
final data = editorState.selectionService
|
||||
.getDropTargetRenderData(details.globalPosition);
|
||||
|
||||
if (data != null) {
|
||||
if (data.cursorNode != null) {
|
||||
if (_excludeFromDropTarget.contains(data.cursorNode?.type)) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -209,14 +223,15 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
}
|
||||
}
|
||||
|
||||
await editorState!.dropImages(
|
||||
data.dropTarget!,
|
||||
await editorState.dropImages(
|
||||
node,
|
||||
imageFiles,
|
||||
widget.view.id,
|
||||
isLocalMode,
|
||||
);
|
||||
await editorState!.dropFiles(
|
||||
data.dropTarget!,
|
||||
|
||||
await editorState.dropFiles(
|
||||
node,
|
||||
otherFiles,
|
||||
widget.view.id,
|
||||
isLocalMode,
|
||||
|
||||
@ -314,6 +314,7 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
blockComponentContext: context,
|
||||
blockComponentState: state,
|
||||
editorState: editorState,
|
||||
blockComponentBuilder: builders,
|
||||
actions: actions,
|
||||
showSlashMenu: slashMenuItems != null
|
||||
? () => customSlashCommand(
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BlockActionList extends StatelessWidget {
|
||||
const BlockActionList({
|
||||
@ -12,6 +13,7 @@ class BlockActionList extends StatelessWidget {
|
||||
required this.editorState,
|
||||
required this.actions,
|
||||
required this.showSlashMenu,
|
||||
required this.blockComponentBuilder,
|
||||
});
|
||||
|
||||
final BlockComponentContext blockComponentContext;
|
||||
@ -19,6 +21,7 @@ class BlockActionList extends StatelessWidget {
|
||||
final List<OptionAction> actions;
|
||||
final VoidCallback showSlashMenu;
|
||||
final EditorState editorState;
|
||||
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -31,14 +34,15 @@ class BlockActionList extends StatelessWidget {
|
||||
editorState: editorState,
|
||||
showSlashMenu: showSlashMenu,
|
||||
),
|
||||
const SizedBox(width: 4.0),
|
||||
const HSpace(4.0),
|
||||
BlockOptionButton(
|
||||
blockComponentContext: blockComponentContext,
|
||||
blockComponentState: blockComponentState,
|
||||
actions: actions,
|
||||
editorState: editorState,
|
||||
blockComponentBuilder: blockComponentBuilder,
|
||||
),
|
||||
const SizedBox(width: 4.0),
|
||||
const HSpace(4.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,48 +1,59 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class BlockOptionButton extends StatelessWidget {
|
||||
import 'drag_to_reorder/draggable_option_button.dart';
|
||||
|
||||
class BlockOptionButton extends StatefulWidget {
|
||||
const BlockOptionButton({
|
||||
super.key,
|
||||
required this.blockComponentContext,
|
||||
required this.blockComponentState,
|
||||
required this.actions,
|
||||
required this.editorState,
|
||||
required this.blockComponentBuilder,
|
||||
});
|
||||
|
||||
final BlockComponentContext blockComponentContext;
|
||||
final BlockComponentActionState blockComponentState;
|
||||
final List<OptionAction> actions;
|
||||
final EditorState editorState;
|
||||
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final popoverActions = actions.map((e) {
|
||||
State<BlockOptionButton> createState() => _BlockOptionButtonState();
|
||||
}
|
||||
|
||||
class _BlockOptionButtonState extends State<BlockOptionButton> {
|
||||
late final List<PopoverAction> popoverActions;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
popoverActions = widget.actions.map((e) {
|
||||
switch (e) {
|
||||
case OptionAction.divider:
|
||||
return DividerOptionAction();
|
||||
case OptionAction.color:
|
||||
return ColorOptionAction(editorState: editorState);
|
||||
return ColorOptionAction(editorState: widget.editorState);
|
||||
case OptionAction.align:
|
||||
return AlignOptionAction(editorState: editorState);
|
||||
return AlignOptionAction(editorState: widget.editorState);
|
||||
case OptionAction.depth:
|
||||
return DepthOptionAction(editorState: editorState);
|
||||
return DepthOptionAction(editorState: widget.editorState);
|
||||
default:
|
||||
return OptionActionWrapper(e);
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopoverActionList<PopoverAction>(
|
||||
popoverMutex: PopoverMutex(),
|
||||
direction:
|
||||
@ -53,13 +64,13 @@ class BlockOptionButton extends StatelessWidget {
|
||||
actions: popoverActions,
|
||||
onPopupBuilder: () {
|
||||
keepEditorFocusNotifier.increase();
|
||||
blockComponentState.alwaysShowActions = true;
|
||||
widget.blockComponentState.alwaysShowActions = true;
|
||||
},
|
||||
onClosed: () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
editorState.selectionType = null;
|
||||
editorState.selection = null;
|
||||
blockComponentState.alwaysShowActions = false;
|
||||
widget.editorState.selectionType = null;
|
||||
widget.editorState.selection = null;
|
||||
widget.blockComponentState.alwaysShowActions = false;
|
||||
keepEditorFocusNotifier.decrease();
|
||||
});
|
||||
},
|
||||
@ -69,62 +80,18 @@ class BlockOptionButton extends StatelessWidget {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
buildChild: (controller) => _buildOptionButton(context, controller),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOptionButton(
|
||||
BuildContext context,
|
||||
PopoverController controller,
|
||||
) {
|
||||
return BlockActionButton(
|
||||
svg: FlowySvgs.drag_element_s,
|
||||
richMessage: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
// todo: customize the color to highlight the text.
|
||||
text: LocaleKeys.document_plugins_optionAction_click.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.document_plugins_optionAction_toOpenMenu.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
],
|
||||
buildChild: (controller) => DraggableOptionButton(
|
||||
controller: controller,
|
||||
editorState: widget.editorState,
|
||||
blockComponentContext: widget.blockComponentContext,
|
||||
blockComponentBuilder: widget.blockComponentBuilder,
|
||||
),
|
||||
onTap: () {
|
||||
controller.show();
|
||||
|
||||
// update selection
|
||||
_updateBlockSelection();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _updateBlockSelection() {
|
||||
final startNode = blockComponentContext.node;
|
||||
var endNode = startNode;
|
||||
while (endNode.children.isNotEmpty) {
|
||||
endNode = endNode.children.last;
|
||||
}
|
||||
|
||||
final start = Position(path: startNode.path);
|
||||
final end = endNode.selectable?.end() ??
|
||||
Position(
|
||||
path: endNode.path,
|
||||
offset: endNode.delta?.length ?? 0,
|
||||
);
|
||||
|
||||
editorState.selectionType = SelectionType.block;
|
||||
editorState.selection = Selection(
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
}
|
||||
|
||||
void _onSelectAction(BuildContext context, OptionAction action) {
|
||||
final node = blockComponentContext.node;
|
||||
final transaction = editorState.transaction;
|
||||
final node = widget.blockComponentContext.node;
|
||||
final transaction = widget.editorState.transaction;
|
||||
switch (action) {
|
||||
case OptionAction.delete:
|
||||
transaction.deleteNode(node);
|
||||
@ -146,7 +113,7 @@ class BlockOptionButton extends StatelessWidget {
|
||||
case OptionAction.depth:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
editorState.apply(transaction);
|
||||
widget.editorState.apply(transaction);
|
||||
}
|
||||
|
||||
void _duplicateBlock(
|
||||
@ -156,8 +123,7 @@ class BlockOptionButton extends StatelessWidget {
|
||||
) {
|
||||
// 1. verify the node integrity
|
||||
final type = node.type;
|
||||
final builder =
|
||||
context.read<EditorState>().renderer.blockComponentBuilder(type);
|
||||
final builder = widget.editorState.renderer.blockComponentBuilder(type);
|
||||
|
||||
if (builder == null) {
|
||||
Log.error('Block type $type is not supported');
|
||||
@ -184,8 +150,7 @@ class BlockOptionButton extends StatelessWidget {
|
||||
Node copiedNode = node.copyWith();
|
||||
|
||||
final type = node.type;
|
||||
final builder =
|
||||
context.read<EditorState>().renderer.blockComponentBuilder(type);
|
||||
final builder = widget.editorState.renderer.blockComponentBuilder(type);
|
||||
|
||||
if (builder == null) {
|
||||
Log.error('Block type $type is not supported');
|
||||
|
||||
@ -0,0 +1,292 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.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_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DraggableOptionButton extends StatefulWidget {
|
||||
const DraggableOptionButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.editorState,
|
||||
required this.blockComponentContext,
|
||||
required this.blockComponentBuilder,
|
||||
});
|
||||
|
||||
final PopoverController controller;
|
||||
final EditorState editorState;
|
||||
final BlockComponentContext blockComponentContext;
|
||||
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
||||
@override
|
||||
State<DraggableOptionButton> createState() => _DraggableOptionButtonState();
|
||||
}
|
||||
|
||||
class _DraggableOptionButtonState extends State<DraggableOptionButton> {
|
||||
late Node node;
|
||||
late BlockComponentContext blockComponentContext;
|
||||
|
||||
Offset? globalPosition;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// copy the node to avoid the node in document being updated
|
||||
node = widget.blockComponentContext.node.copyWith();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
node.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Draggable<Node>(
|
||||
data: node,
|
||||
feedback: _OptionButtonFeedback(
|
||||
controller: widget.controller,
|
||||
editorState: widget.editorState,
|
||||
blockComponentContext: widget.blockComponentContext,
|
||||
blockComponentBuilder: widget.blockComponentBuilder,
|
||||
),
|
||||
onDragStarted: () {
|
||||
widget.editorState.selectionService.removeDropTarget();
|
||||
},
|
||||
onDragUpdate: (details) {
|
||||
widget.editorState.selectionService
|
||||
.renderDropTargetForOffset(details.globalPosition);
|
||||
|
||||
globalPosition = details.globalPosition;
|
||||
|
||||
// auto scroll the page when the drag position is at the edge of the screen
|
||||
widget.editorState.scrollService?.startAutoScroll(
|
||||
details.localPosition,
|
||||
);
|
||||
},
|
||||
onDragEnd: (details) {
|
||||
widget.editorState.selectionService.removeDropTarget();
|
||||
|
||||
if (globalPosition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final data = widget.editorState.selectionService
|
||||
.getDropTargetRenderData(globalPosition!);
|
||||
final acceptedPath = data?.dropPath;
|
||||
|
||||
_moveNodeToNewPosition(node, acceptedPath);
|
||||
},
|
||||
child: _OptionButton(
|
||||
controller: widget.controller,
|
||||
editorState: widget.editorState,
|
||||
blockComponentContext: widget.blockComponentContext,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _moveNodeToNewPosition(Node node, Path? acceptedPath) async {
|
||||
if (acceptedPath == null) {
|
||||
Log.info('acceptedPath is null');
|
||||
return;
|
||||
}
|
||||
|
||||
Log.info('move node($node) to path($acceptedPath)');
|
||||
|
||||
final transaction = widget.editorState.transaction;
|
||||
// use the node in document instead of the local node
|
||||
transaction.moveNode(acceptedPath, widget.blockComponentContext.node);
|
||||
await widget.editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
class _OptionButtonFeedback extends StatefulWidget {
|
||||
const _OptionButtonFeedback({
|
||||
required this.controller,
|
||||
required this.editorState,
|
||||
required this.blockComponentContext,
|
||||
required this.blockComponentBuilder,
|
||||
});
|
||||
|
||||
final PopoverController controller;
|
||||
final EditorState editorState;
|
||||
final BlockComponentContext blockComponentContext;
|
||||
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
||||
|
||||
@override
|
||||
State<_OptionButtonFeedback> createState() => _OptionButtonFeedbackState();
|
||||
}
|
||||
|
||||
class _OptionButtonFeedbackState extends State<_OptionButtonFeedback> {
|
||||
late Node node;
|
||||
late BlockComponentContext blockComponentContext;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_setupLockComponentContext();
|
||||
widget.blockComponentContext.node.addListener(_updateBlockComponentContext);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.blockComponentContext.node
|
||||
.removeListener(_updateBlockComponentContext);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final maxWidth = (widget.editorState.renderBox?.size.width ??
|
||||
MediaQuery.of(context).size.width) *
|
||||
0.8;
|
||||
|
||||
return Opacity(
|
||||
opacity: 0.7,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: maxWidth,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Provider.value(
|
||||
value: widget.editorState,
|
||||
child: _buildBlock(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBlock() {
|
||||
final node = widget.blockComponentContext.node;
|
||||
final builder = widget.blockComponentBuilder[node.type];
|
||||
if (builder == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
const unsupportedRenderBlockTypes = [
|
||||
TableBlockKeys.type,
|
||||
CustomImageBlockKeys.type,
|
||||
MultiImageBlockKeys.type,
|
||||
FileBlockKeys.type,
|
||||
DatabaseBlockKeys.boardType,
|
||||
DatabaseBlockKeys.calendarType,
|
||||
DatabaseBlockKeys.gridType,
|
||||
];
|
||||
|
||||
if (unsupportedRenderBlockTypes.contains(node.type)) {
|
||||
// unable to render table block without provider/context
|
||||
// render a placeholder instead
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: FlowyText(node.type.replaceAll('_', ' ').capitalize()),
|
||||
);
|
||||
}
|
||||
|
||||
return IntrinsicHeight(
|
||||
child: Provider.value(
|
||||
value: widget.editorState,
|
||||
child: builder.build(blockComponentContext),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateBlockComponentContext() {
|
||||
setState(() => _setupLockComponentContext());
|
||||
}
|
||||
|
||||
void _setupLockComponentContext() {
|
||||
node = widget.blockComponentContext.node.copyWith();
|
||||
blockComponentContext = BlockComponentContext(
|
||||
widget.blockComponentContext.buildContext,
|
||||
node,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OptionButton extends StatelessWidget {
|
||||
const _OptionButton({
|
||||
required this.controller,
|
||||
required this.editorState,
|
||||
required this.blockComponentContext,
|
||||
});
|
||||
|
||||
final PopoverController controller;
|
||||
final EditorState editorState;
|
||||
final BlockComponentContext blockComponentContext;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlockActionButton(
|
||||
svg: FlowySvgs.drag_element_s,
|
||||
richMessage: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: LocaleKeys.document_plugins_optionAction_drag.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.document_plugins_optionAction_toMove.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
const TextSpan(text: '\n'),
|
||||
TextSpan(
|
||||
text: LocaleKeys.document_plugins_optionAction_click.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.document_plugins_optionAction_toOpenMenu.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
controller.show();
|
||||
|
||||
// update selection
|
||||
_updateBlockSelection();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _updateBlockSelection() {
|
||||
final startNode = blockComponentContext.node;
|
||||
var endNode = startNode;
|
||||
while (endNode.children.isNotEmpty) {
|
||||
endNode = endNode.children.last;
|
||||
}
|
||||
|
||||
final start = Position(path: startNode.path);
|
||||
final end = endNode.selectable?.end() ??
|
||||
Position(
|
||||
path: endNode.path,
|
||||
offset: endNode.delta?.length ?? 0,
|
||||
);
|
||||
|
||||
editorState.selectionType = SelectionType.block;
|
||||
editorState.selection = Selection(
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
@ -10,6 +8,7 @@ import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
@ -61,7 +60,7 @@ class _ResizableImageState extends State<ResizableImage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
imageWidth = widget.width;
|
||||
_userProfilePB = context.read<DocumentBloc>().state.userProfilePB;
|
||||
_userProfilePB = context.read<DocumentBloc?>()?.state.userProfilePB;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -73,16 +73,19 @@ class FavoriteMenuBloc extends Bloc<FavoriteMenuEvent, FavoriteMenuState> {
|
||||
(List<ViewPB>, List<ViewPB>, List<ViewPB>, List<ViewPB>) _getViews(
|
||||
RepeatedFavoriteViewPB source,
|
||||
) {
|
||||
final now = DateTime.now();
|
||||
|
||||
final List<ViewPB> views = source.items.map((v) => v.item).toList();
|
||||
final List<ViewPB> todayViews = [];
|
||||
final List<ViewPB> thisWeekViews = [];
|
||||
final List<ViewPB> otherViews = [];
|
||||
|
||||
for (final favoriteView in source.items) {
|
||||
final view = favoriteView.item;
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(
|
||||
favoriteView.timestamp.toInt() * 1000,
|
||||
);
|
||||
final diff = DateTime.now().difference(date).inDays;
|
||||
final diff = now.difference(date).inDays;
|
||||
if (diff == 0) {
|
||||
todayViews.add(view);
|
||||
} else if (diff < 7) {
|
||||
@ -91,6 +94,7 @@ class FavoriteMenuBloc extends Bloc<FavoriteMenuEvent, FavoriteMenuState> {
|
||||
otherViews.add(view);
|
||||
}
|
||||
}
|
||||
|
||||
return (views, todayViews, thisWeekViews, otherViews);
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,21 +49,27 @@ class FlowyTooltip extends StatelessWidget {
|
||||
|
||||
extension FlowyToolTipExtension on BuildContext {
|
||||
double tooltipFontSize() => 14.0;
|
||||
double tooltipHeight() => 20.0 / tooltipFontSize();
|
||||
double tooltipHeight({double? fontSize}) =>
|
||||
20.0 / (fontSize ?? tooltipFontSize());
|
||||
Color tooltipFontColor() => Theme.of(this).brightness == Brightness.light
|
||||
? Colors.white
|
||||
: Colors.black;
|
||||
|
||||
TextStyle? tooltipTextStyle({Color? fontColor}) {
|
||||
TextStyle? tooltipTextStyle({Color? fontColor, double? fontSize}) {
|
||||
return Theme.of(this).textTheme.bodyMedium?.copyWith(
|
||||
color: fontColor ?? tooltipFontColor(),
|
||||
fontSize: tooltipFontSize(),
|
||||
fontSize: fontSize ?? tooltipFontSize(),
|
||||
fontWeight: FontWeight.w400,
|
||||
height: tooltipHeight(),
|
||||
height: tooltipHeight(fontSize: fontSize),
|
||||
leadingDistribution: TextLeadingDistribution.even,
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? tooltipHintTextStyle({double? fontSize}) => tooltipTextStyle(
|
||||
fontColor: tooltipFontColor().withOpacity(0.7),
|
||||
fontSize: fontSize,
|
||||
);
|
||||
|
||||
Color tooltipBackgroundColor() =>
|
||||
Theme.of(this).brightness == Brightness.light
|
||||
? const Color(0xFF1D2129)
|
||||
|
||||
@ -53,8 +53,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "200b572"
|
||||
resolved-ref: "200b572fa37cb9802b0aa61cf05045a24800a905"
|
||||
ref: "5d1d311"
|
||||
resolved-ref: "5d1d311a2c22adc8c42707d6008a4408150c869c"
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "3.3.0"
|
||||
|
||||
@ -196,7 +196,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "200b572"
|
||||
ref: "5d1d311"
|
||||
|
||||
appflowy_editor_plugins:
|
||||
git:
|
||||
|
||||
@ -1615,6 +1615,8 @@
|
||||
"optionAction": {
|
||||
"click": "Click",
|
||||
"toOpenMenu": " to open menu",
|
||||
"drag": "Drag",
|
||||
"toMove": " to move",
|
||||
"delete": "Delete",
|
||||
"duplicate": "Duplicate",
|
||||
"turnInto": "Turn into",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user