fix: fix bugs

This commit is contained in:
appflowy 2022-03-30 22:51:52 +08:00
parent 065a72a8da
commit 547b8ec29c
11 changed files with 373 additions and 108 deletions

View File

@ -177,7 +177,9 @@
"limeColor": "Lime", "limeColor": "Lime",
"greenColor": "Green", "greenColor": "Green",
"aquaColor": "Aqua", "aquaColor": "Aqua",
"blueColor": "Blue" "blueColor": "Blue",
"deleteTag": "Delete tag",
"colorPannelTitle": "Colors"
} }
} }
} }

View File

@ -19,6 +19,12 @@ class OptionPannelBloc extends Bloc<OptionPannelEvent, OptionPannelState> {
endAddingOption: (_EndAddingOption value) { endAddingOption: (_EndAddingOption value) {
emit(state.copyWith(isEditingOption: false, newOptionName: none())); 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.createOption(String optionName) = _CreateOption;
const factory OptionPannelEvent.beginAddingOption() = _BeginAddingOption; const factory OptionPannelEvent.beginAddingOption() = _BeginAddingOption;
const factory OptionPannelEvent.endAddingOption() = _EndAddingOption; const factory OptionPannelEvent.endAddingOption() = _EndAddingOption;
const factory OptionPannelEvent.updateOption(SelectOption option) = _UpdateOption;
const factory OptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption;
} }
@freezed @freezed
@ -43,11 +51,15 @@ class OptionPannelState with _$OptionPannelState {
required List<SelectOption> options, required List<SelectOption> options,
required bool isEditingOption, required bool isEditingOption,
required Option<String> newOptionName, required Option<String> newOptionName,
required Option<SelectOption> updateOption,
required Option<SelectOption> deleteOption,
}) = _OptionPannelState; }) = _OptionPannelState;
factory OptionPannelState.initial(List<SelectOption> options) => OptionPannelState( factory OptionPannelState.initial(List<SelectOption> options) => OptionPannelState(
options: options, options: options,
isEditingOption: false, isEditingOption: false,
newOptionName: none(), newOptionName: none(),
updateOption: none(),
deleteOption: none(),
); );
} }

View File

