瀏覽代碼

feat: auto scroll to bottom after crating a new field

appflowy 2 年之前
父節點
當前提交
9a57593690

+ 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.