Add prev/next navigation to intro screen
This commit is contained in:
parent
a056373207
commit
9e4d457339
@ -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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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/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/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();
|
||||
}
|
||||
|
||||
@ -53,15 +56,13 @@ class _IntroScreenState extends State<IntroScreen> {
|
||||
|
||||
void _handleNavTextSemanticTap() => _incrementPage(1);
|
||||
|
||||
void _incrementPage(int dir){
|
||||
void _incrementPage(int dir) {
|
||||
final int current = _pageController.page!.round();
|
||||
if (_isOnLastPage && dir > 0) return;
|
||||
if (_isOnFirstPage && dir < 0) return;
|
||||
_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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user