Merge pull request #440 from AppFlowy-IO/fix_grid_ui_bugs

Fix grid UI bugs
This commit is contained in:
Nathan.fooo 2022-04-08 23:00:28 +08:00 committed by GitHub
commit 1e997ccaad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 199 additions and 117 deletions

View file

@ -0,0 +1,66 @@
import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
part 'field_cell_bloc.freezed.dart';
class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
final FieldListener _fieldListener;
FieldCellBloc({
required GridFieldCellContext cellContext,
}) : _fieldListener = FieldListener(fieldId: cellContext.field.id),
super(FieldCellState.initial(cellContext)) {
on<FieldCellEvent>(
(event, emit) async {
await event.map(
initial: (_InitialCell value) async {
_startListening();
},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
emit(state.copyWith(field: value.field));
},
);
},
);
}
@override
Future<void> close() async {
await _fieldListener.stop();
return super.close();
}
void _startListening() {
_fieldListener.updateFieldNotifier.addPublishListener((result) {
result.fold(
(field) => add(FieldCellEvent.didReceiveFieldUpdate(field)),
(err) => Log.error(err),
);
});
_fieldListener.start();
}
}
@freezed
class FieldCellEvent with _$FieldCellEvent {
const factory FieldCellEvent.initial() = _InitialCell;
const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
}
@freezed
class FieldCellState with _$FieldCellState {
const factory FieldCellState({
required String gridId,
required Field field,
}) = _FieldCellState;
factory FieldCellState.initial(GridFieldCellContext cellContext) => FieldCellState(
gridId: cellContext.gridId,
field: cellContext.field,
);
}

View file

