diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index b751bb6d3e..4bb37ea078 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -177,7 +177,9 @@ "limeColor": "Lime", "greenColor": "Green", "aquaColor": "Aqua", - "blueColor": "Blue" + "blueColor": "Blue", + "deleteTag": "Delete tag", + "colorPannelTitle": "Colors" } } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/option_pannel_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/option_pannel_bloc.dart index 445db413c0..715cde72d7 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/option_pannel_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/option_pannel_bloc.dart @@ -19,6 +19,12 @@ class OptionPannelBloc extends Bloc<OptionPannelEvent, OptionPannelState> { endAddingOption: (_EndAddingOption value) { emit(state.copyWith(isEditingOption: false, newOptionName: none())); }, + updateOption: (_UpdateOption value) { + emit(state.copyWith(updateOption: Some(value.option))); + }, + deleteOption: (_DeleteOption value) { + emit(state.copyWith(deleteOption: Some(value.option))); + }, ); }, ); @@ -35,6 +41,8 @@ class OptionPannelEvent with _$OptionPannelEvent { const factory OptionPannelEvent.createOption(String optionName) = _CreateOption; const factory OptionPannelEvent.beginAddingOption() = _BeginAddingOption; const factory OptionPannelEvent.endAddingOption() = _EndAddingOption; + const factory OptionPannelEvent.updateOption(SelectOption option) = _UpdateOption; + const factory OptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption; } @freezed @@ -43,11 +51,15 @@ class OptionPannelState with _$OptionPannelState { required List<SelectOption> options, required bool isEditingOption, required Option<String> newOptionName, + required Option<SelectOption> updateOption, + required Option<SelectOption> deleteOption, }) = _OptionPannelState; factory OptionPannelState.initial(List<SelectOption> options) => OptionPannelState( options: options, isEditingOption: false, newOptionName: none(), + updateOption: none(), + deleteOption: none(), ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart index 07a26439f3..03053b2dbb 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart @@ -3,7 +3,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; - +import 'package:protobuf/protobuf.dart'; import 'type_option_service.dart'; part 'single_select_bloc.freezed.dart'; @@ -11,9 +11,13 @@ part 'single_select_bloc.freezed.dart'; class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, SingleSelectTypeOptionState> { final TypeOptionService service; - SingleSelectTypeOptionBloc(SingleSelectTypeOption typeOption, String fieldId) - : service = TypeOptionService(fieldId: fieldId), - super(SingleSelectTypeOptionState.initial(typeOption)) { + SingleSelectTypeOptionBloc( + SingleSelectTypeOption typeOption, + String fieldId, + ) : service = TypeOptionService(fieldId: fieldId), + super( + SingleSelectTypeOptionState.initial(typeOption), + ) { on<SingleSelectTypeOptionEvent>( (event, emit) async { await event.map( @@ -21,13 +25,17 @@ class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, Singl final result = await service.createOption(value.optionName); result.fold( (option) { - state.typeOption.options.insert(0, option); - emit(state); + emit(state.copyWith(typeOption: _insertOption(option))); }, (err) => Log.error(err), ); }, - updateOptions: (_UpdateOptions value) async {}, + updateOption: (_UpdateOption value) async { + emit(state.copyWith(typeOption: _updateOption(value.option))); + }, + deleteOption: (_DeleteOption value) { + emit(state.copyWith(typeOption: _deleteOption(value.option))); + }, ); }, ); @@ -37,12 +45,40 @@ class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, Singl Future<void> close() async { return super.close(); } + + SingleSelectTypeOption _insertOption(SelectOption option) { + state.typeOption.freeze(); + return state.typeOption.rebuild((typeOption) { + typeOption.options.insert(0, option); + }); + } + + SingleSelectTypeOption _updateOption(SelectOption option) { + state.typeOption.freeze(); + return state.typeOption.rebuild((typeOption) { + final index = typeOption.options.indexWhere((element) => element.id == option.id); + if (index != -1) { + typeOption.options[index] = option; + } + }); + } + + SingleSelectTypeOption _deleteOption(SelectOption option) { + state.typeOption.freeze(); + return state.typeOption.rebuild((typeOption) { + final index = typeOption.options.indexWhere((element) => element.id == option.id); + if (index != -1) { + typeOption.options.removeAt(index); + } + }); + } } @freezed class SingleSelectTypeOptionEvent with _$SingleSelectTypeOptionEvent { const factory SingleSelectTypeOptionEvent.createOption(String optionName) = _CreateOption; - const factory SingleSelectTypeOptionEvent.updateOptions(List<SelectOption> options) = _UpdateOptions; + const factory SingleSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption; + const factory SingleSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption; } @freezed diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart index 1a418cf543..ce34097118 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart @@ -79,7 +79,7 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> { final list = FieldTypeList(onSelectField: (fieldType) { context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.toFieldType(fieldType)); }); - _showOverlay(context, FieldTypeList.identifier(), list); + _showOverlay(context, list); }, leftIcon: svg(field.fieldType.iconName(), color: theme.iconColor), rightIcon: svg("grid/more", color: theme.iconColor), @@ -92,18 +92,27 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> { required Field field, required TypeOptionData data, }) { - final delegate = TypeOptionOperationDelegate( - didUpdateTypeOptionData: (data) { - context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.didUpdateTypeOptionData(data)); - }, - requireToShowOverlay: _showOverlay, + final overlayDelegate = TypeOptionOverlayDelegate( + showOverlay: _showOverlay, hideOverlay: _hideOverlay, ); - final builder = _makeTypeOptionBuild(field: field, data: data, delegate: delegate); + + final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) { + context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.didUpdateTypeOptionData(data)); + }); + + final builder = _makeTypeOptionBuild( + field: field, + data: data, + overlayDelegate: overlayDelegate, + dataDelegate: dataDelegate, + ); + return builder.customWidget; } - void _showOverlay(BuildContext context, String identifier, Widget child) { + void _showOverlay(BuildContext context, Widget child, {VoidCallback? onRemoved}) { + final identifier = child.toString(); if (currentOverlayIdentifier != null) { FlowyOverlay.of(context).remove(currentOverlayIdentifier!); } @@ -112,7 +121,7 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> { FlowyOverlay.of(context).insertWithAnchor( widget: OverlayContainer( child: child, - constraints: BoxConstraints.loose(const Size(240, 400)), + constraints: BoxConstraints.loose(const Size(340, 400)), ), identifier: identifier, anchorContext: context, @@ -136,19 +145,20 @@ abstract class TypeOptionBuilder { TypeOptionBuilder _makeTypeOptionBuild({ required Field field, required TypeOptionData data, - required TypeOptionOperationDelegate delegate, + required TypeOptionOverlayDelegate overlayDelegate, + required TypeOptionDataDelegate dataDelegate, }) { switch (field.fieldType) { case FieldType.Checkbox: return CheckboxTypeOptionBuilder(data); case FieldType.DateTime: - return DateTypeOptionBuilder(data, delegate); + return DateTypeOptionBuilder(data, overlayDelegate, dataDelegate); case FieldType.SingleSelect: - return SingleSelectTypeOptionBuilder(field.id, data, delegate); + return SingleSelectTypeOptionBuilder(field.id, data, overlayDelegate, dataDelegate); case FieldType.MultiSelect: - return MultiSelectTypeOptionBuilder(data, delegate); + return MultiSelectTypeOptionBuilder(data, overlayDelegate); case FieldType.Number: - return NumberTypeOptionBuilder(data, delegate); + return NumberTypeOptionBuilder(data, overlayDelegate, dataDelegate); case FieldType.RichText: return RichTextTypeOptionBuilder(data); @@ -163,20 +173,30 @@ abstract class TypeOptionWidget extends StatelessWidget { typedef TypeOptionData = Uint8List; typedef TypeOptionDataCallback = void Function(TypeOptionData typeOptionData); -typedef ShowOverlayCallback = void Function(BuildContext anchorContext, String overlayIdentifier, Widget child); +typedef ShowOverlayCallback = void Function( + BuildContext anchorContext, + Widget child, { + VoidCallback? onRemoved, +}); typedef HideOverlayCallback = void Function(BuildContext anchorContext); -class TypeOptionOperationDelegate { - TypeOptionDataCallback didUpdateTypeOptionData; - ShowOverlayCallback requireToShowOverlay; +class TypeOptionOverlayDelegate { + ShowOverlayCallback showOverlay; HideOverlayCallback hideOverlay; - TypeOptionOperationDelegate({ - required this.didUpdateTypeOptionData, - required this.requireToShowOverlay, + TypeOptionOverlayDelegate({ + required this.showOverlay, required this.hideOverlay, }); } +class TypeOptionDataDelegate { + TypeOptionDataCallback didUpdateTypeOptionData; + + TypeOptionDataDelegate({ + required this.didUpdateTypeOptionData, + }); +} + class RichTextTypeOptionBuilder extends TypeOptionBuilder { RichTextTypeOption typeOption; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart index 0019ffa979..4511a84a1f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart @@ -17,22 +17,6 @@ class FieldTypeList extends StatelessWidget { final SelectFieldCallback onSelectField; const FieldTypeList({required this.onSelectField, Key? key}) : super(key: key); - static void show(BuildContext context, SelectFieldCallback onSelectField) { - final list = FieldTypeList(onSelectField: onSelectField); - FieldTypeList.hide(context); - FlowyOverlay.of(context).insertWithAnchor( - widget: OverlayContainer( - child: list, - constraints: BoxConstraints.loose(const Size(140, 300)), - ), - identifier: FieldTypeList.identifier(), - anchorContext: context, - anchorDirection: AnchorDirection.leftWithCenterAligned, - style: FlowyOverlayStyle(blur: false), - anchorOffset: const Offset(-20, 0), - ); - } - static void hide(BuildContext context) { FlowyOverlay.of(context).remove(FieldTypeList.identifier()); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart index 493ab0e02e..581a3934e7 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart @@ -15,30 +15,39 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class DateTypeOptionBuilder extends TypeOptionBuilder { - DateTypeOption typeOption; - TypeOptionOperationDelegate delegate; + final DateTypeOptionWidget _widget; - DateTypeOptionBuilder(TypeOptionData typeOptionData, this.delegate) - : typeOption = DateTypeOption.fromBuffer(typeOptionData); + DateTypeOptionBuilder( + TypeOptionData typeOptionData, + TypeOptionOverlayDelegate overlayDelegate, + TypeOptionDataDelegate dataDelegate, + ) : _widget = DateTypeOptionWidget( + typeOption: DateTypeOption.fromBuffer(typeOptionData), + dataDelegate: dataDelegate, + overlayDelegate: overlayDelegate, + ); @override - Widget? get customWidget => DateTypeOptionWidget( - typeOption: typeOption, - operationDelegate: delegate, - ); + Widget? get customWidget => _widget; } class DateTypeOptionWidget extends TypeOptionWidget { final DateTypeOption typeOption; - final TypeOptionOperationDelegate operationDelegate; - const DateTypeOptionWidget({required this.typeOption, required this.operationDelegate, Key? key}) : super(key: key); + final TypeOptionOverlayDelegate overlayDelegate; + final TypeOptionDataDelegate dataDelegate; + const DateTypeOptionWidget({ + required this.typeOption, + required this.dataDelegate, + required this.overlayDelegate, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => getIt<DateTypeOptionBloc>(param1: typeOption), child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>( - listener: (context, state) => operationDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), + listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), builder: (context, state) { return Column(children: [ _dateFormatButton(context), @@ -61,7 +70,7 @@ class DateTypeOptionWidget extends TypeOptionWidget { final list = DateFormatList(onSelected: (format) { context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectDateFormat(format)); }); - operationDelegate.requireToShowOverlay(context, list.identifier(), list); + overlayDelegate.showOverlay(context, list); }, rightIcon: svg("grid/more", color: theme.iconColor), ), @@ -80,7 +89,7 @@ class DateTypeOptionWidget extends TypeOptionWidget { final list = TimeFormatList(onSelected: (format) { context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectTimeFormat(format)); }); - operationDelegate.requireToShowOverlay(context, list.identifier(), list); + overlayDelegate.showOverlay(context, list); }, rightIcon: svg("grid/more", color: theme.iconColor), ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart index 31f9570602..175f830657 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart @@ -1,18 +1,151 @@ +import 'package:app_flowy/workspace/application/grid/field/type_option/edit_option_bloc.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/widget.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; +class EditSelectOptionPannel extends StatelessWidget { + final SelectOption option; + final VoidCallback onDeleted; + final Function(SelectOption) onUpdated; + const EditSelectOptionPannel({ + required this.option, + required this.onDeleted, + required this.onUpdated, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => EditOptionBloc(option: option), + child: MultiBlocListener( + listeners: [ + BlocListener<EditOptionBloc, EditOptionState>( + listenWhen: (p, c) => p.deleted != c.deleted, + listener: (context, state) { + state.deleted.fold(() => null, (_) => onDeleted()); + }, + ), + BlocListener<EditOptionBloc, EditOptionState>( + listenWhen: (p, c) => p.option != c.option, + listener: (context, state) { + onUpdated(state.option); + }, + ), + ], + child: BlocBuilder<EditOptionBloc, EditOptionState>( + builder: (context, state) { + List<Widget> slivers = [ + SliverToBoxAdapter(child: _OptionNameTextField(state.option.name)), + const SliverToBoxAdapter(child: VSpace(10)), + const SliverToBoxAdapter(child: _DeleteTag()), + const SliverToBoxAdapter(child: TypeOptionSeparator()), + const SliverToBoxAdapter(child: SelectOptionColorList()), + ]; + + return CustomScrollView( + slivers: slivers, + controller: ScrollController(), + physics: StyledScrollPhysics(), + ); + }, + ), + ), + ); + } +} + +class _DeleteTag extends StatelessWidget { + const _DeleteTag({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch<AppTheme>(); + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium(LocaleKeys.grid_selectOption_deleteTag.tr(), fontSize: 12), + hoverColor: theme.hover, + leftIcon: svg("grid/delete", color: theme.iconColor), + onTap: () { + context.read<EditOptionBloc>().add(const EditOptionEvent.delete()); + }, + ), + ); + } +} + +class _OptionNameTextField extends StatelessWidget { + final String name; + const _OptionNameTextField(this.name, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return NameTextField( + name: name, + onCanceled: () {}, + onDone: (optionName) { + context.read<EditOptionBloc>().add(EditOptionEvent.updateName(optionName)); + }, + ); + } +} + class SelectOptionColorList extends StatelessWidget { const SelectOptionColorList({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return Container(); + final optionItems = SelectOptionColor.values.map((option) { + // Color color = option.color(); + // var hex = option.color.value.toRadixString(16); + // if (hex.startsWith('ff')) { + // hex = hex.substring(2); + // } + // hex = '#$hex'; + + return _SelectOptionColorItem(option: option, isSelected: true); + }).toList(); + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: GridSize.typeOptionContentInsets, + child: SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyText.medium( + LocaleKeys.grid_selectOption_colorPannelTitle.tr(), + fontSize: 12, + textAlign: TextAlign.left, + ), + ), + ), + ListView.separated( + shrinkWrap: true, + controller: ScrollController(), + separatorBuilder: (context, index) { + return VSpace(GridSize.typeOptionSeparatorHeight); + }, + itemCount: optionItems.length, + physics: StyledScrollPhysics(), + itemBuilder: (BuildContext context, int index) { + return optionItems[index]; + }, + ), + ], + ); } } @@ -24,12 +157,12 @@ class _SelectOptionColorItem extends StatelessWidget { @override Widget build(BuildContext context) { final theme = context.watch<AppTheme>(); - Widget? checkmark; if (isSelected) { checkmark = svg("grid/details", color: theme.iconColor); } + final String hex = '#${option.color(context).value.toRadixString(16)}'; final colorIcon = SizedBox.square( dimension: 16, child: Container( @@ -40,15 +173,17 @@ class _SelectOptionColorItem extends StatelessWidget { ), ); - return FlowyButton( - text: FlowyText.medium( - option.name(), - fontSize: 12, + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium(option.name(), fontSize: 12), + hoverColor: theme.hover, + leftIcon: colorIcon, + rightIcon: checkmark, + onTap: () { + context.read<EditOptionBloc>().add(EditOptionEvent.updateColor(hex)); + }, ), - hoverColor: theme.hover, - leftIcon: colorIcon, - rightIcon: checkmark, - onTap: () {}, ); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart index 4a3694b5fc..43cf2597fa 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart @@ -9,7 +9,7 @@ import 'option_pannel.dart'; class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { MultiSelectTypeOption typeOption; - TypeOptionOperationDelegate delegate; + TypeOptionOverlayDelegate delegate; MultiSelectTypeOptionBuilder(TypeOptionData typeOptionData, this.delegate) : typeOption = MultiSelectTypeOption.fromBuffer(typeOptionData); @@ -20,8 +20,8 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { class MultiSelectTypeOptionWidget extends TypeOptionWidget { final MultiSelectTypeOption typeOption; - final TypeOptionOperationDelegate delegate; - const MultiSelectTypeOptionWidget(this.typeOption, this.delegate, {Key? key}) : super(key: key); + final TypeOptionOverlayDelegate overlayDelegate; + const MultiSelectTypeOptionWidget(this.typeOption, this.overlayDelegate, {Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -32,14 +32,14 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget { return OptionPannel( options: state.typeOption.options, beginEdit: () { - delegate.hideOverlay(context); + overlayDelegate.hideOverlay(context); }, createOptionCallback: (name) { context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.createOption(name)); }, - updateOptionsCallback: (options) { - context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.updateOptions(options)); - }, + updateOptionCallback: (updateOption) {}, + deleteOptionCallback: (deleteOption) {}, + overlayDelegate: overlayDelegate, ); }, ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart index 57a8254f2e..97dffd17e1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart @@ -15,25 +15,29 @@ import 'package:easy_localization/easy_localization.dart' hide NumberFormat; import 'package:app_flowy/generated/locale_keys.g.dart'; class NumberTypeOptionBuilder extends TypeOptionBuilder { - NumberTypeOption typeOption; - TypeOptionOperationDelegate delegate; + final NumberTypeOptionWidget _widget; NumberTypeOptionBuilder( TypeOptionData typeOptionData, - this.delegate, - ) : typeOption = NumberTypeOption.fromBuffer(typeOptionData); + TypeOptionOverlayDelegate overlayDelegate, + TypeOptionDataDelegate dataDelegate, + ) : _widget = NumberTypeOptionWidget( + typeOption: NumberTypeOption.fromBuffer(typeOptionData), + dataDelegate: dataDelegate, + overlayDelegate: overlayDelegate, + ); @override - Widget? get customWidget => NumberTypeOptionWidget( - typeOption: typeOption, - operationDelegate: delegate, - ); + Widget? get customWidget => _widget; } class NumberTypeOptionWidget extends TypeOptionWidget { - final TypeOptionOperationDelegate operationDelegate; + final TypeOptionDataDelegate dataDelegate; + final TypeOptionOverlayDelegate overlayDelegate; final NumberTypeOption typeOption; - const NumberTypeOptionWidget({required this.typeOption, required this.operationDelegate, Key? key}) : super(key: key); + const NumberTypeOptionWidget( + {required this.typeOption, required this.dataDelegate, required this.overlayDelegate, Key? key}) + : super(key: key); @override Widget build(BuildContext context) { @@ -43,7 +47,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget { child: SizedBox( height: GridSize.typeOptionItemHeight, child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>( - listener: (context, state) => operationDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), + listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), builder: (context, state) { return FlowyButton( text: FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(), fontSize: 12), @@ -53,7 +57,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget { final list = NumberFormatList(onSelected: (format) { context.read<NumberTypeOptionBloc>().add(NumberTypeOptionEvent.didSelectFormat(format)); }); - operationDelegate.requireToShowOverlay(context, list.identifier(), list); + overlayDelegate.showOverlay(context, list); }, rightIcon: svg("grid/more", color: theme.iconColor), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/option_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/option_pannel.dart index 736d03d96d..2c4a1b5e4a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/option_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/option_pannel.dart @@ -1,5 +1,6 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/option_pannel_bloc.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; @@ -11,18 +12,24 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'edit_option_pannel.dart'; import 'widget.dart'; class OptionPannel extends StatelessWidget { final List<SelectOption> options; final VoidCallback beginEdit; final Function(String optionName) createOptionCallback; - final Function(List<SelectOption>) updateOptionsCallback; + final Function(SelectOption) updateOptionCallback; + final Function(SelectOption) deleteOptionCallback; + final TypeOptionOverlayDelegate overlayDelegate; + const OptionPannel({ required this.options, required this.beginEdit, required this.createOptionCallback, - required this.updateOptionsCallback, + required this.updateOptionCallback, + required this.deleteOptionCallback, + required this.overlayDelegate, Key? key, }) : super(key: key); @@ -39,6 +46,16 @@ class OptionPannel extends StatelessWidget { () => null, (optionName) => createOptionCallback(optionName), ); + + state.updateOption.fold( + () => null, + (updateOption) => updateOptionCallback(updateOption), + ); + + state.deleteOption.fold( + () => null, + (deleteOption) => deleteOptionCallback(deleteOption), + ); }, builder: (context, state) { List<Widget> children = [ @@ -46,7 +63,7 @@ class OptionPannel extends StatelessWidget { const OptionTitle(), ]; if (state.isEditingOption) { - children.add(const _AddOptionTextField()); + children.add(const _OptionNameTextField()); } if (state.options.isEmpty && !state.isEditingOption) { @@ -54,7 +71,7 @@ class OptionPannel extends StatelessWidget { } if (state.options.isNotEmpty) { - children.add(_OptionList(key: ObjectKey(state.options))); + children.add(_OptionList(overlayDelegate)); } return Column(children: children); @@ -105,14 +122,18 @@ class OptionTitle extends StatelessWidget { } class _OptionList extends StatelessWidget { - const _OptionList({Key? key}) : super(key: key); + final TypeOptionOverlayDelegate delegate; + const _OptionList(this.delegate, {Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder<OptionPannelBloc, OptionPannelState>( + buildWhen: (previous, current) { + return previous.options != current.options; + }, builder: (context, state) { final optionItems = state.options.map((option) { - return _OptionItem(option: option); + return _makeOptionItem(context, option); }).toList(); return ListView.separated( @@ -129,11 +150,37 @@ class _OptionList extends StatelessWidget { }, ); } + + _OptionItem _makeOptionItem(BuildContext context, SelectOption option) { + return _OptionItem( + option: option, + onEdited: (option) { + final pannel = EditSelectOptionPannel( + option: option, + onDeleted: () { + delegate.hideOverlay(context); + context.read<OptionPannelBloc>().add(OptionPannelEvent.deleteOption(option)); + }, + onUpdated: (updatedOption) { + delegate.hideOverlay(context); + context.read<OptionPannelBloc>().add(OptionPannelEvent.updateOption(updatedOption)); + }, + key: ValueKey(option.id), + ); + delegate.showOverlay(context, pannel); + }, + ); + } } class _OptionItem extends StatelessWidget { final SelectOption option; - const _OptionItem({required this.option, Key? key}) : super(key: key); + final Function(SelectOption) onEdited; + const _OptionItem({ + required this.option, + required this.onEdited, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -143,7 +190,7 @@ class _OptionItem extends StatelessWidget { child: FlowyButton( text: FlowyText.medium(option.name, fontSize: 12), hoverColor: theme.hover, - onTap: () {}, + onTap: () => onEdited(option), rightIcon: svg("grid/details", color: theme.iconColor), ), ); @@ -170,8 +217,8 @@ class _AddOptionButton extends StatelessWidget { } } -class _AddOptionTextField extends StatelessWidget { - const _AddOptionTextField({Key? key}) : super(key: key); +class _OptionNameTextField extends StatelessWidget { + const _OptionNameTextField({Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart index 6690729ff0..76573ef945 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart @@ -12,11 +12,13 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { SingleSelectTypeOptionBuilder( String fieldId, TypeOptionData typeOptionData, - TypeOptionOperationDelegate delegate, + TypeOptionOverlayDelegate overlayDelegate, + TypeOptionDataDelegate dataDelegate, ) : _widget = SingleSelectTypeOptionWidget( - fieldId, - SingleSelectTypeOption.fromBuffer(typeOptionData), - delegate, + fieldId: fieldId, + typeOption: SingleSelectTypeOption.fromBuffer(typeOptionData), + dataDelegate: dataDelegate, + overlayDelegate: overlayDelegate, ); @override @@ -26,27 +28,41 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { class SingleSelectTypeOptionWidget extends TypeOptionWidget { final String fieldId; final SingleSelectTypeOption typeOption; - final TypeOptionOperationDelegate delegate; - const SingleSelectTypeOptionWidget(this.fieldId, this.typeOption, this.delegate, {Key? key}) : super(key: key); + final TypeOptionOverlayDelegate overlayDelegate; + final TypeOptionDataDelegate dataDelegate; + const SingleSelectTypeOptionWidget({ + required this.fieldId, + required this.typeOption, + required this.dataDelegate, + required this.overlayDelegate, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => getIt<SingleSelectTypeOptionBloc>(param1: typeOption, param2: fieldId), child: BlocConsumer<SingleSelectTypeOptionBloc, SingleSelectTypeOptionState>( - listener: (context, state) => delegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), + listener: (context, state) { + dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()); + }, builder: (context, state) { return OptionPannel( options: state.typeOption.options, beginEdit: () { - delegate.hideOverlay(context); + overlayDelegate.hideOverlay(context); }, createOptionCallback: (name) { context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.createOption(name)); }, - updateOptionsCallback: (options) { - context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.updateOptions(options)); + updateOptionCallback: (updateOption) { + context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.updateOption(updateOption)); }, + deleteOptionCallback: (deleteOption) { + context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.deleteOption(deleteOption)); + }, + overlayDelegate: overlayDelegate, + key: ValueKey(state.typeOption.hashCode), ); }, ),