mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-09-11 19:17:43 +03:00
feat: support mention page on mobile (#5158)
* feat: support mention page on mobile * chore: clean up toggle notifier * fix: changes after merge * fix: depends on inherited widget error * fix: amend after merge * feat: add icon to search * chore: slight style changes * chore: revert podfile change * ci: fix disposal
This commit is contained in:
parent
6bfac6b80a
commit
e1e8747f15
@ -1,3 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/row/row_document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
@ -8,7 +10,6 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class RowDocument extends StatelessWidget {
|
||||
@ -82,9 +83,7 @@ class _RowEditorState extends State<RowEditor> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: documentBloc),
|
||||
],
|
||||
providers: [BlocProvider.value(value: documentBloc)],
|
||||
child: BlocListener<DocumentBloc, DocumentState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.isDocumentEmpty != current.isDocumentEmpty,
|
||||
|
@ -1,5 +1,7 @@
|
||||
library document_plugin;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
@ -20,7 +22,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.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';
|
||||
|
||||
class DocumentPluginBuilder extends PluginBuilder {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
@ -16,7 +18,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class DocumentPage extends StatefulWidget {
|
||||
@ -153,9 +154,9 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
onRestore: () => context.read<DocumentBloc>().add(
|
||||
const DocumentEvent.restorePage(),
|
||||
),
|
||||
onDelete: () => context.read<DocumentBloc>().add(
|
||||
const DocumentEvent.deletePermanently(),
|
||||
),
|
||||
onDelete: () => context
|
||||
.read<DocumentBloc>()
|
||||
.add(const DocumentEvent.deletePermanently()),
|
||||
);
|
||||
}
|
||||
|
||||
@ -178,12 +179,10 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
node: page,
|
||||
editorState: editorState,
|
||||
view: widget.view,
|
||||
onIconChanged: (icon) async {
|
||||
await ViewBackendService.updateViewIcon(
|
||||
onIconChanged: (icon) async => ViewBackendService.updateViewIcon(
|
||||
viewId: widget.view.id,
|
||||
viewIcon: icon,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -196,12 +195,11 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
undoCommand.execute(editorState);
|
||||
} else if (type == EditorNotificationType.redo) {
|
||||
redoCommand.execute(editorState);
|
||||
} else if (type == EditorNotificationType.exitEditing) {
|
||||
if (editorState.selection != null) {
|
||||
} else if (type == EditorNotificationType.exitEditing &&
|
||||
editorState.selection != null) {
|
||||
editorState.selection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onNotificationAction(
|
||||
BuildContext context,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:avatar_stack/avatar_stack.dart';
|
||||
import 'package:avatar_stack/positions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CollaboratorAvatarStack extends StatelessWidget {
|
||||
const CollaboratorAvatarStack({
|
||||
@ -17,21 +18,13 @@ class CollaboratorAvatarStack extends StatelessWidget {
|
||||
});
|
||||
|
||||
final List<Widget> avatars;
|
||||
|
||||
final Positions? settings;
|
||||
|
||||
final InfoWidgetBuilder? infoWidgetBuilder;
|
||||
|
||||
final double? width;
|
||||
|
||||
final double? height;
|
||||
|
||||
final double? borderWidth;
|
||||
|
||||
final Color? borderColor;
|
||||
|
||||
final Color? backgroundColor;
|
||||
|
||||
final Widget Function(int value, BorderSide border) plusWidgetBuilder;
|
||||
|
||||
@override
|
||||
|
@ -1,3 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
@ -10,8 +13,6 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
@ -23,25 +24,17 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
ShowPlaceholder? showParagraphPlaceholder,
|
||||
String Function(Node)? placeholderText,
|
||||
}) {
|
||||
final standardActions = [
|
||||
OptionAction.delete,
|
||||
OptionAction.duplicate,
|
||||
// OptionAction.divider,
|
||||
// OptionAction.moveUp,
|
||||
// OptionAction.moveDown,
|
||||
];
|
||||
final standardActions = [OptionAction.delete, OptionAction.duplicate];
|
||||
|
||||
final calloutBGColor = AFThemeExtension.of(context).calloutBGColor;
|
||||
final configuration = BlockComponentConfiguration(
|
||||
// use EdgeInsets.zero to remove the default padding.
|
||||
padding: (node) {
|
||||
padding: (_) {
|
||||
if (PlatformExtension.isMobile) {
|
||||
final pageStyle = context.read<DocumentPageStyleBloc>().state;
|
||||
final factor = pageStyle.fontLayout.factor;
|
||||
final padding = pageStyle.lineHeightLayout.padding * factor;
|
||||
return EdgeInsets.only(
|
||||
top: padding,
|
||||
);
|
||||
return EdgeInsets.only(top: padding);
|
||||
}
|
||||
|
||||
return const EdgeInsets.symmetric(vertical: 5.0);
|
||||
@ -62,10 +55,7 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
placeholderText: (_) => LocaleKeys.blockPlaceholders_todoList.tr(),
|
||||
),
|
||||
iconBuilder: PlatformExtension.isMobile
|
||||
? (context, node, onCheck) => TodoListIcon(
|
||||
node: node,
|
||||
onCheck: onCheck,
|
||||
)
|
||||
? (_, node, onCheck) => TodoListIcon(node: node, onCheck: onCheck)
|
||||
: null,
|
||||
toggleChildrenTriggers: [
|
||||
LogicalKeyboardKey.shift,
|
||||
@ -78,9 +68,7 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
placeholderText: (_) => LocaleKeys.blockPlaceholders_bulletList.tr(),
|
||||
),
|
||||
iconBuilder: PlatformExtension.isMobile
|
||||
? (context, node) => BulletedListIcon(
|
||||
node: node,
|
||||
)
|
||||
? (_, node) => BulletedListIcon(node: node)
|
||||
: null,
|
||||
),
|
||||
NumberedListBlockKeys.type: NumberedListBlockComponentBuilder(
|
||||
@ -88,10 +76,8 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
placeholderText: (_) => LocaleKeys.blockPlaceholders_numberList.tr(),
|
||||
),
|
||||
iconBuilder: PlatformExtension.isMobile
|
||||
? (context, node, textDirection) => NumberedListIcon(
|
||||
node: node,
|
||||
textDirection: textDirection,
|
||||
)
|
||||
? (_, node, textDirection) =>
|
||||
NumberedListIcon(node: node, textDirection: textDirection)
|
||||
: null,
|
||||
),
|
||||
QuoteBlockKeys.type: QuoteBlockComponentBuilder(
|
||||
@ -108,9 +94,7 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
final headingPaddings = pageStyle.lineHeightLayout.headingPaddings
|
||||
.map((e) => e * factor);
|
||||
final level = node.attributes[HeadingBlockKeys.level] ?? 6;
|
||||
return EdgeInsets.only(
|
||||
top: headingPaddings.elementAt(level),
|
||||
);
|
||||
return EdgeInsets.only(top: headingPaddings.elementAt(level));
|
||||
}
|
||||
|
||||
return const EdgeInsets.only(top: 12.0, bottom: 4.0);
|
||||
@ -128,10 +112,7 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 10,
|
||||
child: ImageMenu(
|
||||
node: node,
|
||||
state: state,
|
||||
),
|
||||
child: ImageMenu(node: node, state: state),
|
||||
),
|
||||
),
|
||||
TableBlockKeys.type: TableBlockComponentBuilder(
|
||||
@ -188,14 +169,12 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
DividerBlockKeys.type: DividerBlockComponentBuilder(
|
||||
configuration: configuration,
|
||||
height: 28.0,
|
||||
wrapper: (context, node, child) {
|
||||
return MobileBlockActionButtons(
|
||||
wrapper: (_, node, child) => MobileBlockActionButtons(
|
||||
showThreeDots: false,
|
||||
node: node,
|
||||
editorState: editorState,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
MathEquationBlockKeys.type: MathEquationBlockComponentBuilder(
|
||||
configuration: configuration,
|
||||
@ -223,10 +202,7 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
configuration: configuration.copyWith(
|
||||
placeholderTextStyle: (_) =>
|
||||
styleCustomizer.outlineBlockPlaceholderStyleBuilder(),
|
||||
padding: (_) => const EdgeInsets.only(
|
||||
top: 12.0,
|
||||
bottom: 4.0,
|
||||
),
|
||||
padding: (_) => const EdgeInsets.only(top: 12.0, bottom: 4.0),
|
||||
),
|
||||
),
|
||||
LinkPreviewBlockKeys.type: LinkPreviewBlockComponentBuilder(
|
||||
@ -238,12 +214,9 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
menuBuilder: (context, node, state) => Positioned(
|
||||
top: 10,
|
||||
right: 0,
|
||||
child: LinkPreviewMenu(
|
||||
node: node,
|
||||
state: state,
|
||||
child: LinkPreviewMenu(node: node, state: state),
|
||||
),
|
||||
),
|
||||
builder: (context, node, url, title, description, imageUrl) =>
|
||||
builder: (_, node, url, title, description, imageUrl) =>
|
||||
CustomLinkPreviewWidget(
|
||||
node: node,
|
||||
url: url,
|
||||
@ -283,27 +256,11 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
ToggleListBlockKeys.type,
|
||||
];
|
||||
|
||||
final supportAlignBuilderType = [
|
||||
ImageBlockKeys.type,
|
||||
];
|
||||
|
||||
final supportDepthBuilderType = [
|
||||
OutlineBlockKeys.type,
|
||||
];
|
||||
|
||||
final colorAction = [
|
||||
OptionAction.divider,
|
||||
OptionAction.color,
|
||||
];
|
||||
|
||||
final alignAction = [
|
||||
OptionAction.divider,
|
||||
OptionAction.align,
|
||||
];
|
||||
|
||||
final depthAction = [
|
||||
OptionAction.depth,
|
||||
];
|
||||
final supportAlignBuilderType = [ImageBlockKeys.type];
|
||||
final supportDepthBuilderType = [OutlineBlockKeys.type];
|
||||
final colorAction = [OptionAction.divider, OptionAction.color];
|
||||
final alignAction = [OptionAction.divider, OptionAction.align];
|
||||
final depthAction = [OptionAction.depth];
|
||||
|
||||
final List<OptionAction> actions = [
|
||||
...standardActions,
|
||||
|
@ -1,46 +1,30 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum EditorNotificationType {
|
||||
none,
|
||||
undo,
|
||||
redo,
|
||||
exitEditing,
|
||||
}
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
enum EditorNotificationType { none, undo, redo, exitEditing }
|
||||
|
||||
class EditorNotification {
|
||||
const EditorNotification({
|
||||
required this.type,
|
||||
});
|
||||
const EditorNotification({required this.type});
|
||||
|
||||
EditorNotification.undo() : type = EditorNotificationType.undo;
|
||||
EditorNotification.redo() : type = EditorNotificationType.redo;
|
||||
EditorNotification.exitEditing() : type = EditorNotificationType.exitEditing;
|
||||
|
||||
static final PropertyValueNotifier<EditorNotificationType> _notifier =
|
||||
PropertyValueNotifier(
|
||||
EditorNotificationType.none,
|
||||
);
|
||||
PropertyValueNotifier(EditorNotificationType.none);
|
||||
|
||||
final EditorNotificationType type;
|
||||
|
||||
void post() {
|
||||
_notifier.value = type;
|
||||
}
|
||||
void post() => _notifier.value = type;
|
||||
|
||||
static void addListener(ValueChanged<EditorNotificationType> listener) {
|
||||
_notifier.addListener(() {
|
||||
listener(_notifier.value);
|
||||
});
|
||||
_notifier.addListener(() => listener(_notifier.value));
|
||||
}
|
||||
|
||||
static void removeListener(ValueChanged<EditorNotificationType> listener) {
|
||||
_notifier.removeListener(() {
|
||||
listener(_notifier.value);
|
||||
});
|
||||
_notifier.removeListener(() => listener(_notifier.value));
|
||||
}
|
||||
|
||||
static void dispose() {
|
||||
_notifier.dispose();
|
||||
}
|
||||
static void dispose() => _notifier.dispose();
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||
@ -26,8 +29,6 @@ import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
final codeBlockLocalization = CodeBlockLocalizations(
|
||||
@ -210,9 +211,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
|
||||
if (widget.useViewInfoBloc) {
|
||||
viewInfoBloc.add(
|
||||
ViewInfoEvent.registerEditorState(
|
||||
editorState: widget.editorState,
|
||||
),
|
||||
ViewInfoEvent.registerEditorState(editorState: widget.editorState),
|
||||
);
|
||||
}
|
||||
|
||||
@ -326,25 +325,25 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
final editorState = widget.editorState;
|
||||
|
||||
if (PlatformExtension.isMobile) {
|
||||
return AppFlowyMobileToolbar(
|
||||
return BlocProvider.value(
|
||||
value: documentBloc,
|
||||
child: AppFlowyMobileToolbar(
|
||||
toolbarHeight: 42.0,
|
||||
editorState: editorState,
|
||||
toolbarItemsBuilder: (selection) => buildMobileToolbarItems(
|
||||
editorState,
|
||||
selection,
|
||||
),
|
||||
toolbarItemsBuilder: (selection) =>
|
||||
buildMobileToolbarItems(editorState, selection),
|
||||
child: MobileFloatingToolbar(
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
toolbarBuilder: (context, anchor, closeToolbar) {
|
||||
return CustomMobileFloatingToolbar(
|
||||
toolbarBuilder: (_, anchor, closeToolbar) =>
|
||||
CustomMobileFloatingToolbar(
|
||||
editorState: editorState,
|
||||
anchor: anchor,
|
||||
closeToolbar: closeToolbar,
|
||||
);
|
||||
},
|
||||
),
|
||||
child: editor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -362,9 +361,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
|
||||
List<SelectionMenuItem> _customSlashMenuItems() {
|
||||
final items = [...standardSelectionMenuItems];
|
||||
final imageItem = items.firstWhereOrNull(
|
||||
(element) => element.name == AppFlowyEditorL10n.current.image,
|
||||
);
|
||||
final imageItem = items
|
||||
.firstWhereOrNull((e) => e.name == AppFlowyEditorL10n.current.image);
|
||||
if (imageItem != null) {
|
||||
final imageItemIndex = items.indexOf(imageItem);
|
||||
if (imageItemIndex != -1) {
|
||||
@ -393,15 +391,11 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
|
||||
(bool, Selection?) _computeAutoFocusParameters() {
|
||||
if (widget.editorState.document.isEmpty) {
|
||||
return (
|
||||
true,
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
);
|
||||
return (true, Selection.collapsed(Position(path: [0])));
|
||||
}
|
||||
final nodes = widget.editorState.document.root.children
|
||||
.where((element) => element.delta != null);
|
||||
final isAllEmpty =
|
||||
nodes.isNotEmpty && nodes.every((element) => element.delta!.isEmpty);
|
||||
final nodes =
|
||||
widget.editorState.document.root.children.where((e) => e.delta != null);
|
||||
final isAllEmpty = nodes.isNotEmpty && nodes.every((e) => e.delta!.isEmpty);
|
||||
if (isAllEmpty) {
|
||||
return (true, Selection.collapsed(Position(path: nodes.first.path)));
|
||||
}
|
||||
@ -458,12 +452,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
}
|
||||
|
||||
void _customizeBlockComponentBackgroundColorDecorator() {
|
||||
blockComponentBackgroundColorDecorator =
|
||||
(Node node, String colorString) => buildEditorCustomizedColor(
|
||||
context,
|
||||
node,
|
||||
colorString,
|
||||
);
|
||||
blockComponentBackgroundColorDecorator = (Node node, String colorString) =>
|
||||
buildEditorCustomizedColor(context, node, colorString);
|
||||
}
|
||||
|
||||
void _initEditorL10n() => AppFlowyEditorL10n.current = EditorI18n();
|
||||
@ -528,7 +518,5 @@ bool showInAnyTextType(EditorState editorState) {
|
||||
}
|
||||
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
return nodes.any(
|
||||
(node) => toolbarItemWhiteList.contains(node.type),
|
||||
);
|
||||
return nodes.any((node) => toolbarItemWhiteList.contains(node.type));
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
enum MentionType {
|
||||
@ -69,18 +70,21 @@ class MentionBlock extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final type = MentionType.fromString(mention[MentionBlockKeys.type]);
|
||||
final editorState = context.read<EditorState>();
|
||||
|
||||
switch (type) {
|
||||
case MentionType.page:
|
||||
final String pageId = mention[MentionBlockKeys.pageId];
|
||||
return MentionPageBlock(
|
||||
key: ValueKey(pageId),
|
||||
editorState: editorState,
|
||||
pageId: pageId,
|
||||
node: node,
|
||||
textStyle: textStyle,
|
||||
index: index,
|
||||
);
|
||||
case MentionType.date:
|
||||
final String date = mention[MentionBlockKeys.date];
|
||||
final editorState = context.read<EditorState>();
|
||||
final reminderOption = ReminderOption.values.firstWhereOrNull(
|
||||
(o) => o.name == mention[MentionBlockKeys.reminderOption],
|
||||
);
|
||||
|
@ -1,6 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart';
|
||||
import 'package:appflowy/plugins/trash/application/trash_service.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
@ -9,26 +14,58 @@ import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
show EditorState, PlatformExtension;
|
||||
show
|
||||
Delta,
|
||||
EditorState,
|
||||
Node,
|
||||
PlatformExtension,
|
||||
TextInsert,
|
||||
TextTransaction,
|
||||
paragraphNode;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
final pageMemorizer = <String, ViewPB?>{};
|
||||
|
||||
Node pageMentionNode(String viewId) {
|
||||
return paragraphNode(
|
||||
delta: Delta(
|
||||
operations: [
|
||||
TextInsert(
|
||||
'\$',
|
||||
attributes: {
|
||||
MentionBlockKeys.mention: {
|
||||
MentionBlockKeys.type: MentionType.page.name,
|
||||
MentionBlockKeys.pageId: viewId,
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MentionPageBlock extends StatefulWidget {
|
||||
const MentionPageBlock({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
required this.pageId,
|
||||
required this.node,
|
||||
required this.textStyle,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final String pageId;
|
||||
final Node node;
|
||||
final TextStyle? textStyle;
|
||||
|
||||
// Used to update the block
|
||||
final int index;
|
||||
|
||||
@override
|
||||
State<MentionPageBlock> createState() => _MentionPageBlockState();
|
||||
}
|
||||
@ -71,14 +108,15 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
|
||||
if (view == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
// updateSelection();
|
||||
|
||||
final iconSize = widget.textStyle?.fontSize ?? 16.0;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
child: FlowyHover(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => openPage(widget.pageId),
|
||||
onTap: handleTap,
|
||||
onDoubleTap: handleDoubleTap,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -112,24 +150,51 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
|
||||
);
|
||||
}
|
||||
|
||||
void openPage(String pageId) async {
|
||||
final view = await fetchView(pageId);
|
||||
Future<void> handleTap() async {
|
||||
final view = await fetchView(widget.pageId);
|
||||
if (view == null) {
|
||||
Log.error('Page($pageId) not found');
|
||||
Log.error('Page(${widget.pageId}) not found');
|
||||
return;
|
||||
}
|
||||
if (PlatformExtension.isDesktopOrWeb) {
|
||||
getIt<TabsBloc>().add(
|
||||
TabsEvent.openPlugin(
|
||||
plugin: view.plugin(),
|
||||
view: view,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (mounted) {
|
||||
|
||||
if (PlatformExtension.isMobile && mounted) {
|
||||
await context.pushView(view);
|
||||
} else {
|
||||
getIt<TabsBloc>().add(
|
||||
TabsEvent.openPlugin(plugin: view.plugin(), view: view),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleDoubleTap() async {
|
||||
if (!PlatformExtension.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentViewId = context.read<DocumentBloc>().documentId;
|
||||
final viewId = await showPageSelectorSheet(
|
||||
context,
|
||||
currentViewId: currentViewId,
|
||||
selectedViewId: widget.pageId,
|
||||
);
|
||||
|
||||
if (viewId != null) {
|
||||
// Update this nodes pageId
|
||||
final transaction = widget.editorState.transaction
|
||||
..formatText(
|
||||
widget.node,
|
||||
widget.index,
|
||||
1,
|
||||
{
|
||||
MentionBlockKeys.mention: {
|
||||
MentionBlockKeys.type: MentionType.page.name,
|
||||
MentionBlockKeys.pageId: viewId,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await widget.editorState.apply(transaction, withUpdateSelection: false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<ViewPB?> fetchView(String pageId) async {
|
||||
|
@ -0,0 +1,135 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
|
||||
Future<String?> showPageSelectorSheet(
|
||||
BuildContext context, {
|
||||
String? currentViewId,
|
||||
String? selectedViewId,
|
||||
}) async {
|
||||
return showMobileBottomSheet<String>(
|
||||
context,
|
||||
title: LocaleKeys.document_mobilePageSelector_title.tr(),
|
||||
showHeader: true,
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
builder: (context) => Container(
|
||||
margin: const EdgeInsets.only(top: 12.0),
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 340,
|
||||
minHeight: 80,
|
||||
),
|
||||
child: _MobilePageSelectorBody(
|
||||
currentViewId: currentViewId,
|
||||
selectedViewId: selectedViewId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _MobilePageSelectorBody extends StatefulWidget {
|
||||
const _MobilePageSelectorBody({this.currentViewId, this.selectedViewId});
|
||||
|
||||
final String? currentViewId;
|
||||
final String? selectedViewId;
|
||||
|
||||
@override
|
||||
State<_MobilePageSelectorBody> createState() =>
|
||||
_MobilePageSelectorBodyState();
|
||||
}
|
||||
|
||||
class _MobilePageSelectorBodyState extends State<_MobilePageSelectorBody> {
|
||||
final searchController = TextEditingController();
|
||||
late final Future<List<ViewPB>> _viewsFuture = _fetchViews();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
height: 44.0,
|
||||
child: FlowySearchTextField(
|
||||
controller: searchController,
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _viewsFuture,
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator.adaptive());
|
||||
}
|
||||
|
||||
if (snapshot.hasError || snapshot.data == null) {
|
||||
return Center(
|
||||
child: FlowyText(
|
||||
LocaleKeys.document_mobilePageSelector_failedToLoad.tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final views = snapshot.data!;
|
||||
if (widget.currentViewId != null) {
|
||||
views.removeWhere((v) => v.id == widget.currentViewId);
|
||||
}
|
||||
|
||||
final filtered = views.where(
|
||||
(v) =>
|
||||
searchController.text.isEmpty ||
|
||||
v.name
|
||||
.toLowerCase()
|
||||
.contains(searchController.text.toLowerCase()),
|
||||
);
|
||||
|
||||
if (filtered.isEmpty) {
|
||||
return Center(
|
||||
child: FlowyText(
|
||||
LocaleKeys.document_mobilePageSelector_noPagesFound.tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Flexible(
|
||||
child: ListView(
|
||||
children: filtered
|
||||
.map(
|
||||
(view) => FlowyOptionTile.checkbox(
|
||||
leftIcon: view.icon.value.isNotEmpty
|
||||
? EmojiText(
|
||||
emoji: view.icon.value,
|
||||
fontSize: 18,
|
||||
textAlign: TextAlign.center,
|
||||
lineHeight: 1.3,
|
||||
)
|
||||
: FlowySvg(
|
||||
view.layout.icon,
|
||||
size: const Size.square(20),
|
||||
),
|
||||
text: view.name,
|
||||
isSelected: view.id == widget.selectedViewId,
|
||||
onTap: () => Navigator.of(context).pop(view.id),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<ViewPB>> _fetchViews() async =>
|
||||
(await ViewBackendService.getAllViews()).toNullable()?.items ?? [];
|
||||
}
|
@ -6,8 +6,11 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
@ -15,6 +18,7 @@ import 'package:appflowy/startup/tasks/app_widget.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
final addBlockToolbarItem = AppFlowyMobileToolbarItem(
|
||||
@ -41,15 +45,12 @@ final addBlockToolbarItem = AppFlowyMobileToolbarItem(
|
||||
keepEditorFocusNotifier.increase();
|
||||
final didAddBlock = await showAddBlockMenu(
|
||||
AppGlobals.rootNavKey.currentContext!,
|
||||
documentBloc: context.read<DocumentBloc>(),
|
||||
editorState: editorState,
|
||||
selection: selection!,
|
||||
);
|
||||
if (didAddBlock != true) {
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
selection,
|
||||
),
|
||||
);
|
||||
unawaited(editorState.updateSelectionWithReason(selection));
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -59,6 +60,7 @@ final addBlockToolbarItem = AppFlowyMobileToolbarItem(
|
||||
|
||||
Future<bool?> showAddBlockMenu(
|
||||
BuildContext context, {
|
||||
required DocumentBloc documentBloc,
|
||||
required EditorState editorState,
|
||||
required Selection selection,
|
||||
}) async {
|
||||
@ -73,15 +75,16 @@ Future<bool?> showAddBlockMenu(
|
||||
backgroundColor: theme.toolbarMenuBackgroundColor,
|
||||
elevation: 20,
|
||||
enableDraggableScrollable: true,
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
builder: (_) => Padding(
|
||||
padding: EdgeInsets.all(16 * context.scale),
|
||||
child: BlocProvider.value(
|
||||
value: documentBloc,
|
||||
child: _AddBlockMenu(
|
||||
selection: selection,
|
||||
editorState: editorState,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -96,20 +99,21 @@ class _AddBlockMenu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TypeOptionMenu<String>(
|
||||
return BlocProvider.value(
|
||||
value: context.read<DocumentBloc>(),
|
||||
child: TypeOptionMenu<String>(
|
||||
values: buildTypeOptionMenuItemValues(context),
|
||||
scaleFactor: context.scale,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _insertBlock(Node node) async {
|
||||
AppGlobals.rootNavKey.currentContext?.pop(true);
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
editorState.insertBlockAfterCurrentSelection(
|
||||
selection,
|
||||
node,
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
() => editorState.insertBlockAfterCurrentSelection(selection, node),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
List<TypeOptionMenuItemValue<String>> buildTypeOptionMenuItemValues(
|
||||
@ -208,11 +212,36 @@ class _AddBlockMenu extends StatelessWidget {
|
||||
// date
|
||||
TypeOptionMenuItemValue(
|
||||
value: ParagraphBlockKeys.type,
|
||||
backgroundColor: colorMap['date']!,
|
||||
backgroundColor: colorMap[MentionBlockKeys.type]!,
|
||||
text: LocaleKeys.editor_date.tr(),
|
||||
icon: FlowySvgs.m_add_block_date_s,
|
||||
onTap: (_, __) => _insertBlock(dateMentionNode()),
|
||||
),
|
||||
// page
|
||||
TypeOptionMenuItemValue(
|
||||
value: ParagraphBlockKeys.type,
|
||||
backgroundColor: colorMap[MentionBlockKeys.type]!,
|
||||
text: LocaleKeys.editor_page.tr(),
|
||||
icon: FlowySvgs.document_s,
|
||||
onTap: (_, __) async {
|
||||
AppGlobals.rootNavKey.currentContext?.pop(true);
|
||||
|
||||
final currentViewId = context.read<DocumentBloc>().documentId;
|
||||
final viewId = await showPageSelectorSheet(
|
||||
context,
|
||||
currentViewId: currentViewId,
|
||||
);
|
||||
|
||||
if (viewId != null) {
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
editorState.insertBlockAfterCurrentSelection(
|
||||
selection,
|
||||
pageMentionNode(viewId),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
// divider
|
||||
TypeOptionMenuItemValue(
|
||||
@ -270,7 +299,7 @@ class _AddBlockMenu extends StatelessWidget {
|
||||
NumberedListBlockKeys.type: const Color(0xFFA35F94),
|
||||
ToggleListBlockKeys.type: const Color(0xFFA35F94),
|
||||
ImageBlockKeys.type: const Color(0xFFBAAC74),
|
||||
'date': const Color(0xFF40AAB8),
|
||||
MentionBlockKeys.type: const Color(0xFF40AAB8),
|
||||
DividerBlockKeys.type: const Color(0xFF4BB299),
|
||||
CalloutBlockKeys.type: const Color(0xFF66599B),
|
||||
CodeBlockKeys.type: const Color(0xFF66599B),
|
||||
@ -286,7 +315,7 @@ class _AddBlockMenu extends StatelessWidget {
|
||||
NumberedListBlockKeys.type: const Color(0xFFFFB9EF),
|
||||
ToggleListBlockKeys.type: const Color(0xFFFFB9EF),
|
||||
ImageBlockKeys.type: const Color(0xFFFDEDA7),
|
||||
'date': const Color(0xFF91EAF5),
|
||||
MentionBlockKeys.type: const Color(0xFF91EAF5),
|
||||
DividerBlockKeys.type: const Color(0xFF98F4CD),
|
||||
CalloutBlockKeys.type: const Color(0xFFCABDFF),
|
||||
CodeBlockKeys.type: const Color(0xFFCABDFF),
|
||||
|
@ -1,6 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_close_keyboard_or_menu_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar_item.dart';
|
||||
@ -8,8 +12,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_too
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
@ -66,9 +69,7 @@ class _AppFlowyMobileToolbarState extends State<AppFlowyMobileToolbar> {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: widget.child,
|
||||
),
|
||||
Expanded(child: widget.child),
|
||||
// add a bottom offset to make sure the toolbar is above the keyboard
|
||||
ValueListenableBuilder(
|
||||
valueListenable: isKeyboardShow,
|
||||
@ -108,11 +109,14 @@ class _AppFlowyMobileToolbarState extends State<AppFlowyMobileToolbar> {
|
||||
}
|
||||
|
||||
return RepaintBoundary(
|
||||
child: BlocProvider.value(
|
||||
value: context.read<DocumentBloc>(),
|
||||
child: _MobileToolbar(
|
||||
editorState: widget.editorState,
|
||||
toolbarItems: widget.toolbarItemsBuilder(selection),
|
||||
toolbarHeight: widget.toolbarHeight,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileToolbarMenuItemWrapper extends StatelessWidget {
|
||||
const MobileToolbarMenuItemWrapper({
|
||||
@ -66,10 +67,7 @@ class MobileToolbarMenuItemWrapper extends StatelessWidget {
|
||||
final radius = Radius.circular(12 * scale);
|
||||
final Widget child;
|
||||
if (icon != null) {
|
||||
child = FlowySvg(
|
||||
icon!,
|
||||
color: iconColor,
|
||||
);
|
||||
child = FlowySvg(icon!, color: iconColor);
|
||||
} else if (text != null) {
|
||||
child = Padding(
|
||||
padding: textPadding * scale,
|
||||
@ -137,16 +135,12 @@ class ScaledVerticalDivider extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HSpace(
|
||||
1.5 * context.scale,
|
||||
);
|
||||
return HSpace(1.5 * context.scale);
|
||||
}
|
||||
}
|
||||
|
||||
class ScaledVSpace extends StatelessWidget {
|
||||
const ScaledVSpace({
|
||||
super.key,
|
||||
});
|
||||
const ScaledVSpace({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -166,10 +160,7 @@ final _blocksCanContainChildren = [
|
||||
];
|
||||
|
||||
extension MobileToolbarEditorState on EditorState {
|
||||
bool isBlockTypeSelected(
|
||||
String blockType, {
|
||||
int? level,
|
||||
}) {
|
||||
bool isBlockTypeSelected(String blockType, {int? level}) {
|
||||
final selection = this.selection;
|
||||
if (selection == null) {
|
||||
return false;
|
||||
@ -186,9 +177,7 @@ extension MobileToolbarEditorState on EditorState {
|
||||
return type == blockType;
|
||||
}
|
||||
|
||||
bool isTextDecorationSelected(
|
||||
String richTextKey,
|
||||
) {
|
||||
bool isTextDecorationSelected(String richTextKey) {
|
||||
final selection = this.selection;
|
||||
if (selection == null) {
|
||||
return false;
|
||||
@ -207,18 +196,16 @@ extension MobileToolbarEditorState on EditorState {
|
||||
start: selection.start.copyWith(
|
||||
offset: selection.startIndex - 1,
|
||||
),
|
||||
), (delta) {
|
||||
return delta.everyAttributes(
|
||||
),
|
||||
(delta) => delta.everyAttributes(
|
||||
(attributes) => attributes[richTextKey] == true,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isSelected = nodes.allSatisfyInSelection(selection, (delta) {
|
||||
return delta.everyAttributes(
|
||||
(attributes) => attributes[richTextKey] == true,
|
||||
);
|
||||
return delta.everyAttributes((attr) => attr[richTextKey] == true);
|
||||
});
|
||||
}
|
||||
return isSelected;
|
||||
@ -321,9 +308,7 @@ extension MobileToolbarEditorState on EditorState {
|
||||
text.isNotEmpty &&
|
||||
selection.isCollapsed) {
|
||||
final attributes = href != null && href.isNotEmpty
|
||||
? {
|
||||
AppFlowyRichTextKeys.href: href,
|
||||
}
|
||||
? {AppFlowyRichTextKeys.href: href}
|
||||
: null;
|
||||
transaction.insertText(
|
||||
node,
|
||||
@ -348,9 +333,7 @@ extension MobileToolbarEditorState on EditorState {
|
||||
node,
|
||||
selection.startIndex,
|
||||
text.length,
|
||||
{
|
||||
AppFlowyRichTextKeys.href: href?.isEmpty == true ? null : href,
|
||||
},
|
||||
{AppFlowyRichTextKeys.href: href?.isEmpty == true ? null : href},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
@ -13,17 +16,12 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu
|
||||
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class EditorStyleCustomizer {
|
||||
EditorStyleCustomizer({
|
||||
required this.context,
|
||||
required this.padding,
|
||||
});
|
||||
EditorStyleCustomizer({required this.context, required this.padding});
|
||||
|
||||
final BuildContext context;
|
||||
final EdgeInsets padding;
|
||||
@ -63,9 +61,7 @@ class EditorStyleCustomizer {
|
||||
bold: baseTextStyle(fontFamily, fontWeight: FontWeight.bold).copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
italic: baseTextStyle(fontFamily).copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
italic: baseTextStyle(fontFamily).copyWith(fontStyle: FontStyle.italic),
|
||||
underline: baseTextStyle(fontFamily).copyWith(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
@ -110,15 +106,9 @@ class EditorStyleCustomizer {
|
||||
color: theme.colorScheme.onBackground,
|
||||
height: lineHeight,
|
||||
),
|
||||
bold: baseTextStyle.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
italic: baseTextStyle.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
underline: baseTextStyle.copyWith(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
bold: baseTextStyle.copyWith(fontWeight: FontWeight.w600),
|
||||
italic: baseTextStyle.copyWith(fontStyle: FontStyle.italic),
|
||||
underline: baseTextStyle.copyWith(decoration: TextDecoration.underline),
|
||||
strikethrough: baseTextStyle.copyWith(
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
@ -175,25 +165,23 @@ class EditorStyleCustomizer {
|
||||
}
|
||||
|
||||
TextStyle codeBlockStyleBuilder() {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||
final fontFamily =
|
||||
context.read<DocumentAppearanceCubit>().state.codeFontFamily;
|
||||
return baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
height: 1.5,
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle outlineBlockPlaceholderStyleBuilder() {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||
return TextStyle(
|
||||
fontFamily: builtInFontFamily(),
|
||||
fontSize: fontSize,
|
||||
height: 1.5,
|
||||
color: theme.colorScheme.onBackground.withOpacity(0.6),
|
||||
color: Theme.of(context).colorScheme.onBackground.withOpacity(0.6),
|
||||
);
|
||||
}
|
||||
|
||||
@ -220,38 +208,22 @@ class EditorStyleCustomizer {
|
||||
);
|
||||
}
|
||||
|
||||
FloatingToolbarStyle floatingToolbarStyleBuilder() {
|
||||
final theme = Theme.of(context);
|
||||
return FloatingToolbarStyle(
|
||||
backgroundColor: theme.colorScheme.onTertiary,
|
||||
FloatingToolbarStyle floatingToolbarStyleBuilder() => FloatingToolbarStyle(
|
||||
backgroundColor: Theme.of(context).colorScheme.onTertiary,
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle baseTextStyle(
|
||||
String? fontFamily, {
|
||||
FontWeight? fontWeight,
|
||||
}) {
|
||||
TextStyle baseTextStyle(String? fontFamily, {FontWeight? fontWeight}) {
|
||||
if (fontFamily == null) {
|
||||
return TextStyle(
|
||||
fontWeight: fontWeight,
|
||||
);
|
||||
return TextStyle(fontWeight: fontWeight);
|
||||
}
|
||||
try {
|
||||
return GoogleFonts.getFont(
|
||||
fontFamily,
|
||||
fontWeight: fontWeight,
|
||||
);
|
||||
return GoogleFonts.getFont(fontFamily, fontWeight: fontWeight);
|
||||
} on Exception {
|
||||
if ([builtInFontFamily(), builtInCodeFontFamily].contains(fontFamily)) {
|
||||
return TextStyle(
|
||||
fontFamily: fontFamily,
|
||||
fontWeight: fontWeight,
|
||||
);
|
||||
return TextStyle(fontFamily: fontFamily, fontWeight: fontWeight);
|
||||
}
|
||||
|
||||
return TextStyle(
|
||||
fontWeight: fontWeight,
|
||||
);
|
||||
return TextStyle(fontWeight: fontWeight);
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,7 +253,7 @@ class EditorStyleCustomizer {
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
@ -334,18 +306,13 @@ class EditorStyleCustomizer {
|
||||
..onTap = () {
|
||||
final editorState = context.read<EditorState>();
|
||||
if (editorState.selection == null) {
|
||||
afLaunchUrlString(
|
||||
href,
|
||||
addingHttpSchemeWhenFailed: true,
|
||||
);
|
||||
afLaunchUrlString(href, addingHttpSchemeWhenFailed: true);
|
||||
return;
|
||||
}
|
||||
|
||||
editorState.updateSelectionWithReason(
|
||||
editorState.selection,
|
||||
extraInfo: {
|
||||
selectionExtraInfoDisableMobileToolbarKey: true,
|
||||
},
|
||||
extraInfo: {selectionExtraInfoDisableMobileToolbarKey: true},
|
||||
);
|
||||
|
||||
showEditLinkBottomSheet(
|
||||
|
@ -204,9 +204,9 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
|
||||
),
|
||||
child: overlayManagerBuilder(
|
||||
context,
|
||||
FeatureFlag.search.isOn
|
||||
!PlatformExtension.isMobile && FeatureFlag.search.isOn
|
||||
? CommandPalette(
|
||||
toggleNotifier: _commandPaletteNotifier,
|
||||
notifier: _commandPaletteNotifier,
|
||||
child: child,
|
||||
)
|
||||
: child,
|
||||
|
@ -16,18 +16,12 @@ class CommandPalette extends InheritedWidget {
|
||||
CommandPalette({
|
||||
super.key,
|
||||
required Widget? child,
|
||||
required ValueNotifier<bool> toggleNotifier,
|
||||
}) : _toggleNotifier = toggleNotifier,
|
||||
super(
|
||||
child: _CommandPaletteController(
|
||||
toggleNotifier: toggleNotifier,
|
||||
child: child,
|
||||
),
|
||||
required this.notifier,
|
||||
}) : super(
|
||||
child: _CommandPaletteController(notifier: notifier, child: child),
|
||||
);
|
||||
|
||||
final ValueNotifier<bool> _toggleNotifier;
|
||||
|
||||
void toggle() => _toggleNotifier.value = !_toggleNotifier.value;
|
||||
final ValueNotifier<bool> notifier;
|
||||
|
||||
static CommandPalette of(BuildContext context) {
|
||||
final CommandPalette? result =
|
||||
@ -38,6 +32,8 @@ class CommandPalette extends InheritedWidget {
|
||||
return result!;
|
||||
}
|
||||
|
||||
void toggle() => notifier.value = !notifier.value;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(covariant InheritedWidget oldWidget) => false;
|
||||
}
|
||||
@ -48,12 +44,12 @@ class _ToggleCommandPaletteIntent extends Intent {
|
||||
|
||||
class _CommandPaletteController extends StatefulWidget {
|
||||
const _CommandPaletteController({
|
||||
required this.toggleNotifier,
|
||||
required this.child,
|
||||
required this.notifier,
|
||||
});
|
||||
|
||||
final Widget? child;
|
||||
final ValueNotifier<bool> toggleNotifier;
|
||||
final ValueNotifier<bool> notifier;
|
||||
|
||||
@override
|
||||
State<_CommandPaletteController> createState() =>
|
||||
@ -61,26 +57,9 @@ class _CommandPaletteController extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CommandPaletteControllerState extends State<_CommandPaletteController> {
|
||||
late ValueNotifier<bool> _toggleNotifier = widget.toggleNotifier;
|
||||
late final ValueNotifier<bool> _toggleNotifier = widget.notifier;
|
||||
bool _isOpen = false;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant _CommandPaletteController oldWidget) {
|
||||
if (oldWidget.toggleNotifier != widget.toggleNotifier) {
|
||||
_toggleNotifier.removeListener(_onToggle);
|
||||
_toggleNotifier.dispose();
|
||||
_toggleNotifier = widget.toggleNotifier;
|
||||
|
||||
// If widget is changed, eg. on theme mode hotkey used
|
||||
// while modal is shown, set the value before listening
|
||||
_toggleNotifier.value = _isOpen;
|
||||
|
||||
_toggleNotifier.addListener(_onToggle);
|
||||
}
|
||||
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -1082,6 +1082,11 @@
|
||||
"errorBlock": {
|
||||
"theBlockIsNotSupported": "The current version does not support this block.",
|
||||
"blockContentHasBeenCopied": "The block content has been copied."
|
||||
},
|
||||
"mobilePageSelector": {
|
||||
"title": "Select page",
|
||||
"failedToLoad": "Failed to load page list",
|
||||
"noPagesFound": "No pages found"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
@ -1328,6 +1333,7 @@
|
||||
"color": "Color",
|
||||
"image": "Image",
|
||||
"date": "Date",
|
||||
"page": "Page",
|
||||
"italic": "Italic",
|
||||
"link": "Link",
|
||||
"numberedList": "Numbered List",
|
||||
|
Loading…
Reference in New Issue
Block a user