@ -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:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import 'package:protobuf/protobuf.dart';
import 'type_option_service.dart'; import 'type_option_service.dart';
part 'single_select_bloc.freezed.dart'; part 'single_select_bloc.freezed.dart';
@ -11,9 +11,13 @@ part 'single_select_bloc.freezed.dart';
class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, SingleSelectTypeOptionState> { class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, SingleSelectTypeOptionState> {
final TypeOptionService service; final TypeOptionService service;
SingleSelectTypeOptionBloc(SingleSelectTypeOption typeOption, String fieldId) SingleSelectTypeOptionBloc(
: service = TypeOptionService(fieldId: fieldId), SingleSelectTypeOption typeOption,
super(SingleSelectTypeOptionState.initial(typeOption)) { String fieldId,
) : service = TypeOptionService(fieldId: fieldId),
super(
SingleSelectTypeOptionState.initial(typeOption),
) {
on<SingleSelectTypeOptionEvent>( on<SingleSelectTypeOptionEvent>(
(event, emit) async { (event, emit) async {
await event.map( await event.map(
@ -21,13 +25,17 @@ class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, Singl
final result = await service.createOption(value.optionName); final result = await service.createOption(value.optionName);
result.fold( result.fold(
(option) { (option) {
state.typeOption.options.insert(0, option); emit(state.copyWith(typeOption: _insertOption(option)));
emit(state);
}, },
(err) => Log.error(err), (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 { Future<void> close() async {
return super.close(); 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 @freezed
class SingleSelectTypeOptionEvent with _$SingleSelectTypeOptionEvent { class SingleSelectTypeOptionEvent with _$SingleSelectTypeOptionEvent {
const factory SingleSelectTypeOptionEvent.createOption(String optionName) = _CreateOption; 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 @freezed

View File

@ -79,7 +79,7 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> {
final list = FieldTypeList(onSelectField: (fieldType) { final list = FieldTypeList(onSelectField: (fieldType) {
context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.toFieldType(fieldType)); context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.toFieldType(fieldType));
}); });
_showOverlay(context, FieldTypeList.identifier(), list); _showOverlay(context, list);
}, },
leftIcon: svg(field.fieldType.iconName(), color: theme.iconColor), leftIcon: svg(field.fieldType.iconName(), color: theme.iconColor),
rightIcon: svg("grid/more", color: theme.iconColor), rightIcon: svg("grid/more", color: theme.iconColor),
@ -92,18 +92,27 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> {
required Field field, required Field field,
required TypeOptionData data, required TypeOptionData data,
}) { }) {
final delegate = TypeOptionOperationDelegate( final overlayDelegate = TypeOptionOverlayDelegate(
didUpdateTypeOptionData: (data) { showOverlay: _showOverlay,
context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.didUpdateTypeOptionData(data));
},
requireToShowOverlay: _showOverlay,
hideOverlay: _hideOverlay, 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; 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) { if (currentOverlayIdentifier != null) {
FlowyOverlay.of(context).remove(currentOverlayIdentifier!); FlowyOverlay.of(context).remove(currentOverlayIdentifier!);
} }
@ -112,7 +121,7 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> {
FlowyOverlay.of(context).insertWithAnchor( FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer( widget: OverlayContainer(
child: child, child: child,
constraints: BoxConstraints.loose(const Size(240, 400)), constraints: BoxConstraints.loose(const Size(340, 400)),
), ),
identifier: identifier, identifier: identifier,
anchorContext: context, anchorContext: context,
@ -136,19 +145,20 @@ abstract class TypeOptionBuilder {
TypeOptionBuilder _makeTypeOptionBuild({ TypeOptionBuilder _makeTypeOptionBuild({
required Field field, required Field field,
required TypeOptionData data, required TypeOptionData data,
required TypeOptionOperationDelegate delegate, required TypeOptionOverlayDelegate overlayDelegate,
required TypeOptionDataDelegate dataDelegate,
}) { }) {
switch (field.fieldType) { switch (field.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
return CheckboxTypeOptionBuilder(data); return CheckboxTypeOptionBuilder(data);
case FieldType.DateTime: case FieldType.DateTime:
return DateTypeOptionBuilder(data, delegate); return DateTypeOptionBuilder(data, overlayDelegate, dataDelegate);
case FieldType.SingleSelect: case FieldType.SingleSelect:
return SingleSelectTypeOptionBuilder(field.id, data, delegate); return SingleSelectTypeOptionBuilder(field.id, data, overlayDelegate, dataDelegate);
case FieldType.MultiSelect: case FieldType.MultiSelect:
return MultiSelectTypeOptionBuilder(data, delegate); return MultiSelectTypeOptionBuilder(data, overlayDelegate);
case FieldType.Number: case FieldType.Number:
return NumberTypeOptionBuilder(data, delegate); return NumberTypeOptionBuilder(data, overlayDelegate, dataDelegate);
case FieldType.RichText: case FieldType.RichText:
return RichTextTypeOptionBuilder(data); return RichTextTypeOptionBuilder(data);
@ -163,20 +173,30 @@ abstract class TypeOptionWidget extends StatelessWidget {
typedef TypeOptionData = Uint8List; typedef TypeOptionData = Uint8List;
typedef TypeOptionDataCallback = void Function(TypeOptionData typeOptionData); 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); typedef HideOverlayCallback = void Function(BuildContext anchorContext);
class TypeOptionOperationDelegate { class TypeOptionOverlayDelegate {
TypeOptionDataCallback didUpdateTypeOptionData; ShowOverlayCallback showOverlay;
ShowOverlayCallback requireToShowOverlay;
HideOverlayCallback hideOverlay; HideOverlayCallback hideOverlay;
TypeOptionOperationDelegate({ TypeOptionOverlayDelegate({
required this.didUpdateTypeOptionData, required this.showOverlay,
required this.requireToShowOverlay,
required this.hideOverlay, required this.hideOverlay,
}); });
} }
class TypeOptionDataDelegate {
TypeOptionDataCallback didUpdateTypeOptionData;
TypeOptionDataDelegate({
required this.didUpdateTypeOptionData,
});
}
class RichTextTypeOptionBuilder extends TypeOptionBuilder { class RichTextTypeOptionBuilder extends TypeOptionBuilder {
RichTextTypeOption typeOption; RichTextTypeOption typeOption;

View File

@ -17,22 +17,6 @@ class FieldTypeList extends StatelessWidget {
final SelectFieldCallback onSelectField; final SelectFieldCallback onSelectField;
const FieldTypeList({required this.onSelectField, Key? key}) : super(key: key); 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) { static void hide(BuildContext context) {
FlowyOverlay.of(context).remove(FieldTypeList.identifier()); FlowyOverlay.of(context).remove(FieldTypeList.identifier());
} }

View File

@ -15,30 +15,39 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class DateTypeOptionBuilder extends TypeOptionBuilder { class DateTypeOptionBuilder extends TypeOptionBuilder {
DateTypeOption typeOption; final DateTypeOptionWidget _widget;
TypeOptionOperationDelegate delegate;
DateTypeOptionBuilder(TypeOptionData typeOptionData, this.delegate) DateTypeOptionBuilder(
: typeOption = DateTypeOption.fromBuffer(typeOptionData); TypeOptionData typeOptionData,
TypeOptionOverlayDelegate overlayDelegate,
TypeOptionDataDelegate dataDelegate,
) : _widget = DateTypeOptionWidget(
typeOption: DateTypeOption.fromBuffer(typeOptionData),
dataDelegate: dataDelegate,
overlayDelegate: overlayDelegate,
);
@override @override
Widget? get customWidget => DateTypeOptionWidget( Widget? get customWidget => _widget;
typeOption: typeOption,
operationDelegate: delegate,
);
} }
class DateTypeOptionWidget extends TypeOptionWidget { class DateTypeOptionWidget extends TypeOptionWidget {
final DateTypeOption typeOption; final DateTypeOption typeOption;
final TypeOptionOperationDelegate operationDelegate; final TypeOptionOverlayDelegate overlayDelegate;
const DateTypeOptionWidget({required this.typeOption, required this.operationDelegate, Key? key}) : super(key: key); final TypeOptionDataDelegate dataDelegate;
const DateTypeOptionWidget({
required this.typeOption,
required this.dataDelegate,
required this.overlayDelegate,
Key? key,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => getIt<DateTypeOptionBloc>(param1: typeOption), create: (context) => getIt<DateTypeOptionBloc>(param1: typeOption),
child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>( child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>(
listener: (context, state) => operationDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()),
builder: (context, state) { builder: (context, state) {
return Column(children: [ return Column(children: [
_dateFormatButton(context), _dateFormatButton(context),
@ -61,7 +70,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
final list = DateFormatList(onSelected: (format) { final list = DateFormatList(onSelected: (format) {
context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectDateFormat(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), rightIcon: svg("grid/more", color: theme.iconColor),
), ),
@ -80,7 +89,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
final list = TimeFormatList(onSelected: (format) { final list = TimeFormatList(onSelected: (format) {
context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectTimeFormat(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), rightIcon: svg("grid/more", color: theme.iconColor),
), ),

View File

@ -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/image.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.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/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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.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 { class SelectOptionColorList extends StatelessWidget {
const SelectOptionColorList({Key? key}) : super(key: key); const SelectOptionColorList({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
Widget? checkmark; Widget? checkmark;
if (isSelected) { if (isSelected) {
checkmark = svg("grid/details", color: theme.iconColor); checkmark = svg("grid/details", color: theme.iconColor);
} }
final String hex = '#${option.color(context).value.toRadixString(16)}';
final colorIcon = SizedBox.square( final colorIcon = SizedBox.square(
dimension: 16, dimension: 16,
child: Container( child: Container(
@ -40,15 +173,17 @@ class _SelectOptionColorItem extends StatelessWidget {
), ),
); );
return FlowyButton( return SizedBox(
text: FlowyText.medium( height: GridSize.typeOptionItemHeight,
option.name(), child: FlowyButton(
fontSize: 12, 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: () {},
); );
} }
} }

View File

@ -9,7 +9,7 @@ import 'option_pannel.dart';
class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
MultiSelectTypeOption typeOption; MultiSelectTypeOption typeOption;
TypeOptionOperationDelegate delegate; TypeOptionOverlayDelegate delegate;
MultiSelectTypeOptionBuilder(TypeOptionData typeOptionData, this.delegate) MultiSelectTypeOptionBuilder(TypeOptionData typeOptionData, this.delegate)
: typeOption = MultiSelectTypeOption.fromBuffer(typeOptionData); : typeOption = MultiSelectTypeOption.fromBuffer(typeOptionData);
@ -20,8 +20,8 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
class MultiSelectTypeOptionWidget extends TypeOptionWidget { class MultiSelectTypeOptionWidget extends TypeOptionWidget {
final MultiSelectTypeOption typeOption; final MultiSelectTypeOption typeOption;
final TypeOptionOperationDelegate delegate; final TypeOptionOverlayDelegate overlayDelegate;
const MultiSelectTypeOptionWidget(this.typeOption, this.delegate, {Key? key}) : super(key: key); const MultiSelectTypeOptionWidget(this.typeOption, this.overlayDelegate, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -32,14 +32,14 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
return OptionPannel( return OptionPannel(
options: state.typeOption.options, options: state.typeOption.options,
beginEdit: () { beginEdit: () {
delegate.hideOverlay(context); overlayDelegate.hideOverlay(context);
}, },
createOptionCallback: (name) { createOptionCallback: (name) {
context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.createOption(name)); context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.createOption(name));
}, },
updateOptionsCallback: (options) { updateOptionCallback: (updateOption) {},
context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.updateOptions(options)); deleteOptionCallback: (deleteOption) {},
}, overlayDelegate: overlayDelegate,
); );
}, },
), ),

View File

@ -15,25 +15,29 @@ import 'package:easy_localization/easy_localization.dart' hide NumberFormat;
import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/generated/locale_keys.g.dart';
class NumberTypeOptionBuilder extends TypeOptionBuilder { class NumberTypeOptionBuilder extends TypeOptionBuilder {
NumberTypeOption typeOption; final NumberTypeOptionWidget _widget;
TypeOptionOperationDelegate delegate;
NumberTypeOptionBuilder( NumberTypeOptionBuilder(
TypeOptionData typeOptionData, TypeOptionData typeOptionData,
this.delegate, TypeOptionOverlayDelegate overlayDelegate,
) : typeOption = NumberTypeOption.fromBuffer(typeOptionData); TypeOptionDataDelegate dataDelegate,
) : _widget = NumberTypeOptionWidget(
typeOption: NumberTypeOption.fromBuffer(typeOptionData),
dataDelegate: dataDelegate,
overlayDelegate: overlayDelegate,
);
@override @override
Widget? get customWidget => NumberTypeOptionWidget( Widget? get customWidget => _widget;
typeOption: typeOption,
operationDelegate: delegate,
);
} }
class NumberTypeOptionWidget extends TypeOptionWidget { class NumberTypeOptionWidget extends TypeOptionWidget {
final TypeOptionOperationDelegate operationDelegate; final TypeOptionDataDelegate dataDelegate;
final TypeOptionOverlayDelegate overlayDelegate;
final NumberTypeOption typeOption; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -43,7 +47,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
child: SizedBox( child: SizedBox(
height: GridSize.typeOptionItemHeight, height: GridSize.typeOptionItemHeight,
child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>( child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>(
listener: (context, state) => operationDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()),
builder: (context, state) { builder: (context, state) {
return FlowyButton( return FlowyButton(
text: FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(), fontSize: 12), text: FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(), fontSize: 12),
@ -53,7 +57,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
final list = NumberFormatList(onSelected: (format) { final list = NumberFormatList(onSelected: (format) {
context.read<NumberTypeOptionBloc>().add(NumberTypeOptionEvent.didSelectFormat(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), rightIcon: svg("grid/more", color: theme.iconColor),
); );

View File

@ -1,5 +1,6 @@
import 'package:app_flowy/workspace/application/grid/field/type_option/option_pannel_bloc.dart'; 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/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/image.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.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:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/generated/locale_keys.g.dart';
import 'edit_option_pannel.dart';
import 'widget.dart'; import 'widget.dart';
class OptionPannel extends StatelessWidget { class OptionPannel extends StatelessWidget {
final List<SelectOption> options; final List<SelectOption> options;
final VoidCallback beginEdit; final VoidCallback beginEdit;
final Function(String optionName) createOptionCallback; final Function(String optionName) createOptionCallback;
final Function(List<SelectOption>) updateOptionsCallback; final Function(SelectOption) updateOptionCallback;
final Function(SelectOption) deleteOptionCallback;
final TypeOptionOverlayDelegate overlayDelegate;
const OptionPannel({ const OptionPannel({
required this.options, required this.options,
required this.beginEdit, required this.beginEdit,
required this.createOptionCallback, required this.createOptionCallback,
required this.updateOptionsCallback, required this.updateOptionCallback,
required this.deleteOptionCallback,
required this.overlayDelegate,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -39,6 +46,16 @@ class OptionPannel extends StatelessWidget {
() => null, () => null,
(optionName) => createOptionCallback(optionName), (optionName) => createOptionCallback(optionName),
); );
state.updateOption.fold(
() => null,
(updateOption) => updateOptionCallback(updateOption),
);
state.deleteOption.fold(
() => null,
(deleteOption) => deleteOptionCallback(deleteOption),
);
}, },
builder: (context, state) { builder: (context, state) {
List<Widget> children = [ List<Widget> children = [
@ -46,7 +63,7 @@ class OptionPannel extends StatelessWidget {
const OptionTitle(), const OptionTitle(),
]; ];
if (state.isEditingOption) { if (state.isEditingOption) {
children.add(const _AddOptionTextField()); children.add(const _OptionNameTextField());
} }
if (state.options.isEmpty && !state.isEditingOption) { if (state.options.isEmpty && !state.isEditingOption) {
@ -54,7 +71,7 @@ class OptionPannel extends StatelessWidget {
} }
if (state.options.isNotEmpty) { if (state.options.isNotEmpty) {
children.add(_OptionList(key: ObjectKey(state.options))); children.add(_OptionList(overlayDelegate));
} }
return Column(children: children); return Column(children: children);
@ -105,14 +122,18 @@ class OptionTitle extends StatelessWidget {
} }
class _OptionList 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<OptionPannelBloc, OptionPannelState>( return BlocBuilder<OptionPannelBloc, OptionPannelState>(
buildWhen: (previous, current) {
return previous.options != current.options;
},
builder: (context, state) { builder: (context, state) {
final optionItems = state.options.map((option) { final optionItems = state.options.map((option) {
return _OptionItem(option: option); return _makeOptionItem(context, option);
}).toList(); }).toList();
return ListView.separated( 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 { class _OptionItem extends StatelessWidget {
final SelectOption option; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -143,7 +190,7 @@ class _OptionItem extends StatelessWidget {
child: FlowyButton( child: FlowyButton(
text: FlowyText.medium(option.name, fontSize: 12), text: FlowyText.medium(option.name, fontSize: 12),
hoverColor: theme.hover, hoverColor: theme.hover,
onTap: () {}, onTap: () => onEdited(option),
rightIcon: svg("grid/details", color: theme.iconColor), rightIcon: svg("grid/details", color: theme.iconColor),
), ),
); );
@ -170,8 +217,8 @@ class _AddOptionButton extends StatelessWidget {
} }
} }
class _AddOptionTextField extends StatelessWidget { class _OptionNameTextField extends StatelessWidget {
const _AddOptionTextField({Key? key}) : super(key: key); const _OptionNameTextField({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -12,11 +12,13 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
SingleSelectTypeOptionBuilder( SingleSelectTypeOptionBuilder(
String fieldId, String fieldId,
TypeOptionData typeOptionData, TypeOptionData typeOptionData,
TypeOptionOperationDelegate delegate, TypeOptionOverlayDelegate overlayDelegate,
TypeOptionDataDelegate dataDelegate,
) : _widget = SingleSelectTypeOptionWidget( ) : _widget = SingleSelectTypeOptionWidget(
fieldId, fieldId: fieldId,
SingleSelectTypeOption.fromBuffer(typeOptionData), typeOption: SingleSelectTypeOption.fromBuffer(typeOptionData),
delegate, dataDelegate: dataDelegate,
overlayDelegate: overlayDelegate,
); );
@override @override
@ -26,27 +28,41 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
class SingleSelectTypeOptionWidget extends TypeOptionWidget { class SingleSelectTypeOptionWidget extends TypeOptionWidget {
final String fieldId; final String fieldId;
final SingleSelectTypeOption typeOption; final SingleSelectTypeOption typeOption;
final TypeOptionOperationDelegate delegate; final TypeOptionOverlayDelegate overlayDelegate;
const SingleSelectTypeOptionWidget(this.fieldId, this.typeOption, this.delegate, {Key? key}) : super(key: key); final TypeOptionDataDelegate dataDelegate;
const SingleSelectTypeOptionWidget({
required this.fieldId,
required this.typeOption,
required this.dataDelegate,
required this.overlayDelegate,
Key? key,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => getIt<SingleSelectTypeOptionBloc>(param1: typeOption, param2: fieldId), create: (context) => getIt<SingleSelectTypeOptionBloc>(param1: typeOption, param2: fieldId),
child: BlocConsumer<SingleSelectTypeOptionBloc, SingleSelectTypeOptionState>( child: BlocConsumer<SingleSelectTypeOptionBloc, SingleSelectTypeOptionState>(
listener: (context, state) => delegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), listener: (context, state) {
dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
},
builder: (context, state) { builder: (context, state) {
return OptionPannel( return OptionPannel(
options: state.typeOption.options, options: state.typeOption.options,
beginEdit: () { beginEdit: () {
delegate.hideOverlay(context); overlayDelegate.hideOverlay(context);
}, },
createOptionCallback: (name) { createOptionCallback: (name) {
context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.createOption(name)); context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.createOption(name));
}, },
updateOptionsCallback: (options) { updateOptionCallback: (updateOption) {
context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.updateOptions(options)); context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.updateOption(updateOption));
}, },
deleteOptionCallback: (deleteOption) {
context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.deleteOption(deleteOption));
},
overlayDelegate: overlayDelegate,
key: ValueKey(state.typeOption.hashCode),
); );
}, },
), ),