Skip to content

Commit 2977e68

Browse files
committed
add focus node to allow nav with keyboard
1 parent 8311cbf commit 2977e68

File tree

7 files changed

+129
-85
lines changed

7 files changed

+129
-85
lines changed

app/lib/presentation/resources/dim.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ class Dimen {
1919
static const spacingXxxl = 48.0;
2020

2121
static const double buttonHeightM = 48.0;
22+
static const double onboardingIconSize = 120.0;
2223
}

app/lib/presentation/resources/locale/generated/intl/messages_en.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
2020
class MessageLookup extends MessageLookupByLibrary {
2121
String get localeName => 'en';
2222

23+
static String m0(currentPage, totalPages) =>
24+
"Page ${currentPage} of ${totalPages}";
25+
2326
final messages = _notInlinedMessages(_notInlinedMessages);
2427
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
2528
"appName": MessageLookupByLibrary.simpleMessage("Flutter Target"),
@@ -88,6 +91,7 @@ class MessageLookup extends MessageLookupByLibrary {
8891
"This is the fourth page of the onboarding flow",
8992
),
9093
"onboardingPage4Title": MessageLookupByLibrary.simpleMessage("Get Started"),
94+
"onboardingPageIndicator": m0,
9195
"onboardingStart": MessageLookupByLibrary.simpleMessage("Start"),
9296
"passwordInstructions": MessageLookupByLibrary.simpleMessage(
9397
"Min 8 characters long: 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character.",

app/lib/presentation/resources/locale/generated/intl/messages_es.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
2020
class MessageLookup extends MessageLookupByLibrary {
2121
String get localeName => 'es';
2222

23+
static String m0(currentPage, totalPages) =>
24+
"Página ${currentPage} de ${totalPages}";
25+
2326
final messages = _notInlinedMessages(_notInlinedMessages);
2427
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
2528
"appName": MessageLookupByLibrary.simpleMessage("Flutter Target"),
@@ -90,6 +93,7 @@ class MessageLookup extends MessageLookupByLibrary {
9093
"Esta es la cuarta página del flujo de incorporación",
9194
),
9295
"onboardingPage4Title": MessageLookupByLibrary.simpleMessage("Comenzar"),
96+
"onboardingPageIndicator": m0,
9397
"onboardingStart": MessageLookupByLibrary.simpleMessage("Comenzar"),
9498
"passwordInstructions": MessageLookupByLibrary.simpleMessage(
9599
"Mínimo 8 caracteres: 1 mayúscula, 1 minúscula, 1 número y 1 carácter especial.",

app/lib/presentation/resources/locale/generated/l10n.dart

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/lib/presentation/resources/locale/intl_en.arb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@
4141
"onboardingPage3Title": "Connect",
4242
"onboardingPage3Description": "This is the third page of the onboarding flow",
4343
"onboardingPage4Title": "Get Started",
44-
"onboardingPage4Description": "This is the fourth page of the onboarding flow"
44+
"onboardingPage4Description": "This is the fourth page of the onboarding flow",
45+
"onboardingPageIndicator": "Page {currentPage} of {totalPages}"
4546
}

app/lib/presentation/resources/locale/intl_es.arb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@
4141
"onboardingPage3Title": "Conectar",
4242
"onboardingPage3Description": "Esta es la tercera página del flujo de incorporación",
4343
"onboardingPage4Title": "Comenzar",
44-
"onboardingPage4Description": "Esta es la cuarta página del flujo de incorporación"
44+
"onboardingPage4Description": "Esta es la cuarta página del flujo de incorporación",
45+
"onboardingPageIndicator": "Página {currentPage} de {totalPages}"
4546
}

app/lib/presentation/ui/pages/onboarding/onboarding_page.dart

Lines changed: 106 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:app/presentation/navigation/routers.dart';
22
import 'package:app/presentation/resources/locale/generated/l10n.dart';
33
import 'package:app/presentation/resources/resources.dart';
44
import 'package:flutter/material.dart';
5+
import 'package:flutter/services.dart';
56

67
class OnboardingPage extends StatefulWidget {
78
const OnboardingPage({super.key});
@@ -12,12 +13,14 @@ class OnboardingPage extends StatefulWidget {
1213

1314
class _OnboardingPageState extends State<OnboardingPage> {
1415
final PageController _pageController = PageController();
16+
final FocusNode _focusNode = FocusNode();
1517
int _currentPage = 0;
1618
final int _totalPages = 4;
1719

1820
@override
1921
void dispose() {
2022
_pageController.dispose();
23+
_focusNode.dispose();
2124
super.dispose();
2225
}
2326

@@ -55,95 +58,115 @@ class _OnboardingPageState extends State<OnboardingPage> {
5558
@override
5659
Widget build(BuildContext context) {
5760
return Scaffold(
58-
body: SafeArea(
59-
child: Column(
60-
children: [
61-
// Page Indicator
62-
Padding(
63-
padding: const EdgeInsets.all(Dimen.spacingM),
64-
child: Row(
65-
mainAxisAlignment: MainAxisAlignment.center,
66-
children: List.generate(
67-
_totalPages,
68-
(index) => Container(
69-
margin:
70-
const EdgeInsets.symmetric(horizontal: Dimen.spacingXs),
71-
width:
72-
_currentPage == index ? Dimen.spacingL : Dimen.spacingS,
73-
height: Dimen.spacingS,
74-
decoration: BoxDecoration(
75-
color: _currentPage == index
76-
? Theme.of(context).colorScheme.primary
77-
: Theme.of(context).colorScheme.outline,
78-
borderRadius: BorderRadius.circular(Dimen.spacingXs),
61+
body: Focus(
62+
focusNode: _focusNode,
63+
autofocus: true,
64+
onKeyEvent: (node, event) {
65+
if (event is KeyDownEvent) {
66+
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
67+
_nextPage();
68+
return KeyEventResult.handled;
69+
} else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
70+
_previousPage();
71+
return KeyEventResult.handled;
72+
}
73+
}
74+
return KeyEventResult.ignored;
75+
},
76+
child: SafeArea(
77+
child: Column(
78+
children: [
79+
// Page Indicator
80+
Padding(
81+
padding: const EdgeInsets.all(Dimen.spacingM),
82+
child: Semantics(
83+
label: S.of(context).onboardingPageIndicator(
84+
_currentPage + 1,
85+
_totalPages,
86+
),
87+
child: Row(
88+
mainAxisAlignment: MainAxisAlignment.center,
89+
children: List.generate(
90+
_totalPages,
91+
(index) => Container(
92+
margin: const EdgeInsets.symmetric(
93+
horizontal: Dimen.spacingXs),
94+
width: _currentPage == index
95+
? Dimen.spacingL
96+
: Dimen.spacingS,
97+
height: Dimen.spacingS,
98+
decoration: BoxDecoration(
99+
color: _currentPage == index
100+
? Theme.of(context).colorScheme.primary
101+
: Theme.of(context).colorScheme.outline,
102+
borderRadius: BorderRadius.circular(Dimen.spacingXs),
103+
),
104+
),
79105
),
80106
),
81107
),
82108
),
83-
),
84-
85-
// PageView
86-
Expanded(
87-
child: PageView(
88-
controller: _pageController,
89-
onPageChanged: _onPageChanged,
90-
children: [
91-
_buildPage(
92-
title: S.of(context).onboardingPage1Title,
93-
description: S.of(context).onboardingPage1Description,
94-
icon: Icons.waving_hand,
95-
color: Theme.of(context).colorScheme.primary,
96-
),
97-
_buildPage(
98-
title: S.of(context).onboardingPage2Title,
99-
description: S.of(context).onboardingPage2Description,
100-
icon: Icons.explore,
101-
color: Theme.of(context).colorScheme.secondary,
102-
),
103-
_buildPage(
104-
title: S.of(context).onboardingPage3Title,
105-
description: S.of(context).onboardingPage3Description,
106-
icon: Icons.people,
107-
color: Theme.of(context).colorScheme.tertiary,
108-
),
109-
_buildPage(
110-
title: S.of(context).onboardingPage4Title,
111-
description: S.of(context).onboardingPage4Description,
112-
icon: Icons.rocket_launch,
113-
color: Theme.of(context).colorScheme.error,
114-
),
115-
],
109+
// PageView
110+
Expanded(
111+
child: PageView(
112+
controller: _pageController,
113+
onPageChanged: _onPageChanged,
114+
children: [
115+
_buildPage(
116+
title: S.of(context).onboardingPage1Title,
117+
description: S.of(context).onboardingPage1Description,
118+
icon: Icons.waving_hand,
119+
color: Theme.of(context).colorScheme.primary,
120+
),
121+
_buildPage(
122+
title: S.of(context).onboardingPage2Title,
123+
description: S.of(context).onboardingPage2Description,
124+
icon: Icons.explore,
125+
color: Theme.of(context).colorScheme.secondary,
126+
),
127+
_buildPage(
128+
title: S.of(context).onboardingPage3Title,
129+
description: S.of(context).onboardingPage3Description,
130+
icon: Icons.people,
131+
color: Theme.of(context).colorScheme.tertiary,
132+
),
133+
_buildPage(
134+
title: S.of(context).onboardingPage4Title,
135+
description: S.of(context).onboardingPage4Description,
136+
icon: Icons.rocket_launch,
137+
color: Theme.of(context).colorScheme.error,
138+
),
139+
],
140+
),
116141
),
117-
),
118-
119-
// Navigation Buttons
120-
Padding(
121-
padding: const EdgeInsets.all(Dimen.spacingL),
122-
child: Row(
123-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
124-
children: [
125-
// Back Button (hidden on first page)
126-
if (_currentPage > 0)
127-
TextButton(
128-
onPressed: _previousPage,
129-
child: Text(S.of(context).onboardingBack),
130-
)
131-
else
132-
const SizedBox(width: 80), // Placeholder for alignment
133-
134-
// Next/Start Button
135-
ElevatedButton(
136-
onPressed: _nextPage,
137-
child: Text(
138-
_currentPage == _totalPages - 1
139-
? S.of(context).onboardingStart
140-
: S.of(context).onboardingNext,
142+
// Navigation Buttons
143+
Padding(
144+
padding: const EdgeInsets.all(Dimen.spacingL),
145+
child: Row(
146+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
147+
children: [
148+
// Back Button (hidden on first page)
149+
if (_currentPage > 0)
150+
TextButton(
151+
onPressed: _previousPage,
152+
child: Text(S.of(context).onboardingBack),
153+
)
154+
else
155+
const SizedBox(width: Dimen.spacingL),
156+
// Next/Start Button
157+
ElevatedButton(
158+
onPressed: _nextPage,
159+
child: Text(
160+
_currentPage == _totalPages - 1
161+
? S.of(context).onboardingStart
162+
: S.of(context).onboardingNext,
163+
),
141164
),
142-
),
143-
],
165+
],
166+
),
144167
),
145-
),
146-
],
168+
],
169+
),
147170
),
148171
),
149172
);
@@ -162,7 +185,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
162185
children: [
163186
Icon(
164187
icon,
165-
size: 120,
188+
size: Dimen.onboardingIconSize,
166189
color: color,
167190
),
168191
const SizedBox(height: Dimen.spacingXl),

0 commit comments

Comments
 (0)