Finish first pass of home illustrations

This commit is contained in:
Shawn 2022-11-28 17:54:11 -07:00
parent 859f76d8ce
commit be4e3c1ea4
13 changed files with 548 additions and 797 deletions

View File

@ -21,7 +21,7 @@ class AppScrollBehavior extends ScrollBehavior {
// TODO: Finalize scrollbar strategy (Do we use them at all? Where specifically?) // TODO: Finalize scrollbar strategy (Do we use them at all? Where specifically?)
@override @override
Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) { Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
return child; //return child;
return PlatformInfo.isAndroid return PlatformInfo.isAndroid
? RawScrollbar(controller: details.controller, child: child) ? RawScrollbar(controller: details.controller, child: child)
: CupertinoScrollbar(controller: details.controller, child: child); : CupertinoScrollbar(controller: details.controller, child: child);

View File

@ -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;
}
}

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/fade_color_transition.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/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_builder.dart';
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.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; final fgColor = WonderType.chichenItza.fgColor;
@override @override
Widget build(BuildContext context) { 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<Widget> _buildBg(BuildContext context, Animation<double> anim) { List<Widget> _buildBg(BuildContext context, Animation<double> anim) {
@ -26,21 +32,13 @@ class ChichenItzaIllustration extends StatelessWidget {
flipY: true, flipY: true,
), ),
), ),
Align( IllustrationPiece(
alignment: Alignment(config.shortMode ? .25 : .5, config.shortMode ? 1 : -.15), fileName: 'sun.png',
child: WonderHero( initialOffset: Offset(0, 20),
config, enableHero: true,
'chichen-sun', heightFactor: .25,
child: FractionalTranslation( minHeight: 200,
translation: Offset(0, -.2 * anim.value), fractionalOffset: Offset(1, config.shortMode ? 0 : -.5),
child: Image.asset(
'$assetPath/sun.png',
width: config.shortMode ? 100 : 200,
cacheWidth: context.widthPx.round() * 2,
opacity: anim,
),
),
),
), ),
]; ];
} }
@ -48,90 +46,126 @@ class ChichenItzaIllustration extends StatelessWidget {
List<Widget> _buildMg(BuildContext context, Animation<double> anim) { List<Widget> _buildMg(BuildContext context, Animation<double> anim) {
// We want to size to the shortest side // We want to size to the shortest side
return [ return [
Align( Transform.translate(
alignment: Alignment(0, config.shortMode ? 1 : 0), offset: Offset(0, 20),
child: Transform.translate( child: IllustrationPiece(
offset: Offset(0, config.shortMode ? 30 : 0), fileName: 'chichen.png',
child: FractionallySizedBox( heightFactor: .55,
heightFactor: config.shortMode ? 1 : .6, minHeight: 400,
child: WonderHero( zoomAmt: .05,
config, enableHero: true,
'chichen-mg',
child: Image.asset('$assetPath/chichen.png', opacity: anim, fit: BoxFit.cover),
),
),
), ),
), ),
]; ];
} }
List<Widget> _buildFg(BuildContext context, Animation<double> anim) { List<Widget> _buildFg(BuildContext context, Animation<double> anim) {
final curvedAnim = Curves.easeOut.transform(anim.value);
return [ return [
Stack( IllustrationPiece(
children: [ fileName: 'foreground-left.png',
Transform.scale( alignment: Alignment.bottomCenter,
scale: 1 + config.zoom * .05, initialScale: .9,
child: FractionalTranslation( initialOffset: Offset(-40, 60),
translation: Offset(-.2 * (1 - curvedAnim), 0), heightFactor: .65,
child: BottomLeft( fractionalOffset: Offset(-.4, .2),
child: SizedBox( zoomAmt: .25,
height: 500, dynamicHzOffset: -250,
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-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),
// ),
// ),
// ),
// ),
// ),
// ],
// ),
]; ];
} }
} }

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/fade_color_transition.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/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_builder.dart';
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart';
@ -18,6 +18,7 @@ class ChristRedeemerIllustration extends StatelessWidget {
bgBuilder: _buildBg, bgBuilder: _buildBg,
mgBuilder: _buildMg, mgBuilder: _buildMg,
fgBuilder: _buildFg, fgBuilder: _buildFg,
wonderType: WonderType.christRedeemer,
); );
} }
@ -32,90 +33,52 @@ class ChristRedeemerIllustration extends StatelessWidget {
opacity: anim.drive(Tween(begin: 0, end: .4)), opacity: anim.drive(Tween(begin: 0, end: .4)),
), ),
), ),
Align( IllustrationPiece(
alignment: config.shortMode ? Alignment(.5, -1.5) : Alignment(.5, -.75), fileName: 'sun.png',
child: FractionalTranslation( initialOffset: Offset(0, 20),
translation: Offset(0, .5 * anim.value), enableHero: true,
child: WonderHero( heightFactor: .2,
config, minHeight: 120,
'christ-sun', fractionalOffset: Offset(.5, -1),
child: Transform.scale(
scale: config.shortMode ? 1.4 : 1.6,
child: Image.asset(
'$assetPath/sun.png',
cacheWidth: context.widthPx.round() * 2,
opacity: anim,
),
),
),
),
), ),
]; ];
} }
List<Widget> _buildMg(BuildContext context, Animation<double> anim) { List<Widget> _buildMg(BuildContext context, Animation<double> anim) {
return [ return [
ClipRect( IllustrationPiece(
child: Transform.scale( fileName: 'redeemer.png',
scale: 1 + config.zoom * .2, enableHero: true,
child: FractionalTranslation( heightFactor: 1,
translation: Offset(0, config.shortMode ? .5 : .2), alignment: Alignment.bottomCenter,
child: BottomCenter( fractionalOffset: Offset(0, .1),
child: FractionallySizedBox( zoomAmt: .1,
heightFactor: config.shortMode ? 1.5 : 1.2,
child: WonderHero(
config,
'christ-mg',
child: Image.asset(
'$assetPath/redeemer.png',
opacity: anim,
fit: BoxFit.cover,
),
),
),
),
),
),
) )
//
]; ];
} }
List<Widget> _buildFg(BuildContext context, Animation<double> anim) { List<Widget> _buildFg(BuildContext context, Animation<double> anim) {
final curvedAnim = Curves.easeOut.transform(anim.value);
return [ return [
Stack( IllustrationPiece(
children: [ fileName: 'foreground-left.png',
Transform.scale( alignment: Alignment.bottomCenter,
scale: 1 + config.zoom * .15, initialScale: .9,
child: FractionalTranslation( initialOffset: Offset(-40, 60),
translation: Offset(-.2 * (1 - curvedAnim), 0), heightFactor: .55,
child: BottomLeft( fractionalOffset: Offset(-.25, 0),
child: FractionallySizedBox( zoomAmt: .25,
widthFactor: 1.5, dynamicHzOffset: -100,
child: FractionalTranslation( ),
translation: Offset(-.25, .03), IllustrationPiece(
child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.cover), fileName: 'foreground-right.png',
), alignment: Alignment.bottomCenter,
), initialOffset: Offset(20, 40),
), initialScale: .95,
), heightFactor: .65,
), fractionalOffset: Offset(.2, 0),
Transform.scale( zoomAmt: .1,
scale: 1 + config.zoom * .3, dynamicHzOffset: 100,
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),
),
),
),
),
),
],
), ),
]; ];
} }

