diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart index 3834db112c..c8a0d87f9c 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart @@ -21,6 +21,9 @@ class BoardCheckboxCellBloc didReceiveCellUpdate: (cellData) { emit(state.copyWith(isSelected: _isSelected(cellData))); }, + select: () async { + cellController.saveCellData(!state.isSelected ? "Yes" : "No"); + }, ); }, ); @@ -50,6 +53,7 @@ class BoardCheckboxCellBloc @freezed class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent { const factory BoardCheckboxCellEvent.initial() = _InitialCell; + const factory BoardCheckboxCellEvent.select() = _Selected; const factory BoardCheckboxCellEvent.didReceiveCellUpdate( String cellContent) = _DidReceiveCellUpdate; } diff --git a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart index 1f4df9468a..e6c9b22d9d 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -215,7 +215,7 @@ class _BoardContentState extends State { Widget _buildCard( BuildContext context, - AppFlowyGroupData column, + AppFlowyGroupData group, AppFlowyGroupItem columnItem, ) { final boardColumnItem = columnItem as BoardColumnItem; @@ -242,10 +242,11 @@ class _BoardContentState extends State { }, ); - ValueKey? key = cardKeysCache[columnItem.id]; + final groupItemId = columnItem.id + group.id; + ValueKey? key = cardKeysCache[groupItemId]; if (key == null) { - final newKey = ValueKey(columnItem.id); - cardKeysCache[columnItem.id] = newKey; + final newKey = ValueKey(groupItemId); + cardKeysCache[groupItemId] = newKey; key = newKey; } @@ -255,7 +256,7 @@ class _BoardContentState extends State { decoration: _makeBoxDecoration(context), child: BoardCard( gridId: gridId, - groupId: column.id, + groupId: group.id, fieldId: boardColumnItem.fieldContext.id, isEditing: isEditing, cellBuilder: cellBuilder, diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_checkbox_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_checkbox_cell.dart index f832d3749d..6b94d3399b 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_checkbox_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_checkbox_cell.dart @@ -48,6 +48,9 @@ class _BoardCheckboxCellState extends State { iconPadding: EdgeInsets.zero, icon: icon, width: 20, + onPressed: () => context + .read() + .add(const BoardCheckboxCellEvent.select()), ), ); }, diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart index f5e7a451e2..5f7aee108e 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart @@ -20,7 +20,7 @@ class CheckboxCellBloc extends Bloc { _startListening(); }, select: () async { - _updateCellData(); + cellController.saveCellData(!state.isSelected ? "Yes" : "No"); }, didReceiveCellUpdate: (cellData) { emit(state.copyWith(isSelected: _isSelected(cellData))); @@ -49,10 +49,6 @@ class CheckboxCellBloc extends Bloc { } })); } - - void _updateCellData() { - cellController.saveCellData(!state.isSelected ? "Yes" : "No"); - } } @freezed diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart index 349d95d13f..03abe5f9c9 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart @@ -103,11 +103,19 @@ class SelectOptionCellEditorBloc void _filterOption(String optionName, Emitter emit) { final _MakeOptionResult result = _makeOptions(Some(optionName), state.allOptions); - emit(state.copyWith( - filter: Some(optionName), - options: result.options, - createOption: result.createOption, - )); + if (optionName.isEmpty) { + emit(state.copyWith( + filter: Some(optionName), + options: result.options, + createOption: none(), + )); + } else { + emit(state.copyWith( + filter: Some(optionName), + options: result.options, + createOption: result.createOption, + )); + } } void _loadOptions() { diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart index f045984e66..93b0699462 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart @@ -100,11 +100,7 @@ class SelectOptionTag extends StatelessWidget { backgroundColor: color, labelPadding: const EdgeInsets.symmetric(horizontal: 6), selected: true, - onSelected: (_) { - if (onSelected != null) { - onSelected!(); - } - }, + onSelected: (_) => onSelected?.call(), ); } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart index 024d5ff356..897b6f81c3 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart @@ -23,7 +23,7 @@ import '../../header/type_option/select_option_editor.dart'; import 'extension.dart'; import 'text_field.dart'; -const double _editorPannelWidth = 300; +const double _editorPanelWidth = 300; class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { final GridSelectOptionCellController cellController; @@ -75,8 +75,8 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { // FlowyOverlay.of(context).insertWithAnchor( widget: OverlayContainer( - constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)), - child: SizedBox(width: _editorPannelWidth, child: editor), + constraints: BoxConstraints.loose(const Size(_editorPanelWidth, 300)), + child: SizedBox(width: _editorPanelWidth, child: editor), ), identifier: SelectOptionCellEditor.identifier(), anchorContext: context, @@ -161,7 +161,7 @@ class _TextField extends StatelessWidget { child: SelectOptionTextField( options: state.options, selectedOptionMap: optionMap, - distanceToText: _editorPannelWidth * 0.7, + distanceToText: _editorPanelWidth * 0.7, tagController: _tagController, onClick: () => FlowyOverlay.of(context) .remove(SelectOptionTypeOptionEditor.identifier), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart index 022d411f2b..8149783d47 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart @@ -32,10 +32,10 @@ class SelectOptionTextField extends StatelessWidget { required this.onNewTag, required this.newText, this.onClick, - TextEditingController? controller, + TextEditingController? textController, FocusNode? focusNode, Key? key, - }) : _controller = controller ?? TextEditingController(), + }) : _controller = textController ?? TextEditingController(), _focusNode = focusNode ?? FocusNode(), super(key: key); @@ -48,7 +48,7 @@ class SelectOptionTextField extends StatelessWidget { textfieldTagsController: tagController, initialTags: selectedOptionMap.keys.toList(), focusNode: _focusNode, - textSeparators: const [' ', ','], + textSeparators: const [','], inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) { return ((context, sc, tags, onTagDelegate) { @@ -70,6 +70,7 @@ class SelectOptionTextField extends StatelessWidget { if (text.isNotEmpty) { onNewTag(text); + focusNode.requestFocus(); } }, maxLines: 1, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart index 83a6a27fda..8a8c445ec4 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart @@ -13,12 +13,14 @@ import 'field_type_option_editor.dart'; class FieldEditor extends StatefulWidget { final String gridId; final String fieldName; + final VoidCallback? onRemoved; final IFieldTypeOptionLoader typeOptionLoader; const FieldEditor({ required this.gridId, - required this.fieldName, + this.fieldName = "", required this.typeOptionLoader, + this.onRemoved, Key? key, }) : super(key: key); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_name_input.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_name_input.dart index acc1256cbc..365064e33f 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_name_input.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_name_input.dart @@ -33,6 +33,7 @@ class _FieldNameTextFieldState extends State { final theme = context.watch(); return RoundedInputField( height: 36, + autoFocus: true, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), controller: controller, normalBorderColor: theme.shader4, @@ -47,7 +48,8 @@ class _FieldNameTextFieldState extends State { @override void didUpdateWidget(covariant FieldNameTextField oldWidget) { controller.text = widget.name; - controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length)); + controller.selection = TextSelection.fromPosition( + TextPosition(offset: controller.text.length)); super.didUpdateWidget(oldWidget); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart index 4965fb5407..fe148d9dcb 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart @@ -117,6 +117,7 @@ class _PropertyList extends StatelessWidget { axis: Axis.vertical, controller: _scrollController, barSize: GridSize.scrollBarSize, + autoHideScrollbar: false, child: ListView.separated( controller: _scrollController, itemCount: state.gridCells.length, @@ -132,7 +133,27 @@ class _PropertyList extends StatelessWidget { ), ), ), - _CreateFieldButton(viewId: viewId), + _CreateFieldButton( + viewId: viewId, + onClosed: () { + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 250), + curve: Curves.ease, + ); + }); + }, + onOpened: () { + return OverlayContainer( + constraints: BoxConstraints.loose(const Size(240, 200)), + child: FieldEditor( + gridId: viewId, + typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId), + ), + ); + }, + ), ], ); }, @@ -142,7 +163,14 @@ class _PropertyList extends StatelessWidget { class _CreateFieldButton extends StatelessWidget { final String viewId; - const _CreateFieldButton({required this.viewId, Key? key}) : super(key: key); + final Widget Function() onOpened; + final VoidCallback onClosed; + const _CreateFieldButton({ + required this.viewId, + required this.onOpened, + required this.onClosed, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -150,6 +178,8 @@ class _CreateFieldButton extends StatelessWidget { return Popover( triggerActions: PopoverTriggerActionFlags.click, + direction: PopoverDirection.bottomWithLeftAligned, + onClose: onClosed, child: SizedBox( height: 40, child: FlowyButton( @@ -162,16 +192,7 @@ class _CreateFieldButton extends StatelessWidget { leftIcon: svgWidget("home/add"), ), ), - popupBuilder: (BuildContext context) { - return OverlayContainer( - constraints: BoxConstraints.loose(const Size(240, 200)), - child: FieldEditor( - gridId: viewId, - fieldName: "", - typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId), - ), - ); - }, + popupBuilder: (BuildContext context) => onOpened(), ); } } diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart index 40045ed3cc..619d8b1eaa 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart @@ -14,6 +14,7 @@ class StyledScrollbar extends StatefulWidget { final ScrollController controller; final Function(double)? onDrag; final bool showTrack; + final bool autoHideScrollbar; final Color? handleColor; final Color? trackColor; @@ -30,6 +31,7 @@ class StyledScrollbar extends StatefulWidget { this.onDrag, this.contentSize, this.showTrack = false, + this.autoHideScrollbar = true, this.handleColor, this.trackColor}) : super(key: key); @@ -48,16 +50,13 @@ class ScrollbarState extends State { widget.controller.addListener(() => setState(() {})); widget.controller.position.isScrollingNotifier.addListener( () { - if (!mounted) { - return; - } + if (!mounted) return; + if (!widget.autoHideScrollbar) return; _hideScrollbarOperation?.cancel(); if (!widget.controller.position.isScrollingNotifier.value) { - // scroll is stopped _hideScrollbarOperation = CancelableOperation.fromFuture( Future.delayed(const Duration(seconds: 2), () {}), ).then((_) { - // Opti: hide with animation hideHandler = true; if (mounted) { setState(() {}); @@ -103,7 +102,6 @@ class ScrollbarState extends State { break; case Axis.horizontal: // Use supplied contentSize if we have it, otherwise just fallback to maxScrollExtents - if (contentSize != null && contentSize > 0) { maxExtent = contentSize - constraints.maxWidth; } else { @@ -118,7 +116,8 @@ class ScrollbarState extends State { // Calculate the alignment for the handle, this is a value between 0 and 1, // it automatically takes the handle size into acct // ignore: omit_local_variable_types - double handleAlignment = maxExtent == 0 ? 0 : widget.controller.offset / maxExtent; + double handleAlignment = + maxExtent == 0 ? 0 : widget.controller.offset / maxExtent; // Convert handle alignment from [0, 1] to [-1, 1] handleAlignment *= 2.0; @@ -127,19 +126,25 @@ class ScrollbarState extends State { // Calculate handleSize by comparing the total content size to our viewport var handleExtent = _viewExtent; if (contentExtent > _viewExtent) { - //Make sure handle is never small than the minSize + // Make sure handle is never small than the minSize handleExtent = max(60, _viewExtent * _viewExtent / contentExtent); } + // Hide the handle if content is < the viewExtent var showHandle = contentExtent > _viewExtent && contentExtent > 0; + if (hideHandler) { showHandle = false; } // Handle color - var handleColor = widget.handleColor ?? (theme.isDark ? theme.bg2.withOpacity(.2) : theme.bg2); + var handleColor = widget.handleColor ?? + (theme.isDark ? theme.bg2.withOpacity(.2) : theme.bg2); // Track color - var trackColor = widget.trackColor ?? (theme.isDark ? theme.bg2.withOpacity(.1) : theme.bg2.withOpacity(.3)); + var trackColor = widget.trackColor ?? + (theme.isDark + ? theme.bg2.withOpacity(.1) + : theme.bg2.withOpacity(.3)); //Layout the stack, it just contains a child, and return Stack(children: [ @@ -149,8 +154,12 @@ class ScrollbarState extends State { alignment: const Alignment(1, 1), child: Container( color: trackColor, - width: widget.axis == Axis.vertical ? widget.size : double.infinity, - height: widget.axis == Axis.horizontal ? widget.size : double.infinity, + width: widget.axis == Axis.vertical + ? widget.size + : double.infinity, + height: widget.axis == Axis.horizontal + ? widget.size + : double.infinity, ), ), @@ -167,10 +176,14 @@ class ScrollbarState extends State { // HANDLE SHAPE child: MouseHoverBuilder( builder: (_, isHovered) => Container( - width: widget.axis == Axis.vertical ? widget.size : handleExtent, - height: widget.axis == Axis.horizontal ? widget.size : handleExtent, + width: + widget.axis == Axis.vertical ? widget.size : handleExtent, + height: widget.axis == Axis.horizontal + ? widget.size + : handleExtent, decoration: BoxDecoration( - color: handleColor.withOpacity(isHovered ? 1 : .85), borderRadius: Corners.s3Border), + color: handleColor.withOpacity(isHovered ? 1 : .85), + borderRadius: Corners.s3Border), ), ), ), @@ -182,15 +195,19 @@ class ScrollbarState extends State { void _handleHorizontalDrag(DragUpdateDetails details) { var pos = widget.controller.offset; - var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) / _viewExtent; - widget.controller.jumpTo((pos + details.delta.dx * pxRatio).clamp(0.0, widget.controller.position.maxScrollExtent)); + var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) / + _viewExtent; + widget.controller.jumpTo((pos + details.delta.dx * pxRatio) + .clamp(0.0, widget.controller.position.maxScrollExtent)); widget.onDrag?.call(details.delta.dx); } void _handleVerticalDrag(DragUpdateDetails details) { var pos = widget.controller.offset; - var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) / _viewExtent; - widget.controller.jumpTo((pos + details.delta.dy * pxRatio).clamp(0.0, widget.controller.position.maxScrollExtent)); + var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) / + _viewExtent; + widget.controller.jumpTo((pos + details.delta.dy * pxRatio) + .clamp(0.0, widget.controller.position.maxScrollExtent)); widget.onDrag?.call(details.delta.dy); } } @@ -204,6 +221,7 @@ class ScrollbarListStack extends StatelessWidget { final EdgeInsets? scrollbarPadding; final Color? handleColor; final Color? trackColor; + final bool autoHideScrollbar; const ScrollbarListStack( {Key? key, @@ -214,6 +232,7 @@ class ScrollbarListStack extends StatelessWidget { this.contentSize, this.scrollbarPadding, this.handleColor, + this.autoHideScrollbar = true, this.trackColor}) : super(key: key); @@ -238,6 +257,7 @@ class ScrollbarListStack extends StatelessWidget { contentSize: contentSize, trackColor: trackColor, handleColor: handleColor, + autoHideScrollbar: autoHideScrollbar, ), ) // The animate will be used by the children that using styled_widget.