@ -4,11 +4,11 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
part 'edit_option_bloc.freezed.dart'; part 'edit_select_option_bloc.freezed.dart';
class EditOptionBloc extends Bloc<EditOptionEvent, EditOptionState> { class EditSelectOptionBloc extends Bloc<EditSelectOptionEvent, EditSelectOptionState> {
EditOptionBloc({required SelectOption option}) : super(EditOptionState.initial(option)) { EditSelectOptionBloc({required SelectOption option}) : super(EditSelectOptionState.initial(option)) {
on<EditOptionEvent>( on<EditSelectOptionEvent>(
(event, emit) async { (event, emit) async {
event.map( event.map(
updateName: (_UpdateName value) { updateName: (_UpdateName value) {
@ -46,20 +46,20 @@ class EditOptionBloc extends Bloc<EditOptionEvent, EditOptionState> {
} }
@freezed @freezed
class EditOptionEvent with _$EditOptionEvent { class EditSelectOptionEvent with _$EditSelectOptionEvent {
const factory EditOptionEvent.updateName(String name) = _UpdateName; const factory EditSelectOptionEvent.updateName(String name) = _UpdateName;
const factory EditOptionEvent.updateColor(SelectOptionColor color) = _UpdateColor; const factory EditSelectOptionEvent.updateColor(SelectOptionColor color) = _UpdateColor;
const factory EditOptionEvent.delete() = _Delete; const factory EditSelectOptionEvent.delete() = _Delete;
} }
@freezed @freezed
class EditOptionState with _$EditOptionState { class EditSelectOptionState with _$EditSelectOptionState {
const factory EditOptionState({ const factory EditSelectOptionState({
required SelectOption option, required SelectOption option,
required Option<bool> deleted, required Option<bool> deleted,
}) = _EditOptionState; }) = _EditSelectOptionState;
factory EditOptionState.initial(SelectOption option) => EditOptionState( factory EditSelectOptionState.initial(SelectOption option) => EditSelectOptionState(
option: option, option: option,
deleted: none(), deleted: none(),
); );

View file

@ -3,11 +3,11 @@ 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:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
part 'option_pannel_bloc.freezed.dart'; part 'field_option_pannel_bloc.freezed.dart';
class OptionPannelBloc extends Bloc<OptionPannelEvent, OptionPannelState> { class FieldOptionPannelBloc extends Bloc<FieldOptionPannelEvent, FieldOptionPannelState> {
OptionPannelBloc({required List<SelectOption> options}) : super(OptionPannelState.initial(options)) { FieldOptionPannelBloc({required List<SelectOption> options}) : super(FieldOptionPannelState.initial(options)) {
on<OptionPannelEvent>( on<FieldOptionPannelEvent>(
(event, emit) async { (event, emit) async {
await event.map( await event.map(
createOption: (_CreateOption value) async { createOption: (_CreateOption value) async {
@ -37,25 +37,25 @@ class OptionPannelBloc extends Bloc<OptionPannelEvent, OptionPannelState> {
} }
@freezed @freezed
class OptionPannelEvent with _$OptionPannelEvent { class FieldOptionPannelEvent with _$FieldOptionPannelEvent {
const factory OptionPannelEvent.createOption(String optionName) = _CreateOption; const factory FieldOptionPannelEvent.createOption(String optionName) = _CreateOption;
const factory OptionPannelEvent.beginAddingOption() = _BeginAddingOption; const factory FieldOptionPannelEvent.beginAddingOption() = _BeginAddingOption;
const factory OptionPannelEvent.endAddingOption() = _EndAddingOption; const factory FieldOptionPannelEvent.endAddingOption() = _EndAddingOption;
const factory OptionPannelEvent.updateOption(SelectOption option) = _UpdateOption; const factory FieldOptionPannelEvent.updateOption(SelectOption option) = _UpdateOption;
const factory OptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption; const factory FieldOptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption;
} }
@freezed @freezed
class OptionPannelState with _$OptionPannelState { class FieldOptionPannelState with _$FieldOptionPannelState {
const factory OptionPannelState({ const factory FieldOptionPannelState({
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> updateOption,
required Option<SelectOption> deleteOption, required Option<SelectOption> deleteOption,
}) = _OptionPannelState; }) = _FieldOptionPannelState;
factory OptionPannelState.initial(List<SelectOption> options) => OptionPannelState( factory FieldOptionPannelState.initial(List<SelectOption> options) => FieldOptionPannelState(
options: options, options: options,
isEditingOption: false, isEditingOption: false,
newOptionName: none(), newOptionName: none(),

View file

@ -37,7 +37,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
return SizedBox.expand( return SizedBox.expand(
child: InkWell( child: InkWell(
onTap: () { onTap: () {
SelectOptionEditor.show(context, state.cellData, state.options, state.selectedOptions); SelectOptionCellEditor.show(context, state.cellData, state.options, state.selectedOptions);
}, },
child: Row(children: children), child: Row(children: children),
), ),
@ -86,7 +86,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
return SizedBox.expand( return SizedBox.expand(
child: InkWell( child: InkWell(
onTap: () { onTap: () {
SelectOptionEditor.show(context, state.cellData, state.options, state.selectedOptions); SelectOptionCellEditor.show(context, state.cellData, state.options, state.selectedOptions);
}, },
child: Row(children: children), child: Row(children: children),
), ),

View file

@ -18,19 +18,18 @@ 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';
import 'package:styled_widget/styled_widget.dart';
import 'package:textfield_tags/textfield_tags.dart'; import 'package:textfield_tags/textfield_tags.dart';
import 'extension.dart'; import 'extension.dart';
const double _editorPannelWidth = 300; const double _editorPannelWidth = 300;
class SelectOptionEditor extends StatelessWidget with FlowyOverlayDelegate { class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
final CellData cellData; final CellData cellData;
final List<SelectOption> options; final List<SelectOption> options;
final List<SelectOption> selectedOptions; final List<SelectOption> selectedOptions;
const SelectOptionEditor({ const SelectOptionCellEditor({
required this.cellData, required this.cellData,
required this.options, required this.options,
required this.selectedOptions, required this.selectedOptions,
@ -38,7 +37,7 @@ class SelectOptionEditor extends StatelessWidget with FlowyOverlayDelegate {
}) : super(key: key); }) : super(key: key);
static String identifier() { static String identifier() {
return (SelectOptionEditor).toString(); return (SelectOptionCellEditor).toString();
} }
@override @override
@ -73,8 +72,8 @@ class SelectOptionEditor extends StatelessWidget with FlowyOverlayDelegate {
List<SelectOption> options, List<SelectOption> options,
List<SelectOption> selectedOptions, List<SelectOption> selectedOptions,
) { ) {
SelectOptionEditor.hide(context); SelectOptionCellEditor.remove(context);
final editor = SelectOptionEditor( final editor = SelectOptionCellEditor(
cellData: cellData, cellData: cellData,
options: options, options: options,
selectedOptions: selectedOptions, selectedOptions: selectedOptions,
@ -86,14 +85,14 @@ class SelectOptionEditor extends StatelessWidget with FlowyOverlayDelegate {
child: SizedBox(width: _editorPannelWidth, child: editor), child: SizedBox(width: _editorPannelWidth, child: editor),
constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)), constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)),
), ),
identifier: SelectOptionEditor.identifier(), identifier: SelectOptionCellEditor.identifier(),
anchorContext: context, anchorContext: context,
anchorDirection: AnchorDirection.bottomWithCenterAligned, anchorDirection: AnchorDirection.bottomWithCenterAligned,
delegate: editor, delegate: editor,
); );
} }
static void hide(BuildContext context) { static void remove(BuildContext context) {
FlowyOverlay.of(context).remove(identifier()); FlowyOverlay.of(context).remove(identifier());
} }
@ -208,7 +207,7 @@ class _SelectOptionCell extends StatelessWidget {
if (onHover) { if (onHover) {
children.add(FlowyIconButton( children.add(FlowyIconButton(
width: 30, width: 30,
onPressed: () => _showEditOptionPannel(context), onPressed: () => _showEditPannel(context),
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
icon: svgWidget("editor/details", color: theme.iconColor), icon: svgWidget("editor/details", color: theme.iconColor),
)); ));
@ -224,7 +223,7 @@ class _SelectOptionCell extends StatelessWidget {
); );
} }
void _showEditOptionPannel(BuildContext context) { void _showEditPannel(BuildContext context) {
final pannel = EditSelectOptionPannel( final pannel = EditSelectOptionPannel(
option: option, option: option,
onDeleted: () { onDeleted: () {
@ -233,9 +232,9 @@ class _SelectOptionCell extends StatelessWidget {
onUpdated: (updatedOption) { onUpdated: (updatedOption) {
context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.updateOption(updatedOption)); context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.updateOption(updatedOption));
}, },
// key: ValueKey(option.id), key: ValueKey(option.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
); );
final overlayIdentifier = pannel.toString(); final overlayIdentifier = (EditSelectOptionPannel).toString();
FlowyOverlay.of(context).remove(overlayIdentifier); FlowyOverlay.of(context).remove(overlayIdentifier);
FlowyOverlay.of(context).insertWithAnchor( FlowyOverlay.of(context).insertWithAnchor(

View file

@ -1,3 +1,4 @@
import 'package:app_flowy/workspace/application/grid/field/field_cell_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.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:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
@ -12,46 +13,55 @@ import 'field_cell_action_sheet.dart';
import 'field_editor.dart'; import 'field_editor.dart';
class GridFieldCell extends StatelessWidget { class GridFieldCell extends StatelessWidget {
final GridFieldCellContext fieldCellContext; final GridFieldCellContext cellContext;
const GridFieldCell(this.fieldCellContext, {Key? key}) : super(key: key); const GridFieldCell(this.cellContext, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
final field = fieldCellContext.field;
final button = FlowyButton( return BlocProvider(
hoverColor: theme.hover, create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
onTap: () => _showActionSheet(context), child: BlocBuilder<FieldCellBloc, FieldCellState>(
rightIcon: svgWidget("editor/details", color: theme.iconColor), builder: (context, state) {
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor), final button = FlowyButton(
text: FlowyText.medium(field.name, fontSize: 12), hoverColor: theme.hover,
padding: GridSize.cellContentInsets, onTap: () => _showActionSheet(context),
); rightIcon: svgWidget("editor/details", color: theme.iconColor),
leftIcon: svgWidget(state.field.fieldType.iconName(), color: theme.iconColor),
text: FlowyText.medium(state.field.name, fontSize: 12),
padding: GridSize.cellContentInsets,
);
final borderSide = BorderSide(color: theme.shader4, width: 0.4); final borderSide = BorderSide(color: theme.shader4, width: 0.4);
final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide)); final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
return Container( return Container(
width: field.width.toDouble(), width: state.field.width.toDouble(),
decoration: decoration, decoration: decoration,
child: button, child: button,
);
},
),
); );
} }
void _showActionSheet(BuildContext context) { void _showActionSheet(BuildContext context) {
final state = context.read<FieldCellBloc>().state;
GridFieldCellActionSheet( GridFieldCellActionSheet(
fieldCellContext: fieldCellContext, cellContext: GridFieldCellContext(gridId: state.gridId, field: state.field),
onEdited: () => _showFieldEditor(context), onEdited: () => _showFieldEditor(context),
).show(context); ).show(context);
} }
void _showFieldEditor(BuildContext context) { void _showFieldEditor(BuildContext context) {
final state = context.read<FieldCellBloc>().state;
FieldEditor( FieldEditor(
gridId: fieldCellContext.gridId, gridId: state.gridId,
fieldContextLoader: FieldContextLoaderAdaptor( fieldContextLoader: FieldContextLoaderAdaptor(
gridId: fieldCellContext.gridId, gridId: state.gridId,
field: fieldCellContext.field, field: state.field,
), ),
).show(context); ).show(context);
} }

View file

@ -13,9 +13,9 @@ 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 GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate { class GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate {
final GridFieldCellContext fieldCellContext; final GridFieldCellContext cellContext;
final VoidCallback onEdited; final VoidCallback onEdited;
const GridFieldCellActionSheet({required this.fieldCellContext, required this.onEdited, Key? key}) : super(key: key); const GridFieldCellActionSheet({required this.cellContext, required this.onEdited, Key? key}) : super(key: key);
void show(BuildContext overlayContext) { void show(BuildContext overlayContext) {
FlowyOverlay.of(overlayContext).insertWithAnchor( FlowyOverlay.of(overlayContext).insertWithAnchor(
@ -33,7 +33,7 @@ class GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => getIt<FieldActionSheetBloc>(param1: fieldCellContext), create: (context) => getIt<FieldActionSheetBloc>(param1: cellContext),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
@ -44,15 +44,15 @@ class GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate
}, },
), ),
const VSpace(6), const VSpace(6),
_FieldOperationList(fieldCellContext, () => FlowyOverlay.of(context).remove(identifier())), _FieldOperationList(cellContext, () => FlowyOverlay.of(context).remove(identifier())),
], ],
), ),
), ),
); );
} }
String identifier() { static String identifier() {
return toString(); return (GridFieldCellActionSheet).toString();
} }
@override @override

View file

@ -41,8 +41,8 @@ class FieldEditor extends FlowyOverlayDelegate {
); );
} }
String identifier() { static String identifier() {
return toString(); return (FieldEditor).toString();
} }
@override @override

View file

@ -58,7 +58,7 @@ class _GridHeaderDelegate extends SliverPersistentHeaderDelegate {
@override @override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
if (oldDelegate is _GridHeaderDelegate) { if (oldDelegate is _GridHeaderDelegate) {
return fields != oldDelegate.fields; return fields.length != oldDelegate.fields.length;
} }
return true; return true;
} }

View file

@ -114,7 +114,7 @@ class DateFormatList extends StatelessWidget {
dateFormat: format, dateFormat: format,
onSelected: (format) { onSelected: (format) {
onSelected(format); onSelected(format);
FlowyOverlay.of(context).remove(identifier()); FlowyOverlay.of(context).remove(DateFormatList.identifier());
}, },
isSelected: selectedFormat == format); isSelected: selectedFormat == format);
}).toList(); }).toList();
@ -135,8 +135,8 @@ class DateFormatList extends StatelessWidget {
); );
} }
String identifier() { static String identifier() {
return toString(); return (DateFormatList).toString();
} }
} }
@ -205,7 +205,7 @@ class TimeFormatList extends StatelessWidget {
timeFormat: format, timeFormat: format,
onSelected: (format) { onSelected: (format) {
onSelected(format); onSelected(format);
FlowyOverlay.of(context).remove(identifier()); FlowyOverlay.of(context).remove(TimeFormatList.identifier());
}); });
}).toList(); }).toList();
@ -225,8 +225,8 @@ class TimeFormatList extends StatelessWidget {
); );
} }
String identifier() { static String identifier() {
return toString(); return (TimeFormatList).toString();
} }
} }

