diff --git a/lib/ui/common/controls/circle_buttons.dart b/lib/ui/common/controls/circle_buttons.dart index 342aa3df..9c44a130 100644 --- a/lib/ui/common/controls/circle_buttons.dart +++ b/lib/ui/common/controls/circle_buttons.dart @@ -47,6 +47,7 @@ class CircleIconBtn extends StatelessWidget { this.color, this.size, this.iconSize, + this.flipIcon = false, required this.semanticLabel, }) : super(key: key); @@ -61,6 +62,7 @@ class CircleIconBtn extends StatelessWidget { final String semanticLabel; final double? size; final double? iconSize; + final bool flipIcon; @override Widget build(BuildContext context) { @@ -72,7 +74,10 @@ class CircleIconBtn extends StatelessWidget { size: size, bgColor: bgColor ?? defaultColor, semanticLabel: semanticLabel, - child: AppIcon(icon, size: iconSize ?? defaultSize, color: iconColor), + child: Transform.scale( + scaleX: flipIcon ? -1 : 1, + child: AppIcon(icon, size: iconSize ?? defaultSize, color: iconColor), + ), ); } diff --git a/lib/ui/common/modals/fullscreen_url_img_viewer.dart b/lib/ui/common/modals/fullscreen_url_img_viewer.dart index af7f0dd8..3f44f281 100644 --- a/lib/ui/common/modals/fullscreen_url_img_viewer.dart +++ b/lib/ui/common/modals/fullscreen_url_img_viewer.dart @@ -105,13 +105,11 @@ class _FullscreenUrlImgViewerState extends State { semanticLabel: $strings.semanticsNext(''), ), Gap($styles.insets.xs), - Transform.scale( - scaleX: -1, - child: CircleIconBtn( - icon: AppIcons.prev, - onPressed: page == widget.urls.length - 1 ? null : () => _animateToPage(page + 1), - semanticLabel: $strings.semanticsNext(''), - ), + CircleIconBtn( + icon: AppIcons.prev, + flipIcon: true, + onPressed: page == widget.urls.length - 1 ? null : () => _animateToPage(page + 1), + semanticLabel: $strings.semanticsNext(''), ) ], ); diff --git a/lib/ui/common/previous_next_navigation.dart b/lib/ui/common/previous_next_navigation.dart new file mode 100644 index 00000000..b9ec8320 --- /dev/null +++ b/lib/ui/common/previous_next_navigation.dart @@ -0,0 +1,54 @@ +import 'package:wonders/common_libs.dart'; +import 'package:wonders/logic/common/platform_info.dart'; +import 'package:wonders/ui/common/app_icons.dart'; +import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart'; + +class PreviousNextNavigation extends StatelessWidget { + const PreviousNextNavigation({super.key, this.onPreviousPressed, this.onNextPressed, required this.child}); + final VoidCallback? onPreviousPressed; + final VoidCallback? onNextPressed; + final Widget child; + + bool _handleKeyDown(KeyDownEvent event) { + if (event.logicalKey == LogicalKeyboardKey.arrowLeft && onPreviousPressed != null) { + onPreviousPressed?.call(); + return true; + } + if (event.logicalKey == LogicalKeyboardKey.arrowRight && onNextPressed != null) { + onNextPressed?.call(); + return true; + } + return false; + } + + @override + Widget build(BuildContext context) { + Widget buildBtn(String semanticLabel, VoidCallback? onPressed, Alignment align, {bool isNext = false}) { + if (PlatformInfo.isMobile) return child; + return FullScreenKeyboardListener( + onKeyDown: _handleKeyDown, + child: Align( + alignment: align, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: $styles.insets.md), + child: CircleIconBtn( + icon: AppIcons.prev, + onPressed: onPressed, + semanticLabel: semanticLabel, + flipIcon: isNext, + ), + ), + ), + ); + } + + return Stack( + children: [ + child, + //TODO-LOC: Add localization + buildBtn('previous', onPreviousPressed, Alignment.centerLeft), + buildBtn('next', onNextPressed, Alignment.centerRight, isNext: true), + ], + ); + } +} diff --git a/lib/ui/screens/home/wonders_home_screen.dart b/lib/ui/screens/home/wonders_home_screen.dart index 1b82d36b..518b54ff 100644 --- a/lib/ui/screens/home/wonders_home_screen.dart +++ b/lib/ui/screens/home/wonders_home_screen.dart @@ -4,6 +4,7 @@ import 'package:wonders/ui/common/app_icons.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/gradient_container.dart'; +import 'package:wonders/ui/common/previous_next_navigation.dart'; import 'package:wonders/ui/common/themed_text.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/screens/home_menu/home_menu.dart'; @@ -87,11 +88,16 @@ class _HomeScreenState extends State with SingleTickerProviderStateM void _handlePageIndicatorDotPressed(int index) => _setPageIndex(index); - void _setPageIndex(int index) { + void _setPageIndex(int index, {bool animate = false}) { if (index == _wonderIndex) return; // To support infinite scrolling, we can't jump directly to the pressed index. Instead, make it relative to our current position. final pos = ((_pageController.page ?? 0) / _numWonders).floor() * _numWonders; - _pageController.jumpToPage(pos + index); + final newIndex = pos + index; + if (animate == true) { + _pageController.animateToPage(newIndex, duration: $styles.times.med, curve: Curves.easeOutCubic); + } else { + _pageController.jumpToPage(newIndex); + } } void _showDetailsPage() async { @@ -125,24 +131,24 @@ class _HomeScreenState extends State with SingleTickerProviderStateM return _swipeController.wrapGestureDetector(Container( color: $styles.colors.black, - child: Stack( - children: [ - Stack( - children: [ - /// Background - ..._buildBgAndClouds(), + child: PreviousNextNavigation( + onPreviousPressed: () => _setPageIndex(_wonderIndex - 1, animate: true), + onNextPressed: () => _setPageIndex(_wonderIndex + 1, animate: true), + child: Stack( + children: [ + /// Background + ..._buildBgAndClouds(), - /// Wonders Illustrations (main content) - _buildMgPageView(), + /// Wonders Illustrations (main content) + _buildMgPageView(), - /// Foreground illustrations and gradients - _buildFgAndGradients(), + /// Foreground illustrations and gradients + _buildFgAndGradients(), - /// Controls that float on top of the various illustrations - _buildFloatingUi(), - ], - ).animate().fadeIn(), - ], + /// Controls that float on top of the various illustrations + _buildFloatingUi(), + ], + ).animate().fadeIn(), ), )); }