Skip to content

Instantly share code, notes, and snippets.

@leonardarnold
Last active January 20, 2020 17:17
Show Gist options
  • Save leonardarnold/49c6933a12c444c4f7bed00d5b0526f7 to your computer and use it in GitHub Desktop.
Save leonardarnold/49c6933a12c444c4f7bed00d5b0526f7 to your computer and use it in GitHub Desktop.
Leonard Arnold Contact
import 'package:flutter/material.dart';
import 'dart:math';
enum ThemeOptions { darkTheme, lightTheme }
void main() => runApp(ThemeChanger(
child: MyApp(),
));
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeChanger.of(context),
home: ProfileScreen(),
);
}
}
class ThemeChanger extends StatefulWidget {
final Widget child;
const ThemeChanger({
Key key,
@required this.child,
}) : super(key: key);
@override
ThemeChangerState createState() => ThemeChangerState();
static ThemeData of(BuildContext context) {
// deprecated warning but otherwise it would be incompatible with DartPad
// dependOnInheritedWidgetOfExactType yet
ThemeInheritedWidget inherited =
//ignore: deprecated_member_use
context.inheritFromWidgetOfExactType(ThemeInheritedWidget);
return inherited.appState.theme;
}
static ThemeChangerState instanceOf(BuildContext context) {
ThemeInheritedWidget inherited =
//ignore: deprecated_member_use
context.inheritFromWidgetOfExactType(ThemeInheritedWidget);
return inherited.appState;
}
}
class ThemeChangerState extends State<ThemeChanger> {
ThemeData _theme;
ThemeOptions themeOptions;
bool isDark;
ThemeData get theme => _theme;
@override
void initState() {
super.initState();
_changeTheme(ThemeOptions.darkTheme);
}
void changeTheme(bool darkMode) {
this.isDark = darkMode;
if (darkMode) {
_changeTheme(ThemeOptions.darkTheme);
} else {
_changeTheme(ThemeOptions.lightTheme);
}
}
void _changeTheme(ThemeOptions themeOptions) {
setState(() {
this.themeOptions = themeOptions;
switch (themeOptions) {
case ThemeOptions.lightTheme:
isDark = false;
_theme = ThemeData.light();
break;
case ThemeOptions.darkTheme:
isDark = true;
_theme = ThemeData(
primaryColor: Colors.red,
primaryColorDark: Colors.red[900],
primaryColorLight: Colors.red[300],
brightness: Brightness.dark,
toggleableActiveColor: Colors.red);
break;
}
});
}
@override
Widget build(BuildContext context) {
return ThemeInheritedWidget(appState: this, child: widget.child);
}
}
class ThemeInheritedWidget extends InheritedWidget {
final ThemeChangerState appState;
ThemeInheritedWidget(
{@required this.appState, Key key, @required Widget child})
: super(key: key, child: child);
@override
bool updateShouldNotify(ThemeInheritedWidget oldWidget) {
return true;
}
}
class ProfileScreen extends StatefulWidget {
@override
_ProfileScreenState createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen>
with TickerProviderStateMixin {
AnimationController _animController;
AnimationController _rotateController;
Animation<double> _translateCard;
Animation<double> _rotateCard;
bool flipped = false;
Size size;
final _negativeMiddleMargin = -24;
final _textSpacer = const SizedBox(
height: 8,
);
void _flipCard() {
if (!_rotateController.isAnimating) {
if (!flipped) {
_rotateController.forward(from: 0);
} else {
_rotateController.reverse(from: pi);
}
}
}
@override
void initState() {
super.initState();
_animController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
_rotateController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
_translateCard = Tween(begin: 1.0, end: 0.0)
.animate(CurvedAnimation(parent: _animController, curve: Curves.easeIn))
..addListener(() => setState(() {}));
_rotateCard = Tween(begin: 0.0, end: pi).animate(
CurvedAnimation(parent: _rotateController, curve: Curves.bounceIn))
..addListener(() => setState(() {
if (_rotateCard.value != 0) {
flipped = (_rotateCard.value >= pi / 2) ? true : false;
}
}));
_animController.forward();
}
changeDarkMode({@required BuildContext context, bool value}) {
if (value == null) {
value = !ThemeChanger.instanceOf(context).isDark;
}
ThemeChanger.instanceOf(context).changeTheme(value);
}
@override
Widget build(BuildContext context) {
size = MediaQuery.of(context).size;
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
//image
ClipPath(
clipper: ProfileBackgroundClipper(),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColorLight,
image: DecorationImage(
alignment: Alignment.topCenter,
image: NetworkImage(
"https://arnold.work/wp-content/uploads/2020/01/leonard_portrait.jpg"),
fit: BoxFit.cover,
),
),
),
),
//card
Positioned(
top: size.height / 2 + _negativeMiddleMargin,
width: size.width,
height: size.height / 8 * 3,
child: _getFreelanceCard()),
//top button card
Positioned(
top: size.height / 2 + 2 * _negativeMiddleMargin,
left: 64,
child: Opacity(
opacity: 1 - _translateCard.value,
child: Transform.translate(
offset: Offset(_translateCard.value * size.width, 0.0),
child: RaisedButton.icon(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(18.0),
),
icon: CircleAvatar(
child: Text("LA"),
backgroundColor: Theme.of(context).primaryColorDark,
foregroundColor: Theme.of(context).primaryColorLight,
radius: 14,
),
label: Text(
"Leonard Arnold",
),
color: Theme.of(context).primaryColor,
onPressed: _flipCard),
),
),
),
],
)));
}
Widget _getFreelanceCard() {
var content = Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Opacity(
opacity: 1 - _translateCard.value,
child: Transform.translate(
offset: Offset(0.0, _translateCard.value * size.height / 2),
child: Card(
elevation: 8,
child: LayoutBuilder(builder: (context, constraint) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: (!flipped)
? _getFreelanceCardContent()
: _getFreelanceCardContentFlipped(),
),
),
),
);
}),
),
),
),
);
//Flutter for web has problems on transformed text, even if it's transformed
//back and forth equally. SO it is necessary to put out the transformer
//to avoid blurry text on web(/dartpad)
return (_rotateCard.isCompleted)
? content
: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.0001)
..rotateX(0)
..rotateY(_rotateCard.value),
alignment: FractionalOffset.center,
child: content);
}
Widget _getFreelanceCardContentFlipped() {
Widget content = Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlutterLogo(
size: 96,
),
SizedBox(
height: 8,
),
Text(
'Flutter for Android, iOS and Web (beta)',
textAlign: TextAlign.center,
),
],
));
//Flutter for web has problems on transformed text, even if it's transformed
//back and forth equally. SO it is necessary to put out the transformer
//to avoid blurry text on web(/dartpad)
return (_rotateCard.isCompleted)
? content
: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.0001)
..rotateX(0)
..rotateY(_rotateCard.value),
alignment: FractionalOffset.center,
child: content);
}
Widget _getFreelanceCardContent() {
// text is still blurred on web here
// https://github.com/flutter/flutter/issues/32274
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_textSpacer,
_textSpacer,
_textSpacer,
Text(
"Flutter Freelancer",
style: Theme.of(context).textTheme.headline,
),
_textSpacer,
Text("leonard@arnold.work"),
_textSpacer,
Text("Flutter, Android, iOS, Spanish, English, German"),
_textSpacer,
Expanded(
child: Container(),
),
RoundedOutlineButton(
icon: Icon(
Icons.play_arrow,
),
label: Text(
"Sunrise Animation",
),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return SunriseScreen(
isDark: ThemeChanger.instanceOf(context).isDark);
}));
},
),
Row(
children: <Widget>[
Expanded(
child: Container(),
),
Card(
elevation: 0,
child: InkWell(
onTap: () => changeDarkMode(context: context),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Dark Mode"),
Switch(
value: ThemeChanger.instanceOf(context).isDark,
onChanged: (v) =>
changeDarkMode(context: context, value: v),
),
],
),
),
),
),
],
),
],
);
}
}
class RoundedOutlineButton extends StatelessWidget {
final Widget label;
final Widget icon;
final Function onPressed;
RoundedOutlineButton(
{@required this.label, @required this.icon, @required this.onPressed});
@override
Widget build(BuildContext context) {
Widget icon;
if (this.icon is Icon) {
icon =
Icon((this.icon as Icon).icon, color: Theme.of(context).primaryColor);
}
return RaisedButton.icon(
elevation: 0,
color: Theme.of(context).cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: BorderSide(color: Theme.of(context).primaryColor)),
icon: icon ?? this.icon,
label: label ?? this.label,
onPressed: this.onPressed,
);
}
}
class ProfileBackgroundClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
path.lineTo(0, size.height / 2);
path.lineTo(size.width, size.height / 8 * 5);
path.lineTo(size.width, 0);
path.close();
return path;
}
@override
bool shouldReclip(ProfileBackgroundClipper oldClipper) => false;
}
class SunriseScreen extends StatefulWidget {
final bool isDark;
SunriseScreen({Key key, @required this.isDark}) : super(key: key);
@override
_SunriseScreenState createState() => _SunriseScreenState();
}
class _SunriseScreenState extends State<SunriseScreen>
with TickerProviderStateMixin {
AnimationController _rotationController;
AnimationController _bounceController;
Animation<double> _innerCircleAnimation;
Animation<double> _outerCircleAnimation;
Animation<Color> _colorAnimation;
Animation<Color> _backgroundColorAnimation;
int _sunriseCount = 20;
List<Color> _actualColors;
final List<Color> _darkColors = [
Colors.amber[700],
Colors.amber[900],
Colors.red[500],
Colors.red[900],
];
final List<Color> _lightColors = [
Colors.red,
Colors.red[200],
Colors.blue,
Colors.blue[200],
];
@override
void initState() {
super.initState();
_actualColors = (widget.isDark) ? _darkColors : _lightColors;
_rotationController = AnimationController(
duration: const Duration(milliseconds: 3000), vsync: this);
_rotationController.value = 1;
_bounceController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this);
_rotationController.value = 1;
_innerCircleAnimation = Tween(begin: 1.0, end: 1.6).animate(
CurvedAnimation(parent: _bounceController, curve: Curves.bounceIn))
..addListener(() => setState(() {}));
_outerCircleAnimation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _rotationController, curve: Curves.easeInOut));
_colorAnimation = ColorTween(begin: _actualColors[0], end: _actualColors[2])
.animate(CurvedAnimation(
parent: _rotationController, curve: Curves.easeInOut))
..addListener(() => setState(() {}));
_backgroundColorAnimation =
ColorTween(begin: _actualColors[1], end: _actualColors[3]).animate(
CurvedAnimation(
parent: _rotationController, curve: Curves.easeInOut))
..addListener(() => setState(() {}));
_bounceController.repeat(reverse: true);
}
_startAnimation() {
if (!_rotationController.isAnimating) {
_bounceController.stop(canceled: false);
_bounceController.reverse();
if (_rotationController.value == 0)
_rotationController.forward(from: 0);
else
_rotationController.animateBack(0);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: _backgroundColorAnimation.value,
child: Stack(
children: <Widget>[
RotationTransition(
turns: _outerCircleAnimation,
child: CustomPaint(
foregroundPainter: SunrisePainter(
color: _colorAnimation.value, sunriseCount: _sunriseCount),
child: SizedBox.expand(),
),
),
AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
),
Center(
child: Transform.scale(
scale: _innerCircleAnimation.value,
child: Container(
width: 64,
height: 64,
child: RawMaterialButton(
fillColor: (widget.isDark) ? Colors.black : Colors.white,
shape: CircleBorder(),
elevation: 0,
child: Icon(
Icons.play_arrow,
color: _colorAnimation.value,
size: 32,
),
onPressed: () => _startAnimation(),
),
),
)),
],
),
),
);
}
@override
void dispose() {
_rotationController.dispose();
_bounceController.dispose();
super.dispose();
}
}
class SunrisePainter extends CustomPainter {
final sunriseCount;
final color;
SunrisePainter({this.sunriseCount = 20, this.color = Colors.blue});
@override
void paint(Canvas canvas, Size size) {
//hypotenuse of width/2 and height/2 is the (minimum) radius
final circleRadius = sqrt(pow(size.width / 2, 2) + pow(size.height / 2, 2));
//circumference: pi*diameter = pi * 2 * radius
final sunriseWidth = pi * circleRadius * 2 / sunriseCount;
final angle = 2 * pi / sunriseCount;
var paint = Paint();
paint.color = color;
paint.style = PaintingStyle.fill;
canvas.translate(size.width / 2, size.height / 2);
for (var i = 0; i < sunriseCount; i++) {
if (i % 2 == 0) {
var path = Path();
path.lineTo(circleRadius, 0);
path.lineTo(circleRadius, sunriseWidth);
path.lineTo(0, 0);
canvas.drawPath(path, paint);
}
canvas.rotate(angle);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment