[infra_ui][overlay] Impl new overlay page and route skeleton

This commit is contained in:
Jaylen Bian 2021-07-26 18:37:27 +08:00
parent 440d2940c3
commit 06ac3bc29e
4 changed files with 160 additions and 31 deletions

View File

@ -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,
);
} }
} }

View File

@ -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!);
// }

View File

@ -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;
} }

View File

@ -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,
),
),
);
}
}