Skip to content

Commit

Permalink
Merge pull request #5 from rrousselGit/rework
Browse files Browse the repository at this point in the history
Rework
  • Loading branch information
rrousselGit authored May 6, 2020
2 parents e024cb3 + d2dd415 commit 820201a
Show file tree
Hide file tree
Showing 18 changed files with 974 additions and 1,209 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 0.1.0

- Changed the algorithm behind how portals/overlays are rendered.\
This fixes some problems when combined with `LayoutBuilder`

- Removed the generic parameter of `PortalEntry`

# 0.0.1+2

Fix pub badge
Expand Down
51 changes: 9 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Flutter comes with two classes for manipulating "overlays":
- [Overlay]
- [OverlayEntry]

But [OverlayEntry] is very akward to use. As opposed to most of the framework,
But [OverlayEntry] is very awkward to use. As opposed to most of the framework,
[OverlayEntry] is **not** a widget (which comes with a nice and clean declarative API).

Instead, is uses an imperative API. This comes with a few drawbacks:
Expand All @@ -24,15 +24,11 @@ Instead, is uses an imperative API. This comes with a few drawbacks:
We basically have to do everything ourselves, usually needing an
[addPostFrameCallback] which, again, makes the rendering of our overlay one frame late.

- [Overlay.of](https://api.flutter.dev/flutter/widgets/Overlay/of.html) is hardly
customizable and O(N). If we want to add our [OverlayEntry] on a specific
[Overlay], we may have to rely on [GlobalKey](https://api.flutter.dev/flutter/widgets/GlobalKey-class.html), which is not ideal.

That's where `portal` comes into play.

This library is effectively a reimplementation of [Overlay]/[OverlayEntry], under
the name [Portal]/[PortalEntry] (the name that React uses for overlays) while
fixing all the previously mentionned issues.
fixing all the previously mentioned issues.

## Install

Expand All @@ -57,20 +53,20 @@ To use `portal`, we have to rely on two widgets:
_under_ your overlays.

If you want to display your overlays on the top of _everything_, a good place
to insert that [Portal] is under `MaterialApp` but above `Navigator`,
by doing the following:
to insert that [Portal] is above `MaterialApp`:

```dart
MaterialApp(
builder: (_, child) => Portal(child: child),
Portal(
child: MaterialApp(
...
)
);
```

(works for `CupertinoApp` too)

This way [Portal] will be above your `Navigator` so your overlays will properly
render above your routes. But it will be a descendant of `MaterialApp`, so
overlays will be able to access `Theme`/`MediaQuery`/...
This way [Portal] will render above everything. But you could place it
somewhere else to change the clip behavior.

* [PortalEntry] is the equivalent of [OverlayEntry].

Expand Down Expand Up @@ -192,35 +188,6 @@ which renders the following:

<img src="https://raw.githubusercontent.com/rrousselGit/flutter_portal/master/resources/alignment.png" width="200" />

### Target a specific [Portal] with a [PortalEntry]

Sometimes you may have multiple [Portal], and want to add your [PortalEntry] on
a very specific [Portal].

By default, [PortalEntry] will add its `portal` on the nearest [Portal].

But you can customize this behavior by having subclassing [Portal]:

```dart
mixin NoOp {}
/// Fork [Portal] and all its parameters/properties
class MyPortal = Portal with NoOp;
```

Then you can target a custom [Portal] on [PortalEntry] by specifying a generic
parameter:

```dart
MyPortal(
child: Portal(
// adds the portal on MyPortal instead of Portal
child: PortalEntry<MyPortal>(
...
)
),
),
```

[overlay]: https://api.flutter.dev/flutter/widgets/Overlay-class.html
[overlayentry]: https://api.flutter.dev/flutter/widgets/OverlayEntry-class.html
[addpostframecallback]: https://api.flutter.dev/flutter/scheduler/SchedulerBinding/addPostFrameCallback.html
Expand Down
Binary file added assets/Roboto-Regular.ttf
Binary file not shown.
118 changes: 34 additions & 84 deletions example/lib/medium_clap.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';

Expand All @@ -14,111 +16,59 @@ class MyApp extends StatelessWidget {
appBar: AppBar(
title: const Text('Example'),
),
body: Container(
padding: const EdgeInsets.all(10),
alignment: Alignment.centerLeft,
child: ContextualMenuExample(),
body: Center(
child: ClapButton(),
),
),
);
}
}

class ContextualMenuExample extends StatefulWidget {
ContextualMenuExample({Key key}) : super(key: key);
class ClapButton extends StatefulWidget {
ClapButton({Key key}) : super(key: key);

@override
_ContextualMenuExampleState createState() => _ContextualMenuExampleState();
_ClapButtonState createState() => _ClapButtonState();
}

class _ContextualMenuExampleState extends State<ContextualMenuExample> {
bool showMenu = false;
class _ClapButtonState extends State<ClapButton> {
int clapCount = 0;
bool hasClappedRecently = false;
Timer resetHasClappedRecentlyTimer;

@override
Widget build(BuildContext context) {
return ModalEntry(
visible: showMenu,
onClose: () => setState(() => showMenu = false),
childAnchor: Alignment.topRight,
menuAnchor: Alignment.topLeft,
menu: const Menu(
children: [
PopupMenuItem<void>(
height: 42,
child: Text('first'),
),
PopupMenuItem<void>(
height: 42,
child: Text('second'),
),
],
return PortalEntry(
visible: hasClappedRecently,
// aligns the top-center of `child` with the bottom-center of `portal`
childAnchor: Alignment.topCenter,
portalAnchor: Alignment.bottomCenter,
portal: Material(
elevation: 8,
borderRadius: BorderRadius.circular(40),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('$clapCount'),
),
),
child: RaisedButton(
onPressed: () => setState(() => showMenu = true),
child: const Text('show menu'),
onPressed: _clap,
child: const Icon(Icons.plus_one),
),
);
}
}

class Menu extends StatelessWidget {
const Menu({
Key key,
@required this.children,
}) : super(key: key);

final List<Widget> children;
void _clap() {
resetHasClappedRecentlyTimer?.cancel();

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 10),
child: Card(
elevation: 8,
child: IntrinsicWidth(
child: Column(
mainAxisSize: MainAxisSize.min,
children: children,
),
),
),
resetHasClappedRecentlyTimer = Timer(
const Duration(seconds: 2),
() => setState(() => hasClappedRecently = false),
);
}
}

class ModalEntry extends StatelessWidget {
const ModalEntry({
Key key,
this.onClose,
this.menu,
this.visible,
this.menuAnchor,
this.childAnchor,
this.child,
}) : super(key: key);

final VoidCallback onClose;
final Widget menu;
final bool visible;
final Widget child;
final Alignment menuAnchor;
final Alignment childAnchor;

@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: visible ? onClose : null,
child: PortalEntry(
visible: visible,
portal: menu,
portalAnchor: menuAnchor,
childAnchor: childAnchor,
child: IgnorePointer(
ignoring: visible,
child: child,
),
),
);
setState(() {
hasClappedRecently = true;
clapCount++;
});
}
}
Loading

0 comments on commit 820201a

Please sign in to comment.