Skip to content

Instantly share code, notes, and snippets.

@lukepighetti
Last active June 1, 2022 12:45
Show Gist options
  • Save lukepighetti/bd5e081d418a7a0add032e254845b9ff to your computer and use it in GitHub Desktop.
Save lukepighetti/bd5e081d418a7a0add032e254845b9ff to your computer and use it in GitHub Desktop.
Overlay widgets in any context... only issue, hot reload is broken
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
import 'package:uuid/uuid.dart';
class LocalOverlayScope extends StatefulWidget {
const LocalOverlayScope({Key? key, required this.child}) : super(key: key);
final Widget child;
@override
State<LocalOverlayScope> createState() => LocalOverlayScopeState();
}
class LocalOverlayScopeState extends State<LocalOverlayScope> {
final _links = <String, Tuple3<LayerLink, Widget, _Params>>{};
LayerLink registerWidget(String linkKey, Widget widget, _Params params) {
return (_links[linkKey] ??= Tuple3(LayerLink(), widget, params)).item1;
}
void unregisterWidget(String linkKey) {
_links.remove(linkKey);
}
@override
void initState() {
// would be nice to remove this build race
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));
super.initState();
}
@override
Widget build(BuildContext context) {
return Provider.value(
value: this,
child: Stack(
children: [
widget.child,
for (final link in _links.entries)
CompositedTransformFollower(
key: ValueKey(link.key),
link: link.value.item1,
child: link.value.item2,
showWhenUnlinked: link.value.item3.showWhenUnlinked,
offset: link.value.item3.offset,
targetAnchor: link.value.item3.targetAnchor,
followerAnchor: link.value.item3.followerAnchor,
),
],
),
);
}
}
class LocalOverlayTarget extends StatefulWidget {
LocalOverlayTarget({
Key? key,
required this.child,
required this.follower,
this.targetAnchor = Alignment.topCenter,
this.followerAnchor = Alignment.bottomCenter,
this.offset = Offset.zero,
this.showWhenUnlinked = true,
}) : super(key: key);
final Widget child;
final Widget follower;
final Alignment targetAnchor;
final Alignment followerAnchor;
final Offset offset;
final bool showWhenUnlinked;
@override
State<LocalOverlayTarget> createState() => _LocalOverlayTargetState();
}
class _LocalOverlayTargetState extends State<LocalOverlayTarget> {
late final linkKey = Uuid().v4();
late final scope = Provider.of<LocalOverlayScopeState>(context);
late final _params = _Params(
targetAnchor: widget.targetAnchor,
followerAnchor: widget.followerAnchor,
offset: widget.offset,
showWhenUnlinked: widget.showWhenUnlinked,
);
// would be nice to update this when follower or params are updated,
// because right now hot reload does not work
late LayerLink link = scope.registerWidget(linkKey, widget.follower, _params);
@override
void dispose() {
scope.unregisterWidget(linkKey);
super.dispose();
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: link,
child: widget.child,
);
}
}
class _Params {
_Params({
this.targetAnchor = Alignment.topCenter,
this.followerAnchor = Alignment.bottomCenter,
this.offset = Offset.zero,
this.showWhenUnlinked = false,
});
final Alignment targetAnchor;
final Alignment followerAnchor;
final Offset offset;
final bool showWhenUnlinked;
}