From 35e27b579f3cbd07fc1a1e780d9675810acfa64e Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 27 Mar 2022 22:38:50 +0800 Subject: [PATCH] chore: number field --- .../assets/images/grid/field/euro.svg | 3 + .../assets/images/grid/field/numbers.svg | 3 + .../assets/images/grid/field/us_dollar.svg | 3 + .../assets/images/grid/field/yen.svg | 3 + .../app_flowy/assets/translations/en.json | 5 +- .../app_flowy/lib/startup/deps_resolver.dart | 5 +- .../grid/field/edit_field_bloc.dart | 22 ++- .../grid/field/grid_header_bloc.dart | 26 ++- .../grid/field/grid_header_listener.dart | 0 .../grid/{ => field}/grid_listenr.dart | 14 +- .../grid/field/switch_field_type_bloc.dart | 4 + .../grid/field/type_option/number_bloc.dart | 23 ++- .../workspace/application/grid/grid_bloc.dart | 24 +-- .../application/grid/row/row_bloc.dart | 11 +- .../application/grid/row/row_listener.dart | 41 +---- .../plugins/grid/src/grid_page.dart | 2 - .../grid/src/widgets/content/grid_row.dart | 59 ++++--- .../widgets/header/create_field_pannel.dart | 2 +- .../src/widgets/header/edit_field_pannel.dart | 4 +- .../widgets/header/field_operation_list.dart | 10 +- .../widgets/header/field_tyep_switcher.dart | 71 +++----- .../grid/src/widgets/header/grid_header.dart | 123 +++++++++++++ .../src/widgets/header/grid_header_cell.dart | 36 ++++ .../widgets/header/type_option/number.dart | 165 ++++++++++++++++++ 24 files changed, 492 insertions(+), 167 deletions(-) create mode 100644 frontend/app_flowy/assets/images/grid/field/euro.svg create mode 100644 frontend/app_flowy/assets/images/grid/field/numbers.svg create mode 100644 frontend/app_flowy/assets/images/grid/field/us_dollar.svg create mode 100644 frontend/app_flowy/assets/images/grid/field/yen.svg create mode 100644 frontend/app_flowy/lib/workspace/application/grid/field/grid_header_listener.dart rename frontend/app_flowy/lib/workspace/application/grid/{ => field}/grid_listenr.dart (75%) create mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart create mode 100755 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header_cell.dart create mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart diff --git a/frontend/app_flowy/assets/images/grid/field/euro.svg b/frontend/app_flowy/assets/images/grid/field/euro.svg new file mode 100644 index 0000000000..95f511f687 --- /dev/null +++ b/frontend/app_flowy/assets/images/grid/field/euro.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app_flowy/assets/images/grid/field/numbers.svg b/frontend/app_flowy/assets/images/grid/field/numbers.svg new file mode 100644 index 0000000000..9d8b98d10d --- /dev/null +++ b/frontend/app_flowy/assets/images/grid/field/numbers.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app_flowy/assets/images/grid/field/us_dollar.svg b/frontend/app_flowy/assets/images/grid/field/us_dollar.svg new file mode 100644 index 0000000000..a8485cd6a1 --- /dev/null +++ b/frontend/app_flowy/assets/images/grid/field/us_dollar.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app_flowy/assets/images/grid/field/yen.svg b/frontend/app_flowy/assets/images/grid/field/yen.svg new file mode 100644 index 0000000000..8e9bf47c99 --- /dev/null +++ b/frontend/app_flowy/assets/images/grid/field/yen.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 5e5fc661ec..1031265da1 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -152,9 +152,10 @@ "textFieldName": "Text", "checkboxFieldName": "Checkbox", "dateFieldName": "Date", - "numberFieldName": "Number", + "numberFieldName": "Numbers", "singleSelectFieldName": "Select", - "multiSelectFieldName": "Multiselect" + "multiSelectFieldName": "Multiselect", + "numberFormat": " Number format" } } } diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index 8293efd652..e277bc6a20 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -18,6 +18,7 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart'; import 'package:get_it/get_it.dart'; @@ -221,7 +222,7 @@ void _resolveGridDeps(GetIt getIt) { () => DateTypeOptionBloc(), ); - getIt.registerFactory( - () => NumberTypeOptionBloc(), + getIt.registerFactoryParam( + (typeOption, _) => NumberTypeOptionBloc(typeOption: typeOption), ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart index 6b92b0d0d5..525d5232d7 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart @@ -16,25 +16,29 @@ class EditFieldBloc extends Bloc { (event, emit) async { await event.map( initial: (_InitialField value) {}, - updateFieldName: (_UpdateFieldName value) { - // + updateFieldName: (_UpdateFieldName value) async { + final result = await service.updateField(fieldId: field.id, name: value.name); + result.fold( + (l) => null, + (err) => Log.error(err), + ); }, hideField: (_HideField value) async { - final result = await service.updateField(fieldId: value.fieldId, visibility: false); + final result = await service.updateField(fieldId: field.id, visibility: false); result.fold( (l) => null, (err) => Log.error(err), ); }, deleteField: (_DeleteField value) async { - final result = await service.deleteField(fieldId: value.fieldId); + final result = await service.deleteField(fieldId: field.id); result.fold( (l) => null, (err) => Log.error(err), ); }, duplicateField: (_DuplicateField value) async { - final result = await service.duplicateField(fieldId: value.fieldId); + final result = await service.duplicateField(fieldId: field.id); result.fold( (l) => null, (err) => Log.error(err), @@ -56,9 +60,9 @@ class EditFieldBloc extends Bloc { class EditFieldEvent with _$EditFieldEvent { const factory EditFieldEvent.initial() = _InitialField; const factory EditFieldEvent.updateFieldName(String name) = _UpdateFieldName; - const factory EditFieldEvent.hideField(String fieldId) = _HideField; - const factory EditFieldEvent.duplicateField(String fieldId) = _DuplicateField; - const factory EditFieldEvent.deleteField(String fieldId) = _DeleteField; + const factory EditFieldEvent.hideField() = _HideField; + const factory EditFieldEvent.duplicateField() = _DuplicateField; + const factory EditFieldEvent.deleteField() = _DeleteField; const factory EditFieldEvent.saveField() = _SaveField; } @@ -67,10 +71,12 @@ class EditFieldState with _$EditFieldState { const factory EditFieldState({ required EditFieldContext editContext, required String errorText, + required String fieldName, }) = _EditFieldState; factory EditFieldState.initial(EditFieldContext editContext) => EditFieldState( editContext: editContext, errorText: '', + fieldName: editContext.gridField.name, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart index a5f3a8414a..d249e9b4c1 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart @@ -1,4 +1,6 @@ import 'package:app_flowy/workspace/application/grid/data.dart'; +import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; +import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -9,24 +11,43 @@ part 'grid_header_bloc.freezed.dart'; class GridHeaderBloc extends Bloc { final FieldService service; + final GridFieldsListener fieldListener; GridHeaderBloc({ required GridHeaderData data, required this.service, - }) : super(GridHeaderState.initial(data.fields)) { + }) : fieldListener = GridFieldsListener(gridId: data.gridId), + super(GridHeaderState.initial(data.fields)) { on( (event, emit) async { await event.map( - initial: (_InitialHeader value) async {}, + initial: (_InitialHeader value) async { + _startListening(); + }, createField: (_CreateField value) {}, insertField: (_InsertField value) {}, + didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { + emit(state.copyWith(fields: value.fields)); + }, ); }, ); } + Future _startListening() async { + fieldListener.updateFieldsNotifier.addPublishListener((result) { + result.fold( + (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)), + (err) => Log.error(err), + ); + }); + + fieldListener.start(); + } + @override Future close() async { + await fieldListener.stop(); return super.close(); } } @@ -36,6 +57,7 @@ class GridHeaderEvent with _$GridHeaderEvent { const factory GridHeaderEvent.initial() = _InitialHeader; const factory GridHeaderEvent.createField() = _CreateField; const factory GridHeaderEvent.insertField({required bool onLeft}) = _InsertField; + const factory GridHeaderEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; } @freezed diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_listener.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_listenr.dart b/frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart similarity index 75% rename from frontend/app_flowy/lib/workspace/application/grid/grid_listenr.dart rename to frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart index 4afaa54601..60ff7dd415 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_listenr.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart @@ -9,14 +9,14 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:app_flowy/core/notification_helper.dart'; -// typedef RowsUpdateNotifierValue = Either, FlowyError>; +typedef UpdateFieldNotifiedValue = Either, FlowyError>; -class GridListener { +class GridFieldsListener { final String gridId; - PublishNotifier, FlowyError>> fieldsUpdateNotifier = PublishNotifier(); + PublishNotifier updateFieldsNotifier = PublishNotifier(); StreamSubscription? _subscription; GridNotificationParser? _parser; - GridListener({required this.gridId}); + GridFieldsListener({required this.gridId}); void start() { _parser = GridNotificationParser( @@ -33,8 +33,8 @@ class GridListener { switch (ty) { case GridNotification.DidUpdateFields: result.fold( - (payload) => fieldsUpdateNotifier.value = left(RepeatedField.fromBuffer(payload).items), - (error) => fieldsUpdateNotifier.value = right(error), + (payload) => updateFieldsNotifier.value = left(RepeatedField.fromBuffer(payload).items), + (error) => updateFieldsNotifier.value = right(error), ); break; default: @@ -45,6 +45,6 @@ class GridListener { Future stop() async { _parser = null; await _subscription?.cancel(); - fieldsUpdateNotifier.dispose(); + updateFieldsNotifier.dispose(); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart index 7f5417a396..a2f7438434 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart @@ -29,6 +29,9 @@ class SwitchFieldTypeBloc extends Bloc Log.error(err), ); }, + didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) { + emit(state.copyWith(typeOptionData: value.typeOptionData)); + }, ); }, ); @@ -43,6 +46,7 @@ class SwitchFieldTypeBloc extends Bloc { - NumberTypeOptionBloc() : super(NumberTypeOptionState.initial()) { + NumberTypeOptionBloc({required NumberTypeOption typeOption}) : super(NumberTypeOptionState.initial(typeOption)) { on( (event, emit) async { await event.map( initial: (_InitialField value) async {}, + didSelectFormat: (_DidSelectFormat value) { + state.typeOption.format = value.format; + emit(state); + }, ); }, ); @@ -28,12 +28,17 @@ class NumberTypeOptionBloc extends Bloc NumberTypeOptionState(); + factory NumberTypeOptionState.initial(NumberTypeOption typeOption) => NumberTypeOptionState( + typeOption: typeOption, + ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart index b155126a72..00f90e0bfc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart @@ -8,7 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:equatable/equatable.dart'; import 'grid_block_service.dart'; -import 'grid_listenr.dart'; +import 'field/grid_listenr.dart'; import 'grid_service.dart'; part 'grid_bloc.freezed.dart'; @@ -16,11 +16,11 @@ part 'grid_bloc.freezed.dart'; class GridBloc extends Bloc { final View view; final GridService service; - late GridListener _gridListener; + late GridFieldsListener _fieldListener; late GridBlockService _blockService; GridBloc({required this.view, required this.service}) : super(GridState.initial()) { - _gridListener = GridListener(gridId: view.id); + _fieldListener = GridFieldsListener(gridId: view.id); on( (event, emit) async { @@ -34,10 +34,10 @@ class GridBloc extends Bloc { delete: (_Delete value) {}, rename: (_Rename value) {}, updateDesc: (_Desc value) {}, - rowsDidUpdate: (_RowsDidUpdate value) { + didReceiveRowUpdate: (_DidReceiveRowUpdate value) { emit(state.copyWith(rows: value.rows)); }, - fieldsDidUpdate: (_FieldsDidUpdate value) { + didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { emit(state.copyWith(fields: value.fields)); }, ); @@ -47,19 +47,19 @@ class GridBloc extends Bloc { @override Future close() async { - await _gridListener.stop(); + await _fieldListener.stop(); await _blockService.stop(); return super.close(); } Future _initGrid(Emitter emit) async { - _gridListener.fieldsUpdateNotifier.addPublishListener((result) { + _fieldListener.updateFieldsNotifier.addPublishListener((result) { result.fold( - (fields) => add(GridEvent.fieldsDidUpdate(fields)), + (fields) => add(GridEvent.didReceiveFieldUpdate(fields)), (err) => Log.error(err), ); }); - _gridListener.start(); + _fieldListener.start(); await _loadGrid(emit); } @@ -72,7 +72,7 @@ class GridBloc extends Bloc { _blockService.blocksUpdateNotifier?.addPublishListener((result) { result.fold( - (blockMap) => add(GridEvent.rowsDidUpdate(_buildRows(blockMap))), + (blockMap) => add(GridEvent.didReceiveRowUpdate(_buildRows(blockMap))), (err) => Log.error('$err'), ); }); @@ -128,8 +128,8 @@ class GridEvent with _$GridEvent { const factory GridEvent.updateDesc(String gridId, String desc) = _Desc; const factory GridEvent.delete(String gridId) = _Delete; const factory GridEvent.createRow() = _CreateRow; - const factory GridEvent.rowsDidUpdate(List rows) = _RowsDidUpdate; - const factory GridEvent.fieldsDidUpdate(List fields) = _FieldsDidUpdate; + const factory GridEvent.didReceiveRowUpdate(List rows) = _DidReceiveRowUpdate; + const factory GridEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; } @freezed diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart index 2bb78799f0..ac99eac67b 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart @@ -1,5 +1,6 @@ import 'dart:collection'; +import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:app_flowy/workspace/application/grid/grid_bloc.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; @@ -17,7 +18,7 @@ typedef CellDataMap = HashMap; class RowBloc extends Bloc { final RowService rowService; final RowListener rowlistener; - final RowFieldListener fieldListener; + final GridFieldsListener fieldListener; RowBloc({required GridRowData rowData, required this.rowlistener}) : rowService = RowService( @@ -25,7 +26,7 @@ class RowBloc extends Bloc { blockId: rowData.blockId, rowId: rowData.rowId, ), - fieldListener = RowFieldListener( + fieldListener = GridFieldsListener( gridId: rowData.gridId, ), super(RowState.initial(rowData)) { @@ -65,8 +66,8 @@ class RowBloc extends Bloc { @override Future close() async { - await rowlistener.close(); - await fieldListener.close(); + await rowlistener.stop(); + await fieldListener.stop(); return super.close(); } @@ -89,7 +90,7 @@ class RowBloc extends Bloc { ); }); - fieldListener.updateFieldNotifier.addPublishListener((result) { + fieldListener.updateFieldsNotifier.addPublishListener((result) { result.fold( (fields) => add(RowEvent.didReceiveFieldUpdate(fields)), (err) => Log.error(err), diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart index c5084f4164..011dbd8636 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart @@ -46,49 +46,10 @@ class RowListener { } } - Future close() async { + Future stop() async { _parser = null; await _subscription?.cancel(); updateCellNotifier.dispose(); updateRowNotifier.dispose(); } } - -class RowFieldListener { - final String gridId; - PublishNotifier updateFieldNotifier = PublishNotifier(); - StreamSubscription? _subscription; - GridNotificationParser? _parser; - - RowFieldListener({required this.gridId}); - - void start() { - _parser = GridNotificationParser( - id: gridId, - callback: (ty, result) { - _handleObservableType(ty, result); - }, - ); - - _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable)); - } - - void _handleObservableType(GridNotification ty, Either result) { - switch (ty) { - case GridNotification.DidUpdateFields: - result.fold( - (payload) => updateFieldNotifier.value = left(RepeatedField.fromBuffer(payload).items), - (error) => updateFieldNotifier.value = right(error), - ); - break; - default: - break; - } - } - - Future close() async { - _parser = null; - await _subscription?.cancel(); - updateFieldNotifier.dispose(); - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart index e1c44c253b..984ed147a1 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart @@ -1,6 +1,5 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/grid_bloc.dart'; -import 'package:app_flowy/workspace/application/grid/grid_service.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart'; @@ -97,7 +96,6 @@ class _FlowyGridState extends State { child: ScrollConfiguration( behavior: const ScrollBehavior().copyWith(scrollbars: false), child: CustomScrollView( - shrinkWrap: true, physics: StyledScrollPhysics(), controller: _scrollController.verticalController, slivers: [ diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart index b8834cd213..a813a51dc0 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart @@ -30,28 +30,47 @@ class _GridRowWidgetState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _rowBloc, - child: MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (p) => _rowBloc.add(const RowEvent.activeRow()), - onExit: (p) => _rowBloc.add(const RowEvent.disactiveRow()), - child: BlocBuilder( - buildWhen: (p, c) => p.rowHeight != c.rowHeight, - builder: (context, state) { - return SizedBox( - height: _rowBloc.state.rowHeight, - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: const [ - _RowLeading(), - _RowCells(), - _RowTrailing(), - ], - ), - ); - }, - ), + child: BlocBuilder( + buildWhen: (p, c) => p.rowHeight != c.rowHeight, + builder: (context, state) { + return SizedBox( + height: _rowBloc.state.rowHeight, + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: const [ + _RowLeading(), + _RowCells(), + _RowTrailing(), + ], + ), + ); + }, ), ); + // return BlocProvider.value( + // value: _rowBloc, + // child: MouseRegion( + // cursor: SystemMouseCursors.click, + // onEnter: (p) => _rowBloc.add(const RowEvent.activeRow()), + // onExit: (p) => _rowBloc.add(const RowEvent.disactiveRow()), + // child: BlocBuilder( + // buildWhen: (p, c) => p.rowHeight != c.rowHeight, + // builder: (context, state) { + // return SizedBox( + // height: _rowBloc.state.rowHeight, + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.stretch, + // children: const [ + // _RowLeading(), + // _RowCells(), + // _RowTrailing(), + // ], + // ), + // ); + // }, + // ), + // ), + // ); } @override diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart index 6a7c6d05ef..dd477dd8bb 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart @@ -52,7 +52,7 @@ class _CreateFieldPannelWidget extends StatelessWidget { child: BlocBuilder( builder: (context, state) { return state.field.fold( - () => const SizedBox(), + () => const SizedBox(width: 200), (field) => ListView( shrinkWrap: true, children: [ diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/edit_field_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/edit_field_pannel.dart index ce542b6760..cab5a51138 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/edit_field_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/edit_field_pannel.dart @@ -95,10 +95,10 @@ class _FieldNameTextField extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: ((previous, current) => previous.editContext.gridField.name == current.editContext.gridField.name), + buildWhen: ((previous, current) => previous.fieldName == current.fieldName), builder: (context, state) { return FieldNameTextField( - name: state.editContext.gridField.name, + name: state.fieldName, errorText: state.errorText, onNameChanged: (newName) { context.read().add(EditFieldEvent.updateFieldName(newName)); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart index ddf5eb7660..a4ccc94a94 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart @@ -46,7 +46,7 @@ class FieldActionItem extends StatelessWidget { text: FlowyText.medium(action.title(), fontSize: 12), hoverColor: theme.hover, onTap: () { - action.run(context, fieldId); + action.run(context); onTap(); }, leftIcon: svg(action.iconName(), color: theme.iconColor), @@ -83,16 +83,16 @@ extension _FieldActionExtension on FieldAction { } } - void run(BuildContext context, String fieldId) { + void run(BuildContext context) { switch (this) { case FieldAction.hide: - context.read().add(EditFieldEvent.hideField(fieldId)); + context.read().add(const EditFieldEvent.hideField()); break; case FieldAction.duplicate: - context.read().add(EditFieldEvent.duplicateField(fieldId)); + context.read().add(const EditFieldEvent.duplicateField()); break; case FieldAction.delete: - context.read().add(EditFieldEvent.deleteField(fieldId)); + context.read().add(const EditFieldEvent.deleteField()); break; } } 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 cd9adbd928..1fee89aecb 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 @@ -11,12 +11,13 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'type_option/number.dart'; + typedef SelectFieldCallback = void Function(Field, Uint8List); class FieldTypeSwitcher extends StatelessWidget { @@ -40,8 +41,11 @@ class FieldTypeSwitcher extends StatelessWidget { ]; final builder = _makeTypeOptionBuild( - state.field.fieldType, - state.typeOptionData, + fieldType: state.field.fieldType, + typeOptionData: state.typeOptionData, + typeOptionDataCallback: (newTypeOptionData) { + context.read().add(SwitchFieldTypeEvent.didUpdateTypeOptionData(newTypeOptionData)); + }, ); final typeOptionWidget = builder.customWidget; @@ -77,7 +81,6 @@ class FieldTypeSwitcher extends StatelessWidget { } abstract class TypeOptionBuilder { - Uint8List? get typeOptionData; Widget? get customWidget; } @@ -85,7 +88,14 @@ abstract class TypeOptionWidget extends StatelessWidget { const TypeOptionWidget({Key? key}) : super(key: key); } -TypeOptionBuilder _makeTypeOptionBuild(FieldType fieldType, Uint8List typeOptionData) { +typedef TypeOptionData = Uint8List; +typedef TypeOptionDataCallback = void Function(TypeOptionData typeOptionData); + +TypeOptionBuilder _makeTypeOptionBuild({ + required FieldType fieldType, + required TypeOptionData typeOptionData, + required TypeOptionDataCallback typeOptionDataCallback, +}) { switch (fieldType) { case FieldType.Checkbox: return CheckboxTypeOptionBuilder(typeOptionData); @@ -94,7 +104,7 @@ TypeOptionBuilder _makeTypeOptionBuild(FieldType fieldType, Uint8List typeOption case FieldType.MultiSelect: return MultiSelectTypeOptionBuilder(typeOptionData); case FieldType.Number: - return NumberTypeOptionBuilder(typeOptionData); + return NumberTypeOptionBuilder(typeOptionData, typeOptionDataCallback); case FieldType.RichText: return RichTextTypeOptionBuilder(typeOptionData); case FieldType.SingleSelect: @@ -107,46 +117,16 @@ TypeOptionBuilder _makeTypeOptionBuild(FieldType fieldType, Uint8List typeOption class RichTextTypeOptionBuilder extends TypeOptionBuilder { RichTextTypeOption typeOption; - RichTextTypeOptionBuilder(Uint8List typeOptionData) : typeOption = RichTextTypeOption.fromBuffer(typeOptionData); - - @override - Uint8List? get typeOptionData => typeOption.writeToBuffer(); + RichTextTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = RichTextTypeOption.fromBuffer(typeOptionData); @override Widget? get customWidget => null; } -class NumberTypeOptionBuilder extends TypeOptionBuilder { - NumberTypeOption typeOption; - - NumberTypeOptionBuilder(Uint8List typeOptionData) : typeOption = NumberTypeOption.fromBuffer(typeOptionData); - - @override - Uint8List? get typeOptionData => typeOption.writeToBuffer(); - - @override - Widget? get customWidget => const NumberTypeOptionWidget(); -} - -class NumberTypeOptionWidget extends TypeOptionWidget { - const NumberTypeOptionWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => getIt(), - child: Container(height: 30, color: Colors.green), - ); - } -} - class DateTypeOptionBuilder extends TypeOptionBuilder { DateTypeOption typeOption; - DateTypeOptionBuilder(Uint8List typeOptionData) : typeOption = DateTypeOption.fromBuffer(typeOptionData); - - @override - Uint8List? get typeOptionData => typeOption.writeToBuffer(); + DateTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = DateTypeOption.fromBuffer(typeOptionData); @override Widget? get customWidget => const DateTypeOptionWidget(); @@ -167,10 +147,7 @@ class DateTypeOptionWidget extends TypeOptionWidget { class CheckboxTypeOptionBuilder extends TypeOptionBuilder { CheckboxTypeOption typeOption; - CheckboxTypeOptionBuilder(Uint8List typeOptionData) : typeOption = CheckboxTypeOption.fromBuffer(typeOptionData); - - @override - Uint8List? get typeOptionData => throw UnimplementedError(); + CheckboxTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = CheckboxTypeOption.fromBuffer(typeOptionData); @override Widget? get customWidget => null; @@ -179,12 +156,9 @@ class CheckboxTypeOptionBuilder extends TypeOptionBuilder { class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { SingleSelectTypeOption typeOption; - SingleSelectTypeOptionBuilder(Uint8List typeOptionData) + SingleSelectTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = SingleSelectTypeOption.fromBuffer(typeOptionData); - @override - Uint8List? get typeOptionData => typeOption.writeToBuffer(); - @override Widget? get customWidget => const SingleSelectTypeOptionWidget(); } @@ -204,12 +178,9 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget { class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { MultiSelectTypeOption typeOption; - MultiSelectTypeOptionBuilder(Uint8List typeOptionData) + MultiSelectTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = MultiSelectTypeOption.fromBuffer(typeOptionData); - @override - Uint8List? get typeOptionData => typeOption.writeToBuffer(); - @override Widget? get customWidget => const MultiSelectTypeOptionWidget(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart new file mode 100644 index 0000000000..6e07fec904 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart @@ -0,0 +1,123 @@ +import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.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/text.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'create_field_pannel.dart'; +import 'grid_header_cell.dart'; + +class GridHeaderDelegate extends SliverPersistentHeaderDelegate { + final String gridId; + final List fields; + + GridHeaderDelegate({required this.gridId, required this.fields}); + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { + return GridHeader(gridId: gridId, fields: fields, key: ObjectKey(fields)); + } + + @override + double get maxExtent => GridSize.headerHeight; + + @override + double get minExtent => GridSize.headerHeight; + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { + if (oldDelegate is GridHeaderDelegate) { + return fields != oldDelegate.fields; + } + return false; + } +} + +class GridHeader extends StatelessWidget { + final List fields; + final String gridId; + const GridHeader({required this.gridId, required this.fields, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return BlocProvider( + create: (context) { + final bloc = getIt(param1: gridId, param2: fields); + bloc.add(const GridHeaderEvent.initial()); + return bloc; + }, + child: BlocBuilder( + builder: (context, state) { + final cells = state.fields.map( + (field) => GridHeaderCell( + GridFieldData(gridId: gridId, field: field), + ), + ); + + final row = Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const _HeaderLeading(), + ...cells, + _HeaderTrailing(gridId: gridId), + ], + ); + + return Container(color: theme.surface, child: row); + }, + ), + ); + } +} + +class _HeaderLeading extends StatelessWidget { + const _HeaderLeading({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: GridSize.leadingHeaderPadding, + ); + } +} + +class _HeaderTrailing extends StatelessWidget { + final String gridId; + const _HeaderTrailing({required this.gridId, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + final borderSide = BorderSide(color: theme.shader4, width: 0.4); + return Container( + width: GridSize.trailHeaderPadding, + decoration: BoxDecoration( + border: Border(top: borderSide, bottom: borderSide), + ), + padding: GridSize.headerContentInsets, + child: CreateFieldButton(gridId: gridId), + ); + } +} + +class CreateFieldButton extends StatelessWidget { + final String gridId; + const CreateFieldButton({required this.gridId, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return FlowyButton( + text: const FlowyText.medium('New column', fontSize: 12), + hoverColor: theme.hover, + onTap: () => CreateFieldPannel(gridId: gridId).show(context, gridId), + leftIcon: svg("home/add"), + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header_cell.dart new file mode 100755 index 0000000000..5e5dc5f4fd --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header_cell.dart @@ -0,0 +1,36 @@ +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: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/text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'edit_field_pannel.dart'; + +class GridHeaderCell extends StatelessWidget { + final GridFieldData fieldData; + const GridHeaderCell(this.fieldData, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + final button = FlowyButton( + hoverColor: theme.hover, + onTap: () => EditFieldPannel.show(context, fieldData), + rightIcon: svg("editor/details", color: theme.iconColor), + text: Padding(padding: GridSize.cellContentInsets, child: FlowyText.medium(fieldData.field.name, fontSize: 12)), + ); + + final borderSide = BorderSide(color: theme.shader4, width: 0.4); + final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide)); + + return Container( + width: fieldData.field.width.toDouble(), + decoration: decoration, + padding: GridSize.headerContentInsets, + child: button, + ); + } +} 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 new file mode 100644 index 0000000000..f7016510b7 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart @@ -0,0 +1,165 @@ +import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/grid/field/type_option/number_bloc.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/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:easy_localization/easy_localization.dart' hide NumberFormat; +import 'package:app_flowy/generated/locale_keys.g.dart'; + +class NumberTypeOptionBuilder extends TypeOptionBuilder { + NumberTypeOption typeOption; + TypeOptionDataCallback typeOptionDataCallback; + + NumberTypeOptionBuilder( + TypeOptionData typeOptionData, + this.typeOptionDataCallback, + ) : typeOption = NumberTypeOption.fromBuffer(typeOptionData); + + @override + Widget? get customWidget => NumberTypeOptionWidget( + typeOption: typeOption, + updateCallback: typeOptionDataCallback, + ); +} + +class NumberTypeOptionWidget extends TypeOptionWidget { + final TypeOptionDataCallback updateCallback; + final NumberTypeOption typeOption; + const NumberTypeOptionWidget({required this.typeOption, required this.updateCallback, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return BlocProvider( + create: (context) => getIt(param1: typeOption), + child: SizedBox( + height: 36, + child: BlocConsumer( + listener: (context, state) => updateCallback(state.typeOption.writeToBuffer()), + builder: (context, state) { + return FlowyButton( + text: FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(), fontSize: 12), + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + hoverColor: theme.hover, + onTap: () { + NumberFormatList.show(context, (format) { + context.read().add(NumberTypeOptionEvent.didSelectFormat(format)); + }); + }, + rightIcon: svg("grid/more", color: theme.iconColor), + ); + }, + ), + ), + ); + } +} + +typedef _SelectNumberFormatCallback = Function(NumberFormat format); + +class NumberFormatList extends StatelessWidget { + final _SelectNumberFormatCallback onSelected; + const NumberFormatList({required this.onSelected, Key? key}) : super(key: key); + + static void show(BuildContext context, _SelectNumberFormatCallback onSelected) { + final list = NumberFormatList(onSelected: onSelected); + FlowyOverlay.of(context).insertWithAnchor( + widget: OverlayContainer( + child: list, + constraints: BoxConstraints.loose(const Size(140, 300)), + ), + identifier: list.identifier(), + anchorContext: context, + anchorDirection: AnchorDirection.leftWithCenterAligned, + style: FlowyOverlayStyle(blur: false), + anchorOffset: const Offset(-20, 0), + ); + } + + @override + Widget build(BuildContext context) { + final formatItems = NumberFormat.values.map((format) { + return NumberFormatItem( + format: format, + onSelected: (format) { + onSelected(format); + FlowyOverlay.of(context).remove(identifier()); + }); + }).toList(); + + return ListView.separated( + shrinkWrap: true, + controller: ScrollController(), + separatorBuilder: (context, index) { + return const VSpace(10); + }, + itemCount: formatItems.length, + itemBuilder: (BuildContext context, int index) { + return formatItems[index]; + }, + ); + } + + String identifier() { + return toString(); + } +} + +class NumberFormatItem extends StatelessWidget { + final NumberFormat format; + final Function(NumberFormat format) onSelected; + const NumberFormatItem({required this.format, required this.onSelected, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return SizedBox( + height: 26, + child: FlowyButton( + text: FlowyText.medium(format.title(), fontSize: 12), + hoverColor: theme.hover, + onTap: () => onSelected(format), + leftIcon: svg(format.iconName(), color: theme.iconColor), + ), + ); + } +} + +extension NumberFormatExtension on NumberFormat { + String title() { + switch (this) { + case NumberFormat.CNY: + return "Yen"; + case NumberFormat.EUR: + return "Euro"; + case NumberFormat.Number: + return "Numbers"; + case NumberFormat.USD: + return "US Dollar"; + default: + throw UnimplementedError; + } + } + + String iconName() { + switch (this) { + case NumberFormat.CNY: + return "grid/field/yen"; + case NumberFormat.EUR: + return "grid/field/euro"; + case NumberFormat.Number: + return "grid/field/numbers"; + case NumberFormat.USD: + return "grid/field/us_dollar"; + default: + throw UnimplementedError; + } + } +}