diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 55e57dfdc7..7e20a947dc 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -186,7 +186,8 @@ "blueColor": "Blue", "deleteTag": "Delete tag", "colorPannelTitle": "Colors", - "pannelTitle": "Select an option" + "pannelTitle": "Select an option or create one", + "searchOption": "Search for an option" } } } diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index 9020101083..c23b259bfe 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -151,7 +151,7 @@ void _resolveGridDeps(GetIt getIt) { (view, _) => GridBloc(view: view, service: GridService()), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (data, _) => RowBloc( rowData: data, rowlistener: RowListener(rowId: data.rowId), @@ -179,35 +179,35 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (cellData, _) => TextCellBloc( service: CellService(), cellData: cellData, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (cellData, _) => SelectionCellBloc( service: CellService(), cellData: cellData, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (cellData, _) => NumberCellBloc( service: CellService(), cellData: cellData, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (cellData, _) => DateCellBloc( service: CellService(), cellData: cellData, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (cellData, _) => CheckboxCellBloc( service: CellService(), cellData: cellData, diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart index 3609f39de7..f5bc7e7c18 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart @@ -9,11 +9,11 @@ part 'checkbox_cell_bloc.freezed.dart'; class CheckboxCellBloc extends Bloc { final CellService service; - // final FutureCellData cellData; + // final CellData cellData; CheckboxCellBloc({ required this.service, - required FutureCellData cellData, + required CellData cellData, }) : super(CheckboxCellState.initial()) { on( (event, emit) async { diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart index 33829863dd..7a995aab28 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart @@ -9,7 +9,7 @@ part 'date_cell_bloc.freezed.dart'; class DateCellBloc extends Bloc { final CellService service; - final FutureCellData cellData; + final CellData cellData; DateCellBloc({ required this.service, diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart index 6e58d24a54..83d2eff57d 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart @@ -12,7 +12,7 @@ class NumberCellBloc extends Bloc { NumberCellBloc({ required this.service, - required FutureCellData cellData, + required CellData cellData, }) : super(NumberCellState.initial()) { on( (event, emit) async { diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart index 7c1358bced..2cde7c6a96 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart @@ -1,5 +1,7 @@ +import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; +import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -13,12 +15,17 @@ class SelectionCellBloc extends Bloc { SelectionCellBloc({ required this.service, - required FutureCellData cellData, - }) : super(SelectionCellState.initial()) { + required CellData cellData, + }) : super(SelectionCellState.initial(cellData)) { on( (event, emit) async { await event.map( - initial: (_InitialCell value) async {}, + initial: (_InitialCell value) async { + _loadOptions(); + }, + didReceiveOptions: (_DidReceiveOptions value) { + emit(state.copyWith(options: value.options, selectedOptions: value.selectedOptions)); + }, ); }, ); @@ -28,19 +35,57 @@ class SelectionCellBloc extends Bloc { Future close() async { return super.close(); } + + void _loadOptions() async { + final result = await FieldContextLoaderAdaptor( + gridId: state.cellData.gridId, + field: state.cellData.field, + ).load(); + + result.fold( + (context) { + List options = []; + switch (state.cellData.field.fieldType) { + case FieldType.MultiSelect: + options.addAll(MultiSelectTypeOption.fromBuffer(context.typeOptionData).options); + break; + case FieldType.SingleSelect: + options.addAll(SingleSelectTypeOption.fromBuffer(context.typeOptionData).options); + break; + default: + Log.error("Invalid field type, expect single select or multiple select"); + break; + } + + final ids = state.cellData.cell?.content.split(','); + final selectedOptions = ids?.map((id) => options.firstWhere((option) => option.id == id)).toList() ?? []; + add(SelectionCellEvent.didReceiveOptions(options, selectedOptions)); + }, + (err) => Log.error(err), + ); + } } @freezed class SelectionCellEvent with _$SelectionCellEvent { const factory SelectionCellEvent.initial() = _InitialCell; + const factory SelectionCellEvent.didReceiveOptions( + List options, + List selectedOptions, + ) = _DidReceiveOptions; } @freezed class SelectionCellState with _$SelectionCellState { - const factory SelectionCellState() = _SelectionCellState; - // required String girdId, - // required Field field, - // required List options, + const factory SelectionCellState({ + required CellData cellData, + required List options, + required List selectedOptions, + }) = _SelectionCellState; - factory SelectionCellState.initial() => const SelectionCellState(); + factory SelectionCellState.initial(CellData cellData) => SelectionCellState( + cellData: cellData, + options: [], + selectedOptions: [], + ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart index 7e617069c4..e2109a4c57 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart @@ -11,16 +11,18 @@ import 'cell_service.dart'; part 'selection_editor_bloc.freezed.dart'; -class SelectionEditorBloc extends Bloc { +class SelectOptionEditorBloc extends Bloc { final CellService service = CellService(); final FieldListener _listener; - SelectionEditorBloc({ + SelectOptionEditorBloc({ required String gridId, required Field field, + required List options, + required List selectedOptions, }) : _listener = FieldListener(fieldId: field.id), - super(SelectionEditorState.initial(gridId, field)) { - on( + super(SelectOptionEditorState.initial(gridId, field, options, selectedOptions)) { + on( (event, emit) async { await event.map( initial: (_Initial value) async { @@ -34,6 +36,7 @@ class SelectionEditorBloc extends Bloc add(SelectionEditorEvent.didReceiveFieldUpdate(field)), + (field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)), (err) => Log.error(err), ); }); @@ -70,7 +73,7 @@ class SelectionEditorBloc extends Bloc Log.error(err), ); @@ -78,25 +81,33 @@ class SelectionEditorBloc extends Bloc options) = _DidReceiveOptions; +class SelectOptionEditorEvent with _$SelectOptionEditorEvent { + const factory SelectOptionEditorEvent.initial() = _Initial; + const factory SelectOptionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; + const factory SelectOptionEditorEvent.didReceiveOptions(List options) = _DidReceiveOptions; + const factory SelectOptionEditorEvent.newOption(String optionName) = _newOption; } @freezed -class SelectionEditorState with _$SelectionEditorState { - const factory SelectionEditorState({ +class SelectOptionEditorState with _$SelectOptionEditorState { + const factory SelectOptionEditorState({ required String gridId, required Field field, required List options, - }) = _SelectionEditorState; + required List selectedOptions, + }) = _SelectOptionEditorState; - factory SelectionEditorState.initial(String gridId, Field field) { - return SelectionEditorState( + factory SelectOptionEditorState.initial( + String gridId, + Field field, + List options, + List selectedOptions, + ) { + return SelectOptionEditorState( gridId: gridId, field: field, - options: [], + options: options, + selectedOptions: selectedOptions, ); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart index f2162d8851..5af39bbd9f 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart @@ -11,7 +11,7 @@ class TextCellBloc extends Bloc { TextCellBloc({ required this.service, - required FutureCellData cellData, + required CellData cellData, }) : super(TextCellState.initial(cellData)) { on( (event, emit) async { @@ -53,7 +53,7 @@ class TextCellBloc extends Bloc { @freezed class TextCellEvent with _$TextCellEvent { const factory TextCellEvent.initial() = _InitialCell; - const factory TextCellEvent.didReceiveCellData(GridCellData cellData) = _DidReceiveCellData; + const factory TextCellEvent.didReceiveCellData(CellData cellData) = _DidReceiveCellData; const factory TextCellEvent.updateText(String text) = _UpdateText; } @@ -61,10 +61,10 @@ class TextCellEvent with _$TextCellEvent { class TextCellState with _$TextCellState { const factory TextCellState({ required String content, - required FutureCellData cellData, + required CellData cellData, }) = _TextCellState; - factory TextCellState.initial(FutureCellData cellData) => TextCellState( + factory TextCellState.initial(CellData cellData) => TextCellState( content: cellData.cell?.content ?? "", cellData: cellData, ); 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 00f90e0bfc..4b63ec0251 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart @@ -169,14 +169,14 @@ class GridBlockRow { }); } -class GridRowData extends Equatable { +class RowData extends Equatable { final String gridId; final String rowId; final String blockId; final List fields; final double height; - const GridRowData({ + const RowData({ required this.gridId, required this.rowId, required this.blockId, @@ -184,8 +184,8 @@ class GridRowData extends Equatable { required this.height, }); - factory GridRowData.fromBlockRow(GridBlockRow row, List fields) { - return GridRowData( + factory RowData.fromBlockRow(GridBlockRow row, List fields) { + return RowData( gridId: row.gridId, rowId: row.rowId, blockId: row.blockId, 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 85e5c35650..14f30b4359 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 @@ -13,14 +13,14 @@ import 'package:dartz/dartz.dart'; part 'row_bloc.freezed.dart'; -typedef CellDataMap = LinkedHashMap; +typedef CellDataMap = LinkedHashMap; class RowBloc extends Bloc { final RowService rowService; final RowListener rowlistener; final GridFieldsListener fieldListener; - RowBloc({required GridRowData rowData, required this.rowlistener}) + RowBloc({required RowData rowData, required this.rowlistener}) : rowService = RowService( gridId: rowData.gridId, blockId: rowData.blockId, @@ -112,7 +112,7 @@ class RowBloc extends Bloc { var map = CellDataMap.new(); for (final field in state.fields) { if (field.visibility) { - map[field.id] = GridCellData( + map[field.id] = CellData( rowId: row.id, gridId: rowService.gridId, blockId: rowService.blockId, @@ -143,7 +143,7 @@ class RowState with _$RowState { required Option cellDataMap, }) = _RowState; - factory RowState.initial(GridRowData data) => RowState( + factory RowState.initial(RowData data) => RowState( rowId: data.rowId, rowHeight: data.height, fields: data.fields, diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart index 1ef7b58d4b..a65cb81b29 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart @@ -29,16 +29,14 @@ class RowService { } } -typedef FutureCellData = GridCellData; - -class GridCellData extends Equatable { +class CellData extends Equatable { final String gridId; final String rowId; final String blockId; final Field field; final Cell? cell; - const GridCellData({ + const CellData({ required this.rowId, required this.gridId, required this.blockId, 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 111235841f..936ad68c44 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 @@ -157,7 +157,7 @@ class _FlowyGridState extends State { (context, index) { final blockRow = context.read().state.rows[index]; final fields = context.read().state.fields; - final rowData = GridRowData.fromBlockRow(blockRow, fields); + final rowData = RowData.fromBlockRow(blockRow, fields); return GridRowWidget(data: rowData, key: ValueKey(rowData.rowId)); }, childCount: context.read().state.rows.length, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart index c24bdb4326..23a541e610 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart @@ -9,8 +9,9 @@ class GridSize { static double get leadingHeaderPadding => 30 * scale; static double get trailHeaderPadding => 140 * scale; static double get headerContainerPadding => 0 * scale; - static double get cellContentPadding => 10 * scale; - static double get typeOptionItemHeight => 30 * scale; + static double get cellHPadding => 10 * scale; + static double get cellVPadding => 8 * scale; + static double get typeOptionItemHeight => 32 * scale; static double get typeOptionSeparatorHeight => 6 * scale; // @@ -19,13 +20,13 @@ class GridSize { vertical: GridSize.headerContainerPadding, ); static EdgeInsets get cellContentInsets => EdgeInsets.symmetric( - horizontal: GridSize.cellContentPadding, - vertical: GridSize.cellContentPadding, + horizontal: GridSize.cellHPadding, + vertical: GridSize.cellVPadding, ); static EdgeInsets get fieldContentInsets => EdgeInsets.symmetric( - horizontal: GridSize.cellContentPadding, - vertical: GridSize.cellContentPadding, + horizontal: GridSize.cellHPadding, + vertical: GridSize.cellVPadding, ); static EdgeInsets get typeOptionContentInsets => const EdgeInsets.symmetric( diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index 178a44f9d7..3b84ac557a 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart @@ -7,7 +7,7 @@ import 'number_cell.dart'; import 'selection_cell/selection_cell.dart'; import 'text_cell.dart'; -Widget buildGridCell(FutureCellData cellData) { +Widget buildGridCell(CellData cellData) { final key = ValueKey(cellData.field.id + cellData.rowId); switch (cellData.field.fieldType) { case FieldType.Checkbox: diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart index 8890dc7576..57f3610be2 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart @@ -38,7 +38,7 @@ class CellContainer extends StatelessWidget { ), decoration: _makeBoxDecoration(context, state), padding: GridSize.cellContentInsets, - child: Center(child: IntrinsicHeight(child: child)), + child: Center(child: child), ); }, ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart index dc8f757e36..7e1fdf166a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart @@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class CheckboxCell extends StatefulWidget { - final FutureCellData cellData; + final CellData cellData; const CheckboxCell({ required this.cellData, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart index ec1babd799..e3b48279c0 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart @@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class DateCell extends StatefulWidget { - final FutureCellData cellData; + final CellData cellData; const DateCell({ required this.cellData, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart index bc51829412..41b8cf6ee5 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class NumberCell extends StatefulWidget { - final FutureCellData cellData; + final CellData cellData; const NumberCell({ required this.cellData, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart index 7d5793f768..9a91999a9d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart @@ -1,3 +1,6 @@ +import 'dart:collection'; + +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; @@ -61,10 +64,19 @@ extension SelectOptionColorExtension on SelectOptionColor { } class SelectOptionTextField extends StatelessWidget { - final TextEditingController _controller; final FocusNode _focusNode; + final TextEditingController _controller; + final TextfieldTagsController tagController; + final LinkedHashMap optionMap; + final double distanceToText; + + final Function(String) onNewTag; SelectOptionTextField({ + required this.optionMap, + required this.distanceToText, + required this.tagController, + required this.onNewTag, TextEditingController? controller, FocusNode? focusNode, Key? key, @@ -74,26 +86,45 @@ class SelectOptionTextField extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = context.watch(); + return TextFieldTags( textEditingController: _controller, - initialTags: ["abc", "bdf"], + textfieldTagsController: tagController, + initialTags: optionMap.keys.toList(), focusNode: _focusNode, textSeparators: const [' ', ','], inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) { return ((context, sc, tags, onTagDelegate) { + tags.retainWhere((name) => optionMap.containsKey(name) == false); + if (tags.isNotEmpty) { + assert(tags.length == 1); + onNewTag(tags.first); + } + return TextField( controller: editController, focusNode: focusNode, onChanged: onChanged, onSubmitted: onSubmitted, - onEditingComplete: () => focusNode.unfocus(), maxLines: 1, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), decoration: InputDecoration( - contentPadding: EdgeInsets.zero, - border: InputBorder.none, + border: OutlineInputBorder( + borderSide: BorderSide(color: theme.shader3, width: 1.0), + borderRadius: Corners.s10Border, + ), isDense: true, - prefixIcon: _renderTags(tags, sc), + prefixIcon: _renderTags(sc), + hintText: LocaleKeys.grid_selectOption_searchOption.tr(), + prefixIconConstraints: BoxConstraints(maxWidth: distanceToText), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: theme.main1, + width: 1.0, + ), + borderRadius: Corners.s10Border, + ), ), ); }); @@ -101,41 +132,26 @@ class SelectOptionTextField extends StatelessWidget { ); } - Widget? _renderTags(List tags, ScrollController sc) { - if (tags.isEmpty) { + Widget? _renderTags(ScrollController sc) { + if (optionMap.isEmpty) { return null; } - return SingleChildScrollView( - controller: sc, - scrollDirection: Axis.horizontal, - child: Row(children: [ - Container( - decoration: BoxDecoration( - color: Color.fromARGB(255, 74, 137, 92), - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(6.0), - ), - child: FlowyText.medium("efc", fontSize: 12), - ), - Container( - decoration: BoxDecoration( - color: Color.fromARGB(255, 74, 137, 92), - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(6.0), - ), - child: FlowyText.medium("abc", fontSize: 12), - margin: const EdgeInsets.symmetric(horizontal: 5.0), - padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), - ) - ]), + final children = optionMap.values.map((option) => SelectOptionTag(option: option)).toList(); + return Padding( + padding: const EdgeInsets.all(8.0), + child: SingleChildScrollView( + controller: sc, + scrollDirection: Axis.horizontal, + child: Row(children: children), + ), ); } } -class SelectionBadge extends StatelessWidget { +class SelectOptionTag extends StatelessWidget { final SelectOption option; - const SelectionBadge({required this.option, Key? key}) : super(key: key); + const SelectOptionTag({required this.option, Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -143,9 +159,11 @@ class SelectionBadge extends StatelessWidget { decoration: BoxDecoration( color: option.color.make(context), shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(6.0), + borderRadius: BorderRadius.circular(8.0), ), - child: FlowyText.medium(option.name, fontSize: 12), + child: Center(child: FlowyText.medium(option.name, fontSize: 12)), + margin: const EdgeInsets.symmetric(horizontal: 3.0), + padding: const EdgeInsets.symmetric(horizontal: 6.0), ); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart index 5584b7ded5..3fe1b19c14 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart @@ -6,9 +6,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'extension.dart'; +import 'selection_editor.dart'; class SingleSelectCell extends StatefulWidget { - final FutureCellData cellData; + final CellData cellData; const SingleSelectCell({ required this.cellData, @@ -20,30 +21,28 @@ class SingleSelectCell extends StatefulWidget { } class _SingleSelectCellState extends State { - late CellFocusNode _focusNode; late SelectionCellBloc _cellBloc; - late TextEditingController _controller; @override void initState() { - _cellBloc = getIt(param1: widget.cellData); - _controller = TextEditingController(); - _focusNode = CellFocusNode(); + _cellBloc = getIt(param1: widget.cellData)..add(const SelectionCellEvent.initial()); super.initState(); } @override Widget build(BuildContext context) { - _focusNode.addCallback(context, () { - Log.info(_focusNode.hasFocus); - }); return BlocProvider.value( value: _cellBloc, child: BlocBuilder( builder: (context, state) { - return SelectOptionTextField( - focusNode: _focusNode, - controller: _controller, + final children = state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList(); + return SizedBox.expand( + child: InkWell( + onTap: () { + SelectionEditor.show(context, state.cellData, state.options, state.selectedOptions); + }, + child: Row(children: children), + ), ); }, ), @@ -53,14 +52,13 @@ class _SingleSelectCellState extends State { @override Future dispose() async { _cellBloc.close(); - _focusNode.dispose(); super.dispose(); } } //---------------------------------------------------------------- class MultiSelectCell extends StatefulWidget { - final FutureCellData cellData; + final CellData cellData; const MultiSelectCell({ required this.cellData, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart index 20e329c788..0c8d207a4b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart @@ -1,6 +1,9 @@ +import 'dart:collection'; + import 'package:app_flowy/workspace/application/grid/cell_bloc/selection_editor_bloc.dart'; import 'package:app_flowy/workspace/application/grid/row/row_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/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; @@ -12,42 +15,81 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:textfield_tags/textfield_tags.dart'; import 'extension.dart'; -class SelectionEditor extends StatelessWidget { - final GridCellData cellData; - const SelectionEditor({required this.cellData, Key? key}) : super(key: key); +const double _editorPannelWidth = 300; - void show(BuildContext context) { - FlowyOverlay.of(context).insertWithAnchor( - widget: OverlayContainer( - child: this, - constraints: BoxConstraints.loose(const Size(240, 200)), - ), - identifier: toString(), - anchorContext: context, - anchorDirection: AnchorDirection.bottomWithLeftAligned, - ); +class SelectionEditor extends StatelessWidget { + final CellData cellData; + final List options; + final List selectedOptions; + + const SelectionEditor({ + required this.cellData, + required this.options, + required this.selectedOptions, + Key? key, + }) : super(key: key); + + static String identifier() { + return (SelectionEditor).toString(); } @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => SelectionEditorBloc(gridId: cellData.gridId, field: cellData.field), - child: BlocBuilder( + create: (context) => SelectOptionEditorBloc( + gridId: cellData.gridId, + field: cellData.field, + options: options, + selectedOptions: selectedOptions, + ), + child: BlocBuilder( builder: (context, state) { - return Column( - children: const [ - _Title(), - VSpace(10), - _OptionList(), + return CustomScrollView( + shrinkWrap: true, + slivers: [ + SliverToBoxAdapter(child: _TextField()), + const SliverToBoxAdapter(child: VSpace(10)), + const SliverToBoxAdapter(child: _Title()), + const SliverToBoxAdapter(child: _OptionList()), ], ); }, ), ); } + + static void show( + BuildContext context, + CellData cellData, + List options, + List selectedOptions, + ) { + SelectionEditor.hide(context); + final editor = SelectionEditor( + cellData: cellData, + options: options, + selectedOptions: selectedOptions, + ); + + // + FlowyOverlay.of(context).insertWithAnchor( + widget: OverlayContainer( + child: SizedBox(width: _editorPannelWidth, child: editor), + constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)), + ), + identifier: SelectionEditor.identifier(), + anchorContext: context, + anchorDirection: AnchorDirection.bottomWithCenterAligned, + ); + } + + static void hide(BuildContext context) { + FlowyOverlay.of(context).remove(identifier()); + } } class _OptionList extends StatelessWidget { @@ -55,9 +97,9 @@ class _OptionList extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { - final cells = state.options.map((option) => _SelectionCell(option)).toList(); + final cells = state.options.map((option) => _SelectOptionCell(option)).toList(); final list = ListView.separated( shrinkWrap: true, controller: ScrollController(), @@ -76,40 +118,84 @@ class _OptionList extends StatelessWidget { } } +class _TextField extends StatelessWidget { + final TextfieldTagsController _tagController = TextfieldTagsController(); + + _TextField({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) {}, + buildWhen: (previous, current) => previous.field.id != current.field.id, + builder: (context, state) { + final optionMap = LinkedHashMap.fromIterable(state.selectedOptions, + key: (option) => option.name, value: (option) => option); + return SizedBox( + height: 42, + child: SelectOptionTextField( + optionMap: optionMap, + distanceToText: _editorPannelWidth * 0.7, + tagController: _tagController, + onNewTag: (newTagName) { + context.read().add(SelectOptionEditorEvent.newOption(newTagName)); + }, + ), + ); + }, + ); + } +} + class _Title extends StatelessWidget { const _Title({Key? key}) : super(key: key); @override Widget build(BuildContext context) { + final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, - child: FlowyText.medium(LocaleKeys.grid_selectOption_pannelTitle.tr(), fontSize: 12), - ); - } -} - -class _SelectionCell extends StatelessWidget { - final SelectOption option; - const _SelectionCell(this.option, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final theme = context.watch(); - - // return FlowyButton( - // text: FlowyText.medium(fieldType.title(), fontSize: 12), - // hoverColor: theme.hover, - // onTap: () => onSelectField(fieldType), - // leftIcon: svgWidget(fieldType.iconName(), color: theme.iconColor), - // ); - - return InkWell( - onTap: () {}, - child: FlowyHover( - config: HoverDisplayConfig(hoverColor: theme.hover), - builder: (_, onHover) { - return SelectionBadge(option: option); - }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: FlowyText.medium( + LocaleKeys.grid_selectOption_pannelTitle.tr(), + fontSize: 12, + color: theme.shader3, + ), + ), + ); + } +} + +class _SelectOptionCell extends StatelessWidget { + final SelectOption option; + const _SelectOptionCell(this.option, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: InkWell( + onTap: () {}, + child: FlowyHover( + config: HoverDisplayConfig(hoverColor: theme.hover), + builder: (_, onHover) { + List children = [ + SelectOptionTag(option: option), + const Spacer(), + ]; + + if (onHover) { + children.add(svgWidget("editor/details", color: theme.iconColor)); + } + + return Padding( + padding: const EdgeInsets.all(3.0), + child: Row(children: children), + ); + }, + ), ), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index c4e272aeb4..c7b4219bab 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart @@ -6,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_container.dart'; class GridTextCell extends GridCell { - final FutureCellData cellData; + final CellData cellData; const GridTextCell({ required this.cellData, Key? key, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart new file mode 100644 index 0000000000..41b8cf6ee5 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart @@ -0,0 +1,44 @@ +import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class NumberCell extends StatefulWidget { + final CellData cellData; + + const NumberCell({ + required this.cellData, + Key? key, + }) : super(key: key); + + @override + State createState() => _NumberCellState(); +} + +class _NumberCellState extends State { + late NumberCellBloc _cellBloc; + + @override + void initState() { + _cellBloc = getIt(param1: widget.cellData); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _cellBloc, + child: BlocBuilder( + builder: (context, state) { + return Container(); + }, + ), + ); + } + + @override + Future dispose() async { + _cellBloc.close(); + super.dispose(); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart index 06218f5776..5586328e32 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart @@ -10,7 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; class GridRowWidget extends StatefulWidget { - final GridRowData data; + final RowData data; const GridRowWidget({required this.data, Key? key}) : super(key: key); @override diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/number_cell.dart new file mode 100644 index 0000000000..41b8cf6ee5 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/number_cell.dart @@ -0,0 +1,44 @@ +import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class NumberCell extends StatefulWidget { + final CellData cellData; + + const NumberCell({ + required this.cellData, + Key? key, + }) : super(key: key); + + @override + State createState() => _NumberCellState(); +} + +class _NumberCellState extends State { + late NumberCellBloc _cellBloc; + + @override + void initState() { + _cellBloc = getIt(param1: widget.cellData); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _cellBloc, + child: BlocBuilder( + builder: (context, state) { + return Container(); + }, + ), + ); + } + + @override + Future dispose() async { + _cellBloc.close(); + super.dispose(); + } +} diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart index 7ddfdc1c13..55f29eba93 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -44,7 +44,7 @@ class FlowyText extends StatelessWidget { softWrap: false, textAlign: textAlign, style: TextStyle( - color: theme.textColor, + color: color ?? theme.textColor, fontWeight: fontWeight, fontSize: fontSize + 2, fontFamily: 'Mulish', diff --git a/shared-lib/flowy-grid-data-model/src/entities/meta.rs b/shared-lib/flowy-grid-data-model/src/entities/meta.rs index a543085a78..9be0faa99d 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/meta.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/meta.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString}; -pub const DEFAULT_ROW_HEIGHT: i32 = 36; +pub const DEFAULT_ROW_HEIGHT: i32 = 42; pub const DEFAULT_FIELD_WIDTH: i32 = 150; #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]