View File

@ -1,5 +1,6 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/fade_color_transition.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/paint_textures.dart';
import 'package:wonders/ui/wonder_illustrations/common/wonder_hero.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_builder.dart';
@ -18,6 +19,7 @@ class ColosseumIllustration extends StatelessWidget {
bgBuilder: _buildBg, bgBuilder: _buildBg,
mgBuilder: _buildMg, mgBuilder: _buildMg,
fgBuilder: _buildFg, fgBuilder: _buildFg,
wonderType: WonderType.colosseum,
); );
} }
@ -55,25 +57,12 @@ class ColosseumIllustration extends StatelessWidget {
List<Widget> _buildMg(BuildContext context, Animation<double> anim) { List<Widget> _buildMg(BuildContext context, Animation<double> anim) {
return [ return [
Stack( Stack(
children: [ children: const [
if (config.shortMode) ...[ IllustrationPiece(
FractionalTranslation( fileName: 'colosseum.png',
translation: Offset(0, .9), heightFactor: .55,
child: Container(color: bgColor), minHeight: 400,
) zoomAmt: .15,
],
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),
),
),
),
), ),
], ],
) )
@ -81,40 +70,27 @@ class ColosseumIllustration extends StatelessWidget {
} }
List<Widget> _buildFg(BuildContext context, Animation<double> anim) { List<Widget> _buildFg(BuildContext context, Animation<double> anim) {
final curvedAnim = Curves.easeOut.transform(anim.value);
return [ return [
Stack(children: [ IllustrationPiece(
BottomLeft( fileName: 'foreground-left.png',
child: FractionallySizedBox( alignment: Alignment.bottomCenter,
heightFactor: .56, initialScale: .9,
child: FractionalTranslation( initialOffset: Offset(-40, 60),
translation: Offset(-.2 * (1 - curvedAnim), 0), heightFactor: .65,
child: Transform.scale( fractionalOffset: Offset(-.5, .1),
scale: 1 + config.zoom * .3, zoomAmt: .25,
child: FractionalTranslation( dynamicHzOffset: -150,
translation: Offset(-.1, .1), ),
child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.cover), IllustrationPiece(
), fileName: 'foreground-right.png',
), alignment: Alignment.bottomCenter,
), initialOffset: Offset(20, 40),
), initialScale: .95,
), heightFactor: .75,
BottomRight( fractionalOffset: Offset(.5, .25),
child: FractionallySizedBox( zoomAmt: .1,
heightFactor: .56, dynamicHzOffset: 150,
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),
),
),
),
),
),
])
]; ];
} }
} }