View file

@ -1,4 +1,4 @@
import 'package:app_flowy/workspace/application/grid/field/type_option/edit_option_bloc.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/edit_select_option_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/cell/selection_cell/extension.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/widget.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/widget.dart';
@ -28,23 +28,23 @@ class EditSelectOptionPannel extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditOptionBloc(option: option), create: (context) => EditSelectOptionBloc(option: option),
child: MultiBlocListener( child: MultiBlocListener(
listeners: [ listeners: [
BlocListener<EditOptionBloc, EditOptionState>( BlocListener<EditSelectOptionBloc, EditSelectOptionState>(
listenWhen: (p, c) => p.deleted != c.deleted, listenWhen: (p, c) => p.deleted != c.deleted,
listener: (context, state) { listener: (context, state) {
state.deleted.fold(() => null, (_) => onDeleted()); state.deleted.fold(() => null, (_) => onDeleted());
}, },
), ),
BlocListener<EditOptionBloc, EditOptionState>( BlocListener<EditSelectOptionBloc, EditSelectOptionState>(
listenWhen: (p, c) => p.option != c.option, listenWhen: (p, c) => p.option != c.option,
listener: (context, state) { listener: (context, state) {
onUpdated(state.option); onUpdated(state.option);
}, },
), ),
], ],
child: BlocBuilder<EditOptionBloc, EditOptionState>( child: BlocBuilder<EditSelectOptionBloc, EditSelectOptionState>(
builder: (context, state) { builder: (context, state) {
List<Widget> slivers = [ List<Widget> slivers = [
SliverToBoxAdapter(child: _OptionNameTextField(state.option.name)), SliverToBoxAdapter(child: _OptionNameTextField(state.option.name)),
@ -82,7 +82,7 @@ class _DeleteTag extends StatelessWidget {
hoverColor: theme.hover, hoverColor: theme.hover,
leftIcon: svgWidget("grid/delete", color: theme.iconColor), leftIcon: svgWidget("grid/delete", color: theme.iconColor),
onTap: () { onTap: () {
context.read<EditOptionBloc>().add(const EditOptionEvent.delete()); context.read<EditSelectOptionBloc>().add(const EditSelectOptionEvent.delete());
}, },
), ),
); );
@ -99,7 +99,9 @@ class _OptionNameTextField extends StatelessWidget {
name: name, name: name,
onCanceled: () {}, onCanceled: () {},
onDone: (optionName) { onDone: (optionName) {
context.read<EditOptionBloc>().add(EditOptionEvent.updateName(optionName)); if (name != optionName) {
context.read<EditSelectOptionBloc>().add(EditSelectOptionEvent.updateName(optionName));
}
}, },
); );
} }
@ -178,7 +180,7 @@ class _SelectOptionColorCell extends StatelessWidget {
leftIcon: colorIcon, leftIcon: colorIcon,
rightIcon: checkmark, rightIcon: checkmark,
onTap: () { onTap: () {
context.read<EditOptionBloc>().add(EditOptionEvent.updateColor(color)); context.read<EditSelectOptionBloc>().add(EditSelectOptionEvent.updateColor(color));
}, },
), ),
); );

