feat: enable delete field in edit row detail page

This commit is contained in:
appflowy 2022-09-14 16:35:29 +08:00
parent 988e8db798
commit 34275664b2
12 changed files with 178 additions and 39 deletions

View File

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

View File

@ -287,8 +287,13 @@ class _BoardContentState extends State<BoardContent> {
);
}
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),

View File

@ -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<FieldEditorEvent, FieldEditorState> {
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<FieldEditorEvent>(
(event, emit) async {
await event.when(
@ -35,7 +37,23 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
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<FieldEditorEvent, FieldEditorState> {
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<FieldPB> 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,
);
}

View File

@ -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:
FlowyAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () {
context
.read<FieldActionSheetBloc>()
.add(const FieldActionSheetEvent.deleteField());
},
).show(context);
break;
}
}

View File

@ -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<FieldEditor> {
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<FieldEditorBloc, FieldEditorState>(
buildWhen: (p, c) => false,
@ -56,6 +66,8 @@ class _FieldEditorState extends State<FieldEditor> {
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<AppTheme>();
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
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<FieldEditorBloc>()
.add(const FieldEditorEvent.deleteField());
FlowyOverlay.of(context).remove(FieldEditor.identifier());
},
).show(context);
}
},
leftIcon: svgWidget('grid/delete', color: theme.iconColor),
),
);
},
);
}
void so() {}
}

View File

@ -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:
FlowyAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () {
context
.read<RowActionSheetBloc>()
.add(const RowActionSheetEvent.deleteRow());
},
).show(context);
break;
}
}

View File

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

View File

@ -56,7 +56,8 @@ class _CreateTextFieldDialog extends State<TextFieldDialog> {
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<FlowyAlertDialog> {
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: <Widget>[
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,
),

View File

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

View File

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

View File

@ -1,3 +1,3 @@
class DialogSize {
static double get minDialogWidth => 480;
static double get minDialogWidth => 400;
}

View File

@ -133,7 +133,9 @@ class StyledDialogRoute<T> extends PopupRoute<T> {
super(settings: settings, filter: barrier.filter);
@override
bool get barrierDismissible => barrier.dismissible;
bool get barrierDismissible {
return barrier.dismissible;
}
@override
String get barrierLabel => barrier.label;