first pass at using props

This commit is contained in:
Shawn 2022-12-07 10:14:14 -07:00
parent 1f9bd92f80
commit afbd4b24db
14 changed files with 98 additions and 141 deletions

View File

@ -15,6 +15,7 @@ export 'package:provider/provider.dart';
export 'package:rnd/rnd.dart'; export 'package:rnd/rnd.dart';
export 'package:simple_rich_text/simple_rich_text.dart'; export 'package:simple_rich_text/simple_rich_text.dart';
export 'package:sized_context/sized_context.dart'; export 'package:sized_context/sized_context.dart';
export 'package:stateful_props/stateful_props.dart';
export 'package:wonders/assets.dart'; export 'package:wonders/assets.dart';
export 'package:wonders/logic/app_logic.dart'; export 'package:wonders/logic/app_logic.dart';
export 'package:wonders/logic/data/wonder_type.dart'; export 'package:wonders/logic/data/wonder_type.dart';

View File

@ -12,17 +12,11 @@ class FullscreenUrlImgViewer extends StatefulWidget {
State<FullscreenUrlImgViewer> createState() => _FullscreenUrlImgViewerState(); State<FullscreenUrlImgViewer> createState() => _FullscreenUrlImgViewerState();
} }
class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> { class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> with StatefulPropsMixin {
final _isZoomed = ValueNotifier(false); final _isZoomed = ValueNotifier(false);
late final _controller = PageController(initialPage: widget.index); late final _pages = PageControllerProp(this, initialPage: widget.index);
@override void _handleBackPressed() => Navigator.pop(context, _pages.page.round());
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleBackPressed() => Navigator.pop(context, _controller.page!.round());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -32,7 +26,7 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
final bool enableSwipe = !_isZoomed.value && widget.urls.length > 1; final bool enableSwipe = !_isZoomed.value && widget.urls.length > 1;
return PageView.builder( return PageView.builder(
physics: enableSwipe ? PageScrollPhysics() : NeverScrollableScrollPhysics(), physics: enableSwipe ? PageScrollPhysics() : NeverScrollableScrollPhysics(),
controller: _controller, controller: _pages.controller,
itemCount: widget.urls.length, itemCount: widget.urls.length,
itemBuilder: (_, index) => _Viewer(widget.urls[index], _isZoomed), itemBuilder: (_, index) => _Viewer(widget.urls[index], _isZoomed),
onPageChanged: (_) => AppHaptics.lightImpact(), onPageChanged: (_) => AppHaptics.lightImpact(),
@ -69,25 +63,19 @@ class _Viewer extends StatefulWidget {
State<_Viewer> createState() => _ViewerState(); State<_Viewer> createState() => _ViewerState();
} }
class _ViewerState extends State<_Viewer> with SingleTickerProviderStateMixin { class _ViewerState extends State<_Viewer> with StatefulPropsMixin {
final _controller = TransformationController(); late final _transformation = TransformationControllerProp(this);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
/// Reset zoom level to 1 on double-tap /// Reset zoom level to 1 on double-tap
void _handleDoubleTap() => _controller.value = Matrix4.identity(); void _handleDoubleTap() => _transformation.controller.value = Matrix4.identity();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onDoubleTap: _handleDoubleTap, onDoubleTap: _handleDoubleTap,
child: InteractiveViewer( child: InteractiveViewer(
transformationController: _controller, transformationController: _transformation.controller,
onInteractionEnd: (_) => widget.isZoomed.value = _controller.value.getMaxScaleOnAxis() > 1, onInteractionEnd: (_) => widget.isZoomed.value = _transformation.matrix.getMaxScaleOnAxis() > 1,
minScale: 1, minScale: 1,
maxScale: 5, maxScale: 5,
child: Hero( child: Hero(

View File

@ -153,4 +153,10 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
), ),
); );
} }
@override
void dispose() {
_pageController?.dispose();
super.dispose();
}
} }

View File

@ -37,10 +37,6 @@ part 'widgets/_sliding_image_stack.dart';
part 'widgets/_title_text.dart'; part 'widgets/_title_text.dart';
part 'widgets/_top_illustration.dart'; part 'widgets/_top_illustration.dart';
//TODO: Try and maintain 1.5 : 1 aspect ratio on the featured image
//TODO: Try and move the scrollbar all the way to the edge of the screen
//TODO: Fix arch logic (if necessary)
// or maybe remove
class WonderEditorialScreen extends StatefulWidget { class WonderEditorialScreen extends StatefulWidget {
const WonderEditorialScreen(this.data, {Key? key, required this.onScroll}) : super(key: key); const WonderEditorialScreen(this.data, {Key? key, required this.onScroll}) : super(key: key);
final WonderData data; final WonderData data;
@ -50,20 +46,14 @@ class WonderEditorialScreen extends StatefulWidget {
State<WonderEditorialScreen> createState() => _WonderEditorialScreenState(); State<WonderEditorialScreen> createState() => _WonderEditorialScreenState();
} }
class _WonderEditorialScreenState extends State<WonderEditorialScreen> { class _WonderEditorialScreenState extends State<WonderEditorialScreen> with StatefulPropsMixin {
late final ScrollController _scroller = ScrollController()..addListener(_handleScrollChanged); late final _scroll = ScrollControllerProp(this, onChange: _handleScrollChanged);
final _scrollPos = ValueNotifier(0.0); late final _scrollPos = ValueNotifier(0.0);
final _sectionIndex = ValueNotifier(0); late final _sectionIndex = ValueNotifier(0);
@override
void dispose() {
_scroller.dispose();
super.dispose();
}
/// Various [ValueListenableBuilders] are mapped to the _scrollPos and will rebuild when it changes /// Various [ValueListenableBuilders] are mapped to the _scrollPos and will rebuild when it changes
void _handleScrollChanged() { void _handleScrollChanged() {
_scrollPos.value = _scroller.position.pixels; _scrollPos.value = _scroll.px;
widget.onScroll.call(_scrollPos.value); widget.onScroll.call(_scrollPos.value);
} }
@ -78,7 +68,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
double maxAppBarHeight = min(context.widthPx, $styles.sizes.maxContentWidth1) * 1.5; double maxAppBarHeight = min(context.widthPx, $styles.sizes.maxContentWidth1) * 1.5;
return PopRouterOnOverScroll( return PopRouterOnOverScroll(
controller: _scroller, controller: _scroll.controller,
child: ColoredBox( child: ColoredBox(
color: $styles.colors.offWhite, color: $styles.colors.offWhite,
child: Stack( child: Stack(
@ -91,11 +81,11 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
/// Top Illustration - Sits underneath the scrolling content, fades out as it scrolls /// Top Illustration - Sits underneath the scrolling content, fades out as it scrolls
SizedBox( SizedBox(
height: illustrationHeight, height: illustrationHeight,
child: ValueListenableBuilder<double>( child: ListenableBuilder(
valueListenable: _scrollPos, listenable: _scrollPos,
builder: (_, value, child) { builder: (_, child) {
// get some value between 0 and 1, based on the amt scrolled // get some value between 0 and 1, based on the amt scrolled
double opacity = (1 - value / 700).clamp(0, 1); double opacity = (1 - _scrollPos.value / 700).clamp(0, 1);
return Opacity(opacity: opacity, child: child); return Opacity(opacity: opacity, child: child);
}, },
// This is due to a bug: https://github.com/flutter/flutter/issues/101872 // This is due to a bug: https://github.com/flutter/flutter/issues/101872
@ -109,7 +99,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
//width: $styles.sizes.maxContentWidth1, //width: $styles.sizes.maxContentWidth1,
child: CustomScrollView( child: CustomScrollView(
primary: false, primary: false,
controller: _scroller, controller: _scroll.controller,
scrollBehavior: ScrollConfiguration.of(context).copyWith(), scrollBehavior: ScrollConfiguration.of(context).copyWith(),
cacheExtent: 1000, cacheExtent: 1000,
slivers: [ slivers: [
@ -120,17 +110,17 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
/// Text content, animates itself to hide behind the app bar as it scrolls up /// Text content, animates itself to hide behind the app bar as it scrolls up
SliverToBoxAdapter( SliverToBoxAdapter(
child: ValueListenableBuilder<double>( child: ListenableBuilder(
valueListenable: _scrollPos, listenable: _scrollPos,
builder: (_, value, child) { builder: (_, child) {
double offsetAmt = max(0, value * .3); double offsetAmt = max(0, _scrollPos.value * .3);
double opacity = (1 - offsetAmt / 150).clamp(0, 1); double opacity = (1 - offsetAmt / 150).clamp(0, 1);
return Transform.translate( return Transform.translate(
offset: Offset(0, offsetAmt), offset: Offset(0, offsetAmt),
child: Opacity(opacity: opacity, child: child), child: Opacity(opacity: opacity, child: child),
); );
}, },
child: _TitleText(widget.data, scroller: _scroller), child: _TitleText(widget.data, scroller: _scroll.controller),
), ),
), ),
@ -166,7 +156,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
/// Home Btn /// Home Btn
ListenableBuilder( ListenableBuilder(
listenable: _scroller, listenable: _scroll.controller,
builder: (_, child) { builder: (_, child) {
return AnimatedOpacity( return AnimatedOpacity(
opacity: _scrollPos.value > 0 ? 0 : 1, opacity: _scrollPos.value > 0 ? 0 : 1,

View File

@ -62,22 +62,13 @@ class _AnimatedCircleWithText extends StatefulWidget {
State<_AnimatedCircleWithText> createState() => _AnimatedCircleWithTextState(); State<_AnimatedCircleWithText> createState() => _AnimatedCircleWithTextState();
} }
class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with SingleTickerProviderStateMixin { class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with StatefulPropsMixin {
int _prevIndex = -1; int _prevIndex = -1;
String get oldTitle => _prevIndex == -1 ? '' : widget.titles[_prevIndex]; String get oldTitle => _prevIndex == -1 ? '' : widget.titles[_prevIndex];
String get newTitle => widget.titles[widget.index]; String get newTitle => widget.titles[widget.index];
late final _anim = AnimationController( late final _anim = AnimationControllerProp(this, $styles.times.med);
vsync: this,
duration: $styles.times.med,
)..forward();
bool get isAnimStopped => _anim.value == 0 || _anim.value == _anim.upperBound; bool get isAnimStopped => _anim.value == 0 || _anim.value == _anim.controller.upperBound;
@override
void dispose() {
_anim.dispose();
super.dispose();
}
@override @override
void didUpdateWidget(covariant _AnimatedCircleWithText oldWidget) { void didUpdateWidget(covariant _AnimatedCircleWithText oldWidget) {
@ -86,7 +77,7 @@ class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with S
_prevIndex = oldWidget.index; _prevIndex = oldWidget.index;
// If the animation is already in motion, we don't need to interrupt it, just let the text change // If the animation is already in motion, we don't need to interrupt it, just let the text change
if (isAnimStopped) { if (isAnimStopped) {
_anim.forward(from: 0); _anim.controller.forward(from: 0);
} }
} }
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
@ -95,9 +86,10 @@ class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with S
@override @override
Widget build(_) { Widget build(_) {
return ListenableBuilder( return ListenableBuilder(
listenable: _anim, listenable: _anim.controller,
builder: (_, __) { builder: (_, __) {
var rot = _prevIndex > widget.index ? -pi : pi; var rot = _prevIndex > widget.index ? -pi : pi;
bool isCompleted = _anim.controller.isCompleted;
return Transform.rotate( return Transform.rotate(
angle: Curves.easeInOut.transform(_anim.value) * rot, angle: Curves.easeInOut.transform(_anim.value) * rot,
child: Container( child: Container(
@ -114,13 +106,13 @@ class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with S
child: Stack( child: Stack(
children: [ children: [
Transform.rotate( Transform.rotate(
angle: _anim.isCompleted ? rot : 0, angle: isCompleted ? rot : 0,
child: _buildCircularText(_anim.isCompleted ? newTitle : oldTitle), child: _buildCircularText(isCompleted ? newTitle : oldTitle),
), ),
if (!_anim.isCompleted) ...[ if (!isCompleted) ...[
Transform.rotate( Transform.rotate(
angle: _anim.isCompleted ? 0 : rot, angle: rot,
child: _buildCircularText(_anim.isCompleted ? oldTitle : newTitle), child: _buildCircularText(newTitle),
), ),
] ]
], ],

View File

@ -14,7 +14,7 @@ class IntroScreen extends StatefulWidget {
State<IntroScreen> createState() => _IntroScreenState(); State<IntroScreen> createState() => _IntroScreenState();
} }
class _IntroScreenState extends State<IntroScreen> { class _IntroScreenState extends State<IntroScreen> with StatefulPropsMixin {
static const double _imageSize = 264; static const double _imageSize = 264;
static const double _logoHeight = 126; static const double _logoHeight = 126;
static const double _textHeight = 155; static const double _textHeight = 155;
@ -22,15 +22,9 @@ class _IntroScreenState extends State<IntroScreen> {
static List<_PageData> pageData = []; static List<_PageData> pageData = [];
late final PageController _pageController = PageController()..addListener(_handlePageChanged); late final _page = PageControllerProp(this, onChange: _handlePageChanged);
final ValueNotifier<int> _currentPage = ValueNotifier(0); final ValueNotifier<int> _currentPage = ValueNotifier(0);
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _handleIntroCompletePressed() { void _handleIntroCompletePressed() {
if (_currentPage.value == pageData.length - 1) { if (_currentPage.value == pageData.length - 1) {
context.go(ScreenPaths.home); context.go(ScreenPaths.home);
@ -39,13 +33,16 @@ class _IntroScreenState extends State<IntroScreen> {
} }
void _handlePageChanged() { void _handlePageChanged() {
int newPage = _pageController.page?.round() ?? 0; int newPage = _page.controller.page?.round() ?? 0;
_currentPage.value = newPage; _currentPage.value = newPage;
} }
void _handleSemanticSwipe(int dir) { void _handleSemanticSwipe(int dir) {
_pageController.animateToPage((_pageController.page ?? 0).round() + dir, _page.controller.animateToPage(
duration: $styles.times.fast, curve: Curves.easeOut); (_page.controller.page ?? 0).round() + dir,
duration: $styles.times.fast,
curve: Curves.easeOut,
);
} }
@override @override
@ -61,9 +58,7 @@ class _IntroScreenState extends State<IntroScreen> {
// However, we only want the title / description to actually swipe, // However, we only want the title / description to actually swipe,
// so we stack a PageView with that content over top of all the other // so we stack a PageView with that content over top of all the other
// content, and line up their layouts. // content, and line up their layouts.
final List<Widget> pages = pageData.map((e) => _Page(data: e)).toList(); final List<Widget> pages = pageData.map((e) => _Page(data: e)).toList();
final Widget content = Stack(children: [ final Widget content = Stack(children: [
// page view with title & description: // page view with title & description:
MergeSemantics( MergeSemantics(
@ -71,7 +66,7 @@ class _IntroScreenState extends State<IntroScreen> {
onIncrease: () => _handleSemanticSwipe(1), onIncrease: () => _handleSemanticSwipe(1),
onDecrease: () => _handleSemanticSwipe(-1), onDecrease: () => _handleSemanticSwipe(-1),
child: PageView( child: PageView(
controller: _pageController, controller: _page.controller,
children: pages, children: pages,
onPageChanged: (_) => AppHaptics.lightImpact(), onPageChanged: (_) => AppHaptics.lightImpact(),
), ),
@ -118,8 +113,11 @@ class _IntroScreenState extends State<IntroScreen> {
Container( Container(
height: _pageIndicatorHeight, height: _pageIndicatorHeight,
alignment: Alignment(0.0, -0.75), alignment: Alignment(0.0, -0.75),
child: child: AppPageIndicator(
AppPageIndicator(count: pageData.length, controller: _pageController, color: $styles.colors.offWhite), count: pageData.length,
controller: _page.controller,
color: $styles.colors.offWhite,
),
), ),
Spacer(flex: 2), Spacer(flex: 2),
@ -213,8 +211,8 @@ class _IntroScreenState extends State<IntroScreen> {
child: Semantics( child: Semantics(
onTapHint: $strings.introSemanticNavigate, onTapHint: $strings.introSemanticNavigate,
onTap: () { onTap: () {
final int current = _pageController.page!.round(); final int current = _page.controller.page!.round();
_pageController.animateToPage(current + 1, duration: 250.ms, curve: Curves.easeIn); _page.controller.animateToPage(current + 1, duration: 250.ms, curve: Curves.easeIn);
}, },
child: Text($strings.introSemanticSwipeLeft, style: $styles.text.bodySmall), child: Text($strings.introSemanticSwipeLeft, style: $styles.text.bodySmall),
), ),

View File

@ -1,5 +1,6 @@
part of '../timeline_screen.dart'; part of '../timeline_screen.dart';
/// TODO: This can just be a prop??
class _ScrollingViewportController extends ChangeNotifier { class _ScrollingViewportController extends ChangeNotifier {
_ScrollingViewportController(this.state); _ScrollingViewportController(this.state);
final _ScalingViewportState state; final _ScalingViewportState state;

View File

@ -24,13 +24,6 @@ class _WallpaperPhotoScreenState extends State<WallpaperPhotoScreen> {
Widget? _illustration; Widget? _illustration;
bool _showTitleText = true; bool _showTitleText = true;
Timer? _photoRetryTimer;
@override
void dispose() {
_photoRetryTimer?.cancel();
super.dispose();
}
void _handleTakePhoto(BuildContext context, String wonderName) async { void _handleTakePhoto(BuildContext context, String wonderName) async {
final boundary = _containerKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; final boundary = _containerKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;

View File

@ -16,29 +16,17 @@ class WonderDetailsScreen extends StatefulWidget with GetItStatefulWidgetMixin {
State<WonderDetailsScreen> createState() => _WonderDetailsScreenState(); State<WonderDetailsScreen> createState() => _WonderDetailsScreenState();
} }
class _WonderDetailsScreenState extends State<WonderDetailsScreen> class _WonderDetailsScreenState extends State<WonderDetailsScreen> with GetItStateMixin, StatefulPropsMixin {
with GetItStateMixin, SingleTickerProviderStateMixin { late final _tabs = TabControllerProp(
late final _tabController = TabController( this,
length: 4, length: 4,
vsync: this,
initialIndex: widget.initialTabIndex, initialIndex: widget.initialTabIndex,
)..addListener(_handleTabChanged); autoBuild: true,
AnimationController? _fade; );
final _detailsHasScrolled = ValueNotifier(false); final _detailsHasScrolled = ValueNotifier(false);
double? _tabBarHeight; double? _tabBarHeight;
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _handleTabChanged() {
_fade?.forward(from: 0);
setState(() {});
}
void _handleDetailsScrolled(double scrollPos) => _detailsHasScrolled.value = scrollPos > 0; void _handleDetailsScrolled(double scrollPos) => _detailsHasScrolled.value = scrollPos > 0;
void _handleTabMenuSized(Size size) { void _handleTabMenuSized(Size size) {
@ -48,17 +36,16 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final wonder = wondersLogic.getData(widget.type); final wonder = wondersLogic.getData(widget.type);
int tabIndex = _tabController.index; int tabIndex = _tabs.controller.index;
bool showTabBarBg = tabIndex != 1; bool showTabBarBg = tabIndex != 1;
final tabBarHeight = _tabBarHeight ?? 0; final tabBarHeight = _tabBarHeight ?? 0;
//final double tabBarHeight = WonderDetailsTabMenu.bottomPadding + 60;
return ColoredBox( return ColoredBox(
color: Colors.black, color: Colors.black,
child: Stack( child: Stack(
children: [ children: [
/// Fullscreen tab views /// Fullscreen tab views
LazyIndexedStack( LazyIndexedStack(
index: _tabController.index, index: _tabs.controller.index,
children: [ children: [
WonderEditorialScreen(wonder, onScroll: _handleDetailsScrolled), WonderEditorialScreen(wonder, onScroll: _handleDetailsScrolled),
PhotoGallery(collectionId: wonder.unsplashCollectionId, wonderType: wonder.type), PhotoGallery(collectionId: wonder.unsplashCollectionId, wonderType: wonder.type),
@ -74,7 +61,7 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
builder: (_, value, ___) => MeasurableWidget( builder: (_, value, ___) => MeasurableWidget(
onChange: _handleTabMenuSized, onChange: _handleTabMenuSized,
child: WonderDetailsTabMenu( child: WonderDetailsTabMenu(
tabController: _tabController, tabController: _tabs.controller,
wonderType: wonder.type, wonderType: wonder.type,
showBg: showTabBarBg, showBg: showTabBarBg,
), ),

View File

@ -19,13 +19,8 @@ class _EventsList extends StatefulWidget {
State<_EventsList> createState() => _EventsListState(); State<_EventsList> createState() => _EventsListState();
} }
class _EventsListState extends State<_EventsList> { class _EventsListState extends State<_EventsList> with StatefulPropsMixin {
final ScrollController _scroller = ScrollController(); late final _scroll = ScrollControllerProp(this);
@override
void dispose() {
_scroller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -58,7 +53,7 @@ class _EventsListState extends State<_EventsList> {
children: [ children: [
//TODO: Remove scrollbar on portrait //TODO: Remove scrollbar on portrait
SingleChildScrollView( SingleChildScrollView(
controller: _scroller, controller: _scroll.controller,
child: Column( child: Column(
children: [ children: [
IgnorePointer(child: Gap(widget.topHeight)), IgnorePointer(child: Gap(widget.topHeight)),
@ -104,17 +99,17 @@ class _EventsListState extends State<_EventsList> {
/// Wraps the list in a scroll listener /// Wraps the list in a scroll listener
Widget _buildScrollingListWithBlur() { Widget _buildScrollingListWithBlur() {
return ListenableBuilder( return ListenableBuilder(
listenable: _scroller, listenable: _scroll.controller,
child: _buildScrollingList(), child: _buildScrollingList(),
builder: (_, child) { builder: (_, child) {
bool showBackdrop = true; bool showBackdrop = true;
double backdropAmt = 0; double backdropAmt = 0;
if (_scroller.hasClients && showBackdrop) { if (showBackdrop) {
double blurStart = 50; double blurStart = 50;
double maxScroll = 150; double maxScroll = 150;
double scrollPx = _scroller.position.pixels - blurStart; double scrollPx = _scroll.px - blurStart;
// Normalize scroll position to a value between 0 and 1 // Normalize scroll position to a value between 0 and 1
backdropAmt = (_scroller.position.pixels - blurStart).clamp(0, maxScroll) / maxScroll; backdropAmt = (_scroll.px - blurStart).clamp(0, maxScroll) / maxScroll;
// Disable backdrop once it is offscreen for an easy perf win // Disable backdrop once it is offscreen for an easy perf win
showBackdrop = (scrollPx <= 500); showBackdrop = (scrollPx <= 500);
} }

View File

@ -83,9 +83,9 @@ class _IllustrationPieceState extends State<IllustrationPiece> {
key: ValueKey(aspectRatio), key: ValueKey(aspectRatio),
builder: (_, constraints) { builder: (_, constraints) {
final anim = wonderBuilder.anim; final anim = wonderBuilder.anim;
final curvedAnim = Curves.easeOut.transform(anim.value); final curvedAnim = Curves.easeOut.transform(anim.controller.value);
final config = wonderBuilder.widget.config; final config = wonderBuilder.widget.config;
Widget img = Image.asset(imgPath, opacity: anim, fit: BoxFit.fitHeight); Widget img = Image.asset(imgPath, opacity: anim.controller, fit: BoxFit.fitHeight);
// Add overflow box so image doesn't get clipped as we translate it around // Add overflow box so image doesn't get clipped as we translate it around
img = OverflowBox(maxWidth: 2000, child: img); img = OverflowBox(maxWidth: 2000, child: img);

View File

@ -25,27 +25,24 @@ class WonderIllustrationBuilder extends StatefulWidget {
State<WonderIllustrationBuilder> createState() => WonderIllustrationBuilderState(); State<WonderIllustrationBuilder> createState() => WonderIllustrationBuilderState();
} }
class WonderIllustrationBuilderState extends State<WonderIllustrationBuilder> with SingleTickerProviderStateMixin { class WonderIllustrationBuilderState extends State<WonderIllustrationBuilder> with StatefulPropsMixin {
late final anim = AnimationController(vsync: this, duration: $styles.times.med * .75) late final anim = AnimationControllerProp(
..addListener(() => setState(() {})); this,
$styles.times.med * .75,
autoBuild: true,
autoPlay: false,
);
bool get isShowing => widget.config.isShowing; bool get isShowing => widget.config.isShowing;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (isShowing) anim.forward(from: 0); if (isShowing) anim.controller.forward(from: 0);
}
@override
void dispose() {
anim.dispose();
super.dispose();
} }
@override @override
void didUpdateWidget(covariant WonderIllustrationBuilder oldWidget) { void didUpdateWidget(covariant WonderIllustrationBuilder oldWidget) {
if (isShowing != oldWidget.config.isShowing) { if (isShowing != oldWidget.config.isShowing) {
isShowing ? anim.forward(from: 0) : anim.reverse(from: 1); isShowing ? anim.controller.forward(from: 0) : anim.controller.reverse(from: 1);
} }
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
@ -53,8 +50,8 @@ class WonderIllustrationBuilderState extends State<WonderIllustrationBuilder> wi
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Optimization: no need to return all of these children if the widget is fully invisible. // Optimization: no need to return all of these children if the widget is fully invisible.
if (anim.value == 0 && widget.config.enableAnims) return SizedBox.expand(); if (anim.controller.value == 0 && widget.config.enableAnims) return SizedBox.expand();
Animation<double> animation = widget.config.enableAnims ? anim : AlwaysStoppedAnimation(1); Animation<double> animation = widget.config.enableAnims ? anim.controller : AlwaysStoppedAnimation(1);
return Provider<WonderIllustrationBuilderState>.value( return Provider<WonderIllustrationBuilderState>.value(
value: this, value: this,

View File

@ -1129,6 +1129,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.0"
stateful_props:
dependency: "direct main"
description:
name: stateful_props
sha256: "5f14e2f7c720b044e44d587ed3e7cfa6a3a2431d348416027755e5d331d0c164"
url: "https://pub.dev"
source: hosted
version: "1.2.0+1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:

View File

@ -48,6 +48,7 @@ dependencies:
screenshot: ^1.2.3 screenshot: ^1.2.3
share_plus: ^4.0.10 share_plus: ^4.0.10
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
stateful_props: ^1.2.0+1
simple_rich_text: ^2.0.49 simple_rich_text: ^2.0.49
sized_context: ^1.0.0+1 sized_context: ^1.0.0+1
smooth_page_indicator: ^1.0.0+2 smooth_page_indicator: ^1.0.0+2