peek_and_pop 0.1.9 peek_and_pop: ^0.1.9 copied to clipboard
Peek & Pop implementation for Flutter based on the iOS functionality of the same name.
//Note: Don't forget to add <key>io.flutter.embedded_views_preview</key><string>YES</string> to your Info.plist. See
//[webview_flutter](https://pub.flutter-io.cn/packages/webview_flutter) for more info.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// © Cosmos Software | Ali Yigit Bireroglu /
// All material used in the making of this code, project, program, application, software et cetera (the "Intellectual Property") /
// belongs completely and solely to Ali Yigit Bireroglu. This includes but is not limited to the source code, the multimedia and /
// other asset files. If you were granted this Intellectual Property for personal use, you are obligated to include this copyright /
// text at all times. /
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//@formatter:off
import 'package:flutter/foundation.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'nav_bar.dart' as MyNavBar;
import 'package:snap/snap.dart';
import 'package:peek_and_pop/peek_and_pop.dart';
import 'package:peek_and_pop/misc.dart' as PeekAndPopMisc;
PeekAndPopControllerState peekAndPopController;
final GlobalKey<SnapControllerState> snapController = GlobalKey<SnapControllerState>();
final GlobalKey view = GlobalKey();
final GlobalKey bound = GlobalKey();
final GlobalKey<ScaffoldState> scaffold = GlobalKey<ScaffoldState>();
final GlobalKey<PopUpState> popUp = GlobalKey<PopUpState>();
double screenHeight;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: PeekAndPopMisc.background,
child: MaterialApp(
title: 'Peek & Pop Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Peek & Pop Demo')));
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text(widget.title)), body: Body());
}
}
class Body extends StatelessWidget {
bool willUpdatePeekAndPop(PeekAndPopControllerState _peekAndPopController) {
return snapController.currentState == null || !snapController.currentState.isMoved(25);
}
bool willCancelPeekAndPop(PeekAndPopControllerState _peekAndPopController) {
return snapController.currentState == null || !snapController.currentState.isMoved(25);
}
bool willFinishPeekAndPop(PeekAndPopControllerState _peekAndPopController) {
if (snapController.currentState == null)
return true;
else if (snapController.currentState.isMoved(25))
return false;
else {
snapController.currentState.move(Offset.zero);
return true;
}
}
void onPushPeekAndPop(PeekAndPopControllerState _peekAndPopController) {
peekAndPopController = _peekAndPopController;
}
void onPressStart(dynamic pressDetails) {
if (snapController.currentState != null) snapController.currentState.beginDrag(pressDetails);
}
void onPressUpdate(dynamic pressDetails) {
if (snapController.currentState != null) snapController.currentState.updateDrag(pressDetails);
}
void onPressEnd(dynamic pressDetails) {
if (snapController.currentState != null) snapController.currentState.endDrag(pressDetails);
}
@override
Widget build(BuildContext context) {
return Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
Expanded(
child: PeekAndPopController(normalRow("Normal", Colors.redAccent), staticNormalPeekAndPopBuilder, false,
sigma: 5, onPushPeekAndPop: onPushPeekAndPop, peekScale: 0.9)),
Expanded(
child: PeekAndPopController(normalRow("Moveable", Colors.deepPurpleAccent), moveableNormalPeekAndPopBuilder, false,
overlayBuiler: PopUp(popUp),
sigma: 5,
willUpdatePeekAndPop: willUpdatePeekAndPop,
willCancelPeekAndPop: willCancelPeekAndPop,
willFinishPeekAndPop: willFinishPeekAndPop,
onPushPeekAndPop: onPushPeekAndPop,
onPressStart: onPressStart,
onPressUpdate: onPressUpdate,
onPressEnd: onPressEnd,
peekScale: 0.9)),
Expanded(
child: PeekAndPopController(normalRow("Platform View", Colors.cyan), platformViewPeekAndPopBuilder, false,
sigma: 5, onPushPeekAndPop: onPushPeekAndPop, peekScale: 0.7, peekCoefficient: 0.025)),
Expanded(
child: PeekAndPopController(heroRow(), heroPeekAndPopBuilder, false,
sigma: 5, onPushPeekAndPop: onPushPeekAndPop, useIndicator: false, isHero: true, peekScale: 0.8))
]);
}
}
void onMove(Offset offset) {
if (popUp.currentState == null) return;
if (offset.dy < -125 &&
popUp.currentState.animationController.status != AnimationStatus.forward &&
popUp.currentState.animationController.status != AnimationStatus.completed &&
popUp.currentState.animationController.value != 1) {
popUp.currentState.animationController.forward();
} else if (offset.dy > -125 &&
popUp.currentState.animationController.status != AnimationStatus.reverse &&
popUp.currentState.animationController.status != AnimationStatus.dismissed &&
popUp.currentState.animationController.value != 0) {
popUp.currentState.animationController.reverse();
}
}
void onSnap(Offset offset) {
if (!snapController.currentState.isMoved(25)) {
peekAndPopController.cancelPeekAndPop(null);
}
}
Widget normalRow(String text, Color color) {
return Container(
color: Colors.transparent,
child: Padding(
padding: EdgeInsets.all(25),
child: Container(
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(color: color, borderRadius: const BorderRadius.all(const Radius.circular(10.0))),
child: Center(child: Text(text, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 25))))));
}
Widget staticNormalPeekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(!_peekAndPopController.isComplete ? 10 : 0)),
child: Scaffold(
key: scaffold,
backgroundColor: _peekAndPopController.stage != Stage.Done ? Colors.transparent : Colors.white,
appBar: MyNavBar.CupertinoNavigationBar(
key: header,
backgroundColor: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xff1B1B1B),
border: Border(
bottom: BorderSide(
color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : Colors.black, width: 0.0, style: BorderStyle.solid)),
middle: Text("Peek & Pop",
style: TextStyle(color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500))),
leading: CupertinoButton(
padding: EdgeInsets.only(bottom: 2),
onPressed: () {
HapticFeedback.mediumImpact();
_peekAndPopController.closePeekAndPop();
},
child: Icon(CupertinoIcons.left_chevron,
size: 25, color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500))),
trailing: CupertinoButton(
padding: EdgeInsets.only(bottom: 2),
onPressed: () {
HapticFeedback.mediumImpact();
showSnackbar();
},
child: Icon(CupertinoIcons.heart_solid,
size: 25, color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500)))),
body: Transform.translate(
offset: Offset(0, _peekAndPopController.peekAndPopChild.getHeaderOffset(HeaderOffset.NegativeHalf)),
child: _peekAndPopController.stage != Stage.Done ? normalAtPeek() : normalAtPop())));
}
Widget moveableNormalPeekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(!_peekAndPopController.isComplete ? 10 : 0)),
child: Scaffold(
key: scaffold,
backgroundColor: _peekAndPopController.stage != Stage.Done ? Colors.transparent : Colors.white,
appBar: MyNavBar.CupertinoNavigationBar(
key: header,
backgroundColor: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xff1B1B1B),
border: Border(
bottom: BorderSide(
color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : Colors.black, width: 0.0, style: BorderStyle.solid)),
middle: Text("Peek & Pop",
style: TextStyle(color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500))),
leading: CupertinoButton(
padding: EdgeInsets.only(bottom: 2),
onPressed: () {
HapticFeedback.mediumImpact();
_peekAndPopController.closePeekAndPop();
},
child: Icon(CupertinoIcons.left_chevron,
size: 25, color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500))),
trailing: CupertinoButton(
padding: EdgeInsets.only(bottom: 2),
onPressed: () {
HapticFeedback.mediumImpact();
showSnackbar();
},
child: Icon(CupertinoIcons.heart_solid,
size: 25, color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500)))),
body: Transform.translate(
offset: Offset(0, _peekAndPopController.peekAndPopChild.getHeaderOffset(HeaderOffset.NegativeHalf)),
child: _peekAndPopController.stage != Stage.Done ? moveableAtPeek() : moveableAtPop())));
}
Widget moveableAtPeek() {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Expanded(
child: Container(
key: bound,
constraints: BoxConstraints.expand(),
color: Colors.transparent,
child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
SnapController(
normalAtPeek(), true, view, bound, const Offset(0.0, 0.0), const Offset(1.0, 1.0), const Offset(0.0, 0.75), const Offset(0.0, 0.75),
snapTargets: [
const SnapTarget(Pivot.topLeft, Pivot.topLeft),
const SnapTarget(Pivot.topRight, Pivot.topRight),
const SnapTarget(Pivot.center, Pivot.center)
],
animateSnap: true,
useFlick: false,
onMove: onMove,
onSnap: onSnap,
key: snapController)
])))
]);
}
Widget moveableAtPop() {
return normalAtPop();
}
class PopUp extends StatefulWidget {
PopUp(Key key) : super(key: key);
@override
PopUpState createState() {
return PopUpState();
}
}
class PopUpState extends State<PopUp> with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation<double> animation;
double screenHeight = -1;
@override
void initState() {
super.initState();
animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 333), lowerBound: 0, upperBound: 1);
}
@override
Widget build(BuildContext context) {
if (screenHeight == -1) {
screenHeight = MediaQuery.of(context).size.height;
animation = Tween(begin: screenHeight, end: screenHeight - 240).animate(CurvedAnimation(parent: animationController, curve: Curves.decelerate));
}
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget cachedChild) {
return (Transform.translate(
offset: Offset(0, animation.value),
child: Padding(
padding: EdgeInsets.all(20),
child: ClipRRect(
borderRadius: const BorderRadius.all(const Radius.circular(10.0)),
child: Container(
height: 200,
color: Colors.transparent,
child: Scaffold(
backgroundColor: Colors.transparent,
body: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
Expanded(
child: GestureDetector(
onTap: () {
animationController.reverse();
Future.wait([snapController.currentState.move(const Offset(1, 1))]).then((_) {
peekAndPopController.finishPeekAndPop(null);
});
},
child: Container(
color: Color.fromARGB(189, 255, 255, 255),
child: Center(
child: Text("Pop",
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.normal, fontSize: 25)))))),
Expanded(
child: GestureDetector(
onTap: () {
animationController.reverse();
Future.wait([snapController.currentState.move(Offset(1, 1))]).then((_) {
peekAndPopController.cancelPeekAndPop(null);
});
},
child: Container(
color: Color.fromARGB(189, 189, 189, 189),
child: Center(
child: Text("Dismiss",
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.normal, fontSize: 25))))))
])))))));
});
}
}
Widget normalAtPeek() {
return Transform.scale(
key: view,
scale: 0.9,
child: Center(
child: Container(
constraints: const BoxConstraints.expand(height: 400),
decoration: const BoxDecoration(
image: const DecorationImage(image: const AssetImage("assets/Scenery.jpeg"), fit: BoxFit.cover),
borderRadius: const BorderRadius.all(const Radius.circular(10.0)),
boxShadow: [BoxShadow(color: Colors.black, offset: const Offset(0, 15), spreadRadius: -5, blurRadius: 20)]))));
}
Widget normalAtPop() {
return Center(
child: Container(
constraints: const BoxConstraints.expand(height: 400),
decoration: const BoxDecoration(image: const DecorationImage(image: const AssetImage("assets/Scenery.jpeg"), fit: BoxFit.cover))));
}
Widget platformViewPeekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(!_peekAndPopController.isComplete ? 10 : 0)),
child: Container(
decoration:
const BoxDecoration(boxShadow: [const BoxShadow(color: Colors.black, offset: const Offset(0, 15), spreadRadius: -5, blurRadius: 30)]),
child: Scaffold(
key: scaffold,
backgroundColor: Colors.white,
appBar: MyNavBar.CupertinoNavigationBar(
key: header,
backgroundColor: const Color(0xff1B1B1B),
border: Border(
bottom: BorderSide(
color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : Colors.black,
width: 0.0,
style: BorderStyle.solid)),
middle: Text("Peek & Pop", style: const TextStyle(color: const Color(0xffFF9500))),
leading: CupertinoButton(
padding: EdgeInsets.only(bottom: 2),
onPressed: () {
HapticFeedback.mediumImpact();
_peekAndPopController.closePeekAndPop();
},
child: Icon(CupertinoIcons.left_chevron, size: 25, color: const Color(0xffFF9500))),
trailing: CupertinoButton(
padding: EdgeInsets.only(bottom: 2),
onPressed: () {
HapticFeedback.mediumImpact();
showSnackbar();
},
child: Icon(CupertinoIcons.heart_solid, size: 25, color: const Color(0xffFF9500)))),
body: _peekAndPopController.peekAndPopChild.animationController.status == AnimationStatus.completed ||
_peekAndPopController.peekAndPopChild.animationController.status == AnimationStatus.reverse
? platformViewPeekAndPop()
: Container(color: Colors.white, child: const Center(child: CupertinoActivityIndicator())))));
}
Widget platformViewPeekAndPop() {
return InAppBrowser("https://flutter.dev");
}
class InAppBrowser extends StatefulWidget {
final String url;
InAppBrowser(this.url);
@override
InAppBrowserState createState() => InAppBrowserState();
}
class InAppBrowserState extends State<InAppBrowser> {
@override
Widget build(BuildContext context) {
return WebView(
initialUrl: widget.url,
javascriptMode: JavascriptMode.unrestricted,
navigationDelegate: (NavigationRequest request) => NavigationDecision.navigate,
onPageFinished: (String url) {});
}
}
Widget heroRow() {
return Container(
color: Colors.transparent,
child: Padding(
padding: EdgeInsets.all(25),
child: Container(
constraints: BoxConstraints.expand(),
decoration: const BoxDecoration(color: Colors.greenAccent, borderRadius: const BorderRadius.all(const Radius.circular(10.0))),
child: Center(
child: Row(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
Text("Hero", style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 25)),
Padding(padding: EdgeInsets.all(50), child: Hero(tag: "Superhero", child: Image.asset("assets/Hero.png")))
])))));
}
Widget heroPeekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(!_peekAndPopController.isComplete ? 10 : 0)),
child: Scaffold(
key: scaffold,
backgroundColor: _peekAndPopController.stage != Stage.Done ? Colors.transparent : Colors.white,
appBar: MyNavBar.CupertinoNavigationBar(
key: header,
backgroundColor: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xff1B1B1B),
border: Border(
bottom: BorderSide(
color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : Colors.black, width: 0.0, style: BorderStyle.solid)),
middle: Text("Peek & Pop",
style: TextStyle(color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500))),
leading: CupertinoButton(
padding: EdgeInsets.only(bottom: 2),
onPressed: () {
HapticFeedback.mediumImpact();
_peekAndPopController.closePeekAndPop();
},
child: Icon(CupertinoIcons.left_chevron,
size: 25, color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500))),
trailing: CupertinoButton(
padding: EdgeInsets.only(bottom: 2),
onPressed: () {
HapticFeedback.mediumImpact();
showSnackbar();
},
child: Icon(CupertinoIcons.heart_solid,
size: 25, color: _peekAndPopController.stage != Stage.Done ? Colors.transparent : const Color(0xffFF9500)))),
body: Transform.translate(
offset: Offset(0, _peekAndPopController.peekAndPopChild.getHeaderOffset(HeaderOffset.NegativeHalf)), child: heroPeekAndPop())));
}
Widget heroPeekAndPop() {
return Center(child: Hero(tag: "Superhero", child: Image.asset("assets/Hero.png", scale: 0.8)));
}
void showSnackbar() {
scaffold.currentState.showSnackBar(SnackBar(content: Text("Everything works as usual.")));
}