import 'dart:async'; import 'package:drop_cap_text/drop_cap_text.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_circular_text/circular_text.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/common/platform_info.dart'; import 'package:wonders/logic/common/string_utils.dart'; import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/blend_mask.dart'; import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/compass_divider.dart'; import 'package:wonders/ui/common/curved_clippers.dart'; import 'package:wonders/ui/common/fullscreen_keyboard_list_scroller.dart'; import 'package:wonders/ui/common/google_maps_marker.dart'; import 'package:wonders/ui/common/gradient_container.dart'; import 'package:wonders/ui/common/hidden_collectible.dart'; import 'package:wonders/ui/common/pop_router_on_over_scroll.dart'; import 'package:wonders/ui/common/scaling_list_item.dart'; import 'package:wonders/ui/common/static_text_scale.dart'; import 'package:wonders/ui/common/themed_text.dart'; import 'package:wonders/ui/common/utils/context_utils.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart'; part 'widgets/_app_bar.dart'; part 'widgets/_callout.dart'; part 'widgets/_circular_title_bar.dart'; part 'widgets/_collapsing_pull_quote_image.dart'; part 'widgets/_large_simple_quote.dart'; part 'widgets/_scrolling_content.dart'; part 'widgets/_section_divider.dart'; part 'widgets/_sliding_image_stack.dart'; part 'widgets/_title_text.dart'; part 'widgets/_top_illustration.dart'; class WonderEditorialScreen extends StatefulWidget { const WonderEditorialScreen(this.data, {Key? key, required this.contentPadding}) : super(key: key); final WonderData data; //final void Function(double scrollPos) onScroll; final EdgeInsets contentPadding; @override State createState() => _WonderEditorialScreenState(); } class _WonderEditorialScreenState extends State { late final ScrollController _scroller = ScrollController()..addListener(_handleScrollChanged); final _scrollPos = ValueNotifier(0.0); final _sectionIndex = ValueNotifier(0); @override void dispose() { _scroller.dispose(); super.dispose(); } /// Various [ValueListenableBuilders] are mapped to the _scrollPos and will rebuild when it changes void _handleScrollChanged() { _scrollPos.value = _scroller.position.pixels; } @override Widget build(BuildContext context) { return LayoutBuilder(builder: (_, constraints) { bool shortMode = constraints.biggest.height < 700; double illustrationHeight = shortMode ? 250 : 280; double minAppBarHeight = shortMode ? 80 : 150; /// Attempt to maintain a similar aspect ratio for the image within the app-bar double maxAppBarHeight = min(context.widthPx, $styles.sizes.maxContentWidth1) * 1.2; final backBtnAlign = appLogic.shouldUseNavRail() ? Alignment.topRight : Alignment.topLeft; return PopRouterOnOverScroll( controller: _scroller, child: ColoredBox( color: $styles.colors.offWhite, child: Stack( children: [ /// Background Positioned.fill( child: ColoredBox(color: widget.data.type.bgColor), ), /// Top Illustration - Sits underneath the scrolling content, fades out as it scrolls SizedBox( height: illustrationHeight, child: ValueListenableBuilder( valueListenable: _scrollPos, builder: (_, value, child) { // get some value between 0 and 1, based on the amt scrolled double opacity = (1 - value / 700).clamp(0, 1); return Opacity(opacity: opacity, child: child); }, // This is due to a bug: https://github.com/flutter/flutter/issues/101872 child: RepaintBoundary( child: _TopIllustration( widget.data.type, // Polish: Inject the content padding into the illustration as an offset, so it can center itself relative to the content // this allows the background to extend underneath the vertical side nav when it has rounded corners. fgOffset: Offset(widget.contentPadding.left / 2, 0), )), ), ), /// Scrolling content - Includes an invisible gap at the top, and then scrolls over the illustration TopCenter( child: Padding( padding: widget.contentPadding, child: SizedBox( child: FocusTraversalGroup( child: FullscreenKeyboardListScroller( scrollController: _scroller, child: CustomScrollView( controller: _scroller, scrollBehavior: ScrollConfiguration.of(context).copyWith(), key: PageStorageKey('editorial'), slivers: [ /// Invisible padding at the top of the list, so the illustration shows through the btm SliverToBoxAdapter( child: SizedBox(height: illustrationHeight), ), /// Text content, animates itself to hide behind the app bar as it scrolls up SliverToBoxAdapter( child: ValueListenableBuilder( valueListenable: _scrollPos, builder: (_, value, child) { double offsetAmt = max(0, value * .3); double opacity = (1 - offsetAmt / 150).clamp(0, 1); return Transform.translate( offset: Offset(0, offsetAmt), child: Opacity(opacity: opacity, child: child), ); }, child: _TitleText(widget.data, scroller: _scroller), ), ), /// Collapsing App bar, pins to the top of the list SliverAppBar( pinned: true, collapsedHeight: minAppBarHeight, toolbarHeight: minAppBarHeight, expandedHeight: maxAppBarHeight, backgroundColor: Colors.transparent, elevation: 0, leading: SizedBox.shrink(), flexibleSpace: SizedBox.expand( child: _AppBar( widget.data.type, scrollPos: _scrollPos, sectionIndex: _sectionIndex, ), ), ), /// Editorial content (text and images) _ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex), ], ), ), ), ), ), ), /// Home Btn AnimatedBuilder( animation: _scroller, builder: (_, child) { return AnimatedOpacity( opacity: _scrollPos.value > 0 ? 0 : 1, duration: $styles.times.med, child: child, ); }, child: Align( alignment: backBtnAlign, child: Padding( padding: EdgeInsets.all($styles.insets.sm), child: BackBtn(icon: AppIcons.north), ), ), ) ], ), ), ); }); } }