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';
|
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: Align(
|
child: Stack(
|
||||||
alignment: align,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm),
|
|
||||||
child: CircleIconBtn(
|
|
||||||
icon: AppIcons.prev,
|
|
||||||
onPressed: onPressed,
|
|
||||||
semanticLabel: semanticLabel,
|
|
||||||
flipIcon: isNext,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Stack(
|
|
||||||
children: [
|
children: [
|
||||||
child,
|
child,
|
||||||
//TODO-LOC: Add localization
|
Center(
|
||||||
buildBtn('previous', onPreviousPressed, Alignment.centerLeft),
|
child: SizedBox(
|
||||||
buildBtn('next', onNextPressed, Alignment.centerRight, isNext: true),
|
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/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){
|
|
||||||
if(signal is PointerScrollEvent){
|
|
||||||
_handleScrollWheel(signal.scrollDelta.dy);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: DefaultTextColor(
|
|
||||||
color: $styles.colors.offWhite,
|
color: $styles.colors.offWhite,
|
||||||
child: Container(
|
child: ColoredBox(
|
||||||
color: $styles.colors.black,
|
color: $styles.colors.black,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Animate(
|
child: Animate(
|
||||||
delay: 500.ms,
|
delay: 500.ms,
|
||||||
effects: const [FadeEffect()],
|
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(
|
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:
|
||||||
|
if (PlatformInfo.isMobile) ...[
|
||||||
BottomCenter(
|
BottomCenter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(bottom: $styles.insets.lg),
|
padding: EdgeInsets.only(bottom: $styles.insets.lg),
|
||||||
child: _buildNavText(context),
|
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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user