Created
November 13, 2020 09:15
-
-
Save Zfinix/c4a9836ac33ca9c17cdd2af520d1fab4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
import 'dart:math' as math; | |
import 'dart:typed_data'; | |
import 'dart:ui'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'package:image/image.dart' as image; | |
void main() { | |
runApp( | |
MaterialApp( | |
debugShowCheckedModeBanner: false, | |
home: Frame(app: TPaint()), | |
builder: (v, k) => Scaffold(body: k), | |
), | |
); | |
} | |
final key = List.generate(9, (index) => GlobalKey<SnappableState>()); | |
class TPaint extends StatefulWidget { | |
@override | |
_TPaintState createState() => _TPaintState(); | |
} | |
class _TPaintState extends State<TPaint> with SingleTickerProviderStateMixin { | |
final red = Color(0xffff0032); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: ListView( | |
physics: BouncingScrollPhysics(), | |
children: <Widget>[ | |
Padding( | |
padding: const EdgeInsets.all(18.0) | |
.add(EdgeInsets.symmetric(horizontal: 20)), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
Snappable( | |
key: key[0], | |
child: Row( | |
children: <Widget>[ | |
InkResponse( | |
child: Image.memory( | |
base64.decode(menu), | |
scale: 2.5, | |
), | |
onTap: () {}, | |
), | |
Spacer(), | |
CircleAvatar( | |
backgroundImage: NetworkImage( | |
'https://pbs.twimg.com/profile_images/1325707629113565185/Yqn23Vv7_400x400.jpg'), | |
) | |
], | |
), | |
), | |
const YMargin(38), | |
Snappable( | |
key: key[1], | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Text( | |
'Hi Chizi,\nhave a good day!', | |
style: TextStyle( | |
fontWeight: FontWeight.bold, | |
color: Colors.black, | |
fontSize: 27, | |
), | |
) | |
], | |
), | |
), | |
const YMargin(10), | |
Snappable( | |
key: key[2], | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Text( | |
'You have two (2) shipments\nplanned for today.', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.grey, | |
fontSize: 14, | |
height: 1.4), | |
) | |
], | |
), | |
), | |
], | |
), | |
), | |
const YMargin(15), | |
Snappable( | |
key: key[3], | |
child: Stack( | |
children: <Widget>[ | |
Container( | |
height: 189, | |
margin: EdgeInsets.symmetric(vertical: 30, horizontal: 45), | |
width: context.screenWidth(), | |
decoration: BoxDecoration( | |
color: red, | |
borderRadius: BorderRadius.circular(20), | |
boxShadow: [ | |
BoxShadow( | |
color: Colors.redAccent.withOpacity(0.9), | |
blurRadius: 50, | |
offset: Offset(4, 20), | |
spreadRadius: -11), | |
], | |
), | |
), | |
Column( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
const YMargin(239), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
Container( | |
width: 6.4, | |
height: 6.4, | |
margin: EdgeInsets.all(2), | |
decoration: BoxDecoration( | |
color: red, | |
borderRadius: BorderRadius.circular(40), | |
)), | |
for (var i = 0; i < 3; i++) | |
Container( | |
width: 6, | |
height: 6, | |
margin: EdgeInsets.all(2), | |
decoration: BoxDecoration( | |
color: Colors.grey[400], | |
borderRadius: BorderRadius.circular(40), | |
)), | |
], | |
) | |
], | |
), | |
Container( | |
height: 200, | |
margin: EdgeInsets.symmetric(vertical: 10, horizontal: 20), | |
width: context.screenWidth(), | |
decoration: BoxDecoration( | |
color: red, | |
borderRadius: BorderRadius.circular(20), | |
boxShadow: [ | |
BoxShadow( | |
color: Colors.black.withOpacity(0.2), | |
blurRadius: 50, | |
offset: Offset(4, 20), | |
spreadRadius: -11), | |
], | |
), | |
child: Padding( | |
padding: const EdgeInsets.symmetric( | |
horizontal: 20.0, vertical: 24), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Text( | |
'Shipment Status', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.white, | |
fontSize: 14, | |
height: 1.4), | |
), | |
Spacer(), | |
Text( | |
'#DSX2346', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.white, | |
fontSize: 14, | |
height: 1.4), | |
) | |
], | |
), | |
const YMargin(17), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
Container( | |
height: 50, | |
width: 50, | |
child: Image.memory(base64.decode(box)), | |
), | |
const XMargin(20), | |
Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
Text( | |
'Your shipment is being delivered', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.white, | |
fontSize: 15), | |
), | |
Text( | |
'Last Update: 23 min ago.', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.white70, | |
fontSize: 12), | |
) | |
], | |
), | |
], | |
), | |
const YMargin(20), | |
Container( | |
height: 4, | |
child: ClipRRect( | |
borderRadius: BorderRadius.circular(20), | |
child: Theme( | |
data: ThemeData( | |
primaryColor: Color(0xfff6c833), | |
accentColor: Color(0xfff6c833)), | |
child: LinearProgressIndicator( | |
value: 0.6, | |
backgroundColor: Color(0xffe0002c), | |
), | |
), | |
), | |
), | |
const YMargin(20), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Text( | |
'Track Your Package', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.white, | |
fontSize: 15, | |
height: 1.4), | |
), | |
Spacer(), | |
Icon( | |
Icons.arrow_forward, | |
color: Colors.white, | |
size: 19, | |
) | |
], | |
), | |
], | |
), | |
), | |
), | |
], | |
), | |
), | |
const YMargin(40), | |
Snappable( | |
key: key[4], | |
child: Padding( | |
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 20), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
Padding( | |
padding: const EdgeInsets.only(left: 8.0), | |
child: Text( | |
'Your Activity', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.grey[800], | |
fontSize: 18, | |
height: 1.4), | |
), | |
), | |
const YMargin(9), | |
Container( | |
height: 2, | |
width: 115, | |
decoration: BoxDecoration( | |
color: Colors.black12, | |
borderRadius: BorderRadius.circular(20), | |
)), | |
], | |
), | |
Spacer(), | |
Row( | |
children: <Widget>[ | |
Text( | |
'Rating', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.grey[900], | |
fontSize: 18, | |
height: 1.4), | |
), | |
const XMargin(10), | |
Text( | |
'4.8', | |
style: TextStyle( | |
fontWeight: FontWeight.w800, | |
color: red, | |
fontSize: 18, | |
height: 1.4), | |
), | |
], | |
), | |
], | |
), | |
), | |
), | |
Snappable( | |
key: key[5], | |
child: ListTile( | |
contentPadding: EdgeInsets.symmetric(horizontal: 30, vertical: 5), | |
title: Row( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
CircleAvatar( | |
backgroundImage: NetworkImage( | |
'https://pbs.twimg.com/profile_images/1322301184510746625/ZD-vSRZj_400x400.jpg', | |
), | |
), | |
const XMargin(11), | |
Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Text( | |
'Yesterday', | |
style: TextStyle( | |
fontWeight: FontWeight.w200, | |
color: Colors.grey, | |
fontSize: 13, | |
height: 1.4), | |
), | |
const YMargin(5), | |
RichText( | |
text: TextSpan( | |
text: 'You have recieved a new rating from\n', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.black, | |
fontSize: 15), | |
children: <TextSpan>[ | |
TextSpan( | |
text: 'Maryann Onuoha', | |
style: TextStyle( | |
fontWeight: FontWeight.bold, | |
color: Colors.black, | |
fontSize: 15), | |
) | |
]), | |
), | |
], | |
), | |
], | |
), | |
trailing: Icon( | |
Icons.arrow_forward_ios, | |
size: 14, | |
), | |
), | |
), | |
Snappable( | |
key: key[6], | |
child: ListTile( | |
contentPadding: EdgeInsets.symmetric(horizontal: 30, vertical: 5) | |
.add(EdgeInsets.only(bottom: 10)), | |
title: Row( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
CircleAvatar( | |
backgroundImage: NetworkImage( | |
'https://pbs.twimg.com/profile_images/1319013042378756097/2ymZwpLo_400x400.jpg'), | |
), | |
const XMargin(11), | |
Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
Text( | |
'January 4', | |
style: TextStyle( | |
fontWeight: FontWeight.w200, | |
color: Colors.grey, | |
fontSize: 13, | |
height: 1.4), | |
), | |
const YMargin(5), | |
Container( | |
child: RichText( | |
text: TextSpan( | |
text: 'Atarah, the Figmama.\n', | |
style: TextStyle( | |
fontWeight: FontWeight.bold, | |
color: Colors.black, | |
fontSize: 15), | |
children: <TextSpan>[ | |
TextSpan( | |
text: 'accepted your shipment request.', | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.black, | |
fontSize: 15), | |
) | |
]), | |
), | |
) | |
], | |
), | |
], | |
), | |
trailing: Icon( | |
Icons.arrow_forward_ios, | |
size: 14, | |
), | |
), | |
) | |
], | |
), | |
); | |
} | |
} | |
//https://github.com/MarcinusX/snappable | |
class Snappable extends StatefulWidget { | |
/// Widget to be snapped | |
final Widget child; | |
/// Direction and range of snap effect | |
/// (Where and how far will particles go) | |
final Offset offset; | |
/// Duration of whole snap animation | |
final Duration duration; | |
/// How much can particle be randomized, | |
/// For example if [offset] is (100, 100) and [randomDislocationOffset] is (10,10), | |
/// Each layer can be moved to maximum between 90 and 110. | |
final Offset randomDislocationOffset; | |
/// Number of layers of images, | |
/// The more of them the better effect but the more heavy it is for CPU | |
final int numberOfBuckets; | |
/// Quick helper to snap widgets when touched | |
/// If true wraps the widget in [GestureDetector] and starts [snap] when tapped | |
/// Defaults to false | |
final bool snapOnTap; | |
/// Function that gets called when snap ends | |
final VoidCallback onSnapped; | |
const Snappable({ | |
Key key, | |
@required this.child, | |
this.offset = const Offset(64, -32), | |
this.duration = const Duration(milliseconds: 5000), | |
this.randomDislocationOffset = const Offset(64, 32), | |
this.numberOfBuckets = 16, | |
this.snapOnTap = false, | |
this.onSnapped, | |
}) : super(key: key); | |
@override | |
SnappableState createState() => SnappableState(); | |
} | |
class SnappableState extends State<Snappable> | |
with SingleTickerProviderStateMixin { | |
static const double _singleLayerAnimationLength = 0.6; | |
static const double _lastLayerAnimationStart = | |
1 - _singleLayerAnimationLength; | |
bool get isGone => _animationController.isCompleted; | |
/// Main snap effect controller | |
AnimationController _animationController; | |
/// Key to get image of a [widget.child] | |
GlobalKey _globalKey = GlobalKey(); | |
/// Layers of image | |
List<Uint8List> _layers; | |
/// Values from -1 to 1 to dislocate the layers a bit | |
List<double> _randoms; | |
/// Size of child widget | |
Size size; | |
@override | |
void initState() { | |
super.initState(); | |
_animationController = AnimationController( | |
vsync: this, | |
duration: widget.duration, | |
); | |
if (widget.onSnapped != null) { | |
_animationController.addStatusListener((status) { | |
if (status == AnimationStatus.completed) widget.onSnapped(); | |
}); | |
} | |
} | |
@override | |
void dispose() { | |
_animationController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
onTap: widget.snapOnTap ? () => isGone ? reset() : snap() : null, | |
child: Stack( | |
children: <Widget>[ | |
if (_layers != null) ..._layers.map(_imageToWidget), | |
AnimatedBuilder( | |
animation: _animationController, | |
builder: (context, child) { | |
return _animationController.isDismissed ? child : Container(); | |
}, | |
child: RepaintBoundary( | |
key: _globalKey, | |
child: widget.child, | |
), | |
) | |
], | |
), | |
); | |
} | |
/// I am... INEVITABLE ~Thanos | |
Future<void> snap() async { | |
//get image from child | |
final fullImage = await _getImageFromWidget(); | |
//create an image for every bucket | |
List<image.Image> _images = List<image.Image>.generate( | |
widget.numberOfBuckets, | |
(i) => image.Image(fullImage.width, fullImage.height), | |
); | |
//for every line of pixels | |
for (int y = 0; y < fullImage.height; y++) { | |
//generate weight list of probabilities determining | |
//to which bucket should given pixels go | |
List<int> weights = List.generate( | |
widget.numberOfBuckets, | |
(bucket) => _gauss( | |
y / fullImage.height, | |
bucket / widget.numberOfBuckets, | |
), | |
); | |
int sumOfWeights = weights.fold(0, (sum, el) => sum + el); | |
//for every pixel in a line | |
for (int x = 0; x < fullImage.width; x++) { | |
//get the pixel from fullImage | |
int pixel = fullImage.getPixel(x, y); | |
//choose a bucket for a pixel | |
int imageIndex = _pickABucket(weights, sumOfWeights); | |
//set the pixel from chosen bucket | |
_images[imageIndex].setPixel(x, y, pixel); | |
} | |
} | |
_layers = await compute<List<image.Image>, List<Uint8List>>( | |
_encodeImages, _images); | |
//prepare random dislocations and set state | |
setState(() { | |
_randoms = List.generate( | |
widget.numberOfBuckets, | |
(i) => (math.Random().nextDouble() - 0.5) * 2, | |
); | |
}); | |
//give a short delay to draw images | |
await Future.delayed(Duration(milliseconds: 100)); | |
//start the snap! | |
_animationController.forward(); | |
} | |
/// I am... IRON MAN ~Tony Stark | |
void reset() { | |
setState(() { | |
_layers = null; | |
_animationController.reset(); | |
}); | |
} | |
Widget _imageToWidget(Uint8List layer) { | |
//get layer's index in the list | |
int index = _layers.indexOf(layer); | |
//based on index, calculate when this layer should start and end | |
double animationStart = (index / _layers.length) * _lastLayerAnimationStart; | |
double animationEnd = animationStart + _singleLayerAnimationLength; | |
//create interval animation using only part of whole animation | |
CurvedAnimation animation = CurvedAnimation( | |
parent: _animationController, | |
curve: Interval( | |
animationStart, | |
animationEnd, | |
curve: Curves.easeOut, | |
), | |
); | |
Offset randomOffset = widget.randomDislocationOffset.scale( | |
_randoms[index], | |
_randoms[index], | |
); | |
Animation<Offset> offsetAnimation = Tween<Offset>( | |
begin: Offset.zero, | |
end: widget.offset + randomOffset, | |
).animate(animation); | |
return AnimatedBuilder( | |
animation: _animationController, | |
child: Image.memory(layer), | |
builder: (context, child) { | |
return Transform.translate( | |
offset: offsetAnimation.value, | |
child: Opacity( | |
opacity: math.cos(animation.value * math.pi / 2), | |
child: child, | |
), | |
); | |
}, | |
); | |
} | |
/// Returns index of a randomly chosen bucket | |
int _pickABucket(List<int> weights, int sumOfWeights) { | |
int rnd = math.Random().nextInt(sumOfWeights); | |
int chosenImage = 0; | |
for (int i = 0; i < widget.numberOfBuckets; i++) { | |
if (rnd < weights[i]) { | |
chosenImage = i; | |
break; | |
} | |
rnd -= weights[i]; | |
} | |
return chosenImage; | |
} | |
/// Gets an Image from a [child] and caches [size] for later us | |
Future<image.Image> _getImageFromWidget() async { | |
RenderRepaintBoundary boundary = | |
_globalKey.currentContext.findRenderObject(); | |
//cache image for later | |
size = boundary.size; | |
var img = await boundary.toImage(); | |
var byteData = await img.toByteData(format: ImageByteFormat.png); | |
var pngBytes = byteData.buffer.asUint8List(); | |
return image.decodeImage(pngBytes); | |
} | |
int _gauss(double center, double value) => | |
(1000 * math.exp(-(math.pow((value - center), 2) / 0.14))).round(); | |
} | |
/// This is slow! Run it in separate isolate | |
List<Uint8List> _encodeImages(List<image.Image> images) { | |
return images.map((img) => Uint8List.fromList(image.encodePng(img))).toList(); | |
} | |
class XMargin extends StatelessWidget { | |
final double x; | |
const XMargin(this.x); | |
@override | |
Widget build(BuildContext context) { | |
return SizedBox(width: x); | |
} | |
} | |
class YMargin extends StatelessWidget { | |
final double y; | |
const YMargin(this.y); | |
@override | |
Widget build(BuildContext context) { | |
return SizedBox(height: y); | |
} | |
} | |
extension CustomContext on BuildContext { | |
double screenHeight([double percent = 1]) => | |
MediaQuery.of(this).size.height * percent; | |
double screenWidth([double percent = 1]) => | |
MediaQuery.of(this).size.width * percent; | |
} | |
final menu = | |
"""iVBORw0KGgoAAAANSUhEUgAAAC8AAAAnCAYAAACfdBHBAAAAAXNSR0IArs4c6QAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAL6ADAAQAAAABAAAAJwAAAAD77K8MAAAA3ElEQVRYCe3ZzQ2CQBAF4DcrsQ0twZ9GsAQ6sANLsAY7wEI0WIGWgFcju86aoFzJJuCYNxdCwm4eX7g8Vp5Y5x7YCbCAiQl1AMopzoU8sLoKZG4idydkhmbp9F7RbY5zkG1AuBiKf9fP5iCoKkOZGZUCFKAABShAAQr8t4AMX0a+ZSKVNpaRm5aRWepGfddridhkOJV913Wfj2XE7IxRRt5lIlXdrDiDU4ACFKAABShAAQr8mMAITSpJoNQTkUL/zddxF+cR9nbOo5A3cHn7+jzWaSUGuB4n8J/e+wJhmTc9M2l3OAAAAABJRU5ErkJggg=="""; | |
final box = | |
"""iVBORw0KGgoAAAANSUhEUgAAAGgAAAA/CAYAAAAMl43uAAAAAXNSR0IArs4c6QAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAaKADAAQAAAABAAAAPwAAAADWqGrwAAAEWklEQVR4Ae2dQW7TQBSGCeqesGWVI2TNpu4JgBPgnoACB2i2SCDlBs0SVvQGTfdI5AatxAGaJazC99xx5bbxeMae8TjpG+nhsd97/7z3/3FkeVIxeuY4NptNRuihY3hfYZej0WhZXYw6zzjPq9cGMF9RwxG1rn1rGbkm0PgNsWPX+J7i1jT9slyLGmfMT8vzgR1biXTg0cQxsVOP+D5CpeliIE7OpBTnHcKd33rc/iV/QuRVQ/RHcOcNMffc4I65sMSm2AXnR2CsmT+dQdM5Vo68TeckzwzA0szlvLSF8TUJuHVpcsfYtcH4LedbA/fxIs1OsRvT/Kxtj+SvDUa2DaPilzvBe5AvdZZr/PQG2MUE03QpzqJtD+DkmIxVHQa+eRGx2XRZpyrSWd1ae3EdssbYVVfShAwwVgYnryMH/8TEyAei9VcUufsvkhCEyXe5DCG3C2FZgcLXT5045XWzloTXClnG2o7k77dINHiByegkjpAIxqJAun1Et/EqsbmJvbAGOjjB2U+RaOzMkLTmOHHgojZE8g2WHBqxiBljsq6MxvjahY0DjP0SiYbmwgxDSJo2EdDkB2MmYIxFU2zpl9gig1rKa12OYJV3pcDOumAlzX3QSBaiGDCvhRWGM57EFhk8oISoQTDAq4qUh8LtDSdGAxXMlW8j5JbCvvXNrYuv1MO020NI3RpRrlOsfE+X4yTUIgAuDWjui0neick99821xYP56E4acTEj6dCWGNF3yXupZR0+tU3xif8F9gv7joUYrwD5hP3D5thfzGeMCf5gEr5w9M23rSU9vzEBxyLQmhMhIMW49zb6YQHUtuRaqg/Pw3KSnMvb7BwT1VKMleOiP4j74xjrEvaaILFLbIm1GRlJ8uHpgmFb99TmHIRP7iBMRhayIPBmBWqHx9oQGLaeTH2b57Yg9aVnQAVKr4G1AhXISk96pwqUXgNrBSqQlZ70ThUovQbWClQgKz3pnSpQeg2sFahAVnrSO1Wg9BpYK1CBrPSkd6pA6TWwVuD822xe3mUghX71b90Pslb+RJzOAsHHORZ630j2ou7+OuGJcO7Vpo9AOcih941c94O8mtqnYGeB2JqWO0hMR48M6ENCj2S3Wcr5DmoDvgM5pzz8DHprWe+gHfgUDbZEPt1RfpMw2IYrhdF7MfQOqpAyxKkKNERVKjWpQBUyhjhVgYaoSqUmFahCxhCnKtAQVanUpAJVyBji9ICH7YzCQm8juPaq2w0NTMmrHnkBGnoboWHZO/eamW433NHxeCIC5dj0sauXK6teVtFF4jDA16++6olDraKGYkCf4kIxGQlHBYpEbChYFSgUk5FwVKBIxIaCVYFCMRkJRwWKRGwoWBUoFJORcFSgSMSGglWBQjEZCWdXfhf3ntc+qd64R6LeDdZLIEj67AbrF8XPir81ZOQN/r11O//fDcIAAm1iMIFAW+tguZz1JjHW3BVMrzuIpr722Ri6Lfpcb4hr/QfhQRU4swELMgAAAABJRU5ErkJggg=="""; | |
class FrameBuilder extends StatelessWidget { | |
final Widget app; | |
final TransitionBuilder builder; | |
const FrameBuilder({ | |
this.app, | |
this.builder, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return Frame( | |
app: builder(context, app), | |
); | |
} | |
} | |
class Frame extends StatelessWidget { | |
final Widget app; | |
const Frame({Key key, this.app}) : super(key: key); | |
static TransitionBuilder get builder => (context, app) => Frame(app: app); | |
@override | |
Widget build(BuildContext context) { | |
final theme = DefaultFrameTheme.of(context).data; | |
final shouldDisplayTemplate = MediaQuery.of(context).size.width > 600; | |
if (!shouldDisplayTemplate) { | |
return app; | |
} else { | |
final MediaQueryData mediaQuery = MediaQueryData( | |
size: Size(414, 896), | |
padding: EdgeInsets.only( | |
top: 44, | |
bottom: 34, | |
), | |
devicePixelRatio: 2, | |
); | |
return Container( | |
color: Colors.red.withOpacity(0.13), | |
padding: EdgeInsets.symmetric(vertical: 40), | |
child: FittedBox( | |
child: Material( | |
color: Colors.transparent, | |
child: Builder(builder: (context) { | |
final device = MediaQuery( | |
data: mediaQuery, | |
child: SizedBox.fromSize( | |
size: mediaQuery.size, | |
child: Stack( | |
fit: StackFit.expand, | |
children: <Widget>[ | |
app, | |
Positioned( | |
top: 0, | |
left: 0, | |
right: 0, | |
height: 44, | |
child: _StatusBar(theme: theme), | |
), | |
Align( | |
alignment: Alignment.bottomCenter, | |
child: Container( | |
margin: EdgeInsets.only(bottom: 8), | |
height: 4, | |
width: 140, | |
decoration: BoxDecoration( | |
color: theme.statusBarColor, | |
borderRadius: BorderRadius.circular(4)), | |
), | |
) | |
], | |
)), | |
); | |
return Container( | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: [ | |
XMargin(context.screenWidth(0.12)), | |
Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: [ | |
Container( | |
height: 60, | |
width: 60, | |
decoration: BoxDecoration( | |
color: Colors.black12, | |
borderRadius: BorderRadius.circular(5)), | |
child: Center( | |
child: Text( | |
'✊🏾', | |
style: TextStyle(fontSize: 30), | |
), | |
)), | |
YMargin(30), | |
Snappable( | |
key: key[7], | |
child: Text( | |
"I am\nInevitable", | |
style: TextStyle( | |
fontSize: 40, | |
color: Colors.black, | |
fontWeight: FontWeight.w800), | |
), | |
), | |
YMargin(20), | |
Snappable( | |
key: key[8], | |
child: Container( | |
width: 350, | |
child: Text( | |
"I used the stones to destroy the stones. And it nearly killed me. But the work is done, it always will be. I am inevitable.", | |
style: TextStyle( | |
fontSize: 16, | |
height: 1.4, | |
color: Colors.grey, | |
fontWeight: FontWeight.w300), | |
), | |
), | |
), | |
YMargin(30), | |
Row( | |
children: [ | |
Material( | |
color: Colors.black, | |
child: InkWell( | |
onTap: () async { | |
key.forEach((_) async { | |
await _.currentState.snap(); | |
}); | |
}, | |
child: Padding( | |
padding: EdgeInsets.all(20), | |
child: Text( | |
'Vanish', | |
style: TextStyle( | |
fontSize: 14, | |
color: Colors.white, | |
fontWeight: FontWeight.w300), | |
), | |
), | |
), | |
), | |
XMargin(30), | |
Material( | |
color: Colors.red, | |
child: InkWell( | |
onTap: () async { | |
key.forEach((_) async { | |
_.currentState.reset(); | |
}); | |
}, | |
child: Padding( | |
padding: EdgeInsets.all(20), | |
child: Text( | |
'I am IronMan', | |
style: TextStyle( | |
fontSize: 14, | |
color: Colors.white, | |
fontWeight: FontWeight.w300), | |
), | |
), | |
), | |
), | |
], | |
), | |
], | |
), | |
XMargin(context.screenWidth(0.1)), | |
Container( | |
child: ClipRRect( | |
borderRadius: BorderRadius.circular(10), | |
child: device, | |
), | |
decoration: BoxDecoration( | |
borderRadius: BorderRadius.circular(10), | |
boxShadow: [ | |
BoxShadow( | |
color: Colors.grey.withOpacity(0.2), | |
blurRadius: 43), | |
], | |
), | |
), | |
], | |
), | |
); | |
}), | |
), | |
), | |
); | |
} | |
} | |
} | |
class _StatusBar extends StatelessWidget { | |
final FrameThemeData theme; | |
const _StatusBar({Key key, this.theme}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
final date = DateTime.now(); | |
return Theme( | |
data: ThemeData(brightness: theme.statusBarBrightness), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: <Widget>[ | |
Padding( | |
padding: EdgeInsets.only(left: 30, top: 4), | |
child: Text( | |
'${date.hour}:${date.minute}', | |
style: TextStyle( | |
fontWeight: FontWeight.bold, color: theme.statusBarColor), | |
)), | |
Padding( | |
padding: EdgeInsets.only(right: 18), | |
child: Row( | |
children: <Widget>[ | |
ImageIcon( | |
NetworkImage( | |
'https://github.com/google/material-design-icons/raw/master/png/device/signal_cellular_4_bar/materialiconsround/36dp/2x/round_signal_cellular_4_bar_black_36dp.png'), | |
size: 14, | |
color: theme.statusBarColor), | |
SizedBox(width: 4), | |
ImageIcon( | |
NetworkImage( | |
'https://github.com/google/material-design-icons/raw/master/png/device/signal_wifi_4_bar/materialiconsround/36dp/2x/round_signal_wifi_4_bar_black_36dp.png'), | |
size: 16, | |
color: theme.statusBarColor), | |
SizedBox(width: 4), | |
RotatedBox( | |
quarterTurns: 1, | |
child: ImageIcon( | |
NetworkImage( | |
'https://github.com/google/material-design-icons/raw/master/png/device/battery_charging_60/materialiconstwotone/36dp/2x/twotone_battery_charging_60_black_36dp.png'), | |
size: 19, | |
color: theme.statusBarColor), | |
) | |
], | |
), | |
) | |
], | |
), | |
); | |
} | |
} | |
class FrameThemeData { | |
final Color frameColor; | |
final Brightness statusBarBrightness; | |
factory FrameThemeData({Color frameColor, Brightness statusBarBrightness}) { | |
frameColor ??= Colors.white; | |
statusBarBrightness ??= Brightness.light; | |
return FrameThemeData.raw( | |
frameColor: frameColor, | |
statusBarBrightness: statusBarBrightness, | |
); | |
} | |
const FrameThemeData.raw({ | |
this.frameColor, | |
this.statusBarBrightness, | |
}) : assert(frameColor != null), | |
assert(statusBarBrightness != null); | |
Color get statusBarColor => | |
statusBarBrightness == Brightness.dark ? Colors.white : Colors.black; | |
} | |
class DefaultFrameTheme extends InheritedTheme { | |
final FrameThemeData data; | |
DefaultFrameTheme({ | |
Key key, | |
this.data, | |
@required Widget child, | |
}) : super(key: key, child: child); | |
static DefaultFrameTheme of(BuildContext context) { | |
return context.dependOnInheritedWidgetOfExactType<DefaultFrameTheme>() ?? | |
DefaultFrameTheme.fallback(); | |
} | |
DefaultFrameTheme.fallback() : data = FrameThemeData(); | |
@override | |
bool updateShouldNotify(DefaultFrameTheme oldWidget) { | |
return data != oldWidget.data; | |
} | |
@override | |
Widget wrap(BuildContext context, Widget child) { | |
final DefaultFrameTheme defaultSpacing = | |
context.findAncestorWidgetOfExactType<DefaultFrameTheme>(); | |
return identical(this, defaultSpacing) | |
? child | |
: DefaultFrameTheme( | |
data: data, | |
child: child, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment