mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: auto scroll to bottom after crating a new field
This commit is contained in:
parent
56fdc99990
commit
9a57593690
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user