// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_sprites/flutter_sprites.dart'; import 'package:vector_math/vector_math_64.dart' as vec; ImageMap _images; SpriteSheet _sprites; class FitnessDemo extends StatelessComponent { FitnessDemo({ Key key }) : super(key: key); Widget build(BuildContext context) { return new Scaffold( toolBar: new ToolBar( center: new Text("Fitness") ), body: new _FitnessDemoContents() ); } } class _FitnessDemoContents extends StatefulComponent { _FitnessDemoContents({ Key key }) : super(key: key); _FitnessDemoContentsState createState() => new _FitnessDemoContentsState(); } class _FitnessDemoContentsState extends State<_FitnessDemoContents> { Future _loadAssets(AssetBundle bundle) async { _images = new ImageMap(bundle); await _images.load(<String>[ 'packages/flutter_gallery_assets/jumpingjack.png', ]); String json = await DefaultAssetBundle.of(context).loadString('packages/flutter_gallery_assets/jumpingjack.json'); _sprites = new SpriteSheet(_images['packages/flutter_gallery_assets/jumpingjack.png'], json); } void initState() { super.initState(); AssetBundle bundle = DefaultAssetBundle.of(context); _loadAssets(bundle).then((_) { setState(() { assetsLoaded = true; workoutAnimation = new _WorkoutAnimationNode( onPerformedJumpingJack: () { setState(() { count += 1; }); }, onSecondPassed: (int seconds) { setState(() { time = seconds; }); } ); }); }); } bool assetsLoaded = false; int count = 0; int time = 0; int get kcal => (count * 0.2).toInt(); _WorkoutAnimationNode workoutAnimation; Widget build(BuildContext context) { if (!assetsLoaded) return new Container(); Color buttonColor; String buttonText; VoidCallback onButtonPressed; if (workoutAnimation.workingOut) { buttonColor = Colors.red[500]; buttonText = "STOP WORKOUT"; onButtonPressed = endWorkout; } else { buttonColor = Theme.of(context).primaryColor; buttonText = "START WORKOUT"; onButtonPressed = startWorkout; } return new Material( child: new Column( justifyContent: FlexJustifyContent.center, children: <Widget>[ new Flexible( child: new Container( decoration: new BoxDecoration(backgroundColor: Colors.grey[800]), child: new SpriteWidget(workoutAnimation, SpriteBoxTransformMode.scaleToFit) ) ), new Padding( padding: new EdgeDims.only(top: 20.0), child: new Text("JUMPING JACKS", style: Theme.of(context).text.title) ), new Padding( padding: new EdgeDims.only(top: 20.0, bottom: 20.0), child: new Row( justifyContent: FlexJustifyContent.center, children: <Widget>[ _createInfoPanelCell("action/accessibility", "$count", "COUNT"), _createInfoPanelCell("image/timer", _formatSeconds(time), "TIME"), _createInfoPanelCell("image/flash_on", "$kcal", "KCAL") ] ) ), new Padding( padding: new EdgeDims.only(bottom: 16.0), child: new SizedBox( width: 300.0, height: 72.0, child: new RaisedButton ( onPressed: onButtonPressed, color: buttonColor, child: new Text( buttonText, style: new TextStyle(color: Colors.white, fontSize: 20.0) ) ) ) ) ] ) ); } Widget _createInfoPanelCell(String icon, String value, String description) { Color color; if (workoutAnimation.workingOut) color = Colors.black87; else color = Theme.of(context).disabledColor; return new Container( width: 100.0, child: new Center( child: new Column( children: <Widget>[ new Icon(icon: icon, size: IconSize.s48, color: color), new Text(value, style: new TextStyle(fontSize: 24.0, color: color)), new Text(description, style: new TextStyle(color: color)) ] ) ) ); } String _formatSeconds(int seconds) { int minutes = seconds ~/ 60; String secondsStr = "${seconds % 60}".padLeft(2, "0"); return "$minutes:$secondsStr"; } void startWorkout() { setState(() { count = 0; time = 0; workoutAnimation.start(); }); } void endWorkout() { setState(() { workoutAnimation.stop(); if (count >= 3) { showDialog( context: context, child: new Stack(children: <Widget>[ new _Fireworks(), new Dialog( title: new Text("Awesome workout"), content: new Text("You have completed $count jumping jacks. Good going!"), actions: <Widget>[ new FlatButton( child: new Text("SWEET"), onPressed: () { Navigator.pop(context); } ) ] ) ]) ); } }); } } typedef void _SecondPassedCallback(int seconds); class _WorkoutAnimationNode extends NodeWithSize { _WorkoutAnimationNode({ this.onPerformedJumpingJack, this.onSecondPassed }) : super(const Size(1024.0, 1024.0)) { reset(); _progress = new _ProgressCircle(const Size(800.0, 800.0)); _progress.pivot = const Point(0.5, 0.5); _progress.position = const Point(512.0, 512.0); addChild(_progress); _jumpingJack = new _JumpingJack((){ onPerformedJumpingJack(); }); _jumpingJack.scale = 0.5; _jumpingJack.position = const Point(512.0, 550.0); addChild(_jumpingJack); } final VoidCallback onPerformedJumpingJack; final _SecondPassedCallback onSecondPassed; int seconds; bool workingOut; static const int _kTargetMillis = 1000 * 30; int _startTimeMillis; _ProgressCircle _progress; _JumpingJack _jumpingJack; void reset() { seconds = 0; workingOut = false; } void start() { reset(); _startTimeMillis = new DateTime.now().millisecondsSinceEpoch; workingOut = true; _jumpingJack.animateJumping(); } void stop() { workingOut = false; _jumpingJack.neutralPose(); } void update(double dt) { if (workingOut) { int millis = new DateTime.now().millisecondsSinceEpoch - _startTimeMillis; int newSeconds = (millis) ~/ 1000; if (newSeconds != seconds) { seconds = newSeconds; onSecondPassed(seconds); } _progress.value = millis / _kTargetMillis; } else { _progress.value = 0.0; } } } class _ProgressCircle extends NodeWithSize { _ProgressCircle(Size size, [this.value = 0.0]) : super(size); static const _kTwoPI = math.PI * 2.0; static const _kEpsilon = .0000001; static const _kSweep = _kTwoPI - _kEpsilon; double value; void paint(Canvas canvas) { applyTransformForPivot(canvas); Paint circlePaint = new Paint() ..color = Colors.white30 ..strokeWidth = 24.0 ..style = ui.PaintingStyle.stroke; canvas.drawCircle( new Point(size.width / 2.0, size.height / 2.0), size.width / 2.0, circlePaint ); Paint pathPaint = new Paint() ..color = Colors.purple[500] ..strokeWidth = 25.0 ..style = ui.PaintingStyle.stroke; double angle = value.clamp(0.0, 1.0) * _kSweep; Path path = new Path() ..arcTo(Point.origin & size, -math.PI / 2.0, angle, false); canvas.drawPath(path, pathPaint); } } class _JumpingJack extends Node { _JumpingJack(VoidCallback onPerformedJumpingJack) { left = new _JumpingJackSide(false, onPerformedJumpingJack); right = new _JumpingJackSide(true, null); addChild(left); addChild(right); } void animateJumping() { left.animateJumping(); right.animateJumping(); } void neutralPose() { left.neutralPosition(true); right.neutralPosition(true); } _JumpingJackSide left; _JumpingJackSide right; } class _JumpingJackSide extends Node { _JumpingJackSide(bool right, this.onPerformedJumpingJack) { // Torso and head torso = _createPart('torso.png', const Point(512.0, 512.0)); addChild(torso); head = _createPart('head.png', const Point(512.0, 160.0)); torso.addChild(head); if (right) { torso.opacity = 0.0; head.opacity = 0.0; torso.scaleX = -1.0; } // Left side movable parts upperArm = _createPart('upper-arm.png', const Point(445.0, 220.0)); torso.addChild(upperArm); lowerArm = _createPart('lower-arm.png', const Point(306.0, 200.0)); upperArm.addChild(lowerArm); hand = _createPart('hand.png', const Point(215.0, 127.0)); lowerArm.addChild(hand); upperLeg = _createPart('upper-leg.png', const Point(467.0, 492.0)); torso.addChild(upperLeg); lowerLeg = _createPart('lower-leg.png', const Point(404.0, 660.0)); upperLeg.addChild(lowerLeg); foot = _createPart('foot.png', const Point(380.0, 835.0)); lowerLeg.addChild(foot); torso.setPivotAndPosition(Point.origin); neutralPosition(false); } _JumpingJackPart torso; _JumpingJackPart head; _JumpingJackPart upperArm; _JumpingJackPart lowerArm; _JumpingJackPart hand; _JumpingJackPart lowerLeg; _JumpingJackPart upperLeg; _JumpingJackPart foot; final VoidCallback onPerformedJumpingJack; _JumpingJackPart _createPart(String textureName, Point pivotPosition) { return new _JumpingJackPart(_sprites[textureName], pivotPosition); } void animateJumping() { actions.stopAll(); actions.run(new ActionSequence([ _createPoseAction(null, 0, 0.5), new ActionCallFunction(_animateJumpingLoop) ])); } void _animateJumpingLoop() { actions.run(new ActionRepeatForever( new ActionSequence(<Action>[ _createPoseAction(0, 1, 0.30), _createPoseAction(1, 2, 0.30), _createPoseAction(2, 1, 0.30), _createPoseAction(1, 0, 0.30), new ActionCallFunction(() { if (onPerformedJumpingJack != null) onPerformedJumpingJack(); }) ]) )); } void neutralPosition(bool animate) { actions.stopAll(); if (animate) { actions.run(_createPoseAction(null, 1, 0.5)); } else { List<double> d = _dataForPose(1); upperArm.rotation = d[0]; lowerArm.rotation = d[1]; hand.rotation = d[2]; upperLeg.rotation = d[3]; lowerLeg.rotation = d[4]; foot.rotation = d[5]; torso.position = new Point(0.0, d[6]); } } ActionInterval _createPoseAction(int startPose, int endPose, double duration) { List<double> d0 = _dataForPose(startPose); List<double> d1 = _dataForPose(endPose); List<ActionTween> tweens = <ActionTween>[ _tweenRotation(upperArm, d0[0], d1[0], duration), _tweenRotation(lowerArm, d0[1], d1[1], duration), _tweenRotation(hand, d0[2], d1[2], duration), _tweenRotation(upperLeg, d0[3], d1[3], duration), _tweenRotation(lowerLeg, d0[4], d1[4], duration), _tweenRotation(foot, d0[5], d1[5], duration), new ActionTween( (Point a) => torso.position = a, new Point(0.0, d0[6]), new Point(0.0, d1[6]), duration ) ]; return new ActionGroup(tweens); } ActionTween _tweenRotation(_JumpingJackPart part, double r0, double r1, double duration) { return new ActionTween( (double a) => part.rotation = a, r0, r1, duration ); } List<double> _dataForPose(int pose) { if (pose == null) return _dataForCurrentPose(); if (pose == 0) { return <double>[ -80.0, // Upper arm rotation -30.0, // Lower arm rotation -10.0, // Hand rotation -15.0, // Upper leg rotation 5.0, // Lower leg rotation 15.0, // Foot rotation 0.0 // Torso y offset ]; } else if (pose == 1) { return <double>[ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -70.0 ]; } else { return <double>[ 40.0, 30.0, 10.0, 20.0, -20.0, 15.0, 40.0 ]; } } List<double> _dataForCurrentPose() { return <double>[ upperArm.rotation, lowerArm.rotation, hand.rotation, upperLeg.rotation, lowerLeg.rotation, foot.rotation, torso.position.y ]; } } class _JumpingJackPart extends Sprite { _JumpingJackPart(Texture texture, this.pivotPosition) : super(texture); final Point pivotPosition; void setPivotAndPosition(Point newPosition) { pivot = new Point(pivotPosition.x / 1024.0, pivotPosition.y / 1024.0); position = newPosition; for (Node child in children) { _JumpingJackPart subPart = child; subPart.setPivotAndPosition( new Point( subPart.pivotPosition.x - pivotPosition.x, subPart.pivotPosition.y - pivotPosition.y ) ); } } } class _Fireworks extends StatefulComponent { _Fireworks({ Key key }) : super(key: key); _FireworksState createState() => new _FireworksState(); } class _FireworksState extends State<_Fireworks> { void initState() { super.initState(); fireworks = new _FireworksNode(); } _FireworksNode fireworks; Widget build(BuildContext context) { return new SpriteWidget(fireworks); } } class _FireworksNode extends NodeWithSize { _FireworksNode() : super(const Size(1024.0, 1024.0)); double _countDown = 0.0; void update(double dt) { if (_countDown <= 0.0) { _addExplosion(); _countDown = randomDouble(); } _countDown -= dt; } Color _randomExplosionColor() { double rand = randomDouble(); if (rand < 0.25) return Colors.pink[200]; else if (rand < 0.5) return Colors.lightBlue[200]; else if (rand < 0.75) return Colors.purple[200]; else return Colors.cyan[200]; } void _addExplosion() { Color startColor = _randomExplosionColor(); Color endColor = startColor.withAlpha(0); ParticleSystem system = new ParticleSystem( _sprites['particle-0.png'], numParticlesToEmit: 100, emissionRate: 1000.0, rotateToMovement: true, startRotation: 90.0, endRotation: 90.0, speed: 100.0, speedVar: 50.0, startSize: 1.0, startSizeVar: 0.5, gravity: new vec.Vector2(0.0, 30.0), colorSequence: new ColorSequence.fromStartAndEndColor(startColor, endColor) ); system.position = new Point(randomDouble() * 1024.0, randomDouble() * 1024.0); addChild(system); } }