first pass at using props
This commit is contained in:
parent
1f9bd92f80
commit
afbd4b24db
@ -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';
|
||||||
|
@ -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(
|
||||||
|
@ -153,4 +153,10 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pageController?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
|
@ -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;
|
||||||
|
@ -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?;
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user