mirror of
https://github.com/gskinnerTeam/flutter-wonderous-app.git
synced 2025-08-24 18:11:18 +08:00
Merge pull request #205 from gskinnerTeam/accessability-pass
Accessibility Improvements
This commit is contained in:
@ -32,8 +32,8 @@
|
|||||||
"appModalsButtonOk": "Ok",
|
"appModalsButtonOk": "Ok",
|
||||||
"appModalsButtonCancel": "Cancel",
|
"appModalsButtonCancel": "Cancel",
|
||||||
"appPageDefaultTitlePage": "page",
|
"appPageDefaultTitlePage": "page",
|
||||||
"appPageSemanticSwipe": "{pageTitle} {count} of {total}.",
|
"appPageSemanticSwipe": "{pageTitle} {current} of {total}.",
|
||||||
"@appPageSemanticSwipe": {"placeholders": {"pageTitle": {}, "total": {}, "count": {}}},
|
"@appPageSemanticSwipe": {"placeholders": {"pageTitle": {}, "current": {}, "total": {}}},
|
||||||
"artifactsTitleArtifacts": "ARTIFACTS",
|
"artifactsTitleArtifacts": "ARTIFACTS",
|
||||||
"semanticsPrevious": "Previous {title}",
|
"semanticsPrevious": "Previous {title}",
|
||||||
"@semanticsPrevious": {"placeholders": {"title": {}}},
|
"@semanticsPrevious": {"placeholders": {"title": {}}},
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"appModalsButtonOk": "确定",
|
"appModalsButtonOk": "确定",
|
||||||
"appModalsButtonCancel": "取消",
|
"appModalsButtonCancel": "取消",
|
||||||
"appPageDefaultTitlePage": "页",
|
"appPageDefaultTitlePage": "页",
|
||||||
"appPageSemanticSwipe": "{pageTitle} {total} 之 {count}.",
|
"appPageSemanticSwipe": "{pageTitle} {total} 之 {current}.",
|
||||||
"artifactsTitleArtifacts": "文物",
|
"artifactsTitleArtifacts": "文物",
|
||||||
"semanticsPrevious": "之前的文物{title}",
|
"semanticsPrevious": "之前的文物{title}",
|
||||||
"semanticsNext": "下一个文物{title}",
|
"semanticsNext": "下一个文物{title}",
|
||||||
|
45
lib/logic/common/animate_utils.dart
Normal file
45
lib/logic/common/animate_utils.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:wonders/common_libs.dart';
|
||||||
|
|
||||||
|
// ignore: must_be_immutable
|
||||||
|
class NeverAnimate extends Animate {
|
||||||
|
NeverAnimate({super.key, super.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NeverAnimate> createState() => _NeverAnimateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NeverAnimateState extends State<NeverAnimate> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => widget.child;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MaybeAnimateExtension on Widget {
|
||||||
|
Animate maybeAnimate({
|
||||||
|
Key? key,
|
||||||
|
List<Effect>? effects,
|
||||||
|
AnimateCallback? onInit,
|
||||||
|
AnimateCallback? onPlay,
|
||||||
|
AnimateCallback? onComplete,
|
||||||
|
bool? autoPlay,
|
||||||
|
Duration? delay,
|
||||||
|
AnimationController? controller,
|
||||||
|
Adapter? adapter,
|
||||||
|
double? target,
|
||||||
|
double? value,
|
||||||
|
}) => $styles.disableAnimations
|
||||||
|
? NeverAnimate(child: this)
|
||||||
|
: Animate(
|
||||||
|
key: key,
|
||||||
|
effects: effects,
|
||||||
|
onInit: onInit,
|
||||||
|
onPlay: onPlay,
|
||||||
|
onComplete: onComplete,
|
||||||
|
autoPlay: autoPlay,
|
||||||
|
delay: delay,
|
||||||
|
controller: controller,
|
||||||
|
adapter: adapter,
|
||||||
|
target: target,
|
||||||
|
value: value,
|
||||||
|
child: this,
|
||||||
|
);
|
||||||
|
}
|
@ -42,24 +42,20 @@ class ScreenPaths {
|
|||||||
|
|
||||||
// Routes that are used multiple times
|
// Routes that are used multiple times
|
||||||
AppRoute get _artifactRoute => AppRoute(
|
AppRoute get _artifactRoute => AppRoute(
|
||||||
'artifact/:artifactId',
|
'artifact/:artifactId',
|
||||||
(s) => ArtifactDetailsScreen(artifactId: s.pathParameters['artifactId']!),
|
(s) => ArtifactDetailsScreen(artifactId: s.pathParameters['artifactId']!),
|
||||||
);
|
);
|
||||||
|
|
||||||
AppRoute get _timelineRoute {
|
AppRoute get _timelineRoute => AppRoute(
|
||||||
return AppRoute(
|
'timeline',
|
||||||
'timeline',
|
(s) => TimelineScreen(type: _tryParseWonderType(s.uri.queryParameters['type']!)),
|
||||||
(s) => TimelineScreen(type: _tryParseWonderType(s.uri.queryParameters['type']!)),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AppRoute get _collectionRoute {
|
AppRoute get _collectionRoute => AppRoute(
|
||||||
return AppRoute(
|
'collection',
|
||||||
'collection',
|
(s) => CollectionScreen(fromId: s.uri.queryParameters['id'] ?? ''),
|
||||||
(s) => CollectionScreen(fromId: s.uri.queryParameters['id'] ?? ''),
|
routes: [_artifactRoute],
|
||||||
routes: [_artifactRoute],
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Routing table, matches string paths to UI Screens, optionally parses params from the paths
|
/// Routing table, matches string paths to UI Screens, optionally parses params from the paths
|
||||||
final appRouter = GoRouter(
|
final appRouter = GoRouter(
|
||||||
@ -132,7 +128,7 @@ class AppRoute extends GoRoute {
|
|||||||
body: builder(state),
|
body: builder(state),
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
);
|
);
|
||||||
if (useFade) {
|
if (useFade || $styles.disableAnimations) {
|
||||||
return CustomTransitionPage(
|
return CustomTransitionPage(
|
||||||
key: state.pageKey,
|
key: state.pageKey,
|
||||||
child: pageContent,
|
child: pageContent,
|
||||||
|
@ -7,6 +7,7 @@ class AppColors {
|
|||||||
/// Common
|
/// Common
|
||||||
final Color accent1 = Color(0xFFE4935D);
|
final Color accent1 = Color(0xFFE4935D);
|
||||||
final Color accent2 = Color(0xFFBEABA1);
|
final Color accent2 = Color(0xFFBEABA1);
|
||||||
|
final Color accent3 = Color(0xFFC47642);
|
||||||
final Color offWhite = Color(0xFFF8ECE5);
|
final Color offWhite = Color(0xFFF8ECE5);
|
||||||
final Color caption = const Color(0xFF7D7873);
|
final Color caption = const Color(0xFF7D7873);
|
||||||
final Color body = const Color(0xFF514F4D);
|
final Color body = const Color(0xFF514F4D);
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// ignore_for_file: library_private_types_in_public_api
|
// ignore_for_file: library_private_types_in_public_api
|
||||||
|
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
|
|
||||||
export 'colors.dart';
|
export 'colors.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class AppStyle {
|
class AppStyle {
|
||||||
AppStyle({Size? screenSize}) {
|
AppStyle({Size? screenSize, this.disableAnimations = false, this.highContrast = false}) {
|
||||||
if (screenSize == null) {
|
if (screenSize == null) {
|
||||||
scale = 1;
|
scale = 1;
|
||||||
return;
|
return;
|
||||||
@ -24,6 +25,8 @@ class AppStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
late final double scale;
|
late final double scale;
|
||||||
|
late final bool disableAnimations;
|
||||||
|
late final bool highContrast;
|
||||||
|
|
||||||
/// The current theme colors for the app
|
/// The current theme colors for the app
|
||||||
final AppColors colors = AppColors();
|
final AppColors colors = AppColors();
|
||||||
@ -40,7 +43,7 @@ class AppStyle {
|
|||||||
late final _Text text = _Text(scale);
|
late final _Text text = _Text(scale);
|
||||||
|
|
||||||
/// Animation Durations
|
/// Animation Durations
|
||||||
final _Times times = _Times();
|
late final _Times times = _Times();
|
||||||
|
|
||||||
/// Shared sizes
|
/// Shared sizes
|
||||||
late final _Sizes sizes = _Sizes();
|
late final _Sizes sizes = _Sizes();
|
||||||
@ -133,10 +136,12 @@ class _Text {
|
|||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class _Times {
|
class _Times {
|
||||||
final Duration fast = Duration(milliseconds: 300);
|
_Times();
|
||||||
final Duration med = Duration(milliseconds: 600);
|
late final Duration fast = 300.animateMs;
|
||||||
final Duration slow = Duration(milliseconds: 900);
|
late final Duration med = 600.animateMs;
|
||||||
final Duration pageTransition = Duration(milliseconds: 200);
|
late final Duration slow = 900.animateMs;
|
||||||
|
late final Duration extraSlow = 1300.animateMs;
|
||||||
|
late final Duration pageTransition = 200.animateMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
|
@ -15,7 +15,7 @@ class WondersAppScaffold extends StatelessWidget {
|
|||||||
// Set default timing for animations in the app
|
// Set default timing for animations in the app
|
||||||
Animate.defaultDuration = _style.times.fast;
|
Animate.defaultDuration = _style.times.fast;
|
||||||
// Create a style object that will be passed down the widget tree
|
// Create a style object that will be passed down the widget tree
|
||||||
_style = AppStyle(screenSize: context.sizePx);
|
_style = AppStyle(screenSize: context.sizePx, disableAnimations: mq.disableAnimations, highContrast: mq.highContrast);
|
||||||
return KeyedSubtree(
|
return KeyedSubtree(
|
||||||
key: ValueKey($styles.scale),
|
key: ValueKey($styles.scale),
|
||||||
child: Theme(
|
child: Theme(
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
import 'package:wonders/logic/collectibles_logic.dart';
|
import 'package:wonders/logic/collectibles_logic.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.dart';
|
||||||
import 'package:wonders/logic/data/collectible_data.dart';
|
import 'package:wonders/logic/data/collectible_data.dart';
|
||||||
import 'package:wonders/ui/common/opening_card.dart';
|
import 'package:wonders/ui/common/opening_card.dart';
|
||||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart';
|
import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart';
|
||||||
|
|
||||||
class CollectibleItem extends StatelessWidget with GetItMixin {
|
class CollectibleItem extends StatelessWidget with GetItMixin {
|
||||||
@ -53,8 +55,8 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
|
|||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.animate(onPlay: (controller) => controller.repeat())
|
.maybeAnimate(onPlay: (controller) => controller.repeat())
|
||||||
.shimmer(delay: 4000.ms, duration: $styles.times.med * 3)
|
.shimmer(delay: 4000.delayMs, duration: $styles.times.med * 3)
|
||||||
.shake(curve: Curves.easeInOutCubic, hz: 4)
|
.shake(curve: Curves.easeInOutCubic, hz: 4)
|
||||||
.scale(begin: Offset(1.0, 1.0), end: Offset(1.1, 1.1), duration: $styles.times.med)
|
.scale(begin: Offset(1.0, 1.0), end: Offset(1.1, 1.1), duration: $styles.times.med)
|
||||||
.then(delay: $styles.times.med)
|
.then(delay: $styles.times.med)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
|
|
||||||
class CompassDivider extends StatelessWidget {
|
class CompassDivider extends StatelessWidget {
|
||||||
const CompassDivider({super.key, required this.isExpanded, this.duration, this.linesColor, this.compassColor});
|
const CompassDivider({super.key, required this.isExpanded, this.duration, this.linesColor, this.compassColor});
|
||||||
@ -10,7 +11,7 @@ class CompassDivider extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Duration duration = this.duration ?? 1500.ms;
|
Duration duration = this.duration ?? 1500.animateMs;
|
||||||
Widget buildHzAnimatedDivider({bool alignLeft = false}) {
|
Widget buildHzAnimatedDivider({bool alignLeft = false}) {
|
||||||
return TweenAnimationBuilder<double>(
|
return TweenAnimationBuilder<double>(
|
||||||
duration: duration,
|
duration: duration,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
import 'package:wonders/ui/common/app_icons.dart';
|
import 'package:wonders/ui/common/app_icons.dart';
|
||||||
|
import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||||
|
|
||||||
/// Shared methods across button types
|
/// Shared methods across button types
|
||||||
Widget _buildIcon(BuildContext context, AppIcons icon, {required bool isSecondary, required double? size}) =>
|
Widget _buildIcon(BuildContext context, AppIcons icon, {required bool isSecondary, required double? size}) =>
|
||||||
@ -154,7 +155,7 @@ class AppBtn extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (focus.hasFocus)
|
if (focus.hasFocus)
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: IgnorePointer(
|
child: IgnorePointerAndSemantics(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular($styles.corners.md),
|
borderRadius: BorderRadius.circular($styles.corners.md),
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/ui/common/controls/trackpad_listener.dart';
|
||||||
|
|
||||||
class EightWaySwipeDetector extends StatefulWidget {
|
class EightWaySwipeDetector extends StatefulWidget {
|
||||||
const EightWaySwipeDetector({super.key, required this.child, this.threshold = 50, required this.onSwipe});
|
const EightWaySwipeDetector({super.key, required this.child, this.threshold = 50, required this.onSwipe});
|
||||||
@ -40,12 +42,16 @@ class _EightWaySwipeDetectorState extends State<EightWaySwipeDetector> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSwipeStart(d) {
|
void _trackpadSwipe(Offset delta) {
|
||||||
_isSwiping = true;
|
widget.onSwipe?.call(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleSwipeStart(DragStartDetails d) {
|
||||||
|
_isSwiping = d.kind != null;
|
||||||
_startPos = _endPos = d.localPosition;
|
_startPos = _endPos = d.localPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSwipeUpdate(d) {
|
void _handleSwipeUpdate(DragUpdateDetails d) {
|
||||||
_endPos = d.localPosition;
|
_endPos = d.localPosition;
|
||||||
_maybeTriggerSwipe();
|
_maybeTriggerSwipe();
|
||||||
}
|
}
|
||||||
@ -57,12 +63,24 @@ class _EightWaySwipeDetectorState extends State<EightWaySwipeDetector> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return TrackpadListener(
|
||||||
|
scrollSensitivity: 70,
|
||||||
|
onScroll: _trackpadSwipe,
|
||||||
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onPanStart: _handleSwipeStart,
|
onPanStart: _handleSwipeStart,
|
||||||
onPanUpdate: _handleSwipeUpdate,
|
onPanUpdate: _handleSwipeUpdate,
|
||||||
onPanCancel: _resetSwipe,
|
onPanCancel: _resetSwipe,
|
||||||
onPanEnd: _handleSwipeEnd,
|
onPanEnd: _handleSwipeEnd,
|
||||||
child: widget.child);
|
supportedDevices: const {
|
||||||
|
// Purposely omitting PointerDeviceKind.trackpad.
|
||||||
|
PointerDeviceKind.mouse,
|
||||||
|
PointerDeviceKind.stylus,
|
||||||
|
PointerDeviceKind.touch,
|
||||||
|
PointerDeviceKind.unknown,
|
||||||
|
},
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||||
|
|
||||||
/// Easily add visual decorations to a scrolling widget based on the state of its controller.
|
/// Easily add visual decorations to a scrolling widget based on the state of its controller.
|
||||||
class ScrollDecorator extends StatefulWidget {
|
class ScrollDecorator extends StatefulWidget {
|
||||||
@ -64,7 +65,7 @@ class ScrollDecorator extends StatefulWidget {
|
|||||||
bgBuilder = null;
|
bgBuilder = null;
|
||||||
fgBuilder = (controller) {
|
fgBuilder = (controller) {
|
||||||
final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0;
|
final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0;
|
||||||
return IgnorePointer(
|
return IgnorePointerAndSemantics(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 24,
|
height: 24,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
69
lib/ui/common/controls/trackpad_listener.dart
Normal file
69
lib/ui/common/controls/trackpad_listener.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TrackpadListener extends StatefulWidget {
|
||||||
|
final Widget? child;
|
||||||
|
final double scrollSensitivity;
|
||||||
|
final ValueChanged<Offset>? onScroll;
|
||||||
|
|
||||||
|
const TrackpadListener({
|
||||||
|
super.key,
|
||||||
|
this.child,
|
||||||
|
this.scrollSensitivity = 100,
|
||||||
|
this.onScroll,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TrackpadListener> createState() => _TrackpadListenerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TrackpadListenerState extends State<TrackpadListener> {
|
||||||
|
Offset _scrollOffset = Offset.zero;
|
||||||
|
|
||||||
|
void _handleTrackpadEvent(PointerSignalEvent event) {
|
||||||
|
// Directly process the event here.
|
||||||
|
if (event is PointerScrollEvent && event.kind == PointerDeviceKind.trackpad) {
|
||||||
|
Offset newScroll = _scrollOffset + event.scrollDelta;
|
||||||
|
newScroll = Offset(
|
||||||
|
newScroll.dx.clamp(-widget.scrollSensitivity, widget.scrollSensitivity),
|
||||||
|
newScroll.dy.clamp(-widget.scrollSensitivity, widget.scrollSensitivity),
|
||||||
|
);
|
||||||
|
_scrollOffset = newScroll;
|
||||||
|
_update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _update() {
|
||||||
|
Offset directionScroll = Offset.zero;
|
||||||
|
double sensitivity = widget.scrollSensitivity;
|
||||||
|
if (_scrollOffset.dy >= sensitivity) {
|
||||||
|
// Scroll down
|
||||||
|
_scrollOffset += Offset(0.0, -sensitivity);
|
||||||
|
directionScroll += Offset(0.0, -1.0);
|
||||||
|
} else if (_scrollOffset.dy <= -sensitivity) {
|
||||||
|
// Scroll up
|
||||||
|
_scrollOffset += Offset(0.0, sensitivity);
|
||||||
|
directionScroll += Offset(0.0, 1.0);
|
||||||
|
}
|
||||||
|
if (_scrollOffset.dx >= sensitivity) {
|
||||||
|
// Scroll left
|
||||||
|
_scrollOffset += Offset(-sensitivity, 0.0);
|
||||||
|
directionScroll += Offset(-1.0, 0.0);
|
||||||
|
} else if (_scrollOffset.dx <= -sensitivity) {
|
||||||
|
// Scroll right
|
||||||
|
_scrollOffset += Offset(sensitivity, 0.0);
|
||||||
|
directionScroll += Offset(1.0, 0.0);
|
||||||
|
}
|
||||||
|
if (directionScroll != Offset.zero) {
|
||||||
|
widget.onScroll?.call(directionScroll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Listener(
|
||||||
|
onPointerSignal: _handleTrackpadEvent,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||||
|
|
||||||
class GradientContainer extends StatelessWidget {
|
class GradientContainer extends StatelessWidget {
|
||||||
const GradientContainer(this.colors, this.stops,
|
const GradientContainer(this.colors, this.stops,
|
||||||
@ -24,23 +25,23 @@ class GradientContainer extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => IgnorePointer(
|
Widget build(BuildContext context) => IgnorePointer(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: begin ?? Alignment.centerLeft,
|
begin: begin ?? Alignment.centerLeft,
|
||||||
end: end ?? Alignment.centerRight,
|
end: end ?? Alignment.centerRight,
|
||||||
colors: colors,
|
colors: colors,
|
||||||
stops: stops,
|
stops: stops,
|
||||||
),
|
|
||||||
backgroundBlendMode: blendMode,
|
|
||||||
borderRadius: borderRadius,
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
),
|
),
|
||||||
);
|
backgroundBlendMode: blendMode,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class HzGradient extends GradientContainer {
|
class HzGradient extends GradientContainer {
|
||||||
|
32
lib/ui/common/ignore_pointer.dart
Normal file
32
lib/ui/common/ignore_pointer.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:wonders/common_libs.dart';
|
||||||
|
|
||||||
|
class IgnorePointerKeepSemantics extends SingleChildRenderObjectWidget {
|
||||||
|
const IgnorePointerKeepSemantics({super.key, super.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderIgnorePointerKeepSemantics createRenderObject(BuildContext context) {
|
||||||
|
return RenderIgnorePointerKeepSemantics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderIgnorePointerKeepSemantics extends RenderProxyBox {
|
||||||
|
RenderIgnorePointerKeepSemantics();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool hitTest(BoxHitTestResult result, { required Offset position }) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class IgnorePointerAndSemantics extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
const IgnorePointerAndSemantics({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ExcludeSemantics(
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: child
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,7 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
|
|||||||
|
|
||||||
void _animateToPage(int page) {
|
void _animateToPage(int page) {
|
||||||
if (page >= 0 || page < widget.urls.length) {
|
if (page >= 0 || page < widget.urls.length) {
|
||||||
_controller.animateToPage(page, duration: 300.ms, curve: Curves.easeOut);
|
_controller.animateToPage(page, duration: $styles.times.fast, curve: Curves.easeOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
|
|||||||
CircleIconBtn(
|
CircleIconBtn(
|
||||||
icon: AppIcons.prev,
|
icon: AppIcons.prev,
|
||||||
onPressed: page == 0 ? null : () => _animateToPage(page - 1),
|
onPressed: page == 0 ? null : () => _animateToPage(page - 1),
|
||||||
semanticLabel: $strings.semanticsNext(''),
|
semanticLabel: $strings.semanticsPrevious(''),
|
||||||
),
|
),
|
||||||
Gap($styles.insets.xs),
|
Gap($styles.insets.xs),
|
||||||
CircleIconBtn(
|
CircleIconBtn(
|
||||||
|
6
lib/ui/common/utils/duration_utils.dart
Normal file
6
lib/ui/common/utils/duration_utils.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:wonders/common_libs.dart';
|
||||||
|
|
||||||
|
extension DurationExtensions on int {
|
||||||
|
Duration get delayMs => $styles.disableAnimations ? 0.ms : Duration(milliseconds: this);
|
||||||
|
Duration get animateMs => $styles.disableAnimations ? 1.ms : Duration(milliseconds: this);
|
||||||
|
}
|
@ -9,6 +9,7 @@ class WonderousLogo extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Image.asset(
|
Widget build(BuildContext context) => Image.asset(
|
||||||
ImagePaths.appLogoPlain,
|
ImagePaths.appLogoPlain,
|
||||||
|
excludeFromSemantics: true,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
width: width,
|
width: width,
|
||||||
filterQuality: FilterQuality.high,
|
filterQuality: FilterQuality.high,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.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_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.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/ignore_pointer.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';
|
||||||
|
@ -26,8 +26,7 @@ class _BottomTextContent extends StatelessWidget {
|
|||||||
Gap($styles.insets.md),
|
Gap($styles.insets.md),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
IgnorePointer(
|
IgnorePointerKeepSemantics(
|
||||||
ignoringSemantics: false,
|
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
button: true,
|
button: true,
|
||||||
onIncrease: () => state._handleArtifactTap(_currentPage + 1),
|
onIncrease: () => state._handleArtifactTap(_currentPage + 1),
|
||||||
@ -58,7 +57,7 @@ class _BottomTextContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
).animate(key: ValueKey(artifact.artifactId)).fadeIn(),
|
).maybeAnimate(key: ValueKey(artifact.artifactId)).fadeIn(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.dart';
|
||||||
import 'package:wonders/logic/data/artifact_data.dart';
|
import 'package:wonders/logic/data/artifact_data.dart';
|
||||||
import 'package:wonders/ui/common/compass_divider.dart';
|
import 'package:wonders/ui/common/compass_divider.dart';
|
||||||
import 'package:wonders/ui/common/controls/app_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||||
import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
|
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';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
|
|
||||||
part 'widgets/_artifact_image_btn.dart';
|
part 'widgets/_artifact_image_btn.dart';
|
||||||
part 'widgets/_info_column.dart';
|
part 'widgets/_info_column.dart';
|
||||||
@ -85,6 +87,6 @@ class _ArtifactDetailsScreenState extends State<ArtifactDetailsScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).animate().fadeIn();
|
).maybeAnimate().fadeIn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,59 +9,61 @@ class _InfoColumn extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: $styles.insets.lg),
|
padding: EdgeInsets.symmetric(horizontal: $styles.insets.lg),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Focus(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
Gap($styles.insets.xl),
|
children: [
|
||||||
if (data.culture.isNotEmpty) ...[
|
Gap($styles.insets.xl),
|
||||||
Text(
|
if (data.culture.isNotEmpty) ...[
|
||||||
data.culture.toUpperCase(),
|
Text(
|
||||||
style: $styles.text.titleFont.copyWith(color: $styles.colors.accent1),
|
data.culture.toUpperCase(),
|
||||||
).animate().fade(delay: 150.ms, duration: 600.ms),
|
style: $styles.text.titleFont.copyWith(color: $styles.colors.accent1),
|
||||||
Gap($styles.insets.xs),
|
).maybeAnimate().fade(delay: 150.delayMs, duration: 600.animateMs),
|
||||||
],
|
Gap($styles.insets.xs),
|
||||||
Semantics(
|
|
||||||
header: true,
|
|
||||||
child: Text(
|
|
||||||
data.title,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: $styles.text.h2.copyWith(color: $styles.colors.offWhite, height: 1.2),
|
|
||||||
maxLines: 5,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
).animate().fade(delay: 250.ms, duration: 600.ms),
|
|
||||||
),
|
|
||||||
Gap($styles.insets.lg),
|
|
||||||
Animate().toggle(
|
|
||||||
delay: 500.ms,
|
|
||||||
builder: (_, value, __) {
|
|
||||||
return CompassDivider(isExpanded: !value, duration: $styles.times.med);
|
|
||||||
}),
|
|
||||||
Gap($styles.insets.lg),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
...[
|
|
||||||
_InfoRow($strings.artifactDetailsLabelDate, data.date),
|
|
||||||
_InfoRow($strings.artifactDetailsLabelPeriod, data.period),
|
|
||||||
_InfoRow($strings.artifactDetailsLabelGeography, data.country),
|
|
||||||
_InfoRow($strings.artifactDetailsLabelMedium, data.medium),
|
|
||||||
_InfoRow($strings.artifactDetailsLabelDimension, data.dimension),
|
|
||||||
_InfoRow($strings.artifactDetailsLabelClassification, data.classification),
|
|
||||||
]
|
|
||||||
.animate(interval: 100.ms)
|
|
||||||
.fadeIn(delay: 600.ms, duration: $styles.times.med)
|
|
||||||
.slide(begin: Offset(0.2, 0), curve: Curves.easeOut),
|
|
||||||
],
|
],
|
||||||
),
|
Semantics(
|
||||||
Gap($styles.insets.md),
|
header: true,
|
||||||
Text(
|
child: Text(
|
||||||
$strings.homeMenuAboutMet,
|
data.title,
|
||||||
style: $styles.text.caption.copyWith(color: $styles.colors.accent2),
|
textAlign: TextAlign.center,
|
||||||
).animate(delay: 1.5.seconds).fadeIn().slide(begin: Offset(0.2, 0), curve: Curves.easeOut),
|
style: $styles.text.h2.copyWith(color: $styles.colors.offWhite, height: 1.2),
|
||||||
Gap($styles.insets.offset),
|
maxLines: 5,
|
||||||
],
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).maybeAnimate().fade(delay: 250.delayMs, duration: 600.animateMs),
|
||||||
|
),
|
||||||
|
Gap($styles.insets.lg),
|
||||||
|
Animate().toggle(
|
||||||
|
delay: 500.delayMs,
|
||||||
|
builder: (_, value, __) {
|
||||||
|
return CompassDivider(isExpanded: !value, duration: $styles.times.med);
|
||||||
|
}),
|
||||||
|
Gap($styles.insets.lg),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
...[
|
||||||
|
_InfoRow($strings.artifactDetailsLabelDate, data.date),
|
||||||
|
_InfoRow($strings.artifactDetailsLabelPeriod, data.period),
|
||||||
|
_InfoRow($strings.artifactDetailsLabelGeography, data.country),
|
||||||
|
_InfoRow($strings.artifactDetailsLabelMedium, data.medium),
|
||||||
|
_InfoRow($strings.artifactDetailsLabelDimension, data.dimension),
|
||||||
|
_InfoRow($strings.artifactDetailsLabelClassification, data.classification),
|
||||||
|
]
|
||||||
|
.animate(interval: 100.delayMs)
|
||||||
|
.fadeIn(delay: 600.delayMs, duration: $styles.times.med)
|
||||||
|
.slide(begin: Offset(0.2, 0), curve: Curves.easeOut),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Gap($styles.insets.md),
|
||||||
|
Text(
|
||||||
|
$strings.homeMenuAboutMet,
|
||||||
|
style: $styles.text.caption.copyWith(color: $styles.colors.accent2),
|
||||||
|
).maybeAnimate(delay: 1500.delayMs).fadeIn().slide(begin: Offset(0.2, 0), curve: Curves.easeOut),
|
||||||
|
Gap($styles.insets.offset),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,34 +133,42 @@ class _OpenedTimeRange extends StatelessWidget {
|
|||||||
final TimeRangePainter painter;
|
final TimeRangePainter painter;
|
||||||
final void Function() onClose;
|
final void Function() onClose;
|
||||||
|
|
||||||
List<Widget> _buildChineseDateLayout(TextStyle headingTextStyle, TextStyle captionTextStyle, int startYr, int endYr) {
|
Widget _buildChineseDateLayout(TextStyle headingTextStyle, TextStyle captionTextStyle, int startYr, int endYr) {
|
||||||
return [
|
return MergeSemantics(
|
||||||
Text(StringUtils.getYrSuffix(startYr), style: captionTextStyle),
|
child: Row(
|
||||||
Gap($styles.insets.xxs),
|
children: [
|
||||||
Text(startYr.abs().toString(), style: headingTextStyle),
|
Text(StringUtils.getYrSuffix(startYr), style: captionTextStyle),
|
||||||
Text($strings.year, style: captionTextStyle),
|
Gap($styles.insets.xxs),
|
||||||
Gap($styles.insets.xs),
|
Text(startYr.abs().toString(), style: headingTextStyle),
|
||||||
Text('~', style: captionTextStyle),
|
Text($strings.year, style: captionTextStyle),
|
||||||
Gap($styles.insets.xs),
|
Gap($styles.insets.xs),
|
||||||
Text(StringUtils.getYrSuffix(endYr.round()), style: captionTextStyle),
|
Text('~', style: captionTextStyle),
|
||||||
Gap($styles.insets.xxs),
|
Gap($styles.insets.xs),
|
||||||
Text(endYr.abs().toString(), style: headingTextStyle),
|
Text(StringUtils.getYrSuffix(endYr.round()), style: captionTextStyle),
|
||||||
Text($strings.year, style: captionTextStyle),
|
Gap($styles.insets.xxs),
|
||||||
];
|
Text(endYr.abs().toString(), style: headingTextStyle),
|
||||||
|
Text($strings.year, style: captionTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildDefaultDateLayout(TextStyle headingTextStyle, TextStyle captionTextStyle, int startYr, int endYr) {
|
Widget _buildDefaultDateLayout(TextStyle headingTextStyle, TextStyle captionTextStyle, int startYr, int endYr) {
|
||||||
return [
|
return MergeSemantics(
|
||||||
Text(startYr.abs().toString(), style: headingTextStyle),
|
child: Row(
|
||||||
Gap($styles.insets.xxs),
|
children: [
|
||||||
Text(StringUtils.getYrSuffix(startYr), style: captionTextStyle),
|
Text(startYr.abs().toString(), style: headingTextStyle),
|
||||||
Gap($styles.insets.xs),
|
Gap($styles.insets.xxs),
|
||||||
Text('—', style: captionTextStyle),
|
Text(StringUtils.getYrSuffix(startYr), style: captionTextStyle),
|
||||||
Gap($styles.insets.xs),
|
Gap($styles.insets.xs),
|
||||||
Text(endYr.abs().toString(), style: headingTextStyle),
|
Text('—', style: captionTextStyle),
|
||||||
Gap($styles.insets.xxs),
|
Gap($styles.insets.xs),
|
||||||
Text(StringUtils.getYrSuffix(endYr.round()), style: captionTextStyle),
|
Text(endYr.abs().toString(), style: headingTextStyle),
|
||||||
];
|
Gap($styles.insets.xxs),
|
||||||
|
Text(StringUtils.getYrSuffix(endYr.round()), style: captionTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -181,16 +189,16 @@ class _OpenedTimeRange extends StatelessWidget {
|
|||||||
Gap($styles.insets.xl),
|
Gap($styles.insets.xl),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
if (localeLogic.strings.localeName == 'zh') ...{
|
if (localeLogic.strings.localeName == 'zh') ...{
|
||||||
..._buildChineseDateLayout(headingTextStyle, captionTextStyle, startYr, endYr),
|
_buildChineseDateLayout(headingTextStyle, captionTextStyle, startYr, endYr),
|
||||||
} else ...{
|
} else ...{
|
||||||
..._buildDefaultDateLayout(headingTextStyle, captionTextStyle, startYr, endYr),
|
_buildDefaultDateLayout(headingTextStyle, captionTextStyle, startYr, endYr),
|
||||||
},
|
},
|
||||||
Spacer(),
|
Spacer(),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: $styles.insets.xl,
|
width: $styles.insets.xl,
|
||||||
child: AppBtn.from(
|
child: AppBtn.from(
|
||||||
onPressed: onClose,
|
onPressed: onClose,
|
||||||
semanticLabel: $strings.expandingTimeSelectorSemanticSelector,
|
semanticLabel: $strings.circleButtonsSemanticClose,
|
||||||
enableFeedback: false, // handled when panelController changes.
|
enableFeedback: false, // handled when panelController changes.
|
||||||
icon: AppIcons.close,
|
icon: AppIcons.close,
|
||||||
iconSize: 20,
|
iconSize: 20,
|
||||||
@ -206,60 +214,67 @@ class _OpenedTimeRange extends StatelessWidget {
|
|||||||
// Timeframe slider
|
// Timeframe slider
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: $styles.insets.lg * 2,
|
height: $styles.insets.lg * 2,
|
||||||
child: Stack(children: [
|
child: Stack(
|
||||||
// grid lines:
|
children: [
|
||||||
Container(
|
// grid lines:
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
borderRadius: BorderRadius.circular($styles.corners.md),
|
decoration: BoxDecoration(
|
||||||
color: Color.lerp($styles.colors.black, Colors.black, 0.2),
|
borderRadius: BorderRadius.circular($styles.corners.md),
|
||||||
),
|
color: Color.lerp($styles.colors.black, Colors.black, 0.2),
|
||||||
child: Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
child: Row(
|
||||||
children: timelineGrid,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
),
|
children: timelineGrid,
|
||||||
),
|
|
||||||
|
|
||||||
// results visualization:
|
|
||||||
Positioned.fill(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: RangeSelector.handleWidth),
|
|
||||||
child: RepaintBoundary(
|
|
||||||
child: CustomPaint(painter: painter),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
// wonder minimap:
|
// results visualization:
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: RangeSelector.handleWidth, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: RangeSelector.handleWidth),
|
||||||
child: WondersTimelineBuilder(
|
child: RepaintBoundary(
|
||||||
crossAxisGap: 6,
|
child: CustomPaint(painter: painter),
|
||||||
minSize: 16,
|
),
|
||||||
selectedWonders: [wonder.type],
|
),
|
||||||
timelineBuilder: (_, __, sel) => Container(
|
),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: $styles.colors.offWhite.withOpacity(sel ? 0.75 : 0.25),
|
// wonder minimap:
|
||||||
borderRadius: BorderRadius.circular(999),
|
Positioned.fill(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: RangeSelector.handleWidth, vertical: 12),
|
||||||
|
child: WondersTimelineBuilder(
|
||||||
|
crossAxisGap: 6,
|
||||||
|
minSize: 16,
|
||||||
|
selectedWonders: [wonder.type],
|
||||||
|
timelineBuilder: (_, __, sel) => Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: $styles.colors.offWhite.withOpacity(sel ? 0.75 : 0.25),
|
||||||
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
// Time slider itself
|
// Time slider itself
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: RangeSelector(
|
child: MergeSemantics(
|
||||||
key: ValueKey('RangeSelectorIsWonderTime'),
|
child: Semantics(
|
||||||
min: wondersLogic.timelineStartYear * 1.0,
|
label: $strings.bottomScrubberSemanticTimeline,
|
||||||
max: wondersLogic.timelineEndYear * 1.0,
|
child: RangeSelector(
|
||||||
minDelta: 500,
|
key: ValueKey('RangeSelectorIsWonderTime'),
|
||||||
start: startYear,
|
min: wondersLogic.timelineStartYear * 1.0,
|
||||||
end: endYear,
|
max: wondersLogic.timelineEndYear * 1.0,
|
||||||
onUpdated: onChange,
|
minDelta: 500,
|
||||||
|
start: startYear,
|
||||||
|
end: endYear,
|
||||||
|
onUpdated: onChange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
]),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Gap(safeBottom),
|
Gap(safeBottom),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
part of '../artifact_search_screen.dart';
|
part of '../artifact_search_screen.dart';
|
||||||
|
|
||||||
/// Autopopulating textfield used for searching for Artifacts by name.
|
/// Autopopulating textfield used for searching for Artifacts by name.
|
||||||
|
const double _inputWidth = 400;
|
||||||
class _SearchInput extends StatelessWidget {
|
class _SearchInput extends StatelessWidget {
|
||||||
const _SearchInput({super.key, required this.onSubmit, required this.wonder});
|
const _SearchInput({super.key, required this.onSubmit, required this.wonder});
|
||||||
final void Function(String) onSubmit;
|
final void Function(String) onSubmit;
|
||||||
@ -49,6 +50,7 @@ class _SearchInput extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.only(top: $styles.insets.xxs),
|
margin: EdgeInsets.only(top: $styles.insets.xxs),
|
||||||
width: constraints.maxWidth,
|
width: constraints.maxWidth,
|
||||||
|
constraints: BoxConstraints(maxWidth: _inputWidth),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
@ -120,6 +122,7 @@ class _SearchInput extends StatelessWidget {
|
|||||||
Widget _buildInput(BuildContext context, TextEditingController textController, FocusNode focusNode, _) {
|
Widget _buildInput(BuildContext context, TextEditingController textController, FocusNode focusNode, _) {
|
||||||
Color captionColor = $styles.colors.caption;
|
Color captionColor = $styles.colors.caption;
|
||||||
return Container(
|
return Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: _inputWidth),
|
||||||
height: $styles.insets.xl,
|
height: $styles.insets.xl,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: $styles.colors.offWhite,
|
color: $styles.colors.offWhite,
|
||||||
@ -128,19 +131,19 @@ class _SearchInput extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Gap($styles.insets.xs * 1.5),
|
Gap($styles.insets.xs * 1.5),
|
||||||
Icon(Icons.search, color: $styles.colors.caption),
|
Icon(Icons.search, color: captionColor),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onSubmitted: onSubmit,
|
onSubmitted: onSubmit,
|
||||||
controller: textController,
|
controller: textController,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
style: TextStyle(color: captionColor),
|
style: TextStyle(color: $styles.colors.greyStrong),
|
||||||
textAlignVertical: TextAlignVertical.top,
|
textAlignVertical: TextAlignVertical.top,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
contentPadding: EdgeInsets.all($styles.insets.xs),
|
contentPadding: EdgeInsets.all($styles.insets.xs),
|
||||||
labelStyle: TextStyle(color: captionColor),
|
labelStyle: TextStyle(color: captionColor),
|
||||||
hintStyle: TextStyle(color: captionColor.withOpacity(0.5)),
|
hintStyle: TextStyle(color: $styles.colors.body),
|
||||||
prefixStyle: TextStyle(color: captionColor),
|
prefixStyle: TextStyle(color: captionColor),
|
||||||
focusedBorder: OutlineInputBorder(borderSide: BorderSide.none),
|
focusedBorder: OutlineInputBorder(borderSide: BorderSide.none),
|
||||||
enabledBorder: UnderlineInputBorder(borderSide: BorderSide.none),
|
enabledBorder: UnderlineInputBorder(borderSide: BorderSide.none),
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
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/common/animate_utils.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/app_backdrop.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/controls/app_header.dart';
|
||||||
import 'package:wonders/ui/common/pop_navigator_underlay.dart';
|
import 'package:wonders/ui/common/pop_navigator_underlay.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
|
|
||||||
part 'widgets/_animated_ribbon.dart';
|
part 'widgets/_animated_ribbon.dart';
|
||||||
part 'widgets/_celebration_particles.dart';
|
part 'widgets/_celebration_particles.dart';
|
||||||
@ -21,7 +23,7 @@ class CollectibleFoundScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
child: _buildIntro(context).animate().swap(
|
child: _buildIntro(context).animate().swap(
|
||||||
delay: $styles.times.fast * 3.5,
|
delay: 1050.animateMs,
|
||||||
builder: (_, __) => _buildDetail(context),
|
builder: (_, __) => _buildDetail(context),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -38,7 +38,7 @@ class _AnimatedRibbon extends StatelessWidget {
|
|||||||
if (flip) end = Transform.scale(scaleX: -1, child: end);
|
if (flip) end = Transform.scale(scaleX: -1, child: end);
|
||||||
double m = flip ? 1 : -1;
|
double m = flip ? 1 : -1;
|
||||||
return end
|
return end
|
||||||
.animate()
|
.maybeAnimate()
|
||||||
.move(begin: Offset(m * 8, 2), end: Offset(m * 32, 10), duration: 400.ms, curve: Curves.easeOut);
|
.move(begin: Offset(m * 8, 2), end: Offset(m * 32, 10), duration: 400.animateMs, curve: Curves.easeOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,13 @@ import 'dart:async';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
import 'package:wonders/logic/collectibles_logic.dart';
|
import 'package:wonders/logic/collectibles_logic.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.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/app_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';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
|
|
||||||
part 'widgets/_collectible_image.dart';
|
part 'widgets/_collectible_image.dart';
|
||||||
part 'widgets/_collection_footer.dart';
|
part 'widgets/_collection_footer.dart';
|
||||||
@ -38,7 +40,7 @@ class _CollectionScreenState extends State<CollectionScreen> with GetItStateMixi
|
|||||||
|
|
||||||
void _scrollToTarget([bool animate = true]) {
|
void _scrollToTarget([bool animate = true]) {
|
||||||
if (_scrollKey.currentContext != null) {
|
if (_scrollKey.currentContext != null) {
|
||||||
Scrollable.ensureVisible(_scrollKey.currentContext!, alignment: 0.15, duration: animate ? 300.ms : 0.ms);
|
Scrollable.ensureVisible(_scrollKey.currentContext!, alignment: 0.15, duration: (animate ? 300 : 0).animateMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ class _CollectionFooter extends StatelessWidget {
|
|||||||
color: $styles.colors.accent1,
|
color: $styles.colors.accent1,
|
||||||
borderRadius: BorderRadius.circular(1000),
|
borderRadius: BorderRadius.circular(1000),
|
||||||
),
|
),
|
||||||
).animate().fade(duration: 1500.ms, curve: Curves.easeOutExpo).custom(
|
).maybeAnimate().fade(duration: 1500.animateMs, curve: Curves.easeOutExpo).custom(
|
||||||
builder: (_, m, child) => FractionallySizedBox(
|
builder: (_, m, child) => FractionallySizedBox(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
widthFactor: m * count / total,
|
widthFactor: m * count / total,
|
||||||
|
@ -10,7 +10,7 @@ class _CollectionListCard extends StatelessWidget with GetItMixin {
|
|||||||
|
|
||||||
void _showDetails(BuildContext context, CollectibleData collectible) {
|
void _showDetails(BuildContext context, CollectibleData collectible) {
|
||||||
context.go(ScreenPaths.artifact(collectible.artifactId));
|
context.go(ScreenPaths.artifact(collectible.artifactId));
|
||||||
Future.delayed(300.ms).then((_) => collectiblesLogic.setState(collectible.id, CollectibleState.explored));
|
Future.delayed(300.delayMs).then((_) => collectiblesLogic.setState(collectible.id, CollectibleState.explored));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -5,6 +5,7 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter_circular_text/circular_text.dart';
|
import 'package:flutter_circular_text/circular_text.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.dart';
|
||||||
import 'package:wonders/logic/common/platform_info.dart';
|
import 'package:wonders/logic/common/platform_info.dart';
|
||||||
import 'package:wonders/logic/common/string_utils.dart';
|
import 'package:wonders/logic/common/string_utils.dart';
|
||||||
import 'package:wonders/logic/data/wonder_data.dart';
|
import 'package:wonders/logic/data/wonder_data.dart';
|
||||||
@ -12,16 +13,19 @@ import 'package:wonders/ui/common/app_icons.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/compass_divider.dart';
|
import 'package:wonders/ui/common/compass_divider.dart';
|
||||||
|
import 'package:wonders/ui/common/controls/trackpad_listener.dart';
|
||||||
import 'package:wonders/ui/common/curved_clippers.dart';
|
import 'package:wonders/ui/common/curved_clippers.dart';
|
||||||
import 'package:wonders/ui/common/fullscreen_keyboard_list_scroller.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/google_maps_marker.dart';
|
||||||
import 'package:wonders/ui/common/gradient_container.dart';
|
import 'package:wonders/ui/common/gradient_container.dart';
|
||||||
import 'package:wonders/ui/common/hidden_collectible.dart';
|
import 'package:wonders/ui/common/hidden_collectible.dart';
|
||||||
|
import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||||
import 'package:wonders/ui/common/pop_router_on_over_scroll.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/scaling_list_item.dart';
|
||||||
import 'package:wonders/ui/common/static_text_scale.dart';
|
import 'package:wonders/ui/common/static_text_scale.dart';
|
||||||
import 'package:wonders/ui/common/themed_text.dart';
|
import 'package:wonders/ui/common/themed_text.dart';
|
||||||
import 'package:wonders/ui/common/utils/context_utils.dart';
|
import 'package:wonders/ui/common/utils/context_utils.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration.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_illustration_config.dart';
|
||||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart';
|
import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart';
|
||||||
@ -63,6 +67,13 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
|||||||
_scrollPos.value = _scroller.position.pixels;
|
_scrollPos.value = _scroller.position.pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleTrackpadScroll(Offset direction) {
|
||||||
|
// Trackpad swipe up. Return to home if at the top.
|
||||||
|
if (_scroller.position.pixels == 0 && direction.dy > 0) {
|
||||||
|
_handleBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _handleBackPressed() => context.go(ScreenPaths.home);
|
void _handleBackPressed() => context.go(ScreenPaths.home);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -79,115 +90,119 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
|||||||
controller: _scroller,
|
controller: _scroller,
|
||||||
child: ColoredBox(
|
child: ColoredBox(
|
||||||
color: $styles.colors.offWhite,
|
color: $styles.colors.offWhite,
|
||||||
child: Stack(
|
child: TrackpadListener(
|
||||||
children: [
|
onScroll: _handleTrackpadScroll,
|
||||||
/// Background
|
scrollSensitivity: 120,
|
||||||
Positioned.fill(
|
child: Stack(
|
||||||
child: ColoredBox(color: widget.data.type.bgColor),
|
children: [
|
||||||
),
|
/// Background
|
||||||
|
Positioned.fill(
|
||||||
/// Top Illustration - Sits underneath the scrolling content, fades out as it scrolls
|
child: ColoredBox(color: widget.data.type.bgColor),
|
||||||
SizedBox(
|
|
||||||
height: illustrationHeight,
|
|
||||||
child: ValueListenableBuilder<double>(
|
|
||||||
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
|
/// Top Illustration - Sits underneath the scrolling content, fades out as it scrolls
|
||||||
TopCenter(
|
SizedBox(
|
||||||
child: Padding(
|
height: illustrationHeight,
|
||||||
padding: widget.contentPadding,
|
child: ValueListenableBuilder<double>(
|
||||||
child: SizedBox(
|
valueListenable: _scrollPos,
|
||||||
child: FocusTraversalGroup(
|
builder: (_, value, child) {
|
||||||
child: FullscreenKeyboardListScroller(
|
// get some value between 0 and 1, based on the amt scrolled
|
||||||
scrollController: _scroller,
|
double opacity = (1 - value / 700).clamp(0, 1);
|
||||||
child: CustomScrollView(
|
return Opacity(opacity: opacity, child: child);
|
||||||
controller: _scroller,
|
},
|
||||||
scrollBehavior: ScrollConfiguration.of(context).copyWith(),
|
// This is due to a bug: https://github.com/flutter/flutter/issues/101872
|
||||||
key: PageStorageKey('editorial'),
|
child: RepaintBoundary(
|
||||||
slivers: [
|
child: _TopIllustration(
|
||||||
/// Invisible padding at the top of the list, so the illustration shows through the btm
|
widget.data.type,
|
||||||
SliverToBoxAdapter(
|
// Polish: Inject the content padding into the illustration as an offset, so it can center itself relative to the content
|
||||||
child: SizedBox(height: illustrationHeight),
|
// this allows the background to extend underneath the vertical side nav when it has rounded corners.
|
||||||
),
|
fgOffset: Offset(widget.contentPadding.left / 2, 0),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
/// Text content, animates itself to hide behind the app bar as it scrolls up
|
/// Scrolling content - Includes an invisible gap at the top, and then scrolls over the illustration
|
||||||
SliverToBoxAdapter(
|
TopCenter(
|
||||||
child: ValueListenableBuilder<double>(
|
child: Padding(
|
||||||
valueListenable: _scrollPos,
|
padding: widget.contentPadding,
|
||||||
builder: (_, value, child) {
|
child: SizedBox(
|
||||||
double offsetAmt = max(0, value * .3);
|
child: FocusTraversalGroup(
|
||||||
double opacity = (1 - offsetAmt / 150).clamp(0, 1);
|
child: FullscreenKeyboardListScroller(
|
||||||
return Transform.translate(
|
scrollController: _scroller,
|
||||||
offset: Offset(0, offsetAmt),
|
child: CustomScrollView(
|
||||||
child: Opacity(opacity: opacity, child: child),
|
controller: _scroller,
|
||||||
);
|
scrollBehavior: ScrollConfiguration.of(context).copyWith(),
|
||||||
},
|
key: PageStorageKey('editorial'),
|
||||||
child: _TitleText(widget.data, scroller: _scroller),
|
slivers: [
|
||||||
|
/// Invisible padding at the top of the list, so the illustration shows through the btm
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(height: illustrationHeight),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
/// Collapsing App bar, pins to the top of the list
|
/// Text content, animates itself to hide behind the app bar as it scrolls up
|
||||||
SliverAppBar(
|
SliverToBoxAdapter(
|
||||||
pinned: true,
|
child: ValueListenableBuilder<double>(
|
||||||
collapsedHeight: minAppBarHeight,
|
valueListenable: _scrollPos,
|
||||||
toolbarHeight: minAppBarHeight,
|
builder: (_, value, child) {
|
||||||
expandedHeight: maxAppBarHeight,
|
double offsetAmt = max(0, value * .3);
|
||||||
backgroundColor: Colors.transparent,
|
double opacity = (1 - offsetAmt / 150).clamp(0, 1);
|
||||||
elevation: 0,
|
return Transform.translate(
|
||||||
leading: SizedBox.shrink(),
|
offset: Offset(0, offsetAmt),
|
||||||
flexibleSpace: SizedBox.expand(
|
child: Opacity(opacity: opacity, child: child),
|
||||||
child: _AppBar(
|
);
|
||||||
widget.data.type,
|
},
|
||||||
scrollPos: _scrollPos,
|
child: _TitleText(widget.data, scroller: _scroller),
|
||||||
sectionIndex: _sectionIndex,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
/// Editorial content (text and images)
|
/// Collapsing App bar, pins to the top of the list
|
||||||
_ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex),
|
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
|
/// Home Btn
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: _scroller,
|
animation: _scroller,
|
||||||
builder: (_, child) {
|
builder: (_, child) {
|
||||||
return AnimatedOpacity(
|
return AnimatedOpacity(
|
||||||
opacity: _scrollPos.value > 0 ? 0 : 1,
|
opacity: _scrollPos.value > 0 ? 0 : 1,
|
||||||
duration: $styles.times.med,
|
duration: $styles.times.med,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: backBtnAlign,
|
alignment: backBtnAlign,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all($styles.insets.sm),
|
padding: EdgeInsets.all($styles.insets.sm),
|
||||||
child: BackBtn(icon: AppIcons.north, onPressed: _handleBackPressed),
|
child: BackBtn(icon: AppIcons.north, onPressed: _handleBackPressed),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -67,7 +67,7 @@ class _AppBar extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
).animate(delay: $styles.times.pageTransition + 500.ms).fadeIn(duration: $styles.times.slow),
|
).maybeAnimate(delay: $styles.times.pageTransition + 500.delayMs).fadeIn(duration: $styles.times.slow),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -6,20 +6,22 @@ class _Callout extends StatelessWidget {
|
|||||||
const _Callout({super.key, required this.text});
|
const _Callout({super.key, required this.text});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IntrinsicHeight(
|
return Focus(
|
||||||
child: Row(
|
child: IntrinsicHeight(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: Row(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
Container(color: $styles.colors.accent1, width: 1),
|
children: [
|
||||||
Gap($styles.insets.sm),
|
Container(color: $styles.colors.accent1, width: 1),
|
||||||
Expanded(
|
Gap($styles.insets.sm),
|
||||||
child: Text(
|
Expanded(
|
||||||
text,
|
child: Text(
|
||||||
style: $styles.text.callout,
|
text,
|
||||||
),
|
style: $styles.text.callout,
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ class _CircularTitleBar extends StatelessWidget {
|
|||||||
BottomCenter(
|
BottomCenter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(bottom: 20),
|
padding: EdgeInsets.only(bottom: 20),
|
||||||
child: Image.asset('${ImagePaths.common}/${icons[index]}').animate(key: ValueKey(index)).fade().scale(
|
child: Image.asset('${ImagePaths.common}/${icons[index]}').maybeAnimate(key: ValueKey(index)).fade().scale(
|
||||||
begin: Offset(.5, .5), end: Offset(1, 1), curve: Curves.easeOutBack, duration: $styles.times.med),
|
begin: Offset(.5, .5), end: Offset(1, 1), curve: Curves.easeOutBack, duration: $styles.times.med),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -133,7 +133,7 @@ class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with S
|
|||||||
Widget _buildCircularText(String title) {
|
Widget _buildCircularText(String title) {
|
||||||
final textStyle = $styles.text.monoTitleFont.copyWith(
|
final textStyle = $styles.text.monoTitleFont.copyWith(
|
||||||
fontSize: 22 * $styles.scale,
|
fontSize: 22 * $styles.scale,
|
||||||
color: $styles.colors.accent1,
|
color: $styles.colors.accent3,
|
||||||
);
|
);
|
||||||
return CircularText(
|
return CircularText(
|
||||||
position: CircularTextPosition.inside,
|
position: CircularTextPosition.inside,
|
||||||
|
@ -18,7 +18,7 @@ class _LargeSimpleQuote extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'“',
|
'“',
|
||||||
style: $styles.text.quote1.copyWith(
|
style: $styles.text.quote1.copyWith(
|
||||||
color: $styles.colors.accent1,
|
color: $styles.colors.accent3,
|
||||||
fontSize: 90 * $styles.scale,
|
fontSize: 90 * $styles.scale,
|
||||||
height: .7,
|
height: .7,
|
||||||
),
|
),
|
||||||
@ -32,7 +32,7 @@ class _LargeSimpleQuote extends StatelessWidget {
|
|||||||
Gap($styles.insets.md),
|
Gap($styles.insets.md),
|
||||||
Text(
|
Text(
|
||||||
'- $author',
|
'- $author',
|
||||||
style: $styles.text.quote2Sub.copyWith(color: $styles.colors.accent1),
|
style: $styles.text.quote2Sub.copyWith(color: $styles.colors.accent3),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -19,45 +19,45 @@ class _ScrollingContent extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget buildText(String value) => Focus(child: Text(_fixNewlines(value), style: $styles.text.body));
|
Widget buildText(String value, bool useDropCaps) {
|
||||||
|
final bool skipCaps = !localeLogic.isEnglish || !useDropCaps;
|
||||||
Widget buildDropCapText(String value) {
|
|
||||||
final TextStyle dropStyle = $styles.text.dropCase;
|
final TextStyle dropStyle = $styles.text.dropCase;
|
||||||
final TextStyle bodyStyle = $styles.text.body;
|
final TextStyle bodyStyle = $styles.text.body;
|
||||||
final String dropChar = value.substring(0, 1);
|
final String dropChar = value.substring(0, 1);
|
||||||
final textScale = MediaQuery.of(context).textScaleFactor;
|
final textScale = MediaQuery.of(context).textScaleFactor;
|
||||||
final double dropCapWidth = StringUtils.measure(dropChar, dropStyle).width * textScale;
|
final double dropCapWidth = StringUtils.measure(dropChar, dropStyle).width * textScale;
|
||||||
final bool skipCaps = !localeLogic.isEnglish;
|
return Focus(
|
||||||
return Semantics(
|
child: Semantics(
|
||||||
label: value,
|
label: value,
|
||||||
child: ExcludeSemantics(
|
child: ExcludeSemantics(
|
||||||
child: !skipCaps
|
child: skipCaps
|
||||||
? DropCapText(
|
? Text(_fixNewlines(value), style: bodyStyle )
|
||||||
_fixNewlines(value).substring(1),
|
: DropCapText(
|
||||||
dropCap: DropCap(
|
_fixNewlines(value).substring(1),
|
||||||
width: dropCapWidth,
|
dropCap: DropCap(
|
||||||
height: $styles.text.body.fontSize! * $styles.text.body.height! * 2,
|
width: dropCapWidth,
|
||||||
child: Transform.translate(
|
height: $styles.text.body.fontSize! * $styles.text.body.height! * 2,
|
||||||
offset: Offset(0, bodyStyle.fontSize! * (bodyStyle.height! - 1) - 2),
|
child: Transform.translate(
|
||||||
child: Text(
|
offset: Offset(0, bodyStyle.fontSize! * (bodyStyle.height! - 1) - 2),
|
||||||
dropChar,
|
child: Text(
|
||||||
overflow: TextOverflow.visible,
|
dropChar,
|
||||||
style: $styles.text.dropCase.copyWith(
|
overflow: TextOverflow.visible,
|
||||||
color: $styles.colors.accent1,
|
style: $styles.text.dropCase.copyWith(
|
||||||
height: 1,
|
color: $styles.colors.accent3,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
style: $styles.text.body,
|
||||||
style: $styles.text.body,
|
dropCapPadding: EdgeInsets.only(right: 6),
|
||||||
dropCapPadding: EdgeInsets.only(right: 6),
|
dropCapStyle: $styles.text.dropCase.copyWith(
|
||||||
dropCapStyle: $styles.text.dropCase.copyWith(
|
color: $styles.colors.accent3,
|
||||||
color: $styles.colors.accent1,
|
height: 1,
|
||||||
height: 1,
|
),
|
||||||
),
|
)
|
||||||
)
|
),
|
||||||
: Text(value, style: bodyStyle),
|
)
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ class _ScrollingContent extends StatelessWidget {
|
|||||||
Center(child: buildHiddenCollectible(slot: 0)),
|
Center(child: buildHiddenCollectible(slot: 0)),
|
||||||
|
|
||||||
/// History 1
|
/// History 1
|
||||||
buildDropCapText(data.historyInfo1),
|
buildText(data.historyInfo1, true),
|
||||||
|
|
||||||
/// Quote1
|
/// Quote1
|
||||||
_CollapsingPullQuoteImage(data: data, scrollPos: scrollPos),
|
_CollapsingPullQuoteImage(data: data, scrollPos: scrollPos),
|
||||||
@ -103,11 +103,11 @@ class _ScrollingContent extends StatelessWidget {
|
|||||||
_Callout(text: data.callout1),
|
_Callout(text: data.callout1),
|
||||||
|
|
||||||
/// History 2
|
/// History 2
|
||||||
buildText(data.historyInfo2),
|
buildText(data.historyInfo2, false),
|
||||||
_SectionDivider(scrollPos, sectionNotifier, index: 1),
|
_SectionDivider(scrollPos, sectionNotifier, index: 1),
|
||||||
|
|
||||||
/// Construction 1
|
/// Construction 1
|
||||||
buildDropCapText(data.constructionInfo1),
|
buildText(data.constructionInfo1, true),
|
||||||
Center(child: buildHiddenCollectible(slot: 2)),
|
Center(child: buildHiddenCollectible(slot: 2)),
|
||||||
]),
|
]),
|
||||||
Gap($styles.insets.md),
|
Gap($styles.insets.md),
|
||||||
@ -119,14 +119,14 @@ class _ScrollingContent extends StatelessWidget {
|
|||||||
_Callout(text: data.callout2),
|
_Callout(text: data.callout2),
|
||||||
|
|
||||||
/// Construction 2
|
/// Construction 2
|
||||||
buildText(data.constructionInfo2),
|
buildText(data.constructionInfo2, false),
|
||||||
_SlidingImageStack(scrollPos: scrollPos, type: data.type),
|
_SlidingImageStack(scrollPos: scrollPos, type: data.type),
|
||||||
_SectionDivider(scrollPos, sectionNotifier, index: 2),
|
_SectionDivider(scrollPos, sectionNotifier, index: 2),
|
||||||
|
|
||||||
/// Location
|
/// Location
|
||||||
buildDropCapText(data.locationInfo1),
|
buildText(data.locationInfo1, true),
|
||||||
_LargeSimpleQuote(text: data.pullQuote2, author: data.pullQuote2Author),
|
_LargeSimpleQuote(text: data.pullQuote2, author: data.pullQuote2Author),
|
||||||
buildText(data.locationInfo2),
|
buildText(data.locationInfo2, false),
|
||||||
]),
|
]),
|
||||||
Gap($styles.insets.md),
|
Gap($styles.insets.md),
|
||||||
_MapsThumbnail(data),
|
_MapsThumbnail(data),
|
||||||
|
@ -27,7 +27,7 @@ class _TitleText extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Divider(
|
child: Divider(
|
||||||
color: data.type.fgColor,
|
color: data.type.fgColor,
|
||||||
).animate().scale(curve: Curves.easeOut, delay: 500.ms),
|
).maybeAnimate().scale(curve: Curves.easeOut, delay: 500.delayMs),
|
||||||
),
|
),
|
||||||
Semantics(
|
Semantics(
|
||||||
header: true,
|
header: true,
|
||||||
@ -35,12 +35,12 @@ class _TitleText extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
data.subTitle.toUpperCase(),
|
data.subTitle.toUpperCase(),
|
||||||
style: $styles.text.title2,
|
style: $styles.text.title2,
|
||||||
).animate().fade(delay: 100.ms),
|
).maybeAnimate().fade(delay: 100.delayMs),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Divider(
|
child: Divider(
|
||||||
color: data.type.fgColor,
|
color: data.type.fgColor,
|
||||||
).animate().scale(curve: Curves.easeOut, delay: 500.ms),
|
).maybeAnimate().scale(curve: Curves.easeOut, delay: 500.delayMs),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -17,7 +17,7 @@ class _VerticalSwipeController {
|
|||||||
void handleTapCancelled() => isPointerDown.value = false;
|
void handleTapCancelled() => isPointerDown.value = false;
|
||||||
|
|
||||||
void handleVerticalSwipeCancelled() {
|
void handleVerticalSwipeCancelled() {
|
||||||
swipeReleaseAnim.duration = swipeAmt.value.seconds * .5;
|
swipeReleaseAnim.duration = $styles.disableAnimations ? 1.ms : swipeAmt.value.seconds * .5;
|
||||||
swipeReleaseAnim.reverse(from: swipeAmt.value);
|
swipeReleaseAnim.reverse(from: swipeAmt.value);
|
||||||
isPointerDown.value = false;
|
isPointerDown.value = false;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.dart';
|
||||||
import 'package:wonders/logic/data/wonder_data.dart';
|
import 'package:wonders/logic/data/wonder_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_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.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/trackpad_listener.dart';
|
||||||
import 'package:wonders/ui/common/gradient_container.dart';
|
import 'package:wonders/ui/common/gradient_container.dart';
|
||||||
|
import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||||
import 'package:wonders/ui/common/previous_next_navigation.dart';
|
import 'package:wonders/ui/common/previous_next_navigation.dart';
|
||||||
import 'package:wonders/ui/common/themed_text.dart';
|
import 'package:wonders/ui/common/themed_text.dart';
|
||||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
import 'package:wonders/ui/screens/home_menu/home_menu.dart';
|
import 'package:wonders/ui/screens/home_menu/home_menu.dart';
|
||||||
import 'package:wonders/ui/wonder_illustrations/common/animated_clouds.dart';
|
import 'package:wonders/ui/wonder_illustrations/common/animated_clouds.dart';
|
||||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration.dart';
|
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration.dart';
|
||||||
@ -111,7 +115,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
void _showDetailsPage() async {
|
void _showDetailsPage() async {
|
||||||
_swipeOverride = _swipeController.swipeAmt.value;
|
_swipeOverride = _swipeController.swipeAmt.value;
|
||||||
context.go(ScreenPaths.wonderDetails(currentWonder.type, tabIndex: 0));
|
context.go(ScreenPaths.wonderDetails(currentWonder.type, tabIndex: 0));
|
||||||
await Future.delayed(100.ms);
|
await Future.delayed(100.delayMs);
|
||||||
_swipeOverride = null;
|
_swipeOverride = null;
|
||||||
_fadeInOnNextBuild = true;
|
_fadeInOnNextBuild = true;
|
||||||
}
|
}
|
||||||
@ -121,7 +125,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
for (var a in _fadeAnims) {
|
for (var a in _fadeAnims) {
|
||||||
a.value = 0;
|
a.value = 0;
|
||||||
}
|
}
|
||||||
await Future.delayed(300.ms);
|
await Future.delayed(300.delayMs);
|
||||||
for (var a in _fadeAnims) {
|
for (var a in _fadeAnims) {
|
||||||
a.forward();
|
a.forward();
|
||||||
}
|
}
|
||||||
@ -130,6 +134,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleTrackpadScroll(Offset direction) {
|
||||||
|
if (direction.dy < 0) {
|
||||||
|
_showDetailsPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_fadeInOnNextBuild == true) {
|
if (_fadeInOnNextBuild == true) {
|
||||||
@ -139,25 +149,29 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
return _swipeController.wrapGestureDetector(Container(
|
return _swipeController.wrapGestureDetector(Container(
|
||||||
color: $styles.colors.black,
|
color: $styles.colors.black,
|
||||||
child: PreviousNextNavigation(
|
child: TrackpadListener(
|
||||||
listenToMouseWheel: false,
|
scrollSensitivity: 60,
|
||||||
onPreviousPressed: () => _handlePrevNext(-1),
|
onScroll: _handleTrackpadScroll,
|
||||||
onNextPressed: () => _handlePrevNext(1),
|
child: PreviousNextNavigation(
|
||||||
child: Stack(
|
listenToMouseWheel: false,
|
||||||
children: [
|
onPreviousPressed: () => _handlePrevNext(-1),
|
||||||
/// Background
|
onNextPressed: () => _handlePrevNext(1),
|
||||||
..._buildBgAndClouds(),
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
/// Background
|
||||||
|
..._buildBgAndClouds(),
|
||||||
|
|
||||||
/// Wonders Illustrations (main content)
|
/// Wonders Illustrations (main content)
|
||||||
_buildMgPageView(),
|
_buildMgPageView(),
|
||||||
|
|
||||||
/// Foreground illustrations and gradients
|
/// Foreground illustrations and gradients
|
||||||
_buildFgAndGradients(),
|
_buildFgAndGradients(),
|
||||||
|
|
||||||
/// Controls that float on top of the various illustrations
|
/// Controls that float on top of the various illustrations
|
||||||
_buildFloatingUi(),
|
_buildFloatingUi(),
|
||||||
],
|
],
|
||||||
).animate().fadeIn(),
|
).maybeAnimate().fadeIn(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -210,7 +224,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
Widget _buildFgAndGradients() {
|
Widget _buildFgAndGradients() {
|
||||||
Widget buildSwipeableBgGradient(Color fgColor) {
|
Widget buildSwipeableBgGradient(Color fgColor) {
|
||||||
return _swipeController.buildListener(builder: (swipeAmt, isPointerDown, _) {
|
return _swipeController.buildListener(builder: (swipeAmt, isPointerDown, _) {
|
||||||
return IgnorePointer(
|
return IgnorePointerAndSemantics(
|
||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
heightFactor: .6,
|
heightFactor: .6,
|
||||||
child: Container(
|
child: Container(
|
||||||
@ -248,7 +262,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
return Animate(
|
return Animate(
|
||||||
effects: const [FadeEffect()],
|
effects: const [FadeEffect()],
|
||||||
onPlay: _handleFadeAnimInit,
|
onPlay: _handleFadeAnimInit,
|
||||||
child: IgnorePointer(child: WonderIllustration(e.type, config: config)));
|
child: IgnorePointerAndSemantics(child: WonderIllustration(e.type, config: config)));
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -277,8 +291,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
/// Title Content
|
/// Title Content
|
||||||
LightText(
|
LightText(
|
||||||
child: IgnorePointer(
|
child: IgnorePointerKeepSemantics(
|
||||||
ignoringSemantics: false,
|
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
offset: Offset(0, 30),
|
offset: Offset(0, 30),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.dart';
|
||||||
import 'package:wonders/logic/data/wonder_data.dart';
|
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/app_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.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/common/pop_navigator_underlay.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
import 'package:wonders/ui/common/wonderous_logo.dart';
|
import 'package:wonders/ui/common/wonderous_logo.dart';
|
||||||
import 'package:wonders/ui/screens/home_menu/about_dialog_content.dart';
|
import 'package:wonders/ui/screens/home_menu/about_dialog_content.dart';
|
||||||
|
|
||||||
@ -68,7 +70,7 @@ class _HomeMenuState extends State<HomeMenu> {
|
|||||||
Gap(50),
|
Gap(50),
|
||||||
Gap($styles.insets.md),
|
Gap($styles.insets.md),
|
||||||
_buildIconGrid(context)
|
_buildIconGrid(context)
|
||||||
.animate()
|
.maybeAnimate()
|
||||||
.fade(duration: $styles.times.fast)
|
.fade(duration: $styles.times.fast)
|
||||||
.scale(begin: Offset(.8, .8), curve: Curves.easeOut),
|
.scale(begin: Offset(.8, .8), curve: Curves.easeOut),
|
||||||
Gap($styles.insets.lg),
|
Gap($styles.insets.lg),
|
||||||
@ -127,9 +129,9 @@ class _HomeMenuState extends State<HomeMenu> {
|
|||||||
valueListenable: settingsLogic.currentLocale,
|
valueListenable: settingsLogic.currentLocale,
|
||||||
builder: (_, __, ___) {
|
builder: (_, __, ___) {
|
||||||
return SeparatedColumn(
|
return SeparatedColumn(
|
||||||
separatorBuilder: () => Divider(thickness: 1.5, height: 1).animate().scale(
|
separatorBuilder: () => Divider(thickness: 1.5, height: 1).maybeAnimate().scale(
|
||||||
duration: $styles.times.slow,
|
duration: $styles.times.slow,
|
||||||
delay: $styles.times.pageTransition + 200.ms,
|
delay: $styles.times.pageTransition + 200.delayMs,
|
||||||
curve: Curves.easeOutBack,
|
curve: Curves.easeOutBack,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
@ -147,8 +149,8 @@ class _HomeMenuState extends State<HomeMenu> {
|
|||||||
onPressed: () => _handleAboutPressed(context),
|
onPressed: () => _handleAboutPressed(context),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.animate(interval: 50.ms)
|
.animate(interval: 50.delayMs)
|
||||||
.fade(delay: $styles.times.pageTransition + 50.ms)
|
.fade(delay: $styles.times.pageTransition + 50.delayMs)
|
||||||
.slide(begin: Offset(0, .1), curve: Curves.easeOut),
|
.slide(begin: Offset(0, .1), curve: Curves.easeOut),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -4,10 +4,12 @@ import 'package:wonders/logic/common/platform_info.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/gradient_container.dart';
|
import 'package:wonders/ui/common/gradient_container.dart';
|
||||||
|
import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||||
import 'package:wonders/ui/common/previous_next_navigation.dart';
|
import 'package:wonders/ui/common/previous_next_navigation.dart';
|
||||||
import 'package:wonders/ui/common/static_text_scale.dart';
|
import 'package:wonders/ui/common/static_text_scale.dart';
|
||||||
import 'package:wonders/ui/common/themed_text.dart';
|
import 'package:wonders/ui/common/themed_text.dart';
|
||||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
|
|
||||||
class IntroScreen extends StatefulWidget {
|
class IntroScreen extends StatefulWidget {
|
||||||
const IntroScreen({super.key});
|
const IntroScreen({super.key});
|
||||||
@ -59,7 +61,7 @@ class _IntroScreenState extends State<IntroScreen> {
|
|||||||
final int current = _pageController.page!.round();
|
final int current = _pageController.page!.round();
|
||||||
if (_isOnLastPage && dir > 0) return;
|
if (_isOnLastPage && dir > 0) return;
|
||||||
if (_isOnFirstPage && dir < 0) return;
|
if (_isOnFirstPage && dir < 0) return;
|
||||||
_pageController.animateToPage(current + dir, duration: 250.ms, curve: Curves.easeIn);
|
_pageController.animateToPage(current + dir, duration: $styles.times.fast, curve: Curves.easeIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -84,7 +86,7 @@ class _IntroScreenState extends State<IntroScreen> {
|
|||||||
color: $styles.colors.black,
|
color: $styles.colors.black,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Animate(
|
child: Animate(
|
||||||
delay: 500.ms,
|
delay: 500.delayMs,
|
||||||
effects: const [FadeEffect()],
|
effects: const [FadeEffect()],
|
||||||
child: PreviousNextNavigation(
|
child: PreviousNextNavigation(
|
||||||
maxWidth: 600,
|
maxWidth: 600,
|
||||||
@ -112,8 +114,7 @@ class _IntroScreenState extends State<IntroScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
IgnorePointer(
|
IgnorePointerKeepSemantics(
|
||||||
ignoringSemantics: false,
|
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
Spacer(),
|
Spacer(),
|
||||||
|
|
||||||
@ -325,6 +326,7 @@ class _PageImage extends StatelessWidget {
|
|||||||
SizedBox.expand(
|
SizedBox.expand(
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'${ImagePaths.common}/intro-${data.img}.jpg',
|
'${ImagePaths.common}/intro-${data.img}.jpg',
|
||||||
|
excludeFromSemantics: true,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
),
|
),
|
||||||
@ -332,6 +334,7 @@ class _PageImage extends StatelessWidget {
|
|||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'${ImagePaths.common}/intro-mask-${data.mask}.png',
|
'${ImagePaths.common}/intro-mask-${data.mask}.png',
|
||||||
|
excludeFromSemantics: true,
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
@ -2,11 +2,13 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.dart';
|
||||||
import 'package:wonders/logic/data/unsplash_photo_data.dart';
|
import 'package:wonders/logic/data/unsplash_photo_data.dart';
|
||||||
import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
|
import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
|
||||||
import 'package:wonders/ui/common/controls/eight_way_swipe_detector.dart';
|
import 'package:wonders/ui/common/controls/eight_way_swipe_detector.dart';
|
||||||
import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
|
import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
|
||||||
import 'package:wonders/ui/common/hidden_collectible.dart';
|
import 'package:wonders/ui/common/hidden_collectible.dart';
|
||||||
|
import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||||
import 'package:wonders/ui/common/modals/fullscreen_url_img_viewer.dart';
|
import 'package:wonders/ui/common/modals/fullscreen_url_img_viewer.dart';
|
||||||
import 'package:wonders/ui/common/unsplash_photo.dart';
|
import 'package:wonders/ui/common/unsplash_photo.dart';
|
||||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||||
@ -171,62 +173,63 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
return FullscreenKeyboardListener(
|
return FullscreenKeyboardListener(
|
||||||
onKeyDown: _handleKeyDown,
|
onKeyDown: _handleKeyDown,
|
||||||
child: ValueListenableBuilder<List<String>>(
|
child: ValueListenableBuilder<List<String>>(
|
||||||
valueListenable: _photoIds,
|
valueListenable: _photoIds,
|
||||||
builder: (_, value, __) {
|
builder: (_, value, __) {
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
return Center(child: AppLoadingIndicator());
|
return Center(child: AppLoadingIndicator());
|
||||||
}
|
}
|
||||||
Size imgSize = context.isLandscape
|
Size imgSize = context.isLandscape
|
||||||
? Size(context.widthPx * .5, context.heightPx * .66)
|
? Size(context.widthPx * .5, context.heightPx * .66)
|
||||||
: Size(context.widthPx * .66, context.heightPx * .5);
|
: 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;
|
||||||
final cutoutTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration * .5;
|
final cutoutTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration * .5;
|
||||||
return _AnimatedCutoutOverlay(
|
return _AnimatedCutoutOverlay(
|
||||||
animationKey: ValueKey(_index),
|
animationKey: ValueKey(_index),
|
||||||
cutoutSize: imgSize,
|
cutoutSize: imgSize,
|
||||||
swipeDir: _lastSwipeDir,
|
swipeDir: _lastSwipeDir,
|
||||||
duration: cutoutTweenDuration,
|
duration: cutoutTweenDuration,
|
||||||
opacity: _scale == 1 ? .7 : .5,
|
opacity: _scale == 1 ? .7 : .5,
|
||||||
enabled: useClipPathWorkAroundForWeb == false,
|
enabled: useClipPathWorkAroundForWeb == false,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
// Place content in overflow box, to allow it to flow outside the parent
|
// Place content in overflow box, to allow it to flow outside the parent
|
||||||
child: OverflowBox(
|
child: OverflowBox(
|
||||||
maxWidth: _gridSize * imgSize.width + padding * (_gridSize - 1),
|
maxWidth: _gridSize * imgSize.width + padding * (_gridSize - 1),
|
||||||
maxHeight: _gridSize * imgSize.height + padding * (_gridSize - 1),
|
maxHeight: _gridSize * imgSize.height + padding * (_gridSize - 1),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
// Detect swipes in order to change index
|
// Detect swipes in order to change index
|
||||||
child: EightWaySwipeDetector(
|
child: EightWaySwipeDetector(
|
||||||
onSwipe: _handleSwipe,
|
onSwipe: _handleSwipe,
|
||||||
threshold: 30,
|
threshold: 30,
|
||||||
// A tween animation builder moves from image to image based on current offset
|
// A tween animation builder moves from image to image based on current offset
|
||||||
child: TweenAnimationBuilder<Offset>(
|
child: TweenAnimationBuilder<Offset>(
|
||||||
tween: Tween(begin: gridOffset, end: gridOffset),
|
tween: Tween(begin: gridOffset, end: gridOffset),
|
||||||
duration: offsetTweenDuration,
|
duration: offsetTweenDuration,
|
||||||
curve: Curves.easeOut,
|
curve: Curves.easeOut,
|
||||||
builder: (_, value, child) => Transform.translate(offset: value, child: child),
|
builder: (_, value, child) => Transform.translate(offset: value, child: child),
|
||||||
child: FocusTraversalGroup(
|
child: FocusTraversalGroup(
|
||||||
//policy: OrderedTraversalPolicy(),
|
//policy: OrderedTraversalPolicy(),
|
||||||
child: GridView.count(
|
child: GridView.count(
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
crossAxisCount: _gridSize,
|
crossAxisCount: _gridSize,
|
||||||
childAspectRatio: imgSize.aspectRatio,
|
childAspectRatio: imgSize.aspectRatio,
|
||||||
mainAxisSpacing: padding,
|
mainAxisSpacing: padding,
|
||||||
crossAxisSpacing: padding,
|
crossAxisSpacing: padding,
|
||||||
children: List.generate(_imgCount, (i) => _buildImage(i, swipeDuration, imgSize)),
|
children: List.generate(_imgCount, (i) => _buildImage(i, swipeDuration, imgSize)),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +260,7 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
imgUrl,
|
imgUrl,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
size: UnsplashPhotoSize.large,
|
size: UnsplashPhotoSize.large,
|
||||||
).animate().fade(),
|
).maybeAnimate().fade(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return MergeSemantics(
|
return MergeSemantics(
|
||||||
@ -290,7 +293,7 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
duration: $styles.times.med,
|
duration: $styles.times.med,
|
||||||
opacity: isSelected ? 0 : .7,
|
opacity: isSelected ? 0 : ($styles.highContrast ? 0.4 : 0.7),
|
||||||
child: ColoredBox(color: $styles.colors.black),
|
child: ColoredBox(color: $styles.colors.black),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.dart';
|
||||||
import 'package:wonders/logic/common/debouncer.dart';
|
import 'package:wonders/logic/common/debouncer.dart';
|
||||||
import 'package:wonders/logic/common/string_utils.dart';
|
import 'package:wonders/logic/common/string_utils.dart';
|
||||||
import 'package:wonders/logic/data/timeline_data.dart';
|
import 'package:wonders/logic/data/timeline_data.dart';
|
||||||
@ -11,9 +12,11 @@ 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/app_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/ignore_pointer.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';
|
||||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
import 'package:wonders/ui/common/wonders_timeline_builder.dart';
|
import 'package:wonders/ui/common/wonders_timeline_builder.dart';
|
||||||
|
|
||||||
part 'widgets/_animated_era_text.dart';
|
part 'widgets/_animated_era_text.dart';
|
||||||
|
@ -11,6 +11,6 @@ class _AnimatedEraText extends StatelessWidget {
|
|||||||
return Semantics(
|
return Semantics(
|
||||||
liveRegion: true,
|
liveRegion: true,
|
||||||
child: Text(era, style: style),
|
child: Text(era, style: style),
|
||||||
).animate(key: ValueKey(era)).fadeIn().slide(begin: Offset(0, .2));
|
).maybeAnimate(key: ValueKey(era)).fadeIn().slide(begin: Offset(0, .2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,7 @@ class _EventMarkersState extends State<_EventMarkers> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IgnorePointer(
|
return IgnorePointerKeepSemantics(
|
||||||
ignoringSemantics: false,
|
|
||||||
child: LayoutBuilder(builder: (_, constraints) {
|
child: LayoutBuilder(builder: (_, constraints) {
|
||||||
/// Figure out which event is "selected"
|
/// Figure out which event is "selected"
|
||||||
_updateSelectedEvent(constraints.maxHeight);
|
_updateSelectedEvent(constraints.maxHeight);
|
||||||
|
@ -9,7 +9,7 @@ class _EventPopups extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EventPopupsState extends State<_EventPopups> {
|
class _EventPopupsState extends State<_EventPopups> {
|
||||||
final _debouncer = Debouncer(500.ms);
|
final _debouncer = Debouncer(500.animateMs);
|
||||||
TimelineEvent? _eventToShow;
|
TimelineEvent? _eventToShow;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,8 +35,7 @@ class _EventPopupsState extends State<_EventPopups> {
|
|||||||
final evt = _eventToShow;
|
final evt = _eventToShow;
|
||||||
return TopCenter(
|
return TopCenter(
|
||||||
child: ClipRect(
|
child: ClipRect(
|
||||||
child: IgnorePointer(
|
child: IgnorePointerKeepSemantics(
|
||||||
ignoringSemantics: false,
|
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: $styles.times.fast,
|
duration: $styles.times.fast,
|
||||||
child: evt == null
|
child: evt == null
|
||||||
|
@ -66,11 +66,10 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// Main content area
|
// Main content area
|
||||||
_buildScrollingArea(context).animate().fadeIn(),
|
_buildScrollingArea(context).maybeAnimate().fadeIn(),
|
||||||
|
|
||||||
// Dashed line with a year that changes as we scroll
|
// Dashed line with a year that changes as we scroll
|
||||||
IgnorePointer(
|
IgnorePointerKeepSemantics(
|
||||||
ignoringSemantics: false,
|
|
||||||
child: AnimatedBuilder(
|
child: AnimatedBuilder(
|
||||||
animation: controller.scroller,
|
animation: controller.scroller,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
|
@ -25,7 +25,7 @@ class _ScrollingViewportController extends ChangeNotifier {
|
|||||||
final data = wondersLogic.getData(w);
|
final data = wondersLogic.getData(w);
|
||||||
final pos = calculateScrollPosFromYear(data.startYr);
|
final pos = calculateScrollPosFromYear(data.startYr);
|
||||||
scroller.jumpTo(pos - 200);
|
scroller.jumpTo(pos - 200);
|
||||||
scroller.animateTo(pos, duration: 1.35.seconds, curve: Curves.easeOutCubic);
|
scroller.animateTo(pos, duration: $styles.times.extraSlow, curve: Curves.easeOutCubic);
|
||||||
scroller.addListener(_updateCurrentYear);
|
scroller.addListener(_updateCurrentYear);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -20,8 +20,7 @@ class TimelineSection extends StatelessWidget {
|
|||||||
StringUtils.formatYr(data.startYr),
|
StringUtils.formatYr(data.startYr),
|
||||||
StringUtils.formatYr(data.endYr),
|
StringUtils.formatYr(data.endYr),
|
||||||
)}',
|
)}',
|
||||||
child: IgnorePointer(
|
child: IgnorePointerKeepSemantics(
|
||||||
ignoringSemantics: false,
|
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: Alignment(0, -1 + fraction * 2),
|
alignment: Alignment(0, -1 + fraction * 2),
|
||||||
padding: EdgeInsets.all($styles.insets.xs),
|
padding: EdgeInsets.all($styles.insets.xs),
|
||||||
|
@ -13,8 +13,7 @@ class _YearMarkers extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IgnorePointer(
|
return IgnorePointerKeepSemantics(
|
||||||
ignoringSemantics: false,
|
|
||||||
child: LayoutBuilder(builder: (_, constraints) {
|
child: LayoutBuilder(builder: (_, constraints) {
|
||||||
int interval = 100;
|
int interval = 100;
|
||||||
if (constraints.maxHeight < 800) {
|
if (constraints.maxHeight < 800) {
|
||||||
|
@ -49,11 +49,11 @@ class _EventsListState extends State<_EventsList> {
|
|||||||
|
|
||||||
final listItems = <Widget>[];
|
final listItems = <Widget>[];
|
||||||
for (var e in events.entries) {
|
for (var e in events.entries) {
|
||||||
final delay = 100.ms + (100 * listItems.length).ms;
|
final delay = (100 + (100 * listItems.length)).delayMs;
|
||||||
listItems.add(
|
listItems.add(
|
||||||
TimelineEventCard(year: e.key, text: e.value, darkMode: true)
|
TimelineEventCard(year: e.key, text: e.value, darkMode: true)
|
||||||
.animate()
|
.maybeAnimate()
|
||||||
.fade(delay: delay, duration: $styles.times.med * 1.5)
|
.fade(delay: delay, duration: $styles.times.slow)
|
||||||
.slide(begin: Offset(0, 1), curve: Curves.easeOutBack),
|
.slide(begin: Offset(0, 1), curve: Curves.easeOutBack),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,7 @@ class _WonderImageWithTimeline extends StatelessWidget {
|
|||||||
clipper: CurvedTopClipper(),
|
clipper: CurvedTopClipper(),
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
data.type.flattened,
|
data.type.flattened,
|
||||||
|
excludeFromSemantics: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
alignment: Alignment(0, -.5),
|
alignment: Alignment(0, -.5),
|
||||||
@ -107,7 +108,7 @@ class _WonderImageWithTimeline extends StatelessWidget {
|
|||||||
_buildDot(context),
|
_buildDot(context),
|
||||||
Text(StringUtils.getEra(data.startYr), style: textStyle),
|
Text(StringUtils.getEra(data.startYr), style: textStyle),
|
||||||
],
|
],
|
||||||
).animate().fade(delay: $styles.times.pageTransition);
|
).maybeAnimate().fade(delay: $styles.times.pageTransition);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDot(BuildContext context) {
|
Widget _buildDot(BuildContext context) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/logic/common/animate_utils.dart';
|
||||||
import 'package:wonders/logic/common/platform_info.dart';
|
import 'package:wonders/logic/common/platform_info.dart';
|
||||||
import 'package:wonders/logic/common/string_utils.dart';
|
import 'package:wonders/logic/common/string_utils.dart';
|
||||||
import 'package:wonders/logic/data/wonder_data.dart';
|
import 'package:wonders/logic/data/wonder_data.dart';
|
||||||
@ -8,9 +9,11 @@ import 'package:wonders/ui/common/centered_box.dart';
|
|||||||
import 'package:wonders/ui/common/controls/app_header.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/ignore_pointer.dart';
|
||||||
import 'package:wonders/ui/common/list_gradient.dart';
|
import 'package:wonders/ui/common/list_gradient.dart';
|
||||||
import 'package:wonders/ui/common/themed_text.dart';
|
import 'package:wonders/ui/common/themed_text.dart';
|
||||||
import 'package:wonders/ui/common/timeline_event_card.dart';
|
import 'package:wonders/ui/common/timeline_event_card.dart';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
import 'package:wonders/ui/common/wonders_timeline_builder.dart';
|
import 'package:wonders/ui/common/wonders_timeline_builder.dart';
|
||||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart';
|
import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart';
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ 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';
|
||||||
|
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||||
|
|
||||||
// TODO: Clouds should fade in and out
|
// 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.
|
||||||
@ -21,7 +22,7 @@ class AnimatedClouds extends StatefulWidget with GetItStatefulWidgetMixin {
|
|||||||
class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProviderStateMixin, GetItStateMixin {
|
class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProviderStateMixin, GetItStateMixin {
|
||||||
late List<_Cloud> _clouds = [];
|
late List<_Cloud> _clouds = [];
|
||||||
List<_Cloud> _oldClouds = [];
|
List<_Cloud> _oldClouds = [];
|
||||||
late final AnimationController _anim = AnimationController(vsync: this, duration: 1500.ms);
|
late final AnimationController _anim = AnimationController(vsync: this, duration: 1500.animateMs);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -138,6 +139,7 @@ class _Cloud extends StatelessWidget {
|
|||||||
scaleY: scale * (flipY ? -1 : 1),
|
scaleY: scale * (flipY ? -1 : 1),
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
ImagePaths.cloud,
|
ImagePaths.cloud,
|
||||||
|
excludeFromSemantics: true,
|
||||||
opacity: AlwaysStoppedAnimation(.4 * opacity),
|
opacity: AlwaysStoppedAnimation(.4 * opacity),
|
||||||
width: size * scale,
|
width: size * scale,
|
||||||
fit: BoxFit.fitWidth,
|
fit: BoxFit.fitWidth,
|
||||||
|
@ -86,7 +86,12 @@ class _IllustrationPieceState extends State<IllustrationPiece> {
|
|||||||
final anim = wonderBuilder.anim;
|
final anim = wonderBuilder.anim;
|
||||||
final curvedAnim = Curves.easeOut.transform(anim.value);
|
final curvedAnim = Curves.easeOut.transform(anim.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,
|
||||||
|
excludeFromSemantics: true,
|
||||||
|
opacity: anim,
|
||||||
|
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: 2500, child: img);
|
img = OverflowBox(maxWidth: 2500, child: img);
|
||||||
|
|
||||||
@ -131,7 +136,7 @@ class _IllustrationPieceState extends State<IllustrationPiece> {
|
|||||||
children: [
|
children: [
|
||||||
if (widget.bottom != null) Positioned.fill(child: widget.bottom!.call(context)),
|
if (widget.bottom != null) Positioned.fill(child: widget.bottom!.call(context)),
|
||||||
if (uiImage != null) ...[
|
if (uiImage != null) ...[
|
||||||
widget.enableHero ? Hero(tag: '$type-${widget.fileName}', child: content!) : content!,
|
widget.enableHero && !$styles.disableAnimations ? 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)),
|
||||||
],
|
],
|
||||||
|
@ -18,6 +18,7 @@ class IllustrationTexture extends StatelessWidget {
|
|||||||
scaleX: scale * (flipX ? -1 : 1),
|
scaleX: scale * (flipX ? -1 : 1),
|
||||||
scaleY: scale * (flipY ? -1 : 1),
|
scaleY: scale * (flipY ? -1 : 1),
|
||||||
child: Image.asset(path,
|
child: Image.asset(path,
|
||||||
|
excludeFromSemantics: true,
|
||||||
repeat: ImageRepeat.repeat,
|
repeat: ImageRepeat.repeat,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<!-- app info -->
|
<!-- app info -->
|
||||||
<title>Wonderous</title>
|
<title>Wonderous</title>
|
||||||
|
Reference in New Issue
Block a user