diff --git a/lib/ui/common/app_scroll_behavior.dart b/lib/ui/common/app_scroll_behavior.dart index 04751ecf..32f54fdf 100644 --- a/lib/ui/common/app_scroll_behavior.dart +++ b/lib/ui/common/app_scroll_behavior.dart @@ -21,7 +21,7 @@ class AppScrollBehavior extends ScrollBehavior { // TODO: Finalize scrollbar strategy (Do we use them at all? Where specifically?) @override Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) { - return child; + //return child; return PlatformInfo.isAndroid ? RawScrollbar(controller: details.controller, child: child) : CupertinoScrollbar(controller: details.controller, child: child); diff --git a/lib/ui/common/controls/buttons.dart b/lib/ui/common/controls/buttons.dart index 2abb5aa9..4300bc30 100644 --- a/lib/ui/common/controls/buttons.dart +++ b/lib/ui/common/controls/buttons.dart @@ -180,37 +180,3 @@ class _ButtonPressEffectState extends State<_ButtonPressEffect> { ); } } - -// TODO: this is currently unused, and can probably be removed: -// This is a very simple button for elements that don't require button UI (states, focus, etc) -// For example panel backgrounds. -class BasicBtn extends StatelessWidget { - const BasicBtn({ - required this.onPressed, - required this.semanticLabel, - this.behavior = HitTestBehavior.opaque, - this.enableFeedback = true, - this.child, - Key? key, - }) : super(key: key); - - final VoidCallback onPressed; - final String? semanticLabel; - final HitTestBehavior behavior; - final bool enableFeedback; - final Widget? child; - - @override - Widget build(BuildContext context) { - Widget button = GestureDetector( - excludeFromSemantics: true, - onTap: enableFeedback ? Feedback.wrapForTap(onPressed, context) : onPressed, - behavior: behavior, - child: child, - ); - - if (semanticLabel != null) button = Semantics(label: semanticLabel, button: true, container: true, child: button); - - return button; - } -} diff --git a/lib/ui/wonder_illustrations/chichen_itza_illustration.dart b/lib/ui/wonder_illustrations/chichen_itza_illustration.dart index 35347159..f9eb7b4a 100644 --- a/lib/ui/wonder_illustrations/chichen_itza_illustration.dart +++ b/lib/ui/wonder_illustrations/chichen_itza_illustration.dart @@ -1,7 +1,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/fade_color_transition.dart'; +import 'package:wonders/ui/wonder_illustrations/common/illustration_piece.dart'; import 'package:wonders/ui/wonder_illustrations/common/paint_textures.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; @@ -12,7 +12,13 @@ class ChichenItzaIllustration extends StatelessWidget { final fgColor = WonderType.chichenItza.fgColor; @override Widget build(BuildContext context) { - return WonderIllustrationBuilder(config: config, bgBuilder: _buildBg, mgBuilder: _buildMg, fgBuilder: _buildFg); + return WonderIllustrationBuilder( + config: config, + bgBuilder: _buildBg, + mgBuilder: _buildMg, + fgBuilder: _buildFg, + wonderType: WonderType.chichenItza, + ); } List _buildBg(BuildContext context, Animation anim) { @@ -26,21 +32,13 @@ class ChichenItzaIllustration extends StatelessWidget { flipY: true, ), ), - Align( - alignment: Alignment(config.shortMode ? .25 : .5, config.shortMode ? 1 : -.15), - child: WonderHero( - config, - 'chichen-sun', - child: FractionalTranslation( - translation: Offset(0, -.2 * anim.value), - child: Image.asset( - '$assetPath/sun.png', - width: config.shortMode ? 100 : 200, - cacheWidth: context.widthPx.round() * 2, - opacity: anim, - ), - ), - ), + IllustrationPiece( + fileName: 'sun.png', + initialOffset: Offset(0, 20), + enableHero: true, + heightFactor: .25, + minHeight: 200, + fractionalOffset: Offset(1, config.shortMode ? 0 : -.5), ), ]; } @@ -48,90 +46,126 @@ class ChichenItzaIllustration extends StatelessWidget { List _buildMg(BuildContext context, Animation anim) { // We want to size to the shortest side return [ - Align( - alignment: Alignment(0, config.shortMode ? 1 : 0), - child: Transform.translate( - offset: Offset(0, config.shortMode ? 30 : 0), - child: FractionallySizedBox( - heightFactor: config.shortMode ? 1 : .6, - child: WonderHero( - config, - 'chichen-mg', - child: Image.asset('$assetPath/chichen.png', opacity: anim, fit: BoxFit.cover), - ), - ), + Transform.translate( + offset: Offset(0, 20), + child: IllustrationPiece( + fileName: 'chichen.png', + heightFactor: .55, + minHeight: 400, + zoomAmt: .05, + enableHero: true, ), ), ]; } List _buildFg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); return [ - Stack( - children: [ - Transform.scale( - scale: 1 + config.zoom * .05, - child: FractionalTranslation( - translation: Offset(-.2 * (1 - curvedAnim), 0), - child: BottomLeft( - child: SizedBox( - height: 500, - child: FractionalTranslation( - translation: Offset(-.4, .15), - child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ), - Transform.scale( - scale: 1 + config.zoom * .03, - child: FractionalTranslation( - translation: Offset(.2 * (1 - curvedAnim), 0), - child: BottomRight( - child: SizedBox( - height: 350, - child: FractionalTranslation( - translation: Offset(.35, .2), - child: Image.asset('$assetPath/foreground-right.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ), - Transform.scale( - scale: 1 + config.zoom * .25, - child: FractionalTranslation( - translation: Offset(-.2 * (1 - curvedAnim), 0), - child: TopLeft( - child: SizedBox( - height: 600, - child: FractionalTranslation( - translation: Offset(-.3, -.45), - child: Image.asset('$assetPath/top-left.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ), - Transform.scale( - scale: 1 + config.zoom * .2, - child: FractionalTranslation( - translation: Offset(.2 * (1 - curvedAnim), 0), - child: TopRight( - child: SizedBox( - height: 700, - child: FractionalTranslation( - translation: Offset(.2, -.35), - child: Image.asset('$assetPath/top-right.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ), - ], + IllustrationPiece( + fileName: 'foreground-left.png', + alignment: Alignment.bottomCenter, + initialScale: .9, + initialOffset: Offset(-40, 60), + heightFactor: .65, + fractionalOffset: Offset(-.4, .2), + zoomAmt: .25, + dynamicHzOffset: -250, ), + IllustrationPiece( + fileName: 'foreground-right.png', + alignment: Alignment.bottomCenter, + initialOffset: Offset(20, 40), + initialScale: .95, + heightFactor: .6, + fractionalOffset: Offset(.4, .2), + zoomAmt: .1, + dynamicHzOffset: 250, + ), + IllustrationPiece( + fileName: 'top-left.png', + alignment: Alignment.topLeft, + initialScale: .9, + initialOffset: Offset(-40, 60), + heightFactor: .75, + fractionalOffset: Offset(-.5, -.3), + zoomAmt: .25, + dynamicHzOffset: 100, + ), + IllustrationPiece( + fileName: 'top-right.png', + alignment: Alignment.topRight, + initialOffset: Offset(20, 40), + initialScale: .95, + heightFactor: .85, + fractionalOffset: Offset(.4, -.4), + zoomAmt: .1, + dynamicHzOffset: -100, + ), + + // Stack( + // children: [ + // Transform.scale( + // scale: 1 + config.zoom * .05, + // child: FractionalTranslation( + // translation: Offset(-.2 * (1 - curvedAnim), 0), + // child: BottomLeft( + // child: SizedBox( + // height: 500, + // child: FractionalTranslation( + // translation: Offset(-.4, .15), + // child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.cover), + // ), + // ), + // ), + // ), + // ), + // Transform.scale( + // scale: 1 + config.zoom * .03, + // child: FractionalTranslation( + // translation: Offset(.2 * (1 - curvedAnim), 0), + // child: BottomRight( + // child: SizedBox( + // height: 350, + // child: FractionalTranslation( + // translation: Offset(.35, .2), + // child: Image.asset('$assetPath/foreground-right.png', opacity: anim, fit: BoxFit.cover), + // ), + // ), + // ), + // ), + // ), + // Transform.scale( + // scale: 1 + config.zoom * .25, + // child: FractionalTranslation( + // translation: Offset(-.2 * (1 - curvedAnim), 0), + // child: TopLeft( + // child: SizedBox( + // height: 600, + // child: FractionalTranslation( + // translation: Offset(-.3, -.45), + // child: Image.asset('$assetPath/top-left.png', opacity: anim, fit: BoxFit.cover), + // ), + // ), + // ), + // ), + // ), + // Transform.scale( + // scale: 1 + config.zoom * .2, + // child: FractionalTranslation( + // translation: Offset(.2 * (1 - curvedAnim), 0), + // child: TopRight( + // child: SizedBox( + // height: 700, + // child: FractionalTranslation( + // translation: Offset(.2, -.35), + // child: Image.asset('$assetPath/top-right.png', opacity: anim, fit: BoxFit.cover), + // ), + // ), + // ), + // ), + // ), + // ], + // ), ]; } } diff --git a/lib/ui/wonder_illustrations/christ_redeemer_illustration.dart b/lib/ui/wonder_illustrations/christ_redeemer_illustration.dart index fefd334f..7387e5fd 100644 --- a/lib/ui/wonder_illustrations/christ_redeemer_illustration.dart +++ b/lib/ui/wonder_illustrations/christ_redeemer_illustration.dart @@ -1,7 +1,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/fade_color_transition.dart'; +import 'package:wonders/ui/wonder_illustrations/common/illustration_piece.dart'; import 'package:wonders/ui/wonder_illustrations/common/paint_textures.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; @@ -18,6 +18,7 @@ class ChristRedeemerIllustration extends StatelessWidget { bgBuilder: _buildBg, mgBuilder: _buildMg, fgBuilder: _buildFg, + wonderType: WonderType.christRedeemer, ); } @@ -32,90 +33,52 @@ class ChristRedeemerIllustration extends StatelessWidget { opacity: anim.drive(Tween(begin: 0, end: .4)), ), ), - Align( - alignment: config.shortMode ? Alignment(.5, -1.5) : Alignment(.5, -.75), - child: FractionalTranslation( - translation: Offset(0, .5 * anim.value), - child: WonderHero( - config, - 'christ-sun', - child: Transform.scale( - scale: config.shortMode ? 1.4 : 1.6, - child: Image.asset( - '$assetPath/sun.png', - cacheWidth: context.widthPx.round() * 2, - opacity: anim, - ), - ), - ), - ), + IllustrationPiece( + fileName: 'sun.png', + initialOffset: Offset(0, 20), + enableHero: true, + heightFactor: .2, + minHeight: 120, + fractionalOffset: Offset(.5, -1), ), ]; } List _buildMg(BuildContext context, Animation anim) { return [ - ClipRect( - child: Transform.scale( - scale: 1 + config.zoom * .2, - child: FractionalTranslation( - translation: Offset(0, config.shortMode ? .5 : .2), - child: BottomCenter( - child: FractionallySizedBox( - heightFactor: config.shortMode ? 1.5 : 1.2, - child: WonderHero( - config, - 'christ-mg', - child: Image.asset( - '$assetPath/redeemer.png', - opacity: anim, - fit: BoxFit.cover, - ), - ), - ), - ), - ), - ), + IllustrationPiece( + fileName: 'redeemer.png', + enableHero: true, + heightFactor: 1, + alignment: Alignment.bottomCenter, + fractionalOffset: Offset(0, .1), + zoomAmt: .1, ) + // ]; } List _buildFg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); return [ - Stack( - children: [ - Transform.scale( - scale: 1 + config.zoom * .15, - child: FractionalTranslation( - translation: Offset(-.2 * (1 - curvedAnim), 0), - child: BottomLeft( - child: FractionallySizedBox( - widthFactor: 1.5, - child: FractionalTranslation( - translation: Offset(-.25, .03), - child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ), - Transform.scale( - scale: 1 + config.zoom * .3, - child: FractionalTranslation( - translation: Offset(.2 * (1 - curvedAnim), 0), - child: BottomRight( - child: FractionallySizedBox( - widthFactor: 1.5, - child: FractionalTranslation( - translation: Offset(.3, .2), - child: Image.asset('$assetPath/foreground-right.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ), - ], + IllustrationPiece( + fileName: 'foreground-left.png', + alignment: Alignment.bottomCenter, + initialScale: .9, + initialOffset: Offset(-40, 60), + heightFactor: .55, + fractionalOffset: Offset(-.25, 0), + zoomAmt: .25, + dynamicHzOffset: -100, + ), + IllustrationPiece( + fileName: 'foreground-right.png', + alignment: Alignment.bottomCenter, + initialOffset: Offset(20, 40), + initialScale: .95, + heightFactor: .65, + fractionalOffset: Offset(.2, 0), + zoomAmt: .1, + dynamicHzOffset: 100, ), ]; } diff --git a/lib/ui/wonder_illustrations/colosseum_illustration.dart b/lib/ui/wonder_illustrations/colosseum_illustration.dart index 703d7972..f01e0538 100644 --- a/lib/ui/wonder_illustrations/colosseum_illustration.dart +++ b/lib/ui/wonder_illustrations/colosseum_illustration.dart @@ -1,5 +1,6 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/fade_color_transition.dart'; +import 'package:wonders/ui/wonder_illustrations/common/illustration_piece.dart'; import 'package:wonders/ui/wonder_illustrations/common/paint_textures.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; @@ -18,6 +19,7 @@ class ColosseumIllustration extends StatelessWidget { bgBuilder: _buildBg, mgBuilder: _buildMg, fgBuilder: _buildFg, + wonderType: WonderType.colosseum, ); } @@ -55,25 +57,12 @@ class ColosseumIllustration extends StatelessWidget { List _buildMg(BuildContext context, Animation anim) { return [ Stack( - children: [ - if (config.shortMode) ...[ - FractionalTranslation( - translation: Offset(0, .9), - child: Container(color: bgColor), - ) - ], - Center( - child: FractionalTranslation( - translation: Offset(0, config.shortMode ? .1 : -.15), - child: Transform.scale( - scale: config.shortMode ? .85 : 1.55 + config.zoom * .2, - child: WonderHero( - config, - 'colosseum-mg', - child: Image.asset('$assetPath/colosseum.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), + children: const [ + IllustrationPiece( + fileName: 'colosseum.png', + heightFactor: .55, + minHeight: 400, + zoomAmt: .15, ), ], ) @@ -81,40 +70,27 @@ class ColosseumIllustration extends StatelessWidget { } List _buildFg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); return [ - Stack(children: [ - BottomLeft( - child: FractionallySizedBox( - heightFactor: .56, - child: FractionalTranslation( - translation: Offset(-.2 * (1 - curvedAnim), 0), - child: Transform.scale( - scale: 1 + config.zoom * .3, - child: FractionalTranslation( - translation: Offset(-.1, .1), - child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ), - BottomRight( - child: FractionallySizedBox( - heightFactor: .56, - child: FractionalTranslation( - translation: Offset(.2 * (1 - curvedAnim), 0), - child: Transform.scale( - scale: 1 + config.zoom * .3, - child: FractionalTranslation( - translation: Offset(.3, .2), - child: Image.asset('$assetPath/foreground-right.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ), - ]) + IllustrationPiece( + fileName: 'foreground-left.png', + alignment: Alignment.bottomCenter, + initialScale: .9, + initialOffset: Offset(-40, 60), + heightFactor: .65, + fractionalOffset: Offset(-.5, .1), + zoomAmt: .25, + dynamicHzOffset: -150, + ), + IllustrationPiece( + fileName: 'foreground-right.png', + alignment: Alignment.bottomCenter, + initialOffset: Offset(20, 40), + initialScale: .95, + heightFactor: .75, + fractionalOffset: Offset(.5, .25), + zoomAmt: .1, + dynamicHzOffset: 150, + ), ]; } } diff --git a/lib/ui/wonder_illustrations/common/illustration_fg.dart b/lib/ui/wonder_illustrations/common/illustration_fg.dart deleted file mode 100644 index 9255b6b9..00000000 --- a/lib/ui/wonder_illustrations/common/illustration_fg.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:wonders/common_libs.dart'; - -/// Combines [Align], [FractionalBoxWithMinSize], [Image] and [Transform.translate] -/// to standardize behavior across the various wonder illustrations -class IllustrationPiece extends StatelessWidget { - const IllustrationPiece({ - Key? key, - required this.type, - required this.anim, - required this.fileName, - required this.heightFactor, - this.alignment = Alignment.center, - this.minHeight, - this.translation, - }) : super(key: key); - - final WonderType type; - final Animation anim; - final double heightFactor; - final String fileName; - final Offset? translation; - final Alignment alignment; - final double? minHeight; - final BoxFit boxFit = BoxFit.cover; - - @override - Widget build(BuildContext context) { - return Align( - alignment: alignment, - child: Transform.translate( - offset: translation ?? Offset.zero, - child: FractionalBoxWithMinSize( - heightFactor: heightFactor, - minHeight: minHeight ?? 0, - child: Image.asset('${type.assetPath}/$fileName', opacity: anim, fit: boxFit), - ), - ), - ); - } -} -// -// class IllustrationPieceStack extends StatelessWidget { -// const IllustrationPieceStack({Key? key, required this.pieces}) : super(key: key); -// final List pieces; -// -// @override -// Widget build(BuildContext context) { -// return Stack( -// children: pieces, -// // [ -// // BottomCenter( -// // child: Transform.translate( -// // offset: Offset(context.widthPx * .05, 0), -// // child: FractionalBoxWithMinSize( -// // heightFactor: backHeightFactor, -// // minHeight: 300, -// // child: Image.asset( -// // '${type.assetPath}/foreground-back.png', -// // opacity: anim, -// // fit: BoxFit.cover, -// // ), -// // ), -// // ), -// // ), -// // BottomCenter( -// // child: Transform.translate( -// // offset: Offset(-context.widthPx * .1, 0), -// // child: FractionalBoxWithMinSize( -// // heightFactor: frontHeightFactor, -// // minHeight: 300, -// // child: Image.asset( -// // '${type.assetPath}/foreground-front.png', -// // opacity: anim, -// // fit: BoxFit.cover, -// // ), -// // ), -// // ), -// // ) -// -// // BottomCenter( -// // child: Transform.scale( -// // scale: 1 + config.zoom * .05, -// // child: FractionallySizedBox( -// // heightFactor: backHeightFactor, -// // child: FractionalTranslation( -// // translation: Offset(.1, 0), -// // child: Image.asset('${type.assetPath}/foreground-back.png', opacity: anim, fit: BoxFit.cover)), -// // ), -// // ), -// // ), -// // BottomCenter( -// // child: Transform.scale( -// // scale: 1 + config.zoom * .2, -// // child: FractionallySizedBox( -// // heightFactor: frontHeightFactor, -// // child: FractionalTranslation( -// // translation: Offset(-.2, 0), -// // child: Image.asset('${type.assetPath}/foreground-front.png', opacity: anim, fit: BoxFit.cover)), -// // ), -// // ), -// // ), -// // ], -// ); -// } -// } - -/// Encapsulates a common behavior where -/// - we want something to be fractionally sized, down to a pt... -/// - when we hit a minSize, stop reducing in size... -/// - until available space is less < minSize, then allow piece to reduce -/// eg, Take a piece with 50% height, and 500px minHeight. As available height is reduced it will attempt to use 50% height, -/// At 200px it it will stop reducing itself in height and ignore the fractional sizing. -/// One the available height is < 200px, the piece will then reduce itself so it still -/// fits on screen without being clipped. -class FractionalBoxWithMinSize extends StatelessWidget { - const FractionalBoxWithMinSize( - {Key? key, this.minWidth, this.minHeight, this.widthFactor, this.heightFactor, required this.child}) - : super(key: key); - - final double? widthFactor; - final double? heightFactor; - final double? minWidth; - final double? minHeight; - final Widget child; - - @override - Widget build(BuildContext context) { - assert((widthFactor == null && minWidth == null) || (widthFactor != null && minWidth != null), - 'widthFactor and minWidth must be provided together'); - assert((heightFactor == null && minHeight == null) || (heightFactor != null && minHeight != null), - 'heightFactor and minWidth must be provided together'); - return LayoutBuilder(builder: (context, constraints) { - var c = child; - if (widthFactor != null) { - double size = max(minWidth!, widthFactor! * constraints.maxWidth); - c = SizedBox(width: size, child: c); - } - if (heightFactor != null) { - double size = max(minHeight!, heightFactor! * constraints.maxHeight); - c = SizedBox(height: size, child: c); - } - return c; - }); - } -} diff --git a/lib/ui/wonder_illustrations/common/illustration_mg.dart b/lib/ui/wonder_illustrations/common/illustration_mg.dart deleted file mode 100644 index 228f887a..00000000 --- a/lib/ui/wonder_illustrations/common/illustration_mg.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:wonders/common_libs.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; - -/// TODO: This can just be an IllustrrationPiece -/// TODO: Add counter scaling to illustration piece like this -/// TODO: Add hero support to [IllustrationPiece]? Or at least use [FractionalBoxWithMinSize] -class IllustrationMg extends StatelessWidget { - const IllustrationMg( - this.imagePath, { - Key? key, - required this.config, - required this.anim, - required this.type, - required this.heightFraction, - this.maxHeight = 700, - }) : super(key: key); - - final String imagePath; - final WonderIllustrationConfig config; - final Animation anim; - final WonderType type; - final double maxHeight; - final double heightFraction; - String get assetPath => type.assetPath; - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.only(top: $styles.insets.sm), - child: LayoutBuilder(builder: (_, constraints) { - final height = min(maxHeight, constraints.maxHeight) * heightFraction; - return Center( - child: WonderHero( - config, - '$imagePath-hero', - child: Transform.scale( - scale: 4 + config.zoom * .5, - child: Image.asset( - '$assetPath/$imagePath', - opacity: anim, - height: height * .25, - fit: BoxFit.cover, - ), - ), - ), - ); - }), - ); - } -} diff --git a/lib/ui/wonder_illustrations/common/illustration_piece.dart b/lib/ui/wonder_illustrations/common/illustration_piece.dart new file mode 100644 index 00000000..efbbab24 --- /dev/null +++ b/lib/ui/wonder_illustrations/common/illustration_piece.dart @@ -0,0 +1,144 @@ +import 'dart:ui' as ui; + +import 'package:wonders/common_libs.dart'; +import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; + +/// Combines [Align], [FractionalBoxWithMinSize], [Image] and [Transform.translate] +/// to standardize behavior across the various wonder illustrations +class IllustrationPiece extends StatefulWidget { + const IllustrationPiece({ + Key? key, + required this.fileName, + required this.heightFactor, + this.alignment = Alignment.center, + this.minHeight, + this.offset = Offset.zero, + this.fractionalOffset, + this.zoomAmt = 0, + this.initialOffset = Offset.zero, + this.boxFit = BoxFit.fitHeight, + this.overflow = true, + this.enableHero = false, + this.initialScale = 1, + this.dynamicHzOffset = 0, + this.top, + this.bottom, + }) : super(key: key); + + final String fileName; + + final Alignment alignment; + + /// Will animate from this position to Offset.zero, eg is value is Offset(0, 100), the piece will slide up vertically 100px as it enters the screen + final Offset initialOffset; + + /// Will animate from this scale to 1, eg if scale is .7, the piece will scale from .7 to 1.0 as it enters the screen. + final double initialScale; + + /// % height, will be overridden by minHeight + final double heightFactor; + + /// min height in pixels, piece will not be allowed to go below this height in px, unless it has to (available height is too small) + final double? minHeight; + + /// px offset for this piece + final Offset offset; + + /// offset based on a fraction of the piece size + final Offset? fractionalOffset; + + /// The % amount that this object should scale up as the user drags their finger up the screen + final double zoomAmt; + + /// Applied to the underlying image in the piece, defaults to [BoxFit.cover] + final BoxFit boxFit; + + /// Whether or not this piece can overflow it's parent on the horizontal bounds + final bool overflow; + + /// Adds a hero tag to this piece, made from wonderType + fileName + final bool enableHero; + + /// Max px offset of the piece as the screen size grows horizontally + final double dynamicHzOffset; + + final Widget Function(BuildContext context)? top; + final Widget Function(BuildContext context)? bottom; + + @override + State createState() => _IllustrationPieceState(); +} + +class _IllustrationPieceState extends State { + double? aspectRatio; + ui.Image? uiImage; + @override + Widget build(BuildContext context) { + final wonderBuilder = context.watch(); + final type = wonderBuilder.widget.wonderType; + final imgPath = '${type.assetPath}/${widget.fileName}'; + if (aspectRatio == null) { + aspectRatio == 0; // indicates load has started, so we don't run twice + rootBundle.load(imgPath).then((img) async { + uiImage = await decodeImageFromList(img.buffer.asUint8List()); + if (!mounted) return; + setState(() => aspectRatio = uiImage!.width / uiImage!.height); + }); + } + return Align( + alignment: widget.alignment, + child: LayoutBuilder( + key: ValueKey(aspectRatio), + builder: (_, constraints) { + final anim = wonderBuilder.anim; + final curvedAnim = Curves.easeOut.transform(anim.value); + final config = wonderBuilder.widget.config; + Widget img = Image.asset(imgPath, opacity: anim, fit: widget.boxFit); + if (widget.overflow) { + img = OverflowBox(maxWidth: 2000, child: img); + } + final double introZoom = (widget.initialScale - 1) * (1 - curvedAnim); + + /// Determine target height + final double height = max(widget.minHeight ?? 0, constraints.maxHeight * widget.heightFactor); + + /// Combine all the translations, initial + offset + dynamicHzOffset + fractionalOffset + Offset finalTranslation = widget.offset; + // Initial + if (widget.initialOffset != Offset.zero) { + finalTranslation += widget.initialOffset * (1 - curvedAnim); + } + // Dynamic + final dynamicOffsetAmt = min(context.widthPx / 1500, 1); + finalTranslation += Offset(dynamicOffsetAmt * widget.dynamicHzOffset, 0); + // Fractional + final width = height * (aspectRatio ?? 0); + if (widget.fractionalOffset != null) { + finalTranslation += Offset( + widget.fractionalOffset!.dx * width, + height * widget.fractionalOffset!.dy, + ); + } + return Stack( + children: [ + if (widget.bottom != null) Positioned.fill(child: widget.bottom!.call(context)), + if (uiImage != null) ...[ + Transform.translate( + offset: finalTranslation, + child: Transform.scale( + scale: 1 + (widget.zoomAmt * config.zoom) + introZoom, + child: SizedBox( + height: height, + width: height * aspectRatio!, + child: !widget.enableHero ? img : Hero(tag: '$type-${widget.fileName}', child: img), + ), + ), + ), + ], + if (widget.top != null) Positioned.fill(child: widget.top!.call(context)), + ], + ); + }), + ); + } +} diff --git a/lib/ui/wonder_illustrations/great_wall_illustration.dart b/lib/ui/wonder_illustrations/great_wall_illustration.dart index 67682ae8..b9d6c78d 100644 --- a/lib/ui/wonder_illustrations/great_wall_illustration.dart +++ b/lib/ui/wonder_illustrations/great_wall_illustration.dart @@ -1,8 +1,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/fade_color_transition.dart'; -import 'package:wonders/ui/wonder_illustrations/common/illustration_mg.dart'; +import 'package:wonders/ui/wonder_illustrations/common/illustration_piece.dart'; import 'package:wonders/ui/wonder_illustrations/common/paint_textures.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; @@ -20,6 +19,7 @@ class GreatWallIllustration extends StatelessWidget { bgBuilder: _buildBg, mgBuilder: _buildMg, fgBuilder: _buildFg, + wonderType: WonderType.greatWall, ); } @@ -34,104 +34,51 @@ class GreatWallIllustration extends StatelessWidget { opacity: anim.drive(Tween(begin: 0, end: .5)), ), ), - Align( - alignment: config.shortMode ? Alignment(-.5, -.5) : Alignment(-.45, -.63), - child: FractionalTranslation( - translation: Offset(0, -.5 * anim.value), - child: WonderHero( - config, - 'great-wall-sun', - child: Image.asset( - '$assetPath/sun.png', - cacheWidth: context.widthPx.round() * 2, - width: config.shortMode ? 100 : 150, - opacity: anim, - ), - ), - ), + IllustrationPiece( + fileName: 'sun.png', + initialOffset: Offset(0, 20), + enableHero: true, + heightFactor: .15, + minHeight: 150, + offset: config.shortMode ? Offset(-70, context.heightPx * -.05) : Offset(-150, context.heightPx * -.25), ), ]; } List _buildMg(BuildContext context, Animation anim) { return [ - IllustrationMg( - 'great-wall.png', - type: WonderType.greatWall, - anim: anim, - config: config, - maxHeight: 800, - heightFraction: .85, + IllustrationPiece( + fileName: 'great-wall.png', + heightFactor: .55, + minHeight: 600, + zoomAmt: .05, + enableHero: true, ), ]; - return [ - Center( - child: FractionalTranslation( - translation: Offset(0, config.shortMode ? .1 * anim.value : 0), - child: FractionallySizedBox( - widthFactor: config.shortMode ? null : 1.3, - child: WonderHero( - config, - 'great-wall-mg', - child: Image.asset( - '$assetPath/great-wall.png', - opacity: anim, - width: config.shortMode ? 300 : 500, - ), - ), - ), - ), - - // child: FractionalTranslation( - // translation: Offset(0, 0), - // child: Transform.scale( - // scale: 1, //config.shortMode ? .95 : 1.4 + config.zoom * .2, - // child: WonderHero( - // config, - // 'great-wall-mg', - // child: Image.asset( - // '$assetPath/great-wall.png', - // opacity: anim, - // width: 700, - // ), - // ), - // ), - // ), - ) - ]; } List _buildFg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); return [ - Stack(children: [ - BottomRight( - child: FractionalTranslation( - translation: Offset(.2 * (1 - curvedAnim), 0), - child: Transform.scale( - scale: 1.5 + config.zoom * .1, - child: FractionalTranslation( - translation: Offset(.46, -.22), - child: Image.asset('$assetPath/foreground-right.png', - opacity: anim, cacheWidth: context.widthPx.round() * 3), - ), - ), - ), - ), - BottomLeft( - child: FractionalTranslation( - translation: Offset(-.2 * (1 - curvedAnim), 0), - child: Transform.scale( - scale: 1 + config.zoom * .3, - child: FractionalTranslation( - translation: Offset(-.3, -.01), - child: Image.asset('$assetPath/foreground-left.png', - opacity: anim, cacheWidth: context.widthPx.round() * 3), - ), - ), - ), - ), - ]) + IllustrationPiece( + fileName: 'foreground-left.png', + alignment: Alignment.bottomCenter, + initialScale: .9, + initialOffset: Offset(-40, 60), + heightFactor: .75, + fractionalOffset: Offset(-.4, .45), + zoomAmt: .25, + dynamicHzOffset: -150, + ), + IllustrationPiece( + fileName: 'foreground-right.png', + alignment: Alignment.bottomCenter, + initialOffset: Offset(20, 40), + initialScale: .95, + heightFactor: .85, + fractionalOffset: Offset(.4, .25), + zoomAmt: .1, + dynamicHzOffset: 150, + ), ]; } } diff --git a/lib/ui/wonder_illustrations/machu_picchu_illustration.dart b/lib/ui/wonder_illustrations/machu_picchu_illustration.dart index 63276740..7c7bc009 100644 --- a/lib/ui/wonder_illustrations/machu_picchu_illustration.dart +++ b/lib/ui/wonder_illustrations/machu_picchu_illustration.dart @@ -1,7 +1,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/fade_color_transition.dart'; +import 'package:wonders/ui/wonder_illustrations/common/illustration_piece.dart'; import 'package:wonders/ui/wonder_illustrations/common/paint_textures.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; @@ -19,6 +19,7 @@ class MachuPicchuIllustration extends StatelessWidget { bgBuilder: _buildBg, mgBuilder: _buildMg, fgBuilder: _buildFg, + wonderType: WonderType.machuPicchu, ); } @@ -33,80 +34,49 @@ class MachuPicchuIllustration extends StatelessWidget { opacity: anim.drive(Tween(begin: 0, end: .7)), ), ), - Align( - alignment: config.shortMode ? Alignment.center : Alignment(.75, -.6), - child: FractionalTranslation( - translation: Offset(0, -.5 * anim.value), - child: Transform.scale( - scale: config.shortMode ? .75 : 1, - child: WonderHero( - config, - 'machu-sun', - child: Image.asset( - '$assetPath/sun.png', - cacheWidth: context.widthPx.round() * 2, - opacity: anim, - ), - ), - ), - ), + IllustrationPiece( + fileName: 'sun.png', + initialOffset: Offset(0, 20), + enableHero: true, + heightFactor: .15, + minHeight: 150, + offset: config.shortMode ? Offset(-70, context.heightPx * -.05) : Offset(-150, context.heightPx * -.25), ), ]; } List _buildMg(BuildContext context, Animation anim) => [ - Center( - child: Transform.scale( - scale: config.shortMode ? 1.2 : 2.5 + config.zoom * .2, - alignment: Alignment(config.shortMode ? 0 : .15, config.shortMode ? -0.6 : .3), - child: WonderHero( - config, - 'machu-mg', - child: Image.asset( - '$assetPath/machu-picchu.png', - fit: BoxFit.contain, - opacity: anim, - ), - ), - ), - ) + IllustrationPiece( + fileName: 'machu-picchu.png', + heightFactor: .65, + minHeight: 500, + zoomAmt: .05, + enableHero: true, + ), ]; List _buildFg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); return [ - Transform.translate( - offset: Offset(0, 20 * (1 - curvedAnim)), - child: Stack(children: [ - BottomRight( - child: Transform.scale( - scale: 1 + config.zoom * .05, - child: FractionallySizedBox( - widthFactor: 1.5, - child: FractionalTranslation( - translation: Offset(0, .1), - child: Image.asset('$assetPath/foreground-back.png', opacity: anim), - ), - ), - ), - ), - BottomLeft( - child: FractionalTranslation( - translation: Offset(-.2 * (1 - curvedAnim), 0), - child: Transform.scale( - scale: 1 + config.zoom * .25, - child: FractionallySizedBox( - widthFactor: 1.5, - child: FractionalTranslation( - translation: Offset(-.3, .4), - child: Image.asset('$assetPath/foreground-front.png', opacity: anim), - ), - ), - ), - ), - ), - ]), - ) + IllustrationPiece( + fileName: 'foreground-back.png', + alignment: Alignment.bottomCenter, + initialScale: .9, + initialOffset: Offset(0, 60), + heightFactor: .6, + fractionalOffset: Offset(0, .3), + zoomAmt: .1, + dynamicHzOffset: 150, + ), + IllustrationPiece( + fileName: 'foreground-front.png', + alignment: Alignment.bottomCenter, + initialOffset: Offset(20, 40), + heightFactor: .5, + initialScale: .95, + fractionalOffset: Offset(-.25, .25), + zoomAmt: .12, + dynamicHzOffset: -50, + ), ]; } } diff --git a/lib/ui/wonder_illustrations/petra_illustration.dart b/lib/ui/wonder_illustrations/petra_illustration.dart index c885909f..d353e1d5 100644 --- a/lib/ui/wonder_illustrations/petra_illustration.dart +++ b/lib/ui/wonder_illustrations/petra_illustration.dart @@ -1,7 +1,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/fade_color_transition.dart'; +import 'package:wonders/ui/wonder_illustrations/common/illustration_piece.dart'; import 'package:wonders/ui/wonder_illustrations/common/paint_textures.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; @@ -19,6 +19,7 @@ class PetraIllustration extends StatelessWidget { bgBuilder: _buildBg, mgBuilder: _buildMg, fgBuilder: _buildFg, + wonderType: WonderType.petra, ); } @@ -33,74 +34,69 @@ class PetraIllustration extends StatelessWidget { opacity: anim.drive(Tween(begin: 0, end: .25)), ), ), - Align( - alignment: Alignment(-.3, config.shortMode ? -1.5 : -1.23), - child: FractionalTranslation( - translation: Offset(0, .5 * anim.value), - child: WonderHero( - config, - 'petra-moon', - child: Image.asset( - '$assetPath/moon.png', - opacity: anim, - ), - ), - ), + IllustrationPiece( + fileName: 'moon.png', + heightFactor: .15, + minHeight: 100, + alignment: Alignment.topCenter, + fractionalOffset: Offset(-.7, 0), ), ]; } List _buildMg(BuildContext context, Animation anim) => [ - Center( - child: FractionalTranslation( - translation: Offset(0, config.shortMode ? 0.05 : -.1), - child: FractionallySizedBox( - widthFactor: config.shortMode ? 1 : 2, - child: WonderHero( - config, - 'petra-mg', - child: Image.asset('$assetPath/petra.png', fit: BoxFit.contain, opacity: anim), - ), - ), + FractionallySizedBox( + heightFactor: config.shortMode ? 1 : .8, + alignment: Alignment.bottomCenter, + child: IllustrationPiece( + fileName: 'petra.png', + heightFactor: .65, + minHeight: 500, + zoomAmt: .1, + enableHero: true, ), ), ]; List _buildFg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); return [ - Stack(children: [ - CenterLeft( - child: FractionallySizedBox( - widthFactor: .63, - child: FractionalTranslation( - translation: Offset(-.3 * (1 - curvedAnim), 0), - child: Transform.scale( - scale: 1.1 + config.zoom * .2, - child: FractionalTranslation( - translation: Offset(-.35, -.07), - child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.contain), - ), - ), - ), - ), - ), - CenterRight( - child: FractionallySizedBox( - widthFactor: .72, - child: FractionalTranslation( - translation: Offset(.3 * (1 - curvedAnim), 0), - child: Transform.scale( - scale: 1 + config.zoom * .4, - child: FractionalTranslation( - translation: Offset(.4, -.03), - child: Image.asset('$assetPath/foreground-right.png', opacity: anim, fit: BoxFit.contain), - ), - ), - ), - ), - ), - ]) + IllustrationPiece( + fileName: 'foreground-left.png', + alignment: Alignment.bottomCenter, + initialOffset: Offset(-80, 0), + heightFactor: 1, + fractionalOffset: Offset(-.5, 0), + zoomAmt: .1, + dynamicHzOffset: -130, + bottom: (_) { + /// To cover everything behind this piece with a solid color, we scale up a container + /// and then offset it in negative space + const double scaleX = 5; + return FractionalTranslation( + translation: Offset(-1 - scaleX / 2, 0), + child: + Transform.scale(scaleX: 5, child: Container(color: WonderType.petra.fgColor.withOpacity(anim.value))), + ); + }, + ), + IllustrationPiece( + fileName: 'foreground-right.png', + alignment: Alignment.bottomCenter, + initialOffset: Offset(80, 00), + heightFactor: 1, + fractionalOffset: Offset(.5, 0), + zoomAmt: .15, + dynamicHzOffset: 130, + bottom: (_) { + /// To cover everything behind this piece with a solid color, we scale up a container and then offset it in negative space + const double scaleX = 5; + return FractionalTranslation( + translation: Offset(1 + scaleX / 2, 0), + child: + Transform.scale(scaleX: 5, child: Container(color: WonderType.petra.fgColor.withOpacity(anim.value))), + ); + }, + ), ]; } } diff --git a/lib/ui/wonder_illustrations/pyramids_giza_illustration.dart b/lib/ui/wonder_illustrations/pyramids_giza_illustration.dart index df45c084..e1e097e0 100644 --- a/lib/ui/wonder_illustrations/pyramids_giza_illustration.dart +++ b/lib/ui/wonder_illustrations/pyramids_giza_illustration.dart @@ -1,9 +1,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/fade_color_transition.dart'; -import 'package:wonders/ui/wonder_illustrations/common/illustration_fg.dart'; -import 'package:wonders/ui/wonder_illustrations/common/illustration_mg.dart'; +import 'package:wonders/ui/wonder_illustrations/common/illustration_piece.dart'; import 'package:wonders/ui/wonder_illustrations/common/paint_textures.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; @@ -17,6 +15,7 @@ class PyramidsGizaIllustration extends StatelessWidget { @override Widget build(BuildContext context) { return WonderIllustrationBuilder( + wonderType: WonderType.pyramidsGiza, config: config, bgBuilder: _buildBg, mgBuilder: _buildMg, @@ -35,93 +34,54 @@ class PyramidsGizaIllustration extends StatelessWidget { flipY: true, ), ), - Align( - alignment: Alignment(.75, config.shortMode ? -.2 : -.5), - child: FractionalTranslation( - translation: Offset(0, -.5 * anim.value), - child: WonderHero( - config, - 'pyramids-moon', - child: Transform.scale( - scale: config.shortMode ? 0.8 : 1.2, - child: Image.asset('$assetPath/moon.png', opacity: anim), - ), - ), - )), + IllustrationPiece( + fileName: 'moon.png', + initialOffset: Offset(0, 20), + enableHero: true, + heightFactor: .15, + minHeight: 100, + offset: config.shortMode ? Offset(100, context.heightPx * -.1) : Offset(150, context.heightPx * -.15), + zoomAmt: .05, + ), ]; } List _buildMg(BuildContext context, Animation anim) { return [ - IllustrationMg( - 'pyramids.png', - type: WonderType.pyramidsGiza, - anim: anim, - config: config, - maxHeight: 600, - heightFraction: .85, - ), - - // Align( - // alignment: Alignment(0, config.shortMode ? 0.9 : 0), - // child: WonderHero(config, 'pyramids-mg', - // child: Transform.scale( - // scale: 1 + config.zoom * .1, - // child: FractionallySizedBox( - // widthFactor: config.shortMode ? 1 : 1.94, - // child: Image.asset('$assetPath/pyramids.png', fit: BoxFit.contain, opacity: anim), - // ), - // )), - // ), + IllustrationPiece( + fileName: 'pyramids.png', + enableHero: true, + heightFactor: .5, + minHeight: 300, + zoomAmt: .1, + boxFit: BoxFit.contain, + overflow: !config.shortMode, + ) ]; } List _buildFg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); return [ IllustrationPiece( - type: WonderType.pyramidsGiza, - anim: anim, fileName: 'foreground-back.png', - heightFactor: .5, alignment: Alignment.bottomCenter, + initialOffset: Offset(20, 40), + initialScale: .95, + heightFactor: .55, + fractionalOffset: Offset(.1, .06), + zoomAmt: .1, + dynamicHzOffset: 150, ), IllustrationPiece( - type: WonderType.pyramidsGiza, - anim: anim, fileName: 'foreground-front.png', - heightFactor: .5, alignment: Alignment.bottomCenter, - ) - // Transform.scale( - // scale: 1 + config.zoom * .2, - // child: Transform.translate( - // offset: Offset(0, 10 * (1 - curvedAnim)), - // child: BottomCenter( - // child: FractionallySizedBox( - // widthFactor: 1.2, - // child: FractionalTranslation( - // translation: Offset(0, -1.2), - // child: Image.asset('$assetPath/foreground-back.png', opacity: anim, fit: BoxFit.cover)), - // ), - // ), - // ), - // ), - // Transform.scale( - // scale: 1 + config.zoom * .4, - // child: Transform.translate( - // offset: Offset(0, 30 * (1 - curvedAnim)), - // child: BottomCenter( - // child: FractionallySizedBox( - // widthFactor: 1.52, - // child: FractionalTranslation( - // translation: Offset(0, 0.1), - // child: Image.asset('$assetPath/foreground-front.png', opacity: anim, fit: BoxFit.cover), - // ), - // ), - // ), - // ), - // ), + initialScale: .9, + initialOffset: Offset(-40, 60), + heightFactor: .55, + fractionalOffset: Offset(-.1, .1), + zoomAmt: .25, + dynamicHzOffset: -150, + ), ]; } } diff --git a/lib/ui/wonder_illustrations/taj_mahal_illustration.dart b/lib/ui/wonder_illustrations/taj_mahal_illustration.dart index fbe9dbbb..56e23ede 100644 --- a/lib/ui/wonder_illustrations/taj_mahal_illustration.dart +++ b/lib/ui/wonder_illustrations/taj_mahal_illustration.dart @@ -1,7 +1,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/fade_color_transition.dart'; +import 'package:wonders/ui/wonder_illustrations/common/illustration_piece.dart'; import 'package:wonders/ui/wonder_illustrations/common/paint_textures.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_builder.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; @@ -20,11 +20,11 @@ class TajMahalIllustration extends StatelessWidget { bgBuilder: _buildBg, mgBuilder: _buildMg, fgBuilder: _buildFg, + wonderType: WonderType.tajMahal, ); } List _buildBg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); return [ // Bg color FadeColorTransition(color: fgColor, animation: anim), @@ -38,78 +38,69 @@ class TajMahalIllustration extends StatelessWidget { ), ), // Sun - Align( - alignment: config.shortMode ? Alignment(-1.25, -2.8) : Alignment(-1.25, -1.15), - child: FractionalTranslation( - translation: Offset(-.2 + curvedAnim * .2, .4 - curvedAnim * .2), - child: WonderHero(config, 'taj-sun', child: Image.asset('$assetPath/sun.png', opacity: anim)), - ), - ) + IllustrationPiece( + fileName: 'sun.png', + initialOffset: Offset(0, 20), + enableHero: true, + heightFactor: .15, + minHeight: 140, + offset: config.shortMode ? Offset(-100, context.heightPx * -.05) : Offset(-150, context.heightPx * -.15), + ), ]; } List _buildMg(BuildContext context, Animation anim) { return [ - Transform.scale( - scale: 1 + config.zoom * .1, - child: Align( - alignment: Alignment(0, config.shortMode ? 1 : -.15), - child: FractionallySizedBox( - widthFactor: config.shortMode ? 1 : 1.7, - child: Stack( - children: [ - WonderHero( - config, - 'taj-mg', - child: Image.asset('$assetPath/taj-mahal.png', opacity: anim, fit: BoxFit.cover), - ), - if (!config.shortMode) - FractionalTranslation( - translation: Offset(0, 1.33), - child: Image.asset('$assetPath/pool.png', opacity: anim, fit: BoxFit.cover), - ), - ], + LayoutBuilder(builder: (_, constraints) { + const double minHeight = 500, heightFactor = .6, poolScale = 1; + return Stack( + children: [ + IllustrationPiece( + fileName: 'taj-mahal.png', + heightFactor: heightFactor, + minHeight: minHeight, + zoomAmt: .05, + top: config.shortMode + ? null + : (_) => FractionalTranslation( + translation: Offset(0, .85), + child: IllustrationPiece( + fileName: 'pool.png', + heightFactor: heightFactor * poolScale, + minHeight: minHeight * poolScale, + zoomAmt: .05, + ), + ), ), - ), - ), - ) + ], + ); + }), ]; } List _buildFg(BuildContext context, Animation anim) { - final curvedAnim = Curves.easeOut.transform(anim.value); + /// Let the mangos scale up as the width of the screen grows + final mangoScale = max(context.widthPx - 400, 0) / 1000; return [ - Transform.scale( - scale: 1 + config.zoom * .2, - child: Stack( - children: [ - FractionalTranslation( - translation: Offset(-.2 * (1 - curvedAnim), 0), - child: BottomLeft( - child: FractionallySizedBox( - heightFactor: .6, - child: FractionalTranslation( - translation: Offset(-.4, -.04), - child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - FractionalTranslation( - translation: Offset(.2 * (1 - curvedAnim), 0), - child: BottomRight( - child: FractionallySizedBox( - heightFactor: .6, - child: FractionalTranslation( - translation: Offset(.4, -.04), - child: Image.asset('$assetPath/foreground-right.png', opacity: anim, fit: BoxFit.cover), - ), - ), - ), - ), - ], - ), - ) + IllustrationPiece( + fileName: 'foreground-right.png', + alignment: Alignment.bottomRight, + initialOffset: Offset(20, 40), + initialScale: .85, + heightFactor: .5 + .3 * mangoScale, + fractionalOffset: Offset(.3, 0), + zoomAmt: .1, + ), + IllustrationPiece( + fileName: 'foreground-left.png', + alignment: Alignment.bottomLeft, + initialScale: .9, + initialOffset: Offset(-40, 60), + heightFactor: .5 + .3 * mangoScale, + fractionalOffset: Offset(-.2, 0), + zoomAmt: .25, + dynamicHzOffset: 0, + ), ]; } }