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

View File

@ -1,9 +1,11 @@
import 'package:flutter/gestures.dart';
import 'package:flutter_svg/flutter_svg.dart';
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/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/static_text_scale.dart';
import 'package:wonders/ui/common/themed_text.dart';
import 'package:wonders/ui/common/utils/app_haptics.dart';
@ -24,13 +26,14 @@ class _IntroScreenState extends State<IntroScreen> {
static List<_PageData> pageData = [];
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 _isOnFirstPage => _currentPage.value.round() == 0;
@override
void dispose() {
_pageController.dispose();
_currentPage.dispose();
super.dispose();
}
@ -60,8 +63,6 @@ class _IntroScreenState extends State<IntroScreen> {
_pageController.animateToPage(current + dir, duration: 250.ms, curve: Curves.easeIn);
}
void _handleScrollWheel(double delta) => _incrementPage(delta >0? 1 : -1);
@override
Widget build(BuildContext context) {
// 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();
/// Return resulting widget tree
return Listener(
onPointerSignal: (signal){
if(signal is PointerScrollEvent){
_handleScrollWheel(signal.scrollDelta.dy);
}
},
child: DefaultTextColor(
return DefaultTextColor(
color: $styles.colors.offWhite,
child: Container(
child: ColoredBox(
color: $styles.colors.black,
child: SafeArea(
child: Animate(
delay: 500.ms,
effects: const [FadeEffect()],
child: PreviousNextNavigation(
maxWidth: 600,
nextBtnColor: _isOnLastPage ? $styles.colors.accent1 : null,
onPreviousPressed: _isOnFirstPage ? null : () => _incrementPage(-1),
onNextPressed: () {
if (_isOnLastPage) {
_handleIntroCompletePressed();
} else {
_incrementPage(1);
}
},
child: Stack(
children: [
// page view with title & description:
@ -159,20 +165,15 @@ class _IntroScreenState extends State<IntroScreen> {
_buildHzGradientOverlay(left: true),
_buildHzGradientOverlay(),
// finish button:
Positioned(
right: $styles.insets.lg,
bottom: $styles.insets.lg,
child: _buildFinishBtn(context),
),
// nav help text:
if (PlatformInfo.isMobile) ...[
BottomCenter(
child: Padding(
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) {
return ValueListenableBuilder(
valueListenable: _currentPage,