mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support changing block background color (#3998)
This commit is contained in:
parent
1fad713477
commit
414bb26e56
@ -41,7 +41,10 @@ class BottomSheetActionWidget extends StatelessWidget {
|
||||
size: const Size.square(22.0),
|
||||
color: iconColor,
|
||||
),
|
||||
label: FlowyText(text),
|
||||
label: FlowyText(
|
||||
text,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
style: Theme.of(context)
|
||||
.outlinedButtonTheme
|
||||
.style
|
||||
|
@ -0,0 +1,88 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.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';
|
||||
|
||||
class FlowyMobileColorPicker extends StatelessWidget {
|
||||
const FlowyMobileColorPicker({
|
||||
super.key,
|
||||
required this.onSelectedColor,
|
||||
});
|
||||
|
||||
final void Function(FlowyColorOption? option) onSelectedColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const defaultColor = Colors.transparent;
|
||||
final colors = [
|
||||
// reset to default background color
|
||||
FlowyColorOption(
|
||||
color: defaultColor,
|
||||
i18n: LocaleKeys.document_plugins_optionAction_defaultColor.tr(),
|
||||
id: optionActionColorDefaultColor,
|
||||
),
|
||||
...FlowyTint.values.map(
|
||||
(e) => FlowyColorOption(
|
||||
color: e.color(context),
|
||||
i18n: e.tintName(AppFlowyEditorL10n.current),
|
||||
id: e.id,
|
||||
),
|
||||
),
|
||||
];
|
||||
return ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
final color = colors[index];
|
||||
return SizedBox(
|
||||
height: 56,
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText(
|
||||
color.i18n,
|
||||
),
|
||||
leftIcon: _ColorIcon(
|
||||
color: color.color,
|
||||
size: 24.0,
|
||||
),
|
||||
leftIconSize: const Size.square(36.0),
|
||||
iconPadding: 12.0,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
onTap: () => onSelectedColor(color),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(
|
||||
height: 1,
|
||||
),
|
||||
itemCount: colors.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ColorIcon extends StatelessWidget {
|
||||
const _ColorIcon({
|
||||
this.size = 24.0,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
final double size;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox.square(
|
||||
dimension: size,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy/plugins/base/color/color_picker.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileColorPickerScreen extends StatelessWidget {
|
||||
static const routeName = '/color_picker';
|
||||
static const pageTitle = 'title';
|
||||
|
||||
const MobileColorPickerScreen({
|
||||
super.key,
|
||||
this.title,
|
||||
});
|
||||
|
||||
final String? title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
title: FlowyText.semibold(
|
||||
title ?? LocaleKeys.titleBar_pageIcon.tr(),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
leading: AppBarBackButton(
|
||||
onTap: () => context.pop(),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: FlowyMobileColorPicker(
|
||||
onSelectedColor: (option) => context.pop(option),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -39,11 +39,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_text.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
paragraphNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -54,11 +54,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_checkbox.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
todoListNode(checked: false),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -69,11 +69,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_heading1.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
headingNode(level: 1),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
BlockMenuItem(
|
||||
@ -82,11 +82,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_heading2.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
headingNode(level: 2),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
BlockMenuItem(
|
||||
@ -95,11 +95,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_heading3.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
headingNode(level: 3),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -110,11 +110,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_bulletedList.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
bulletedListNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -125,11 +125,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_numberedList.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
numberedListNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -140,11 +140,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.document_plugins_toggleList.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
toggleListBlockNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -155,11 +155,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_quote.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
quoteNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -171,11 +171,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.document_plugins_callout.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
calloutNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -186,11 +186,11 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.document_selectionMenu_codeBlock.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
service.closeItemMenu();
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
codeBlockNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
@ -201,8 +201,8 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.editor_divider.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertDivider(selection);
|
||||
service.closeItemMenu();
|
||||
await editorState.insertDivider(selection);
|
||||
},
|
||||
),
|
||||
|
||||
@ -216,8 +216,8 @@ final _addBlockMenuItems = [
|
||||
label: LocaleKeys.document_plugins_mathEquation_name.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertMathEquation(selection);
|
||||
service.closeItemMenu();
|
||||
await editorState.insertMathEquation(selection);
|
||||
},
|
||||
),
|
||||
];
|
||||
|
@ -1,9 +1,13 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_block_action_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/color/color_picker_screen.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:go_router/go_router.dart';
|
||||
|
||||
final mobileBlockSettingsToolbarItem = MobileToolbarItem.action(
|
||||
@ -24,55 +28,105 @@ final mobileBlockSettingsToolbarItem = MobileToolbarItem.action(
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await showFlowyMobileBottomSheet<bool>(
|
||||
await _showBlockActionSheet(
|
||||
context,
|
||||
title: LocaleKeys.document_plugins_action.tr(),
|
||||
builder: (context) {
|
||||
return BlockActionBottomSheet(
|
||||
onAction: (action) async {
|
||||
context.pop(true);
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
switch (action) {
|
||||
case BlockActionBottomSheetType.delete:
|
||||
transaction.deleteNode(node);
|
||||
break;
|
||||
case BlockActionBottomSheetType.duplicate:
|
||||
transaction.insertNode(
|
||||
node.path.next,
|
||||
node.copyWith(),
|
||||
);
|
||||
break;
|
||||
case BlockActionBottomSheetType.insertAbove:
|
||||
case BlockActionBottomSheetType.insertBelow:
|
||||
final path = action == BlockActionBottomSheetType.insertAbove
|
||||
? node.path
|
||||
: node.path.next;
|
||||
transaction
|
||||
..insertNode(
|
||||
path,
|
||||
paragraphNode(),
|
||||
)
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: path,
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
if (transaction.operations.isNotEmpty) {
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
editorState,
|
||||
node,
|
||||
selection,
|
||||
);
|
||||
|
||||
if (result != true) {
|
||||
// restore the selection
|
||||
editorState.selection = selection;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Future<void> _showBlockActionSheet(
|
||||
BuildContext context,
|
||||
EditorState editorState,
|
||||
Node node,
|
||||
Selection selection,
|
||||
) async {
|
||||
final result = await showFlowyMobileBottomSheet<bool>(
|
||||
context,
|
||||
title: LocaleKeys.document_plugins_action.tr(),
|
||||
builder: (context) {
|
||||
return BlockActionBottomSheet(
|
||||
extendActionWidgets: [
|
||||
const VSpace(8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_color_m,
|
||||
text: LocaleKeys.document_plugins_optionAction_color.tr(),
|
||||
onTap: () async {
|
||||
final option = await context.push<FlowyColorOption?>(
|
||||
Uri(
|
||||
path: MobileColorPickerScreen.routeName,
|
||||
queryParameters: {
|
||||
MobileColorPickerScreen.pageTitle: LocaleKeys
|
||||
.document_plugins_optionAction_color
|
||||
.tr(),
|
||||
},
|
||||
).toString(),
|
||||
);
|
||||
if (option != null) {
|
||||
final transaction = editorState.transaction;
|
||||
transaction.updateNode(node, {
|
||||
blockComponentBackgroundColor: option.id,
|
||||
});
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
if (context.mounted) {
|
||||
context.pop(true);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
// more options ...
|
||||
],
|
||||
),
|
||||
],
|
||||
onAction: (action) async {
|
||||
context.pop(true);
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
switch (action) {
|
||||
case BlockActionBottomSheetType.delete:
|
||||
transaction.deleteNode(node);
|
||||
break;
|
||||
case BlockActionBottomSheetType.duplicate:
|
||||
transaction.insertNode(
|
||||
node.path.next,
|
||||
node.copyWith(),
|
||||
);
|
||||
break;
|
||||
case BlockActionBottomSheetType.insertAbove:
|
||||
case BlockActionBottomSheetType.insertBelow:
|
||||
final path = action == BlockActionBottomSheetType.insertAbove
|
||||
? node.path
|
||||
: node.path.next;
|
||||
transaction
|
||||
..insertNode(
|
||||
path,
|
||||
paragraphNode(),
|
||||
)
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: path,
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
if (transaction.operations.isNotEmpty) {
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (result != true) {
|
||||
// restore the selection
|
||||
editorState.selection = selection;
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ export 'mobile_toolbar_item/mobile_block_settings_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_convert_block_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_indent_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_text_decoration_item.dart';
|
||||
export 'mobile_toolbar_item/undo_redo/redo_mobile_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/undo_redo/undo_mobile_toolbar_item.dart';
|
||||
export 'openai/widgets/auto_completion_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_toolbar_item.dart';
|
||||
@ -41,5 +43,3 @@ export 'table/table_menu.dart';
|
||||
export 'table/table_option_action.dart';
|
||||
export 'toggle/toggle_block_component.dart';
|
||||
export 'toggle/toggle_block_shortcut_event.dart';
|
||||
export 'undo_redo/redo_mobile_toolbar_item.dart';
|
||||
export 'undo_redo/undo_mobile_toolbar_item.dart';
|
||||
|
@ -9,9 +9,10 @@ import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_page.dart'
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/base/color/color_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_block_settings_screen.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
@ -69,6 +70,9 @@ GoRouter generateRouter(Widget child) {
|
||||
_mobileEmojiPickerPageRoute(),
|
||||
_mobileImagePickerPageRoute(),
|
||||
|
||||
// color picker
|
||||
_mobileColorPickerPageRoute(),
|
||||
|
||||
// code language picker
|
||||
_mobileCodeLanguagePickerPageRoute(),
|
||||
_mobileLanguagePickerPageRoute(),
|
||||
@ -263,6 +267,22 @@ GoRoute _mobileEmojiPickerPageRoute() {
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _mobileColorPickerPageRoute() {
|
||||
return GoRoute(
|
||||
parentNavigatorKey: AppGlobals.rootNavKey,
|
||||
path: MobileColorPickerScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
final title =
|
||||
state.uri.queryParameters[MobileColorPickerScreen.pageTitle] ?? '';
|
||||
return MaterialPage(
|
||||
child: MobileColorPickerScreen(
|
||||
title: title,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _mobileImagePickerPageRoute() {
|
||||
return GoRoute(
|
||||
parentNavigatorKey: AppGlobals.rootNavKey,
|
||||
|
@ -27,6 +27,7 @@ class FlowyButton extends StatelessWidget {
|
||||
final bool expandText;
|
||||
final MainAxisAlignment mainAxisAlignment;
|
||||
final bool showDefaultBoxDecorationOnMobile;
|
||||
final double iconPadding;
|
||||
|
||||
const FlowyButton({
|
||||
Key? key,
|
||||
@ -48,6 +49,7 @@ class FlowyButton extends StatelessWidget {
|
||||
this.expandText = true,
|
||||
this.mainAxisAlignment = MainAxisAlignment.center,
|
||||
this.showDefaultBoxDecorationOnMobile = false,
|
||||
this.iconPadding = 6,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -92,7 +94,7 @@ class FlowyButton extends StatelessWidget {
|
||||
child: leftIcon!,
|
||||
),
|
||||
);
|
||||
children.add(const HSpace(6));
|
||||
children.add(HSpace(iconPadding));
|
||||
}
|
||||
|
||||
if (expandText) {
|
||||
|
Loading…
Reference in New Issue
Block a user