mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 22:57:12 -04:00
[infra_ui][overlay] Impl new overlay page and route skeleton
This commit is contained in:
parent
440d2940c3
commit
06ac3bc29e
4 changed files with 160 additions and 31 deletions
|
@ -1,50 +1,47 @@
|
||||||
|
import 'dart:math' as math;
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'overlay_route.dart';
|
||||||
import 'overlay_basis.dart';
|
import 'overlay_basis.dart';
|
||||||
|
|
||||||
class OverlayLayoutDelegate extends SingleChildLayoutDelegate {
|
class OverlayLayoutDelegate extends SingleChildLayoutDelegate {
|
||||||
OverlayLayoutDelegate({
|
OverlayLayoutDelegate({
|
||||||
required this.anchorRect,
|
required this.route,
|
||||||
required this.targetRect,
|
required this.padding,
|
||||||
|
required this.anchorPosition,
|
||||||
required this.anchorDirection,
|
required this.anchorDirection,
|
||||||
required this.safeAreaEnabled,
|
|
||||||
required this.insets,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final OverlayPannelRoute route;
|
||||||
|
final EdgeInsets padding;
|
||||||
final AnchorDirection anchorDirection;
|
final AnchorDirection anchorDirection;
|
||||||
final bool safeAreaEnabled;
|
final Offset anchorPosition;
|
||||||
final EdgeInsets insets;
|
|
||||||
final Rect anchorRect;
|
|
||||||
final Rect targetRect;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRelayout(OverlayLayoutDelegate oldDelegate) {
|
bool shouldRelayout(OverlayLayoutDelegate oldDelegate) {
|
||||||
return anchorRect != oldDelegate.anchorRect ||
|
return anchorPosition != oldDelegate.anchorPosition || anchorDirection != oldDelegate.anchorDirection;
|
||||||
insets != oldDelegate.insets ||
|
|
||||||
safeAreaEnabled != oldDelegate.safeAreaEnabled ||
|
|
||||||
anchorDirection != oldDelegate.anchorDirection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Offset getPositionForChild(Size size, Size childSize) {
|
Offset getPositionForChild(Size size, Size childSize) {
|
||||||
// calculate the pannel maximum available rect
|
|
||||||
var pannelRect = Rect.fromLTWH(0, 0, size.width, size.height);
|
|
||||||
pannelRect = insets.deflateRect(pannelRect);
|
|
||||||
// apply safearea
|
|
||||||
if (safeAreaEnabled) {
|
|
||||||
final safeArea = MediaQueryData.fromWindow(window).padding;
|
|
||||||
pannelRect = safeArea.deflateRect(pannelRect);
|
|
||||||
}
|
|
||||||
|
|
||||||
// clip pannel rect
|
|
||||||
|
|
||||||
// TODO: junlin - calculate child position
|
// TODO: junlin - calculate child position
|
||||||
return Offset.zero;
|
return Offset.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||||
return constraints.loosen();
|
double maxHeight = math.max(
|
||||||
|
0.0,
|
||||||
|
math.min(route.maxHeight, constraints.maxHeight - padding.top - padding.bottom),
|
||||||
|
);
|
||||||
|
double width = math.min(route.maxWidth, constraints.maxWidth);
|
||||||
|
return BoxConstraints(
|
||||||
|
minHeight: 0.0,
|
||||||
|
maxHeight: maxHeight,
|
||||||
|
minWidth: width,
|
||||||
|
maxWidth: width,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,3 +50,22 @@ class OverlayManagerState extends State<OverlayManager> {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Impl show method
|
||||||
|
// void show(BuildContext context) {
|
||||||
|
// assert(_overlayRoute == null, 'Can\'t push single overlay twice.');
|
||||||
|
// final NavigatorState navigator = Navigator.of(context);
|
||||||
|
// final RenderBox renderBox = context.findRenderObject()! as RenderBox;
|
||||||
|
|
||||||
|
// _overlayRoute = OverlayPannelRoute(
|
||||||
|
// anchorDirection: widget.anchorDirection,
|
||||||
|
// barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
|
// anchorPosition: widget.anchorPosition,
|
||||||
|
// maxWidth: widget.maxWidth ?? renderBox.size.width,
|
||||||
|
// maxHeight: widget.maxHeight ?? renderBox.size.height,
|
||||||
|
// );
|
||||||
|
// _createRouteAnimation(_overlayRoute!);
|
||||||
|
|
||||||
|
// navigator.push(_overlayRoute!);
|
||||||
|
// }
|
|
@ -1,17 +1,25 @@
|
||||||
import 'dart:ui' show window;
|
import 'dart:ui' show window;
|
||||||
|
|
||||||
|
import 'package:flowy_infra_ui/src/overlay/overlay_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'overlay_basis.dart';
|
import 'overlay_basis.dart';
|
||||||
import 'overlay_layout_delegate.dart';
|
|
||||||
|
|
||||||
class OverlayPannel extends StatefulWidget {
|
class OverlayPannel extends StatefulWidget {
|
||||||
const OverlayPannel({
|
const OverlayPannel({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
|
this.padding = EdgeInsets.zero,
|
||||||
|
this.anchorDirection = AnchorDirection.topRight,
|
||||||
|
required this.anchorPosition,
|
||||||
|
required this.route,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
final AnchorDirection anchorDirection;
|
||||||
|
final Offset anchorPosition;
|
||||||
|
final OverlayPannelRoute route;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_OverlayPannelState createState() => _OverlayPannelState();
|
_OverlayPannelState createState() => _OverlayPannelState();
|
||||||
|
@ -22,10 +30,25 @@ class _OverlayPannelState extends State<OverlayPannel> with WidgetsBindingObserv
|
||||||
FocusNode? get focusNode => widget.focusNode ?? _internalNode;
|
FocusNode? get focusNode => widget.focusNode ?? _internalNode;
|
||||||
late FocusHighlightMode _focusHighlightMode;
|
late FocusHighlightMode _focusHighlightMode;
|
||||||
bool _hasPrimaryFocus = false;
|
bool _hasPrimaryFocus = false;
|
||||||
|
late CurvedAnimation _fadeOpacity;
|
||||||
|
late CurvedAnimation _resize;
|
||||||
|
OverlayPannelRoute? _overlayRoute;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_fadeOpacity = CurvedAnimation(
|
||||||
|
parent: widget.route.animation!,
|
||||||
|
curve: const Interval(0.0, 0.25),
|
||||||
|
reverseCurve: const Interval(0.75, 1.0),
|
||||||
|
);
|
||||||
|
_resize = CurvedAnimation(
|
||||||
|
parent: widget.route.animation!,
|
||||||
|
curve: const Interval(0.25, 0.5),
|
||||||
|
reverseCurve: const Threshold(0.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: junlin - handle focus action or remove it
|
||||||
if (widget.focusNode == null) {
|
if (widget.focusNode == null) {
|
||||||
_internalNode ??= _createFocusNode();
|
_internalNode ??= _createFocusNode();
|
||||||
}
|
}
|
||||||
|
@ -85,17 +108,11 @@ class _OverlayPannelState extends State<OverlayPannel> with WidgetsBindingObserv
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeOverlayRoute() {
|
|
||||||
// TODO: junlin
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Layout
|
// MARK: Layout
|
||||||
|
|
||||||
Orientation _getOrientation(BuildContext context) {
|
Orientation _getOrientation(BuildContext context) {
|
||||||
Orientation? result = MediaQuery.maybeOf(context)?.orientation;
|
Orientation? result = MediaQuery.maybeOf(context)?.orientation;
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
// If there's no MediaQuery, then use the window aspect to determine
|
|
||||||
// orientation.
|
|
||||||
final Size size = window.physicalSize;
|
final Size size = window.physicalSize;
|
||||||
result = size.width > size.height ? Orientation.landscape : Orientation.portrait;
|
result = size.width > size.height ? Orientation.landscape : Orientation.portrait;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import 'package:flowy_infra_ui/src/overlay/overlay_pannel.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'overlay_basis.dart';
|
||||||
|
import 'overlay_layout_delegate.dart';
|
||||||
|
|
||||||
|
class _OverlayRouteResult {}
|
||||||
|
|
||||||
|
const Duration _kOverlayDurationDuration = Duration(milliseconds: 500);
|
||||||
|
|
||||||
|
class OverlayPannelRoute extends PopupRoute<_OverlayRouteResult> {
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
final AnchorDirection anchorDirection;
|
||||||
|
final Offset anchorPosition;
|
||||||
|
final double maxWidth;
|
||||||
|
final double maxHeight;
|
||||||
|
|
||||||
|
OverlayPannelRoute({
|
||||||
|
this.padding = EdgeInsets.zero,
|
||||||
|
required this.anchorDirection,
|
||||||
|
this.barrierColor,
|
||||||
|
required this.barrierLabel,
|
||||||
|
required this.anchorPosition,
|
||||||
|
required this.maxWidth,
|
||||||
|
required this.maxHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get barrierDismissible => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? barrierColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? barrierLabel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get transitionDuration => _kOverlayDurationDuration;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||||
|
return LayoutBuilder(builder: (context, contraints) {
|
||||||
|
return _OverlayRoutePage(
|
||||||
|
route: this,
|
||||||
|
anchorDirection: anchorDirection,
|
||||||
|
anchorPosition: anchorPosition,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OverlayRoutePage extends StatelessWidget {
|
||||||
|
const _OverlayRoutePage({
|
||||||
|
Key? key,
|
||||||
|
required this.route,
|
||||||
|
this.padding = EdgeInsets.zero,
|
||||||
|
required this.anchorDirection,
|
||||||
|
required this.anchorPosition,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final OverlayPannelRoute route;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
final AnchorDirection anchorDirection;
|
||||||
|
final Offset anchorPosition;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasDirectionality(context));
|
||||||
|
final TextDirection? textDirection = Directionality.maybeOf(context);
|
||||||
|
final OverlayPannel overlayPannel = OverlayPannel(
|
||||||
|
route: route,
|
||||||
|
padding: padding,
|
||||||
|
anchorDirection: anchorDirection,
|
||||||
|
anchorPosition: anchorPosition,
|
||||||
|
);
|
||||||
|
|
||||||
|
return MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
removeBottom: true,
|
||||||
|
removeLeft: true,
|
||||||
|
removeRight: true,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => CustomSingleChildLayout(
|
||||||
|
delegate: OverlayLayoutDelegate(
|
||||||
|
route: route,
|
||||||
|
padding: padding.resolve(textDirection),
|
||||||
|
anchorPosition: anchorPosition,
|
||||||
|
anchorDirection: anchorDirection,
|
||||||
|
),
|
||||||
|
child: overlayPannel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue