feat: support changing block background color (#3998)

This commit is contained in:
Lucas.Xu 2023-11-24 15:30:20 +08:00 committed by GitHub
parent 1fad713477
commit 414bb26e56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 273 additions and 66 deletions

View File

@ -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

View File

@ -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,
),
),
);
}
}

View File

@ -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),
),
),
);
}
}

View File

@ -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);
},
),
];

View File

@ -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;
}
}

View File

@ -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';

View File

@ -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,

View File

@ -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) {