View file

@ -1,4 +1,4 @@
import 'package:app_flowy/workspace/application/grid/field/type_option/option_pannel_bloc.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/field_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_switcher.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
@ -15,7 +15,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'edit_option_pannel.dart'; import 'edit_option_pannel.dart';
import 'widget.dart'; import 'widget.dart';
class OptionPannel extends StatelessWidget { class FieldSelectOptionPannel 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;
@ -23,7 +23,7 @@ class OptionPannel extends StatelessWidget {
final Function(SelectOption) deleteOptionCallback; final Function(SelectOption) deleteOptionCallback;
final TypeOptionOverlayDelegate overlayDelegate; final TypeOptionOverlayDelegate overlayDelegate;
const OptionPannel({ const FieldSelectOptionPannel({
required this.options, required this.options,
required this.beginEdit, required this.beginEdit,
required this.createOptionCallback, required this.createOptionCallback,
@ -36,8 +36,8 @@ class OptionPannel extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => OptionPannelBloc(options: options), create: (context) => FieldOptionPannelBloc(options: options),
child: BlocConsumer<OptionPannelBloc, OptionPannelState>( child: BlocConsumer<FieldOptionPannelBloc, FieldOptionPannelState>(
listener: (context, state) { listener: (context, state) {
if (state.isEditingOption) { if (state.isEditingOption) {
beginEdit(); beginEdit();
@ -88,7 +88,7 @@ class OptionTitle extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
return BlocBuilder<OptionPannelBloc, OptionPannelState>( return BlocBuilder<FieldOptionPannelBloc, FieldOptionPannelState>(
builder: (context, state) { builder: (context, state) {
List<Widget> children = [FlowyText.medium(LocaleKeys.grid_field_optionTitle.tr(), fontSize: 12)]; List<Widget> children = [FlowyText.medium(LocaleKeys.grid_field_optionTitle.tr(), fontSize: 12)];
if (state.options.isNotEmpty) { if (state.options.isNotEmpty) {
@ -105,7 +105,7 @@ class OptionTitle extends StatelessWidget {
), ),
hoverColor: theme.hover, hoverColor: theme.hover,
onTap: () { onTap: () {
context.read<OptionPannelBloc>().add(const OptionPannelEvent.beginAddingOption()); context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.beginAddingOption());
}, },
), ),
), ),
@ -127,7 +127,7 @@ class _OptionList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<OptionPannelBloc, OptionPannelState>( return BlocBuilder<FieldOptionPannelBloc, FieldOptionPannelState>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
return previous.options != current.options; return previous.options != current.options;
}, },
@ -159,12 +159,13 @@ class _OptionList extends StatelessWidget {
option: option, option: option,
onDeleted: () { onDeleted: () {
delegate.hideOverlay(context); delegate.hideOverlay(context);
context.read<OptionPannelBloc>().add(OptionPannelEvent.deleteOption(option)); context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.deleteOption(option));
}, },
onUpdated: (updatedOption) { onUpdated: (updatedOption) {
delegate.hideOverlay(context); delegate.hideOverlay(context);
context.read<OptionPannelBloc>().add(OptionPannelEvent.updateOption(updatedOption)); context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.updateOption(updatedOption));
}, },
key: ValueKey(option.id),
); );
delegate.showOverlay(context, pannel); delegate.showOverlay(context, pannel);
}, },
@ -208,7 +209,7 @@ class _AddOptionButton extends StatelessWidget {
text: FlowyText.medium(LocaleKeys.grid_field_addSelectOption.tr(), fontSize: 12), text: FlowyText.medium(LocaleKeys.grid_field_addSelectOption.tr(), fontSize: 12),
hoverColor: theme.hover, hoverColor: theme.hover,
onTap: () { onTap: () {
context.read<OptionPannelBloc>().add(const OptionPannelEvent.beginAddingOption()); context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.beginAddingOption());
}, },
leftIcon: svgWidget("home/add", color: theme.iconColor), leftIcon: svgWidget("home/add", color: theme.iconColor),
), ),
@ -222,12 +223,13 @@ class _OptionNameTextField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return NameTextField( return NameTextField(
name: "", name: "",
onCanceled: () { onCanceled: () {
context.read<OptionPannelBloc>().add(const OptionPannelEvent.endAddingOption()); context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.endAddingOption());
}, },
onDone: (optionName) { onDone: (optionName) {
context.read<OptionPannelBloc>().add(OptionPannelEvent.createOption(optionName)); context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.createOption(optionName));
}); },
);
} }
} }

