Add prev/next navigation to intro screen

This commit is contained in:
Shawn 2023-12-04 11:30:11 -07:00
parent a056373207
commit 9e4d457339
2 changed files with 75 additions and 74 deletions

View File

@ -4,10 +4,20 @@ import 'package:wonders/ui/common/app_icons.dart';
import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart'; import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
class PreviousNextNavigation extends StatelessWidget { class PreviousNextNavigation extends StatelessWidget {
const PreviousNextNavigation({super.key, this.onPreviousPressed, this.onNextPressed, required this.child}); const PreviousNextNavigation(
{super.key,
required this.onPreviousPressed,
required this.onNextPressed,
required this.child,
this.maxWidth = 1000,
this.nextBtnColor,
this.previousBtnColor});
final VoidCallback? onPreviousPressed; final VoidCallback? onPreviousPressed;
final VoidCallback? onNextPressed; final VoidCallback? onNextPressed;
final Color? nextBtnColor;
final Color? previousBtnColor;
final Widget child; final Widget child;
final double? maxWidth;
bool _handleKeyDown(KeyDownEvent event) { bool _handleKeyDown(KeyDownEvent event) {
if (event.logicalKey == LogicalKeyboardKey.arrowLeft && onPreviousPressed != null) { if (event.logicalKey == LogicalKeyboardKey.arrowLeft && onPreviousPressed != null) {
@ -23,32 +33,40 @@ class PreviousNextNavigation extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget buildBtn(String semanticLabel, VoidCallback? onPressed, Alignment align, {bool isNext = false}) { if (PlatformInfo.isMobile) return child;
if (PlatformInfo.isMobile) return child; return FullscreenKeyboardListener(
return FullScreenKeyboardListener( onKeyDown: _handleKeyDown,
onKeyDown: _handleKeyDown, child: Stack(
child: Align( children: [
alignment: align, child,
child: Padding( Center(
padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm), child: SizedBox(
child: CircleIconBtn( width: maxWidth ?? double.infinity,
icon: AppIcons.prev, child: Padding(
onPressed: onPressed, padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm),
semanticLabel: semanticLabel, child: Row(
flipIcon: isNext, children: [
CircleIconBtn(
icon: AppIcons.prev,
onPressed: onPreviousPressed,
semanticLabel: 'Previous',
bgColor: previousBtnColor,
),
Spacer(),
CircleIconBtn(
icon: AppIcons.prev,
onPressed: onNextPressed,
semanticLabel: 'Next',
flipIcon: true,
bgColor: nextBtnColor,
)
],
),
),
), ),
), ),
), ],
); ),
}
return Stack(
children: [
child,
//TODO-LOC: Add localization
buildBtn('previous', onPreviousPressed, Alignment.centerLeft),
buildBtn('next', onNextPressed, Alignment.centerRight, isNext: true),
],
); );
} }
} }

View File

@ -1,9 +1,11 @@
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
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/app_icons.dart'; import 'package:wonders/logic/common/platform_info.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/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';
@ -24,13 +26,14 @@ class _IntroScreenState extends State<IntroScreen> {
static List<_PageData> pageData = []; static List<_PageData> pageData = [];
late final PageController _pageController = PageController()..addListener(_handlePageChanged); late final PageController _pageController = PageController()..addListener(_handlePageChanged);
final ValueNotifier<int> _currentPage = ValueNotifier(0); late final ValueNotifier<int> _currentPage = ValueNotifier(0)..addListener(() => setState(() {}));
bool get _isOnLastPage => _currentPage.value.round() == pageData.length - 1; bool get _isOnLastPage => _currentPage.value.round() == pageData.length - 1;
bool get _isOnFirstPage => _currentPage.value.round() == 0; bool get _isOnFirstPage => _currentPage.value.round() == 0;
@override @override
void dispose() { void dispose() {
_pageController.dispose(); _pageController.dispose();
_currentPage.dispose();
super.dispose(); super.dispose();
} }
@ -53,15 +56,13 @@ class _IntroScreenState extends State<IntroScreen> {
void _handleNavTextSemanticTap() => _incrementPage(1); void _handleNavTextSemanticTap() => _incrementPage(1);
void _incrementPage(int dir){ void _incrementPage(int dir) {
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: 250.ms, curve: Curves.easeIn);
} }
void _handleScrollWheel(double delta) => _incrementPage(delta >0? 1 : -1);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Set the page data, as strings may have changed based on locale // Set the page data, as strings may have changed based on locale
@ -78,20 +79,25 @@ class _IntroScreenState extends State<IntroScreen> {
final List<Widget> pages = pageData.map((e) => _Page(data: e)).toList(); final List<Widget> pages = pageData.map((e) => _Page(data: e)).toList();
/// Return resulting widget tree /// Return resulting widget tree
return Listener( return DefaultTextColor(
onPointerSignal: (signal){ color: $styles.colors.offWhite,
if(signal is PointerScrollEvent){ child: ColoredBox(
_handleScrollWheel(signal.scrollDelta.dy); color: $styles.colors.black,
} child: SafeArea(
}, child: Animate(
child: DefaultTextColor( delay: 500.ms,
color: $styles.colors.offWhite, effects: const [FadeEffect()],
child: Container( child: PreviousNextNavigation(
color: $styles.colors.black, maxWidth: 600,
child: SafeArea( nextBtnColor: _isOnLastPage ? $styles.colors.accent1 : null,
child: Animate( onPreviousPressed: _isOnFirstPage ? null : () => _incrementPage(-1),
delay: 500.ms, onNextPressed: () {
effects: const [FadeEffect()], if (_isOnLastPage) {
_handleIntroCompletePressed();
} else {
_incrementPage(1);
}
},
child: Stack( child: Stack(
children: [ children: [
// page view with title & description: // page view with title & description:
@ -159,20 +165,15 @@ class _IntroScreenState extends State<IntroScreen> {
_buildHzGradientOverlay(left: true), _buildHzGradientOverlay(left: true),
_buildHzGradientOverlay(), _buildHzGradientOverlay(),
// finish button:
Positioned(
right: $styles.insets.lg,
bottom: $styles.insets.lg,
child: _buildFinishBtn(context),
),
// nav help text: // nav help text:
BottomCenter( if (PlatformInfo.isMobile) ...[
child: Padding( BottomCenter(
padding: EdgeInsets.only(bottom: $styles.insets.lg), child: Padding(
child: _buildNavText(context), padding: EdgeInsets.only(bottom: $styles.insets.lg),
child: _buildNavText(context),
),
), ),
), ]
], ],
), ),
), ),
@ -203,24 +204,6 @@ class _IntroScreenState extends State<IntroScreen> {
); );
} }
Widget _buildFinishBtn(BuildContext context) {
return ValueListenableBuilder<int>(
valueListenable: _currentPage,
builder: (_, pageIndex, __) {
return AnimatedOpacity(
opacity: pageIndex == pageData.length - 1 ? 1 : 0,
duration: $styles.times.fast,
child: CircleIconBtn(
icon: AppIcons.next_large,
bgColor: $styles.colors.accent1,
onPressed: _handleIntroCompletePressed,
semanticLabel: $strings.introSemanticEnterApp,
),
);
},
);
}
Widget _buildNavText(BuildContext context) { Widget _buildNavText(BuildContext context) {
return ValueListenableBuilder( return ValueListenableBuilder(
valueListenable: _currentPage, valueListenable: _currentPage,