Browse Source

Merge pull request #1004 from AppFlowy-IO/fix/board_ui_bugs

Fix/board UI bugs
Nathan.fooo 2 years ago
parent
commit
69da9acd8b

+ 4 - 0
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;
 }

+ 6 - 5
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -215,7 +215,7 @@ class _BoardContentState extends State<BoardContent> {
 
   Widget _buildCard(
     BuildContext context,
-    AppFlowyGroupData column,
+    AppFlowyGroupData group,
     AppFlowyGroupItem columnItem,
   ) {
     final boardColumnItem = columnItem as BoardColumnItem;
@@ -242,10 +242,11 @@ class _BoardContentState extends State<BoardContent> {
       },
     );
 
-    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<BoardContent> {
       decoration: _makeBoxDecoration(context),
       child: BoardCard(
         gridId: gridId,
-        groupId: column.id,
+        groupId: group.id,
         fieldId: boardColumnItem.fieldContext.id,
         isEditing: isEditing,
         cellBuilder: cellBuilder,

+ 3 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/board_checkbox_cell.dart

@@ -48,6 +48,9 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
               iconPadding: EdgeInsets.zero,
               icon: icon,
               width: 20,
+              onPressed: () => context
+                  .read<BoardCheckboxCellBloc>()
+                  .add(const BoardCheckboxCellEvent.select()),
             ),
           );
         },

+ 1 - 5
frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart

@@ -20,7 +20,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
             _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<CheckboxCellEvent, CheckboxCellState> {
       }
     }));
   }
-
-  void _updateCellData() {
-    cellController.saveCellData(!state.isSelected ? "Yes" : "No");
-  }
 }
 
 @freezed

+ 13 - 5
frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart

@@ -103,11 +103,19 @@ class SelectOptionCellEditorBloc
   void _filterOption(String optionName, Emitter<SelectOptionEditorState> 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() {

+ 1 - 5
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(),
     );
   }
 }

+ 4 - 4
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),

+ 4 - 3
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,

+ 3 - 1
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);
 

+ 3 - 1
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_name_input.dart

@@ -33,6 +33,7 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
     final theme = context.watch<AppTheme>();
     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<FieldNameTextField> {
   @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);
   }

+ 33 - 12
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(),
     );
   }
 }

+ 39 - 19
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<StyledScrollbar> {
     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<StyledScrollbar> {
             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<StyledScrollbar> {
         // 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<StyledScrollbar> {
         // 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: <Widget>[
@@ -149,8 +154,12 @@ class ScrollbarState extends State<StyledScrollbar> {
               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<StyledScrollbar> {
               // 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<StyledScrollbar> {
 
   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.