View file

@ -5,7 +5,7 @@ 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 'option_pannel.dart'; import 'field_option_pannel.dart';
class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
final MultiSelectTypeOptionWidget _widget; final MultiSelectTypeOptionWidget _widget;
@ -48,7 +48,7 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()); dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
}, },
builder: (context, state) { builder: (context, state) {
return OptionPannel( return FieldSelectOptionPannel(
options: state.typeOption.options, options: state.typeOption.options,
beginEdit: () { beginEdit: () {
overlayDelegate.hideOverlay(context); overlayDelegate.hideOverlay(context);

View file

@ -81,7 +81,7 @@ class NumberFormatList extends StatelessWidget {
format: format, format: format,
onSelected: (format) { onSelected: (format) {
onSelected(format); onSelected(format);
FlowyOverlay.of(context).remove(identifier()); FlowyOverlay.of(context).remove(NumberFormatList.identifier());
}); });
}).toList(); }).toList();
@ -101,8 +101,8 @@ class NumberFormatList extends StatelessWidget {
); );
} }
String identifier() { static String identifier() {
return toString(); return (NumberFormatList).toString();
} }
} }

View file

@ -4,7 +4,7 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.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 'option_pannel.dart'; import 'field_option_pannel.dart';
class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
final SingleSelectTypeOptionWidget _widget; final SingleSelectTypeOptionWidget _widget;
@ -47,7 +47,7 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()); dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
}, },
builder: (context, state) { builder: (context, state) {
return OptionPannel( return FieldSelectOptionPannel(
options: state.typeOption.options, options: state.typeOption.options,
beginEdit: () { beginEdit: () {
overlayDelegate.hideOverlay(context); overlayDelegate.hideOverlay(context);

View file

@ -60,13 +60,12 @@ class _NameTextFieldState extends State<NameTextField> {
} }
void notifyDidEndEditing() { void notifyDidEndEditing() {
if (_controller.text.isEmpty) { if (!_focusNode.hasFocus) {
if (isEdited) { if (_controller.text.isEmpty) {
widget.onCanceled(); widget.onCanceled();
} else {
widget.onDone(_controller.text);
} }
isEdited = true;
} else {
widget.onDone(_controller.text);
} }
} }
} }

View file

@ -8,7 +8,7 @@ edition = "2018"
name = "dart_ffi" name = "dart_ffi"
# this value will change depending on the target os # this value will change depending on the target os
# default static lib # default static lib
crate-type = ["staticlib"] crate-type = ["cdylib"]
[dependencies] [dependencies]

View file

@ -58,7 +58,7 @@ impl ClientGridEditor {
start_field_id, start_field_id,
grid_id, grid_id,
} = params; } = params;
let field_id = field.id.clone();
let _ = self let _ = self
.modify(|grid| { .modify(|grid| {
if grid.contain_field(&field.id) { if grid.contain_field(&field.id) {
@ -84,6 +84,7 @@ impl ClientGridEditor {
}) })
.await?; .await?;
let _ = self.notify_did_update_grid().await?; let _ = self.notify_did_update_grid().await?;
let _ = self.notify_did_update_field(&field_id).await?;
Ok(()) Ok(())
} }
@ -113,7 +114,9 @@ impl ClientGridEditor {
} }
pub async fn replace_field(&self, field_meta: FieldMeta) -> FlowyResult<()> { pub async fn replace_field(&self, field_meta: FieldMeta) -> FlowyResult<()> {
let field_id = field_meta.id.clone();
let _ = self.modify(|pad| Ok(pad.replace_field(field_meta)?)).await?; let _ = self.modify(|pad| Ok(pad.replace_field(field_meta)?)).await?;
let _ = self.notify_did_update_field(&field_id).await?;
Ok(()) Ok(())
} }
@ -407,12 +410,13 @@ impl ClientGridEditor {
Ok(()) Ok(())
} }
#[tracing::instrument(level = "trace", skip_all, err)]
async fn notify_did_update_field(&self, field_id: &str) -> FlowyResult<()> { async fn notify_did_update_field(&self, field_id: &str) -> FlowyResult<()> {
let mut field_metas = self.get_field_metas(Some(vec![field_id])).await?; let mut field_metas = self.get_field_metas(Some(vec![field_id])).await?;
debug_assert!(field_metas.len() == 1); debug_assert!(field_metas.len() == 1);
if let Some(field_meta) = field_metas.pop() { if let Some(field_meta) = field_metas.pop() {
send_dart_notification(&self.grid_id, GridNotification::DidUpdateField) send_dart_notification(&field_id, GridNotification::DidUpdateField)
.payload(field_meta) .payload(field_meta)
.send(); .send();
} }