From 34275664b2131c7e72b51ece2390282c43e5d466 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 14 Sep 2022 16:35:29 +0800 Subject: [PATCH] feat: enable delete field in edit row detail page --- .../app_flowy/assets/translations/en.json | 3 +- .../board/presentation/board_page.dart | 9 ++- .../application/field/field_editor_bloc.dart | 28 ++++++++- .../header/field_cell_action_sheet.dart | 13 +++- .../widgets/header/field_editor.dart | 62 ++++++++++++++++++- .../widgets/row/row_action_sheet.dart | 13 +++- .../presentation/widgets/row/row_detail.dart | 14 ++++- .../presentation/widgets/dialogs.dart | 18 ++++-- .../lib/widget/buttons/primary_button.dart | 26 +++++--- .../lib/widget/buttons/secondary_button.dart | 25 +++++--- .../lib/widget/dialog/dialog_size.dart | 2 +- .../lib/widget/dialog/styled_dialogs.dart | 4 +- 12 files changed, 178 insertions(+), 39 deletions(-) diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 141999b4fd..873bbc93ee 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -191,7 +191,8 @@ "optionTitle": "Options", "addOption": "Add option", "editProperty": "Edit property", - "newColumn": "New column" + "newColumn": "New column", + "deleteFieldPromptMessage": "Are you sure? This property will be deleted" }, "row": { "duplicate": "Duplicate", diff --git a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart index 3ad8d670d7..3a34469cd7 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -287,8 +287,13 @@ class _BoardContentState extends State { ); } - void _openCard(String gridId, GridFieldController fieldController, - RowPB rowPB, GridRowCache rowCache, BuildContext context) { + void _openCard( + String gridId, + GridFieldController fieldController, + RowPB rowPB, + GridRowCache rowCache, + BuildContext context, + ) { final rowInfo = RowInfo( gridId: gridId, fields: UnmodifiableListView(fieldController.fieldContexts), diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart index d7b024d67d..af50f47d67 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart @@ -2,6 +2,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'dart:async'; import 'package:dartz/dartz.dart'; +import 'field_service.dart'; import 'type_option/type_option_context.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -15,10 +16,11 @@ class FieldEditorBloc extends Bloc { FieldEditorBloc({ required String gridId, required String fieldName, + required bool isGroupField, required IFieldTypeOptionLoader loader, }) : dataController = TypeOptionDataController(gridId: gridId, loader: loader), - super(FieldEditorState.initial(gridId, fieldName)) { + super(FieldEditorState.initial(gridId, fieldName, isGroupField)) { on( (event, emit) async { await event.when( @@ -35,7 +37,23 @@ class FieldEditorBloc extends Bloc { emit(state.copyWith(name: name)); }, didReceiveFieldChanged: (FieldPB field) { - emit(state.copyWith(field: Some(field), name: field.name)); + emit(state.copyWith( + field: Some(field), + name: field.name, + canDelete: field.isPrimary, + )); + }, + deleteField: () { + state.field.fold( + () => null, + (field) { + final fieldService = FieldService( + gridId: gridId, + fieldId: field.id, + ); + fieldService.deleteField(); + }, + ); }, ); }, @@ -52,6 +70,7 @@ class FieldEditorBloc extends Bloc { class FieldEditorEvent with _$FieldEditorEvent { const factory FieldEditorEvent.initial() = _InitialField; const factory FieldEditorEvent.updateName(String name) = _UpdateName; + const factory FieldEditorEvent.deleteField() = _DeleteField; const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) = _DidReceiveFieldChanged; } @@ -63,16 +82,21 @@ class FieldEditorState with _$FieldEditorState { required String errorText, required String name, required Option field, + required bool canDelete, + required bool isGroupField, }) = _FieldEditorState; factory FieldEditorState.initial( String gridId, String fieldName, + bool isGroupField, ) => FieldEditorState( gridId: gridId, errorText: '', field: none(), + canDelete: false, name: fieldName, + isGroupField: isGroupField, ); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart index 8f11bb008f..d293a8d400 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart @@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; +import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -215,9 +216,15 @@ extension _FieldActionExtension on FieldAction { .add(const FieldActionSheetEvent.duplicateField()); break; case FieldAction.delete: - context - .read() - .add(const FieldActionSheetEvent.deleteField()); + FlowyAlertDialog( + title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), + confirm: () { + context + .read() + .add(const FieldActionSheetEvent.deleteField()); + }, + ).show(context); + break; } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart index 8a8c445ec4..907725bde1 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart @@ -2,6 +2,13 @@ import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart' import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:appflowy_popover/popover.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart'; +import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; @@ -13,6 +20,7 @@ import 'field_type_option_editor.dart'; class FieldEditor extends StatefulWidget { final String gridId; final String fieldName; + final bool isGroupField; final VoidCallback? onRemoved; final IFieldTypeOptionLoader typeOptionLoader; @@ -20,6 +28,7 @@ class FieldEditor extends StatefulWidget { required this.gridId, this.fieldName = "", required this.typeOptionLoader, + this.isGroupField = false, this.onRemoved, Key? key, }) : super(key: key); @@ -41,9 +50,10 @@ class _FieldEditorState extends State { Widget build(BuildContext context) { return BlocProvider( create: (context) => FieldEditorBloc( - gridId: widget.gridId, - fieldName: widget.fieldName, - loader: widget.typeOptionLoader, + gridId: gridId, + fieldName: fieldName, + isGroupField: isGroupField, + loader: typeOptionLoader, )..add(const FieldEditorEvent.initial()), child: BlocBuilder( buildWhen: (p, c) => false, @@ -56,6 +66,8 @@ class _FieldEditorState extends State { const VSpace(10), const _FieldNameCell(), const VSpace(10), + const _DeleteFieldButton(), + const VSpace(10), _FieldTypeOptionCell(popoverMutex: popoverMutex), ], ); @@ -114,3 +126,47 @@ class _FieldNameCell extends StatelessWidget { ); } } + +class _DeleteFieldButton extends StatelessWidget { + const _DeleteFieldButton({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return BlocBuilder( + builder: (context, state) { + final enable = !state.canDelete && !state.isGroupField; + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium( + LocaleKeys.grid_field_delete.tr(), + fontSize: 12, + color: enable ? null : theme.shader4, + ), + hoverColor: theme.hover, + onTap: () { + if (enable) { + FlowyAlertDialog( + title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), + cancel: () { + FlowyOverlay.of(context).remove(FieldEditor.identifier()); + }, + confirm: () { + context + .read() + .add(const FieldEditorEvent.deleteField()); + FlowyOverlay.of(context).remove(FieldEditor.identifier()); + }, + ).show(context); + } + }, + leftIcon: svgWidget('grid/delete', color: theme.iconColor), + ), + ); + }, + ); + } + + void so() {} +} diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart index 8c828ec627..4ab76a7f25 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart @@ -1,4 +1,5 @@ import 'package:app_flowy/plugins/grid/application/row/row_action_sheet_bloc.dart'; +import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flowy_infra/image.dart'; @@ -150,9 +151,15 @@ extension _RowActionExtension on _RowAction { .add(const RowActionSheetEvent.duplicateRow()); break; case _RowAction.delete: - context - .read() - .add(const RowActionSheetEvent.deleteRow()); + FlowyAlertDialog( + title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), + confirm: () { + context + .read() + .add(const RowActionSheetEvent.deleteRow()); + }, + ).show(context); + break; } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart index fe148d9dcb..66a9b4b77d 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart @@ -133,6 +133,7 @@ class _PropertyList extends StatelessWidget { ), ), ), + const VSpace(10), _CreateFieldButton( viewId: viewId, onClosed: () { @@ -180,8 +181,9 @@ class _CreateFieldButton extends StatelessWidget { triggerActions: PopoverTriggerActionFlags.click, direction: PopoverDirection.bottomWithLeftAligned, onClose: onClosed, - child: SizedBox( + child: Container( height: 40, + decoration: _makeBoxDecoration(context), child: FlowyButton( text: FlowyText.medium( LocaleKeys.grid_field_newColumn.tr(), @@ -195,6 +197,15 @@ class _CreateFieldButton extends StatelessWidget { popupBuilder: (BuildContext context) => onOpened(), ); } + + BoxDecoration _makeBoxDecoration(BuildContext context) { + final theme = context.read(); + final borderSide = BorderSide(color: theme.shader6, width: 1.0); + return BoxDecoration( + color: theme.surface, + border: Border(top: borderSide), + ); + } } class _RowDetailCell extends StatefulWidget { @@ -247,6 +258,7 @@ class _RowDetailCellState extends State<_RowDetailCell> { child: FieldEditor( gridId: widget.cellId.gridId, fieldName: widget.cellId.fieldContext.field.name, + isGroupField: widget.cellId.fieldContext.isGroupField, typeOptionLoader: FieldTypeOptionLoader( gridId: widget.cellId.gridId, field: widget.cellId.fieldContext.field, diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart index 3a5c1c79e5..be6e7ed580 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart @@ -56,7 +56,8 @@ class _CreateTextFieldDialog extends State { FlowyFormTextInput( hintText: LocaleKeys.dialogCreatePageNameHint.tr(), initialValue: widget.value, - textStyle: const TextStyle(fontSize: 24, fontWeight: FontWeight.w400), + textStyle: + const TextStyle(fontSize: 24, fontWeight: FontWeight.w400), autoFocus: true, onChanged: (text) { newValue = text; @@ -120,7 +121,7 @@ class _CreateFlowyAlertDialog extends State { const VSpace(20), OkCancelButton( onOkPressed: widget.confirm!, - onCancelPressed: widget.confirm, + onCancelPressed: widget.cancel, ) ] ], @@ -158,7 +159,7 @@ class OkCancelDialog extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (title != null) ...[ - Text(title!.toUpperCase(), style: TextStyles.T1.textColor(theme.shader1)), + FlowyText.medium(title!.toUpperCase(), color: theme.shader1), VSpace(Insets.sm * 1.5), Container(color: theme.bg1, height: 1), VSpace(Insets.m * 1.5), @@ -185,7 +186,12 @@ class OkCancelButton extends StatelessWidget { final double? minHeight; const OkCancelButton( - {Key? key, this.onOkPressed, this.onCancelPressed, this.okTitle, this.cancelTitle, this.minHeight}) + {Key? key, + this.onOkPressed, + this.onCancelPressed, + this.okTitle, + this.cancelTitle, + this.minHeight}) : super(key: key); @override @@ -200,7 +206,7 @@ class OkCancelButton extends StatelessWidget { cancelTitle ?? LocaleKeys.button_Cancel.tr(), onPressed: () { onCancelPressed!(); - AppGlobals.nav.pop(); + Navigator.of(context).pop(); }, bigMode: true, ), @@ -210,7 +216,7 @@ class OkCancelButton extends StatelessWidget { okTitle ?? LocaleKeys.button_OK.tr(), onPressed: () { onOkPressed!(); - AppGlobals.nav.pop(); + Navigator.of(context).pop(); }, bigMode: true, ), diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart index 2c0725288c..63e9ce2698 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart @@ -1,22 +1,30 @@ +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra/theme.dart'; import 'base_styled_button.dart'; -import 'package:textstyle_extensions/textstyle_extensions.dart'; class PrimaryTextButton extends StatelessWidget { final String label; final VoidCallback? onPressed; final bool bigMode; - const PrimaryTextButton(this.label, {Key? key, this.onPressed, this.bigMode = false}) : super(key: key); + const PrimaryTextButton(this.label, + {Key? key, this.onPressed, this.bigMode = false}) + : super(key: key); @override Widget build(BuildContext context) { - TextStyle txtStyle = TextStyles.Btn.textColor(Colors.white); - return PrimaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle)); + final theme = context.watch(); + return PrimaryButton( + bigMode: bigMode, + onPressed: onPressed, + child: FlowyText.regular( + label, + color: theme.surface, + ), + ); } } @@ -25,14 +33,16 @@ class PrimaryButton extends StatelessWidget { final VoidCallback? onPressed; final bool bigMode; - const PrimaryButton({Key? key, required this.child, this.onPressed, this.bigMode = false}) : super(key: key); + const PrimaryButton( + {Key? key, required this.child, this.onPressed, this.bigMode = false}) + : super(key: key); @override Widget build(BuildContext context) { final theme = context.watch(); return BaseStyledButton( - minWidth: bigMode ? 170 : 78, - minHeight: bigMode ? 48 : 28, + minWidth: bigMode ? 100 : 80, + minHeight: bigMode ? 40 : 38, contentPadding: EdgeInsets.zero, bgColor: theme.main1, hoverColor: theme.main1, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart index 9e6f7d331d..ef7a6e6051 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart @@ -1,9 +1,8 @@ +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // ignore: import_of_legacy_library_into_null_safe -import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra/theme.dart'; import 'base_styled_button.dart'; @@ -12,13 +11,21 @@ class SecondaryTextButton extends StatelessWidget { final VoidCallback? onPressed; final bool bigMode; - const SecondaryTextButton(this.label, {Key? key, this.onPressed, this.bigMode = false}) : super(key: key); + const SecondaryTextButton(this.label, + {Key? key, this.onPressed, this.bigMode = false}) + : super(key: key); @override Widget build(BuildContext context) { final theme = context.watch(); - TextStyle txtStyle = TextStyles.Btn.textColor(theme.main1); - return SecondaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle)); + return SecondaryButton( + bigMode: bigMode, + onPressed: onPressed, + child: FlowyText.regular( + label, + color: theme.main1, + ), + ); } } @@ -27,14 +34,16 @@ class SecondaryButton extends StatelessWidget { final VoidCallback? onPressed; final bool bigMode; - const SecondaryButton({Key? key, required this.child, this.onPressed, this.bigMode = false}) : super(key: key); + const SecondaryButton( + {Key? key, required this.child, this.onPressed, this.bigMode = false}) + : super(key: key); @override Widget build(BuildContext context) { final theme = context.watch(); return BaseStyledButton( - minWidth: bigMode ? 170 : 78, - minHeight: bigMode ? 48 : 28, + minWidth: bigMode ? 100 : 80, + minHeight: bigMode ? 40 : 38, contentPadding: EdgeInsets.zero, bgColor: theme.shader7, hoverColor: theme.hover, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/dialog_size.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/dialog_size.dart index e4a95b7b5f..b0a13d6bd3 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/dialog_size.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/dialog_size.dart @@ -1,3 +1,3 @@ class DialogSize { - static double get minDialogWidth => 480; + static double get minDialogWidth => 400; } diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart index ecd8cfb4ae..47cd51ae48 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart @@ -133,7 +133,9 @@ class StyledDialogRoute extends PopupRoute { super(settings: settings, filter: barrier.filter); @override - bool get barrierDismissible => barrier.dismissible; + bool get barrierDismissible { + return barrier.dismissible; + } @override String get barrierLabel => barrier.label;