Merge pull request #60 from gskinnerTeam/feature-v2-polish

Feature v2 polish
This commit is contained in:
Shawn 2022-12-20 12:35:18 -07:00 committed by GitHub
commit e107cd287f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 299 additions and 194 deletions

View File

@ -11,11 +11,12 @@ class AppLogic {
/// The router will use this to prevent redirects while bootstrapping. /// The router will use this to prevent redirects while bootstrapping.
bool isBootstrapComplete = false; bool isBootstrapComplete = false;
bool get isDesktopOrTablet => PlatformInfo.isDesktopOrWeb || deviceSize.shortestSide > 480; bool get isLandscapeEnabled =>
PlatformInfo.isDesktopOrWeb || deviceSize.shortestSide > 500;
/// Support portrait and landscape on desktop, web and tablets. Stick to portrait for phones. /// Support portrait and landscape on desktop, web and tablets. Stick to portrait for phones.
/// A return value of null indicated both orientations are supported. /// A return value of null indicated both orientations are supported.
Axis? get supportedOrientations => isDesktopOrTablet ? null : Axis.vertical; Axis? get supportedOrientations => isLandscapeEnabled ? null : Axis.vertical;
Size get deviceSize { Size get deviceSize {
final w = WidgetsBinding.instance.platformDispatcher.views.first; final w = WidgetsBinding.instance.platformDispatcher.views.first;
@ -25,9 +26,8 @@ class AppLogic {
/// Initialize the app and all main actors. /// Initialize the app and all main actors.
/// Loads settings, sets up services etc. /// Loads settings, sets up services etc.
Future<void> bootstrap() async { Future<void> bootstrap() async {
debugPrint('bootstrap app, deviceSize: $deviceSize, isTablet: $isDesktopOrTablet'); debugPrint(
// Default error handler 'bootstrap app, deviceSize: $deviceSize, isTablet: $isLandscapeEnabled');
FlutterError.onError = _handleFlutterError;
// Set min-sizes for desktop apps // Set min-sizes for desktop apps
if (PlatformInfo.isDesktop) { if (PlatformInfo.isDesktop) {
@ -89,13 +89,10 @@ class AppLogic {
SystemChrome.setPreferredOrientations(orientations); SystemChrome.setPreferredOrientations(orientations);
} }
void _handleFlutterError(FlutterErrorDetails details) { Future<T?> showFullscreenDialogRoute<T>(BuildContext context, Widget child,
FlutterError.dumpErrorToConsole(details); {bool transparent = false}) async {
}
Future<T?> showFullscreenDialogRoute<T>(BuildContext context, Widget child) async {
return await Navigator.of(context).push<T>( return await Navigator.of(context).push<T>(
PageRoutes.dialog<T>(child, $styles.times.pageTransition), PageRoutes.dialog<T>(child, duration: $styles.times.pageTransition),
); );
} }
} }

View File

@ -18,7 +18,7 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
void _handleTap(BuildContext context) async { void _handleTap(BuildContext context) async {
final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider); final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider);
appLogic.showFullscreenDialogRoute(context, screen); appLogic.showFullscreenDialogRoute(context, screen, transparent: true);
AppHaptics.mediumImpact(); AppHaptics.mediumImpact();
// wait to update the state, to ensure the hero works properly: // wait to update the state, to ensure the hero works properly:

View File

@ -1,10 +1,16 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class SimpleHeader extends StatelessWidget { class AppHeader extends StatelessWidget {
const SimpleHeader(this.title, const AppHeader(
{Key? key, this.subtitle, this.showBackBtn = true, this.isTransparent = false, this.onBack, this.trailing}) {Key? key,
this.title,
this.subtitle,
this.showBackBtn = true,
this.isTransparent = false,
this.onBack,
this.trailing})
: super(key: key); : super(key: key);
final String title; final String? title;
final String? subtitle; final String? subtitle;
final bool showBackBtn; final bool showBackBtn;
final bool isTransparent; final bool isTransparent;
@ -40,10 +46,12 @@ class SimpleHeader extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (title != null)
Text( Text(
title.toUpperCase(), title!.toUpperCase(),
textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false), textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false),
style: $styles.text.h4.copyWith(color: $styles.colors.offWhite, fontWeight: FontWeight.w500), style:
$styles.text.h4.copyWith(color: $styles.colors.offWhite, fontWeight: FontWeight.w500),
), ),
if (subtitle != null) if (subtitle != null)
Text( Text(

View File

@ -123,8 +123,8 @@ class _ScrollDecoratorState extends State<ScrollDecorator> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
content = widget.builder(currentController); content = widget.builder(currentController);
return ListenableBuilder( return AnimatedBuilder(
listenable: currentController, animation: currentController,
builder: (_, __) { builder: (_, __) {
return Stack( return Stack(
children: [ children: [

View File

@ -8,8 +8,8 @@ class FadeColorTransition extends StatelessWidget {
final Color color; final Color color;
@override @override
Widget build(BuildContext context) => ListenableBuilder( Widget build(BuildContext context) => AnimatedBuilder(
listenable: animation, animation: animation,
builder: (_, __) => Container(color: color.withOpacity(animation.value)), builder: (_, __) => Container(color: color.withOpacity(animation.value)),
); );
} }

View File

@ -1,11 +0,0 @@
import 'package:flutter/cupertino.dart';
/// Replacement for the built in [AnimatedBuilder] because that name is semantically confusing.
class ListenableBuilder extends AnimatedBuilder {
const ListenableBuilder({
super.key,
required Listenable listenable,
required super.builder,
super.child,
}) : super(animation: listenable);
}

View File

@ -87,7 +87,11 @@ class _BaseContentModal extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return IntrinsicHeight(
child: Center(
child: SizedBox(
width: $styles.sizes.maxContentWidth3,
child: Padding(
padding: EdgeInsets.all($styles.insets.lg), padding: EdgeInsets.all($styles.insets.lg),
child: LightText( child: LightText(
child: SeparatedColumn( child: SeparatedColumn(
@ -102,6 +106,9 @@ class _BaseContentModal extends StatelessWidget {
], ],
), ),
), ),
),
),
),
); );
} }
} }

View File

@ -26,8 +26,8 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget content = ListenableBuilder( Widget content = AnimatedBuilder(
listenable: _isZoomed, animation: _isZoomed,
builder: (_, __) { builder: (_, __) {
final bool enableSwipe = !_isZoomed.value && widget.urls.length > 1; final bool enableSwipe = !_isZoomed.value && widget.urls.length > 1;
return PageView.builder( return PageView.builder(

View File

@ -0,0 +1,16 @@
import 'package:wonders/common_libs.dart';
class PopNavigatorUnderlay extends StatelessWidget {
const PopNavigatorUnderlay({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ExcludeSemantics(
child: AppBtn.basic(
onPressed: () => Navigator.of(context).pop(),
semanticLabel: '',
child: const SizedBox.expand(),
),
);
}
}

View File

@ -1,9 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart';
class PageRoutes { class PageRoutes {
static const Duration kDefaultDuration = Duration(milliseconds: 300); static const Duration kDefaultDuration = Duration(milliseconds: 300);
static Route<T> dialog<T>(Widget child, [Duration duration = kDefaultDuration, bool opaque = false]) { static Route<T> dialog<T>(Widget child, {Duration duration = kDefaultDuration, bool opaque = false}) {
// Use cupertino routes for all dialogs so we get the 'swipe right to go back' behavior
if (opaque) {
return CupertinoPageRoute(builder: (_) => child);
}
// SB: Removed this in favor of Cupertino routes, we could restore with a `useFade` option
return PageRouteBuilder<T>( return PageRouteBuilder<T>(
transitionDuration: duration, transitionDuration: duration,
reverseTransitionDuration: duration, reverseTransitionDuration: duration,

View File

@ -4,7 +4,7 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/data/highlight_data.dart'; import 'package:wonders/logic/data/highlight_data.dart';
import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/app_icons.dart';
import 'package:wonders/ui/common/controls/app_page_indicator.dart'; import 'package:wonders/ui/common/controls/app_page_indicator.dart';
import 'package:wonders/ui/common/controls/simple_header.dart'; import 'package:wonders/ui/common/controls/app_header.dart';
import 'package:wonders/ui/common/static_text_scale.dart'; import 'package:wonders/ui/common/static_text_scale.dart';
part 'widgets/_blurred_image_bg.dart'; part 'widgets/_blurred_image_bg.dart';
@ -122,8 +122,8 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
), ),
/// Header /// Header
SimpleHeader( AppHeader(
$strings.artifactsTitleArtifacts, title: $strings.artifactsTitleArtifacts,
showBackBtn: false, showBackBtn: false,
isTransparent: true, isTransparent: true,
trailing: (context) => CircleBtn( trailing: (context) => CircleBtn(

View File

@ -5,8 +5,8 @@ import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
import 'package:wonders/ui/common/gradient_container.dart'; import 'package:wonders/ui/common/gradient_container.dart';
import 'package:wonders/ui/common/modals/fullscreen_url_img_viewer.dart'; import 'package:wonders/ui/common/modals/fullscreen_url_img_viewer.dart';
part 'widgets/_content.dart'; part 'widgets/_info_column.dart';
part 'widgets/_header.dart'; part 'widgets/_image_btn.dart';
class ArtifactDetailsScreen extends StatefulWidget { class ArtifactDetailsScreen extends StatefulWidget {
const ArtifactDetailsScreen({Key? key, required this.artifactId}) : super(key: key); const ArtifactDetailsScreen({Key? key, required this.artifactId}) : super(key: key);
@ -36,8 +36,8 @@ class _ArtifactDetailsScreenState extends State<ArtifactDetailsScreen> {
} else { } else {
content = hzMode content = hzMode
? Row(children: [ ? Row(children: [
Expanded(child: _Header(data: data!)), Expanded(child: _ImageBtn(data: data!)),
Expanded(child: Center(child: SizedBox(width: 600, child: _Content(data: data)))), Expanded(child: Center(child: SizedBox(width: 600, child: _InfoColumn(data: data)))),
]) ])
: CustomScrollView( : CustomScrollView(
slivers: [ slivers: [
@ -47,9 +47,9 @@ class _ArtifactDetailsScreenState extends State<ArtifactDetailsScreen> {
leading: SizedBox.shrink(), leading: SizedBox.shrink(),
expandedHeight: context.heightPx * .5, expandedHeight: context.heightPx * .5,
collapsedHeight: context.heightPx * .35, collapsedHeight: context.heightPx * .35,
flexibleSpace: _Header(data: data!), flexibleSpace: _ImageBtn(data: data!),
), ),
SliverToBoxAdapter(child: _Content(data: data)), SliverToBoxAdapter(child: _InfoColumn(data: data)),
], ],
); );
} }

View File

@ -1,7 +1,7 @@
part of '../artifact_details_screen.dart'; part of '../artifact_details_screen.dart';
class _Header extends StatelessWidget { class _ImageBtn extends StatelessWidget {
const _Header({Key? key, required this.data}) : super(key: key); const _ImageBtn({Key? key, required this.data}) : super(key: key);
final ArtifactData data; final ArtifactData data;
@override @override

View File

@ -1,7 +1,7 @@
part of '../artifact_details_screen.dart'; part of '../artifact_details_screen.dart';
class _Content extends StatelessWidget { class _InfoColumn extends StatelessWidget {
const _Content({Key? key, required this.data}) : super(key: key); const _InfoColumn({Key? key, required this.data}) : super(key: key);
final ArtifactData data; final ArtifactData data;
@override @override

View File

@ -3,7 +3,7 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/logic/data/wonder_data.dart';
import 'package:wonders/logic/data/wonders_data/search/search_data.dart'; import 'package:wonders/logic/data/wonders_data/search/search_data.dart';
import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/app_icons.dart';
import 'package:wonders/ui/common/controls/simple_header.dart'; import 'package:wonders/ui/common/controls/app_header.dart';
import 'package:wonders/ui/common/static_text_scale.dart'; import 'package:wonders/ui/common/static_text_scale.dart';
import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart';
import 'package:wonders/ui/screens/artifact/artifact_search/time_range_selector/expanding_time_range_selector.dart'; import 'package:wonders/ui/screens/artifact/artifact_search/time_range_selector/expanding_time_range_selector.dart';
@ -38,7 +38,7 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
@override @override
void initState() { void initState() {
_search(); _updateResults();
panelController.addListener(() { panelController.addListener(() {
AppHaptics.lightImpact(); AppHaptics.lightImpact();
}); });
@ -47,16 +47,18 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
void _handleSearchSubmitted(String query) { void _handleSearchSubmitted(String query) {
_query = query; _query = query;
_search(); _updateResults();
} }
void _handleTimelineChanged(double start, double end) { void _handleTimelineChanged(double start, double end) {
_startYear = start; _startYear = start;
_endYear = end; _endYear = end;
_filter(); _updateFilter();
} }
void _search() { void _handleResultPressed(SearchData o) => context.push(ScreenPaths.artifact(o.id.toString()));
void _updateResults() {
if (_query.isEmpty) { if (_query.isEmpty) {
_searchResults = wonder.searchData; _searchResults = wonder.searchData;
} else { } else {
@ -66,10 +68,10 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
_searchResults = wonder.searchData.where((o) => o.title.contains(q) || o.keywords.contains(q)).toList(); _searchResults = wonder.searchData.where((o) => o.title.contains(q) || o.keywords.contains(q)).toList();
} }
vizController.value = _searchResults; vizController.value = _searchResults;
_filter(); _updateFilter();
} }
void _filter() { void _updateFilter() {
_filteredResults = _searchResults.where((o) => o.year >= _startYear && o.year <= _endYear).toList(); _filteredResults = _searchResults.where((o) => o.year >= _startYear && o.year <= _endYear).toList();
setState(() {}); setState(() {});
} }
@ -79,11 +81,11 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
// tone down the orange just a bit: // tone down the orange just a bit:
vizController.color = Color.lerp($styles.colors.accent1, $styles.colors.black, 0.2)!; vizController.color = Color.lerp($styles.colors.accent1, $styles.colors.black, 0.2)!;
Widget content = GestureDetector( Widget content = GestureDetector(
onTap: () => WidgetsBinding.instance.focusManager.primaryFocus?.unfocus(), onTap: FocusManager.instance.primaryFocus?.unfocus,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
SimpleHeader($strings.artifactsSearchTitleBrowse, subtitle: wonder.title), AppHeader(title: $strings.artifactsSearchTitleBrowse, subtitle: wonder.title),
Gap($styles.insets.xs), Gap($styles.insets.xs),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm), padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm),
@ -101,7 +103,7 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
? _buildEmptyIndicator(context) ? _buildEmptyIndicator(context)
: _ResultsGrid( : _ResultsGrid(
searchResults: _filteredResults, searchResults: _filteredResults,
onPressed: (o) => context.push(ScreenPaths.artifact(o.id.toString())), onPressed: _handleResultPressed,
), ),
), ),
), ),

View File

@ -34,8 +34,16 @@ class _SearchInput extends StatelessWidget {
Widget _buildSuggestionsView(BuildContext context, onSelected, Iterable<String> results, BoxConstraints constraints) { Widget _buildSuggestionsView(BuildContext context, onSelected, Iterable<String> results, BoxConstraints constraints) {
List<Widget> items = results.map((str) => _buildSuggestion(context, str, () => onSelected(str))).toList(); List<Widget> items = results.map((str) => _buildSuggestion(context, str, () => onSelected(str))).toList();
items.insert(0, _buildSuggestionTitle(context)); items.insert(0, _buildSuggestionTitle(context));
return Stack(
return TopLeft( children: [
ExcludeSemantics(
child: AppBtn.basic(
onPressed: FocusManager.instance.primaryFocus!.unfocus,
semanticLabel: '',
child: SizedBox.expand(),
),
),
TopLeft(
child: Container( child: Container(
margin: EdgeInsets.only(top: $styles.insets.xxs), margin: EdgeInsets.only(top: $styles.insets.xxs),
width: constraints.maxWidth, width: constraints.maxWidth,
@ -64,6 +72,8 @@ class _SearchInput extends StatelessWidget {
), ),
), ),
), ),
),
],
); );
} }

View File

@ -1,7 +1,9 @@
import 'package:particle_field/particle_field.dart'; import 'package:particle_field/particle_field.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/data/collectible_data.dart'; import 'package:wonders/logic/data/collectible_data.dart';
import 'package:wonders/ui/common/app_backdrop.dart';
import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/centered_box.dart';
import 'package:wonders/ui/common/pop_navigator_underlay.dart';
part 'widgets/_animated_ribbon.dart'; part 'widgets/_animated_ribbon.dart';
part 'widgets/_celebration_particles.dart'; part 'widgets/_celebration_particles.dart';
@ -49,8 +51,19 @@ class CollectibleFoundScreen extends StatelessWidget {
Widget _buildDetail(BuildContext context) { Widget _buildDetail(BuildContext context) {
Duration t = $styles.times.fast; Duration t = $styles.times.fast;
return Stack(key: ValueKey('detail'), children: [ return Stack(key: ValueKey('detail'), children: [
Animate().custom(duration: t, builder: (context, ratio, _) => _buildGradient(context, 1, ratio)), /// Background
AppBackdrop(
strength: .5,
child: Container(color: $styles.colors.greyStrong.withOpacity(.96)),
).animate().fadeIn(),
/// Particles
_CelebrationParticles(fadeMs: (t * 6).inMilliseconds), _CelebrationParticles(fadeMs: (t * 6).inMilliseconds),
/// invisible close btn
PopNavigatorUnderlay(),
/// Content
SafeArea( SafeArea(
child: CenteredBox( child: CenteredBox(
width: $styles.sizes.maxContentWidth3, width: $styles.sizes.maxContentWidth3,

View File

@ -5,7 +5,7 @@ import 'package:wonders/logic/collectibles_logic.dart';
import 'package:wonders/logic/data/collectible_data.dart'; import 'package:wonders/logic/data/collectible_data.dart';
import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/logic/data/wonder_data.dart';
import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/centered_box.dart';
import 'package:wonders/ui/common/controls/simple_header.dart'; import 'package:wonders/ui/common/controls/app_header.dart';
import 'package:wonders/ui/common/modals/app_modals.dart'; import 'package:wonders/ui/common/modals/app_modals.dart';
part 'widgets/_collectible_image.dart'; part 'widgets/_collectible_image.dart';
@ -63,7 +63,7 @@ class _CollectionScreenState extends State<CollectionScreen> with GetItStateMixi
children: [ children: [
Expanded( Expanded(
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
SimpleHeader($strings.collectionTitleCollection), AppHeader(title: $strings.collectionTitleCollection),
_NewlyDiscoveredItemsBtn(count: discovered, onPressed: _scrollToTarget), _NewlyDiscoveredItemsBtn(count: discovered, onPressed: _scrollToTarget),
Flexible( Flexible(
child: _CollectionList( child: _CollectionList(

View File

@ -164,8 +164,8 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
), ),
/// Home Btn /// Home Btn
ListenableBuilder( AnimatedBuilder(
listenable: _scroller, animation: _scroller,
builder: (_, child) { builder: (_, child) {
return AnimatedOpacity( return AnimatedOpacity(
opacity: _scrollPos.value > 0 ? 0 : 1, opacity: _scrollPos.value > 0 ? 0 : 1,

View File

@ -94,8 +94,8 @@ class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with S
@override @override
Widget build(_) { Widget build(_) {
return ListenableBuilder( return AnimatedBuilder(
listenable: _anim, animation: _anim,
builder: (_, __) { builder: (_, __) {
var rot = _prevIndex > widget.index ? -pi : pi; var rot = _prevIndex > widget.index ? -pi : pi;
return Transform.rotate( return Transform.rotate(

View File

@ -49,8 +49,8 @@ class _TitleText extends StatelessWidget {
/// Wonder title text /// Wonder title text
Semantics( Semantics(
sortKey: OrdinalSortKey(0), sortKey: OrdinalSortKey(0),
child: ListenableBuilder( child: AnimatedBuilder(
listenable: scroller, animation: scroller,
builder: (_, __) { builder: (_, __) {
final yPos = ContextUtils.getGlobalPos(context)?.dy ?? 0; final yPos = ContextUtils.getGlobalPos(context)?.dy ?? 0;
bool enableHero = yPos > -100; bool enableHero = yPos > -100;
@ -71,8 +71,8 @@ class _TitleText extends StatelessWidget {
ExcludeSemantics( ExcludeSemantics(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: $styles.insets.md), padding: EdgeInsets.symmetric(horizontal: $styles.insets.md),
child: ListenableBuilder( child: AnimatedBuilder(
listenable: scroller, animation: scroller,
builder: (_, __) => CompassDivider( builder: (_, __) => CompassDivider(
isExpanded: scroller.position.pixels <= 0, isExpanded: scroller.position.pixels <= 0,
linesColor: data.type.fgColor, linesColor: data.type.fgColor,

View File

@ -11,7 +11,7 @@ class _TopIllustration extends StatelessWidget {
WonderIllustration(type, config: WonderIllustrationConfig.bg(enableAnims: false, shortMode: true)), WonderIllustration(type, config: WonderIllustrationConfig.bg(enableAnims: false, shortMode: true)),
Positioned.fill( Positioned.fill(
bottom: 50, bottom: 50,
child: AnimatedClouds(wonderType: type, enableAnimations: false, opacity: .5), child: AnimatedClouds(wonderType: type, enableAnimations: false, opacity: .5, cloudSize: 400),
), ),
Transform.translate( Transform.translate(
// Small bump down to make sure we cover the edge between the editorial page and the sky. // Small bump down to make sure we cover the edge between the editorial page and the sky.

View File

@ -62,6 +62,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
WonderType? pickedWonder = await appLogic.showFullscreenDialogRoute<WonderType>( WonderType? pickedWonder = await appLogic.showFullscreenDialogRoute<WonderType>(
context, context,
HomeMenu(data: currentWonder), HomeMenu(data: currentWonder),
transparent: true,
); );
setState(() => _isMenuOpen = false); setState(() => _isMenuOpen = false);
if (pickedWonder != null) { if (pickedWonder != null) {

View File

@ -5,6 +5,7 @@ import 'package:wonders/logic/data/wonder_data.dart';
import 'package:wonders/ui/common/app_backdrop.dart'; import 'package:wonders/ui/common/app_backdrop.dart';
import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/app_icons.dart';
import 'package:wonders/ui/common/controls/locale_switcher.dart'; import 'package:wonders/ui/common/controls/locale_switcher.dart';
import 'package:wonders/ui/common/pop_navigator_underlay.dart';
import 'package:wonders/ui/screens/home_menu/about_dialog_content.dart'; import 'package:wonders/ui/screens/home_menu/about_dialog_content.dart';
class HomeMenu extends StatelessWidget { class HomeMenu extends StatelessWidget {
@ -48,11 +49,11 @@ class HomeMenu extends StatelessWidget {
/// Backdrop / Underlay /// Backdrop / Underlay
AppBackdrop( AppBackdrop(
strength: .5, strength: .5,
child: Container( child: Container(color: $styles.colors.greyStrong.withOpacity(.7)),
color: $styles.colors.greyStrong.withOpacity(.7),
),
), ),
PopNavigatorUnderlay(),
SafeArea( SafeArea(
child: PaddedRow( child: PaddedRow(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(

View File

@ -107,15 +107,14 @@ class _PhotoGalleryState extends State<PhotoGallery> {
Future<void> _handleImageTapped(int index) async { Future<void> _handleImageTapped(int index) async {
if (_index == index) { if (_index == index) {
int? newIndex = await Navigator.push(
context,
CupertinoPageRoute(builder: (_) {
final urls = _photoIds.value.map((e) { final urls = _photoIds.value.map((e) {
return UnsplashPhotoData.getSelfHostedUrl(e, UnsplashPhotoSize.med); return UnsplashPhotoData.getSelfHostedUrl(e, UnsplashPhotoSize.med);
}).toList(); }).toList();
return FullscreenUrlImgViewer(urls: urls, index: _index); int? newIndex = await appLogic.showFullscreenDialogRoute(
}), context,
FullscreenUrlImgViewer(urls: urls, index: _index),
); );
if (newIndex != null) { if (newIndex != null) {
_setIndex(newIndex, skipAnimation: true); _setIndex(newIndex, skipAnimation: true);
} }
@ -137,13 +136,10 @@ class _PhotoGalleryState extends State<PhotoGallery> {
return Center(child: AppLoadingIndicator()); return Center(child: AppLoadingIndicator());
} }
Size imgSize = context.isLandscape Size imgSize = Size(context.widthPx * .5, context.heightPx * .5);
? Size(context.widthPx * .5, context.heightPx * .66)
: Size(context.widthPx * .66, context.heightPx * .5);
imgSize = (widget.imageSize ?? imgSize) * _scale; imgSize = (widget.imageSize ?? imgSize) * _scale;
// Get transform offset for the current _index // Get transform offset for the current _index
final padding = $styles.insets.md; final padding = $styles.insets.md;
var gridOffset = _calculateCurrentOffset(padding, imgSize); var gridOffset = _calculateCurrentOffset(padding, imgSize);
gridOffset += Offset(0, -context.mq.padding.top / 2); gridOffset += Offset(0, -context.mq.padding.top / 2);
final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration; final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration;

View File

@ -9,7 +9,7 @@ import 'package:wonders/logic/data/timeline_data.dart';
import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/logic/data/wonder_data.dart';
import 'package:wonders/ui/common/blend_mask.dart'; import 'package:wonders/ui/common/blend_mask.dart';
import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/centered_box.dart';
import 'package:wonders/ui/common/controls/simple_header.dart'; import 'package:wonders/ui/common/controls/app_header.dart';
import 'package:wonders/ui/common/dashed_line.dart'; import 'package:wonders/ui/common/dashed_line.dart';
import 'package:wonders/ui/common/list_gradient.dart'; import 'package:wonders/ui/common/list_gradient.dart';
import 'package:wonders/ui/common/timeline_event_card.dart'; import 'package:wonders/ui/common/timeline_event_card.dart';
@ -55,7 +55,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
padding: EdgeInsets.only(bottom: 0), padding: EdgeInsets.only(bottom: 0),
child: Column( child: Column(
children: [ children: [
SimpleHeader($strings.timelineTitleGlobalTimeline), AppHeader(title: $strings.timelineTitleGlobalTimeline),
/// Vertically scrolling timeline, manages a ScrollController. /// Vertically scrolling timeline, manages a ScrollController.
Expanded( Expanded(

View File

@ -57,8 +57,8 @@ class _BottomScrubber extends StatelessWidget {
), ),
/// Visible area, follows the position of scroller /// Visible area, follows the position of scroller
ListenableBuilder( AnimatedBuilder(
listenable: scroller, animation: scroller,
builder: (_, __) { builder: (_, __) {
ScrollPosition? pos; ScrollPosition? pos;
if (scroller.hasClients) pos = scroller.position; if (scroller.hasClients) pos = scroller.position;

View File

@ -61,8 +61,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
// Dashed line with a year that changes as we scroll // Dashed line with a year that changes as we scroll
IgnorePointer( IgnorePointer(
ignoringSemantics: false, ignoringSemantics: false,
child: ListenableBuilder( child: AnimatedBuilder(
listenable: controller.scroller, animation: controller.scroller,
builder: (_, __) { builder: (_, __) {
return _DashedDividerWithYear(controller.calculateYearFromScrollPos()); return _DashedDividerWithYear(controller.calculateYearFromScrollPos());
}, },
@ -79,8 +79,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
Widget buildTimelineSection(WonderData data) { Widget buildTimelineSection(WonderData data) {
return ClipRRect( return ClipRRect(
borderRadius: BorderRadius.circular(99), borderRadius: BorderRadius.circular(99),
child: ListenableBuilder( child: AnimatedBuilder(
listenable: controller.scroller, animation: controller.scroller,
builder: (_, __) => TimelineSection( builder: (_, __) => TimelineSection(
data, data,
controller.calculateYearFromScrollPos(), controller.calculateYearFromScrollPos(),
@ -127,8 +127,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
), ),
/// Event Markers, rebuilds on scroll /// Event Markers, rebuilds on scroll
ListenableBuilder( AnimatedBuilder(
listenable: controller.scroller, animation: controller.scroller,
builder: (_, __) => _EventMarkers( builder: (_, __) => _EventMarkers(
controller.calculateYearFromScrollPos(), controller.calculateYearFromScrollPos(),
onEventChanged: _handleEventMarkerChanged, onEventChanged: _handleEventMarkerChanged,

View File

@ -4,7 +4,11 @@ class WonderDetailsTabMenu extends StatelessWidget {
static double bottomPadding = 0; static double bottomPadding = 0;
static double buttonInset = 12; static double buttonInset = 12;
const WonderDetailsTabMenu({Key? key, required this.tabController, this.showBg = false, required this.wonderType}) const WonderDetailsTabMenu(
{Key? key,
required this.tabController,
this.showBg = false,
required this.wonderType})
: super(key: key); : super(key: key);
final TabController tabController; final TabController tabController;
@ -32,7 +36,10 @@ class WonderDetailsTabMenu extends StatelessWidget {
), ),
// Buttons // Buttons
Padding( Padding(
padding: EdgeInsets.only(left: $styles.insets.sm, right: $styles.insets.xxs, bottom: bottomPadding), padding: EdgeInsets.only(
left: $styles.insets.sm,
right: $styles.insets.xxs,
bottom: bottomPadding),
// TabButtons are a Stack with a row of icon buttons, and an illustrated home button sitting on top. // TabButtons are a Stack with a row of icon buttons, and an illustrated home button sitting on top.
// The home buttons shows / hides itself based on `showHomeBtn` // The home buttons shows / hides itself based on `showHomeBtn`
// The row contains an animated placeholder gap which makes room for the icon as it transitions in. // The row contains an animated placeholder gap which makes room for the icon as it transitions in.
@ -52,13 +59,21 @@ class WonderDetailsTabMenu extends StatelessWidget {
borderSize: showBg ? 6 : 2, borderSize: showBg ? 6 : 2,
), ),
_TabBtn(0, tabController, _TabBtn(0, tabController,
iconImg: 'editorial', label: $strings.wonderDetailsTabLabelInformation, color: iconColor), iconImg: 'editorial',
label: $strings.wonderDetailsTabLabelInformation,
color: iconColor),
_TabBtn(1, tabController, _TabBtn(1, tabController,
iconImg: 'photos', label: $strings.wonderDetailsTabLabelImages, color: iconColor), iconImg: 'photos',
label: $strings.wonderDetailsTabLabelImages,
color: iconColor),
_TabBtn(2, tabController, _TabBtn(2, tabController,
iconImg: 'artifacts', label: $strings.wonderDetailsTabLabelArtifacts, color: iconColor), iconImg: 'artifacts',
label: $strings.wonderDetailsTabLabelArtifacts,
color: iconColor),
_TabBtn(3, tabController, _TabBtn(3, tabController,
iconImg: 'timeline', label: $strings.wonderDetailsTabLabelEvents, color: iconColor), iconImg: 'timeline',
label: $strings.wonderDetailsTabLabelEvents,
color: iconColor),
], ],
), ),
), ),
@ -72,7 +87,11 @@ class WonderDetailsTabMenu extends StatelessWidget {
} }
class _WonderHomeBtn extends StatelessWidget { class _WonderHomeBtn extends StatelessWidget {
const _WonderHomeBtn({Key? key, required this.size, required this.wonderType, required this.borderSize}) const _WonderHomeBtn(
{Key? key,
required this.size,
required this.wonderType,
required this.borderSize})
: super(key: key); : super(key: key);
final double size; final double size;
@ -94,7 +113,8 @@ class _WonderHomeBtn extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(99), borderRadius: BorderRadius.circular(99),
color: wonderType.fgColor, color: wonderType.fgColor,
image: DecorationImage(image: AssetImage(wonderType.homeBtn), fit: BoxFit.fill), image: DecorationImage(
image: AssetImage(wonderType.homeBtn), fit: BoxFit.fill),
), ),
), ),
); );
@ -111,6 +131,9 @@ class _TabBtn extends StatelessWidget {
required this.label, required this.label,
}) : super(key: key); }) : super(key: key);
static const double _minWidth = 50;
static const double _maxWidth = 120;
final int index; final int index;
final TabController tabController; final TabController tabController;
final String iconImg; final String iconImg;
@ -120,23 +143,29 @@ class _TabBtn extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool selected = tabController.index == index; bool selected = tabController.index == index;
final MaterialLocalizations localizations = MaterialLocalizations.of(context); final MaterialLocalizations localizations =
final iconImgPath = '${ImagePaths.common}/tab-$iconImg${selected ? '-active' : ''}.png'; MaterialLocalizations.of(context);
String tabLabel = localizations.tabLabel(tabIndex: index + 1, tabCount: tabController.length); final iconImgPath =
'${ImagePaths.common}/tab-$iconImg${selected ? '-active' : ''}.png';
String tabLabel = localizations.tabLabel(
tabIndex: index + 1, tabCount: tabController.length);
tabLabel = '$label: $tabLabel'; tabLabel = '$label: $tabLabel';
final double btnWidth = (context.widthPx / 6).clamp(50, 120); final double btnWidth = (context.widthPx / 6).clamp(_minWidth, _maxWidth);
return MergeSemantics( return MergeSemantics(
child: Semantics( child: Semantics(
selected: selected, selected: selected,
label: tabLabel, label: tabLabel,
child: ExcludeSemantics( child: ExcludeSemantics(
child: AppBtn.basic( child: AppBtn.basic(
padding: EdgeInsets.only(top: $styles.insets.md + $styles.insets.xs, bottom: $styles.insets.sm), padding: EdgeInsets.only(
top: $styles.insets.md + $styles.insets.xs,
bottom: $styles.insets.sm),
onPressed: () => tabController.index = index, onPressed: () => tabController.index = index,
semanticLabel: label, semanticLabel: label,
minimumSize: Size(btnWidth, 0), minimumSize: Size(btnWidth, 0),
// Image icon // Image icon
child: Image.asset(iconImgPath, height: 32, width: 32, color: selected ? null : color), child: Image.asset(iconImgPath,
height: 32, width: 32, color: selected ? null : color),
), ),
), ),
), ),

View File

@ -56,7 +56,6 @@ class _EventsListState extends State<_EventsList> {
} }
return Stack( return Stack(
children: [ children: [
//TODO: Remove scrollbar on portrait
SingleChildScrollView( SingleChildScrollView(
controller: _scroller, controller: _scroller,
child: Column( child: Column(
@ -101,10 +100,11 @@ class _EventsListState extends State<_EventsList> {
); );
} }
/// Wraps the list in a scroll listener /// Wraps the list in a scroll listener that fades in an underlay as the content
/// is scrolled
Widget _buildScrollingListWithBlur() { Widget _buildScrollingListWithBlur() {
return ListenableBuilder( return AnimatedBuilder(
listenable: _scroller, animation: _scroller,
child: _buildScrollingList(), child: _buildScrollingList(),
builder: (_, child) { builder: (_, child) {
bool showBackdrop = true; bool showBackdrop = true;

View File

@ -4,6 +4,7 @@ import 'package:wonders/logic/data/wonder_data.dart';
import 'package:wonders/ui/common/app_backdrop.dart'; import 'package:wonders/ui/common/app_backdrop.dart';
import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/app_icons.dart';
import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/centered_box.dart';
import 'package:wonders/ui/common/controls/app_header.dart';
import 'package:wonders/ui/common/curved_clippers.dart'; import 'package:wonders/ui/common/curved_clippers.dart';
import 'package:wonders/ui/common/hidden_collectible.dart'; import 'package:wonders/ui/common/hidden_collectible.dart';
import 'package:wonders/ui/common/list_gradient.dart'; import 'package:wonders/ui/common/list_gradient.dart';
@ -35,17 +36,20 @@ class WonderEvents extends StatelessWidget {
/// Main view switches between portrait and landscape views /// Main view switches between portrait and landscape views
Positioned.fill( Positioned.fill(
top: $styles.insets.lg, top: $styles.insets.lg,
child: context.isLandscape ? _buildLandscape() : _buildPortrait(), child: context.isLandscape ? _buildLandscape(context) : _buildPortrait(),
), ),
/// Floating TimelineBtn /// Header w/ TimelineBtn
Positioned( TopCenter(
right: $styles.insets.lg, child: AppHeader(
top: $styles.insets.lg, showBackBtn: false,
child: CircleIconBtn( isTransparent: true,
trailing: (_) => CircleIconBtn(
icon: AppIcons.timeline, icon: AppIcons.timeline,
onPressed: handleTimelineBtnPressed, onPressed: handleTimelineBtnPressed,
semanticLabel: $strings.eventsListButtonOpenGlobal)), semanticLabel: $strings.eventsListButtonOpenGlobal),
),
),
], ],
), ),
), ),
@ -54,7 +58,7 @@ class WonderEvents extends StatelessWidget {
} }
/// Landscape layout is a row, with the WonderImage on left and EventsList on the right /// Landscape layout is a row, with the WonderImage on left and EventsList on the right
Widget _buildLandscape() { Widget _buildLandscape(BuildContext context) {
return Row( return Row(
children: [ children: [
/// WonderImage w/ Timeline btn /// WonderImage w/ Timeline btn
@ -66,10 +70,19 @@ class WonderEvents extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Gap($styles.insets.lg), Gap($styles.insets.lg),
Expanded(child: Center(child: _WonderImageWithTimeline(data: _data, height: 500))), Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_WonderImageWithTimeline(data: _data, height: min(500, context.heightPx - 300)),
Gap($styles.insets.lg), Gap($styles.insets.lg),
SizedBox(width: 300, child: _TimelineBtn(type: type)), SizedBox(width: 300, child: _TimelineBtn(type: type)),
Gap($styles.insets.xl), ],
),
),
),
Gap($styles.insets.lg),
], ],
), ),
), ),
@ -78,7 +91,7 @@ class WonderEvents extends StatelessWidget {
/// EventsList /// EventsList
Expanded( Expanded(
child: CenteredBox( child: CenteredBox(
width: $styles.sizes.maxContentWidth1, width: $styles.sizes.maxContentWidth2,
child: _EventsList( child: _EventsList(
data: _data, data: _data,
topHeight: 100, topHeight: 100,

View File

@ -3,16 +3,18 @@ import 'dart:async';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/utils/context_utils.dart'; import 'package:wonders/ui/common/utils/context_utils.dart';
//TODO: Make the clouds fade out and in // TODO: Clouds should fade in and out
// Shows a set of clouds that animated onto stage. // Shows a set of clouds that animated onto stage.
// When value-key is changed, a new set of clouds will animate into place and the old ones will animate out. // When value-key is changed, a new set of clouds will animate into place and the old ones will animate out.
// Uses a random seed system, to make sure we get the same set of clouds for each wonder, without actually having to hand-position them. // Uses a random seed system, to make sure we get the same set of clouds for each wonder, without actually having to hand-position them.
class AnimatedClouds extends StatefulWidget with GetItStatefulWidgetMixin { class AnimatedClouds extends StatefulWidget with GetItStatefulWidgetMixin {
AnimatedClouds({Key? key, this.enableAnimations = true, required this.wonderType, required this.opacity}) AnimatedClouds(
{Key? key, this.enableAnimations = true, required this.wonderType, required this.opacity, this.cloudSize = 500})
: super(key: key); : super(key: key);
final WonderType wonderType; final WonderType wonderType;
final bool enableAnimations; final bool enableAnimations;
final double opacity; final double opacity;
final double cloudSize;
@override @override
State<AnimatedClouds> createState() => _AnimatedCloudsState(); State<AnimatedClouds> createState() => _AnimatedCloudsState();
} }
@ -76,7 +78,7 @@ class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProvid
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Old clouds animate from 0 to startOffset, new clouds do the opposite. // Old clouds animate from 0 to startOffset, new clouds do the opposite.
Widget buildCloud(c, {required bool isOld, required int startOffset}) { Widget buildCloud(_Cloud c, {required bool isOld, required int startOffset}) {
// Use a positive, or negative start offset, based on index // Use a positive, or negative start offset, based on index
final stOffset = _clouds.indexOf(c) % 2 == 0 ? -startOffset : startOffset; final stOffset = _clouds.indexOf(c) % 2 == 0 ? -startOffset : startOffset;
// If old, we will end at the stOffset and start at 0, if new, start at stOffset, and end at 0 // If old, we will end at the stOffset and start at 0, if new, start at stOffset, and end at 0
@ -84,15 +86,15 @@ class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProvid
return Positioned( return Positioned(
top: c.pos.dy, top: c.pos.dy,
left: isOld ? c.pos.dx - stOffset * curvedValue : c.pos.dx + stOffset * (1 - curvedValue), left: isOld ? c.pos.dx - stOffset * curvedValue : c.pos.dx + stOffset * (1 - curvedValue),
child: c, child: Opacity(opacity: isOld ? 1 - _anim.value : _anim.value, child: c),
); );
} }
return RepaintBoundary( return RepaintBoundary(
child: ClipRect( child: ClipRect(
child: OverflowBox( child: OverflowBox(
child: ListenableBuilder( child: AnimatedBuilder(
listenable: _anim, animation: _anim,
builder: (_, __) { builder: (_, __) {
// A stack with 2 sets of clouds, one set is moving out of view while the other moves in. // A stack with 2 sets of clouds, one set is moving out of view while the other moves in.
return Stack( return Stack(
@ -123,19 +125,22 @@ class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProvid
flipX: rnd.getBool(), flipX: rnd.getBool(),
flipY: rnd.getBool(), flipY: rnd.getBool(),
opacity: widget.opacity, opacity: widget.opacity,
size: widget.cloudSize,
); );
}).toList(); }).toList();
} }
} }
class _Cloud extends StatelessWidget { class _Cloud extends StatelessWidget {
const _Cloud(this.pos,
{this.scale = 1, this.flipX = false, this.flipY = false, required this.opacity, required this.size});
final Offset pos; final Offset pos;
final double scale; final double scale;
final bool flipX; final bool flipX;
final bool flipY; final bool flipY;
final double opacity; final double opacity;
final double size;
const _Cloud(this.pos, {this.scale = 1, this.flipX = false, this.flipY = false, required this.opacity});
@override @override
Widget build(BuildContext context) => Transform.scale( Widget build(BuildContext context) => Transform.scale(
@ -144,7 +149,7 @@ class _Cloud extends StatelessWidget {
child: Image.asset( child: Image.asset(
ImagePaths.cloud, ImagePaths.cloud,
opacity: AlwaysStoppedAnimation(.4 * opacity), opacity: AlwaysStoppedAnimation(.4 * opacity),
width: 400 * scale, width: size * scale,
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
), ),
); );

View File

@ -69,6 +69,7 @@ class _IllustrationPieceState extends State<IllustrationPiece> {
final wonderBuilder = context.watch<WonderIllustrationBuilderState>(); final wonderBuilder = context.watch<WonderIllustrationBuilderState>();
final type = wonderBuilder.widget.wonderType; final type = wonderBuilder.widget.wonderType;
final imgPath = '${type.assetPath}/${widget.fileName}'; final imgPath = '${type.assetPath}/${widget.fileName}';
// Dynamically determine the aspect ratio of the image, so we can more easily position it
if (aspectRatio == null) { if (aspectRatio == null) {
aspectRatio == 0; // indicates load has started, so we don't run twice aspectRatio == 0; // indicates load has started, so we don't run twice
rootBundle.load(imgPath).then((img) async { rootBundle.load(imgPath).then((img) async {
@ -111,21 +112,26 @@ class _IllustrationPieceState extends State<IllustrationPiece> {
height * widget.fractionalOffset!.dy, height * widget.fractionalOffset!.dy,
); );
} }
return Stack( Widget? content;
children: [ if (uiImage != null) {
if (widget.bottom != null) Positioned.fill(child: widget.bottom!.call(context)), content = Transform.translate(
if (uiImage != null) ...[
Transform.translate(
offset: finalTranslation, offset: finalTranslation,
child: Transform.scale( child: Transform.scale(
scale: 1 + (widget.zoomAmt * config.zoom) + introZoom, scale: 1 + (widget.zoomAmt * config.zoom) + introZoom,
child: SizedBox( child: SizedBox(
height: height, height: height,
width: height * aspectRatio!, width: height * aspectRatio!,
child: !widget.enableHero ? img : Hero(tag: '$type-${widget.fileName}', child: img), child: img,
),
), ),
), ),
);
}
return Stack(
children: [
if (widget.bottom != null) Positioned.fill(child: widget.bottom!.call(context)),
if (uiImage != null) ...[
widget.enableHero ? Hero(tag: '$type-${widget.fileName}', child: content!) : content!,
], ],
if (widget.top != null) Positioned.fill(child: widget.top!.call(context)), if (widget.top != null) Positioned.fill(child: widget.top!.call(context)),
], ],

View File

@ -12,13 +12,19 @@ class IllustrationTexture extends StatelessWidget {
final Animation<double>? opacity; final Animation<double>? opacity;
@override @override
Widget build(BuildContext context) => ListenableBuilder( Widget build(BuildContext context) => AnimatedBuilder(
listenable: opacity ?? AlwaysStoppedAnimation(1), animation: opacity ?? AlwaysStoppedAnimation(1),
builder: (context, child) => ClipRect( builder: (context, child) => ClipRect(
child: Transform.scale( child: Transform.scale(
scaleX: scale * (flipX ? -1 : 1), scaleX: scale * (flipX ? -1 : 1),
scaleY: scale * (flipY ? -1 : 1), scaleY: scale * (flipY ? -1 : 1),
child: Image.asset(path, repeat: ImageRepeat.repeat, fit: BoxFit.contain, alignment: Alignment.topCenter, color: color, opacity: opacity, cacheWidth: 2048)), child: Image.asset(path,
repeat: ImageRepeat.repeat,
fit: BoxFit.contain,
alignment: Alignment.topCenter,
color: color,
opacity: opacity,
cacheWidth: 2048)),
), ),
); );
} }

View File

@ -571,10 +571,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_fade name: image_fade
sha256: "6ec32003f451f3341f1b8ebd899b97e94f80ce0d66b13e59ade41ae03c34b464" sha256: "7296c9c53cd5de98e675ef1e27bdaa4035d6c3a45cf5b86094b2e545689b4ea6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.6.2"
image_gallery_saver: image_gallery_saver:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -34,7 +34,7 @@ dependencies:
google_maps_flutter_web: ^0.4.0+1 google_maps_flutter_web: ^0.4.0+1
go_router: ^4.2.8 go_router: ^4.2.8
http: ^0.13.5 http: ^0.13.5
image_fade: ^0.6.1 image_fade: ^0.6.2
image_gallery_saver: ^1.7.1 image_gallery_saver: ^1.7.1
internet_connection_checker: ^0.0.1+3 internet_connection_checker: ^0.0.1+3
intl: ^0.17.0 intl: ^0.17.0