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