View File

@ -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<double> 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<IllustrationPiece> 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;
});
}
}

View File

@ -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<double> 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,
),
),
),
);
}),
);
}
}

View File

@ -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<IllustrationPiece> createState() => _IllustrationPieceState();
}
class _IllustrationPieceState extends State<IllustrationPiece> {
double? aspectRatio;
ui.Image? uiImage;
@override
Widget build(BuildContext context) {
final wonderBuilder = context.watch<WonderIllustrationBuilderState>();
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)),
],
);
}),
);
}
}

View File

@ -1,8 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/fade_color_transition.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/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_builder.dart';
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart';
@ -20,6 +19,7 @@ class GreatWallIllustration extends StatelessWidget {
bgBuilder: _buildBg, bgBuilder: _buildBg,
mgBuilder: _buildMg, mgBuilder: _buildMg,
fgBuilder: _buildFg, fgBuilder: _buildFg,
wonderType: WonderType.greatWall,
); );
} }
@ -34,104 +34,51 @@ class GreatWallIllustration extends StatelessWidget {
opacity: anim.drive(Tween(begin: 0, end: .5)), opacity: anim.drive(Tween(begin: 0, end: .5)),
), ),
), ),
Align( IllustrationPiece(
alignment: config.shortMode ? Alignment(-.5, -.5) : Alignment(-.45, -.63), fileName: 'sun.png',
child: FractionalTranslation( initialOffset: Offset(0, 20),
translation: Offset(0, -.5 * anim.value), enableHero: true,
child: WonderHero( heightFactor: .15,
config, minHeight: 150,
'great-wall-sun', offset: config.shortMode ? Offset(-70, context.heightPx * -.05) : Offset(-150, context.heightPx * -.25),
child: Image.asset(
'$assetPath/sun.png',
cacheWidth: context.widthPx.round() * 2,
width: config.shortMode ? 100 : 150,
opacity: anim,
),
),
),
), ),
]; ];
} }
List<Widget> _buildMg(BuildContext context, Animation<double> anim) { List<Widget> _buildMg(BuildContext context, Animation<double> anim) {
return [ return [
IllustrationMg( IllustrationPiece(
'great-wall.png', fileName: 'great-wall.png',
type: WonderType.greatWall, heightFactor: .55,
anim: anim, minHeight: 600,
config: config, zoomAmt: .05,
maxHeight: 800, enableHero: true,
heightFraction: .85,
), ),
]; ];
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<Widget> _buildFg(BuildContext context, Animation<double> anim) { List<Widget> _buildFg(BuildContext context, Animation<double> anim) {
final curvedAnim = Curves.easeOut.transform(anim.value);
return [ return [
Stack(children: [ IllustrationPiece(
BottomRight( fileName: 'foreground-left.png',
child: FractionalTranslation( alignment: Alignment.bottomCenter,
translation: Offset(.2 * (1 - curvedAnim), 0), initialScale: .9,
child: Transform.scale( initialOffset: Offset(-40, 60),
scale: 1.5 + config.zoom * .1, heightFactor: .75,
child: FractionalTranslation( fractionalOffset: Offset(-.4, .45),
translation: Offset(.46, -.22), zoomAmt: .25,
child: Image.asset('$assetPath/foreground-right.png', dynamicHzOffset: -150,
opacity: anim, cacheWidth: context.widthPx.round() * 3), ),
), IllustrationPiece(
), fileName: 'foreground-right.png',
), alignment: Alignment.bottomCenter,
), initialOffset: Offset(20, 40),
BottomLeft( initialScale: .95,
child: FractionalTranslation( heightFactor: .85,
translation: Offset(-.2 * (1 - curvedAnim), 0), fractionalOffset: Offset(.4, .25),
child: Transform.scale( zoomAmt: .1,
scale: 1 + config.zoom * .3, dynamicHzOffset: 150,
child: FractionalTranslation( ),
translation: Offset(-.3, -.01),
child: Image.asset('$assetPath/foreground-left.png',
opacity: anim, cacheWidth: context.widthPx.round() * 3),
),
),
),
),
])
]; ];
} }
} }

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/fade_color_transition.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/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_builder.dart';
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart';
@ -19,6 +19,7 @@ class MachuPicchuIllustration extends StatelessWidget {
bgBuilder: _buildBg, bgBuilder: _buildBg,
mgBuilder: _buildMg, mgBuilder: _buildMg,
fgBuilder: _buildFg, fgBuilder: _buildFg,
wonderType: WonderType.machuPicchu,
); );
} }
@ -33,80 +34,49 @@ class MachuPicchuIllustration extends StatelessWidget {
opacity: anim.drive(Tween(begin: 0, end: .7)), opacity: anim.drive(Tween(begin: 0, end: .7)),
), ),
), ),
Align( IllustrationPiece(
alignment: config.shortMode ? Alignment.center : Alignment(.75, -.6), fileName: 'sun.png',
child: FractionalTranslation( initialOffset: Offset(0, 20),
translation: Offset(0, -.5 * anim.value), enableHero: true,
child: Transform.scale( heightFactor: .15,
scale: config.shortMode ? .75 : 1, minHeight: 150,
child: WonderHero( offset: config.shortMode ? Offset(-70, context.heightPx * -.05) : Offset(-150, context.heightPx * -.25),
config,
'machu-sun',
child: Image.asset(
'$assetPath/sun.png',
cacheWidth: context.widthPx.round() * 2,
opacity: anim,
),
),
),
),
), ),
]; ];
} }
List<Widget> _buildMg(BuildContext context, Animation<double> anim) => [ List<Widget> _buildMg(BuildContext context, Animation<double> anim) => [
Center( IllustrationPiece(
child: Transform.scale( fileName: 'machu-picchu.png',
scale: config.shortMode ? 1.2 : 2.5 + config.zoom * .2, heightFactor: .65,
alignment: Alignment(config.shortMode ? 0 : .15, config.shortMode ? -0.6 : .3), minHeight: 500,
child: WonderHero( zoomAmt: .05,
config, enableHero: true,
'machu-mg', ),
child: Image.asset(
'$assetPath/machu-picchu.png',
fit: BoxFit.contain,
opacity: anim,
),
),
),
)
]; ];
List<Widget> _buildFg(BuildContext context, Animation<double> anim) { List<Widget> _buildFg(BuildContext context, Animation<double> anim) {
final curvedAnim = Curves.easeOut.transform(anim.value);
return [ return [
Transform.translate( IllustrationPiece(
offset: Offset(0, 20 * (1 - curvedAnim)), fileName: 'foreground-back.png',
child: Stack(children: [ alignment: Alignment.bottomCenter,
BottomRight( initialScale: .9,
child: Transform.scale( initialOffset: Offset(0, 60),
scale: 1 + config.zoom * .05, heightFactor: .6,
child: FractionallySizedBox( fractionalOffset: Offset(0, .3),
widthFactor: 1.5, zoomAmt: .1,
child: FractionalTranslation( dynamicHzOffset: 150,
translation: Offset(0, .1), ),
child: Image.asset('$assetPath/foreground-back.png', opacity: anim), IllustrationPiece(
), fileName: 'foreground-front.png',
), alignment: Alignment.bottomCenter,
), initialOffset: Offset(20, 40),
), heightFactor: .5,
BottomLeft( initialScale: .95,
child: FractionalTranslation( fractionalOffset: Offset(-.25, .25),
translation: Offset(-.2 * (1 - curvedAnim), 0), zoomAmt: .12,
child: Transform.scale( dynamicHzOffset: -50,
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),
),
),
),
),
),
]),
)
]; ];
} }
} }

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/fade_color_transition.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/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_builder.dart';
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart';
@ -19,6 +19,7 @@ class PetraIllustration extends StatelessWidget {
bgBuilder: _buildBg, bgBuilder: _buildBg,
mgBuilder: _buildMg, mgBuilder: _buildMg,
fgBuilder: _buildFg, fgBuilder: _buildFg,
wonderType: WonderType.petra,
); );
} }
@ -33,74 +34,69 @@ class PetraIllustration extends StatelessWidget {
opacity: anim.drive(Tween(begin: 0, end: .25)), opacity: anim.drive(Tween(begin: 0, end: .25)),
), ),
), ),
Align( IllustrationPiece(
alignment: Alignment(-.3, config.shortMode ? -1.5 : -1.23), fileName: 'moon.png',
child: FractionalTranslation( heightFactor: .15,
translation: Offset(0, .5 * anim.value), minHeight: 100,
child: WonderHero( alignment: Alignment.topCenter,
config, fractionalOffset: Offset(-.7, 0),
'petra-moon',
child: Image.asset(
'$assetPath/moon.png',
opacity: anim,
),
),
),
), ),
]; ];
} }
List<Widget> _buildMg(BuildContext context, Animation<double> anim) => [ List<Widget> _buildMg(BuildContext context, Animation<double> anim) => [
Center( FractionallySizedBox(
child: FractionalTranslation( heightFactor: config.shortMode ? 1 : .8,
translation: Offset(0, config.shortMode ? 0.05 : -.1), alignment: Alignment.bottomCenter,
child: FractionallySizedBox( child: IllustrationPiece(
widthFactor: config.shortMode ? 1 : 2, fileName: 'petra.png',
child: WonderHero( heightFactor: .65,
config, minHeight: 500,
'petra-mg', zoomAmt: .1,
child: Image.asset('$assetPath/petra.png', fit: BoxFit.contain, opacity: anim), enableHero: true,
),
),
), ),
), ),
]; ];
List<Widget> _buildFg(BuildContext context, Animation<double> anim) { List<Widget> _buildFg(BuildContext context, Animation<double> anim) {
final curvedAnim = Curves.easeOut.transform(anim.value);
return [ return [
Stack(children: [ IllustrationPiece(
CenterLeft( fileName: 'foreground-left.png',
child: FractionallySizedBox( alignment: Alignment.bottomCenter,
widthFactor: .63, initialOffset: Offset(-80, 0),
child: FractionalTranslation( heightFactor: 1,
translation: Offset(-.3 * (1 - curvedAnim), 0), fractionalOffset: Offset(-.5, 0),
child: Transform.scale( zoomAmt: .1,
scale: 1.1 + config.zoom * .2, dynamicHzOffset: -130,
child: FractionalTranslation( bottom: (_) {
translation: Offset(-.35, -.07), /// To cover everything behind this piece with a solid color, we scale up a container
child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.contain), /// 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))),
CenterRight( );
child: FractionallySizedBox( },
widthFactor: .72, ),
child: FractionalTranslation( IllustrationPiece(
translation: Offset(.3 * (1 - curvedAnim), 0), fileName: 'foreground-right.png',
child: Transform.scale( alignment: Alignment.bottomCenter,
scale: 1 + config.zoom * .4, initialOffset: Offset(80, 00),
child: FractionalTranslation( heightFactor: 1,
translation: Offset(.4, -.03), fractionalOffset: Offset(.5, 0),
child: Image.asset('$assetPath/foreground-right.png', opacity: anim, fit: BoxFit.contain), 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))),
);
},
),
]; ];
} }
} }

View File

@ -1,9 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/fade_color_transition.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_piece.dart';
import 'package:wonders/ui/wonder_illustrations/common/illustration_mg.dart';
import 'package:wonders/ui/wonder_illustrations/common/paint_textures.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_builder.dart';
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart';
@ -17,6 +15,7 @@ class PyramidsGizaIllustration extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WonderIllustrationBuilder( return WonderIllustrationBuilder(
wonderType: WonderType.pyramidsGiza,
config: config, config: config,
bgBuilder: _buildBg, bgBuilder: _buildBg,
mgBuilder: _buildMg, mgBuilder: _buildMg,
@ -35,93 +34,54 @@ class PyramidsGizaIllustration extends StatelessWidget {
flipY: true, flipY: true,
), ),
), ),
Align( IllustrationPiece(
alignment: Alignment(.75, config.shortMode ? -.2 : -.5), fileName: 'moon.png',
child: FractionalTranslation( initialOffset: Offset(0, 20),
translation: Offset(0, -.5 * anim.value), enableHero: true,
child: WonderHero( heightFactor: .15,
config, minHeight: 100,
'pyramids-moon', offset: config.shortMode ? Offset(100, context.heightPx * -.1) : Offset(150, context.heightPx * -.15),
child: Transform.scale( zoomAmt: .05,
scale: config.shortMode ? 0.8 : 1.2, ),
child: Image.asset('$assetPath/moon.png', opacity: anim),
),
),
)),
]; ];
} }
List<Widget> _buildMg(BuildContext context, Animation<double> anim) { List<Widget> _buildMg(BuildContext context, Animation<double> anim) {
return [ return [
IllustrationMg( IllustrationPiece(
'pyramids.png', fileName: 'pyramids.png',
type: WonderType.pyramidsGiza, enableHero: true,
anim: anim, heightFactor: .5,
config: config, minHeight: 300,
maxHeight: 600, zoomAmt: .1,
heightFraction: .85, boxFit: BoxFit.contain,
), overflow: !config.shortMode,
)
// 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),
// ),
// )),
// ),
]; ];
} }
List<Widget> _buildFg(BuildContext context, Animation<double> anim) { List<Widget> _buildFg(BuildContext context, Animation<double> anim) {
final curvedAnim = Curves.easeOut.transform(anim.value);
return [ return [
IllustrationPiece( IllustrationPiece(
type: WonderType.pyramidsGiza,
anim: anim,
fileName: 'foreground-back.png', fileName: 'foreground-back.png',
heightFactor: .5,
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
initialOffset: Offset(20, 40),
initialScale: .95,
heightFactor: .55,
fractionalOffset: Offset(.1, .06),
zoomAmt: .1,
dynamicHzOffset: 150,
), ),
IllustrationPiece( IllustrationPiece(
type: WonderType.pyramidsGiza,
anim: anim,
fileName: 'foreground-front.png', fileName: 'foreground-front.png',
heightFactor: .5,
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
) initialScale: .9,
// Transform.scale( initialOffset: Offset(-40, 60),
// scale: 1 + config.zoom * .2, heightFactor: .55,
// child: Transform.translate( fractionalOffset: Offset(-.1, .1),
// offset: Offset(0, 10 * (1 - curvedAnim)), zoomAmt: .25,
// child: BottomCenter( dynamicHzOffset: -150,
// 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),
// ),
// ),
// ),
// ),
// ),
]; ];
} }
} }

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/fade_color_transition.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/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_builder.dart';
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart';
@ -20,11 +20,11 @@ class TajMahalIllustration extends StatelessWidget {
bgBuilder: _buildBg, bgBuilder: _buildBg,
mgBuilder: _buildMg, mgBuilder: _buildMg,
fgBuilder: _buildFg, fgBuilder: _buildFg,
wonderType: WonderType.tajMahal,
); );
} }
List<Widget> _buildBg(BuildContext context, Animation<double> anim) { List<Widget> _buildBg(BuildContext context, Animation<double> anim) {
final curvedAnim = Curves.easeOut.transform(anim.value);
return [ return [
// Bg color // Bg color
FadeColorTransition(color: fgColor, animation: anim), FadeColorTransition(color: fgColor, animation: anim),
@ -38,78 +38,69 @@ class TajMahalIllustration extends StatelessWidget {
), ),
), ),
// Sun // Sun
Align( IllustrationPiece(
alignment: config.shortMode ? Alignment(-1.25, -2.8) : Alignment(-1.25, -1.15), fileName: 'sun.png',
child: FractionalTranslation( initialOffset: Offset(0, 20),
translation: Offset(-.2 + curvedAnim * .2, .4 - curvedAnim * .2), enableHero: true,
child: WonderHero(config, 'taj-sun', child: Image.asset('$assetPath/sun.png', opacity: anim)), heightFactor: .15,
), minHeight: 140,
) offset: config.shortMode ? Offset(-100, context.heightPx * -.05) : Offset(-150, context.heightPx * -.15),
),
]; ];
} }
List<Widget> _buildMg(BuildContext context, Animation<double> anim) { List<Widget> _buildMg(BuildContext context, Animation<double> anim) {
return [ return [
Transform.scale( LayoutBuilder(builder: (_, constraints) {
scale: 1 + config.zoom * .1, const double minHeight = 500, heightFactor = .6, poolScale = 1;
child: Align( return Stack(
alignment: Alignment(0, config.shortMode ? 1 : -.15), children: [
child: FractionallySizedBox( IllustrationPiece(
widthFactor: config.shortMode ? 1 : 1.7, fileName: 'taj-mahal.png',
child: Stack( heightFactor: heightFactor,
children: [ minHeight: minHeight,
WonderHero( zoomAmt: .05,
config, top: config.shortMode
'taj-mg', ? null
child: Image.asset('$assetPath/taj-mahal.png', opacity: anim, fit: BoxFit.cover), : (_) => FractionalTranslation(
), translation: Offset(0, .85),
if (!config.shortMode) child: IllustrationPiece(
FractionalTranslation( fileName: 'pool.png',
translation: Offset(0, 1.33), heightFactor: heightFactor * poolScale,
child: Image.asset('$assetPath/pool.png', opacity: anim, fit: BoxFit.cover), minHeight: minHeight * poolScale,
), zoomAmt: .05,
], ),
),
), ),
), ],
), );
) }),
]; ];
} }
List<Widget> _buildFg(BuildContext context, Animation<double> anim) { List<Widget> _buildFg(BuildContext context, Animation<double> 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 [ return [
Transform.scale( IllustrationPiece(
scale: 1 + config.zoom * .2, fileName: 'foreground-right.png',
child: Stack( alignment: Alignment.bottomRight,
children: [ initialOffset: Offset(20, 40),
FractionalTranslation( initialScale: .85,
translation: Offset(-.2 * (1 - curvedAnim), 0), heightFactor: .5 + .3 * mangoScale,
child: BottomLeft( fractionalOffset: Offset(.3, 0),
child: FractionallySizedBox( zoomAmt: .1,
heightFactor: .6, ),
child: FractionalTranslation( IllustrationPiece(
translation: Offset(-.4, -.04), fileName: 'foreground-left.png',
child: Image.asset('$assetPath/foreground-left.png', opacity: anim, fit: BoxFit.cover), alignment: Alignment.bottomLeft,
), initialScale: .9,
), initialOffset: Offset(-40, 60),
), heightFactor: .5 + .3 * mangoScale,
), fractionalOffset: Offset(-.2, 0),
FractionalTranslation( zoomAmt: .25,
translation: Offset(.2 * (1 - curvedAnim), 0), dynamicHzOffset: 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),
),
),
),
),
],
),
)
]; ];
} }
} }