Merge remote-tracking branch 'origin/develop' into components

# Conflicts:
#	modules/auth/lib/src/onboarding/presentation/welcome_screen.dart
This commit is contained in:
2025-12-04 09:01:32 +01:00
17 changed files with 626 additions and 170 deletions

View File

@@ -0,0 +1,31 @@
import 'package:sf_localizations/sf_localizations.dart';
class OnboardingPage {
final String image;
final String title;
final String subtitle;
const OnboardingPage({
required this.image,
required this.title,
required this.subtitle,
});
}
const List<OnboardingPage> onboardingPages = <OnboardingPage>[
OnboardingPage(
image: 'assets/images/ui/bienvenida_paso1.svg',
title: I18n.onboardingTitle1,
subtitle: I18n.onboardingSubtitle1,
),
OnboardingPage(
image: 'assets/images/ui/bienvenida_paso2.svg',
title: I18n.onboardingTitle2,
subtitle: I18n.onboardingSubtitle2,
),
OnboardingPage(
image: 'assets/images/ui/bienvenida_paso3.svg',
title: I18n.onboardingTitle3,
subtitle: I18n.onboardingSubtitle3,
),
];

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/onboarding/presentation/welcome_screen.dart';
import 'package:auth/src/onboarding/presentation/onboarding_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:get_it/get_it.dart';
@@ -12,7 +12,7 @@ class OnboardingBuilder {
return MaterialPage<void>(
key: state.pageKey,
child: WelcomeScreen(navigationContract: navigationContract),
child: OnboardingScreen(navigationContract: navigationContract),
);
}
}

View File

@@ -0,0 +1,127 @@
import 'package:auth/src/onboarding/domain/onboarding_page.dart';
import 'package:auth/src/onboarding/presentation/onboarding_view_model.dart';
import 'package:auth/src/onboarding/presentation/widgets/onboarding_content.dart';
import 'package:auth/src/onboarding/presentation/widgets/onboarding_dots_indicator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
final onboardingPageControllerProvider = Provider.autoDispose<PageController>((
ref,
) {
final controller = PageController();
ref.onDispose(controller.dispose);
return controller;
});
class OnboardingScreen extends ConsumerWidget {
final NavigationContract navigationContract;
const OnboardingScreen({super.key, required this.navigationContract});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(onBoardingViewModelProvider);
final viewModel = ref.read(onBoardingViewModelProvider.notifier);
final pageController = ref.watch(onboardingPageControllerProvider);
final isLast = state.cardIndex >= onboardingPages.length - 1;
void goToNext() {
if (isLast) {
navigationContract.goTo(AppRoutes.linkPhone);
} else {
pageController.nextPage(
duration: const Duration(milliseconds: 400),
curve: Curves.easeOut,
);
}
}
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
children: [
Expanded(
child: PageView.builder(
controller: pageController,
itemCount: onboardingPages.length,
onPageChanged: viewModel.onPageChanged,
itemBuilder: (context, index) {
final page = onboardingPages[index];
return OnboardingContent(
image: page.image,
title: page.title,
subtitle: page.subtitle,
);
},
),
),
OnboardingDotsIndicator(
currentIndex: state.cardIndex,
total: onboardingPages.length,
),
const SizedBox(height: 38),
Container(
padding: const EdgeInsets.symmetric(horizontal: 24),
width: double.infinity,
child: TextButton(
onPressed: goToNext,
style: TextButton.styleFrom(
backgroundColor: isLast
? const Color(0xFF329E95)
: const Color(0xFF333333),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 24,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
),
child: Text(
isLast
? context.translate(I18n.start)
: context.translate(I18n.next),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(height: 8),
SizedBox(
height: 48,
child: Center(
child: isLast
? const SizedBox.shrink()
: TextButton(
onPressed: () =>
navigationContract.goTo(AppRoutes.linkPhone),
child: Text(
context.translate(I18n.skip),
style: TextStyle(
color: Color(0xFF333333),
decoration: TextDecoration.underline,
fontWeight: FontWeight.w500,
fontSize: 18,
),
),
),
),
),
const SizedBox(height: 36),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,19 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:auth/src/onboarding/presentation/onboarding_view_state.dart';
final onBoardingViewModelProvider =
NotifierProvider.autoDispose<OnBoardingViewModel, OnboardingViewState>(
OnBoardingViewModel.new,
);
class OnBoardingViewModel extends Notifier<OnboardingViewState> {
@override
OnboardingViewState build() {
return const OnboardingViewState();
}
void onPageChanged(int index) {
state = state.copyWith(cardIndex: index);
}
}

View File

@@ -0,0 +1,11 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'onboarding_view_state.freezed.dart';
@freezed
abstract class OnboardingViewState with _$OnboardingViewState {
const factory OnboardingViewState({
@Default(0) int cardIndex,
String? error,
}) = _OnboardingViewState;
}

View File

@@ -0,0 +1,274 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'onboarding_view_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$OnboardingViewState {
int get cardIndex; String? get error;
/// Create a copy of OnboardingViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$OnboardingViewStateCopyWith<OnboardingViewState> get copyWith => _$OnboardingViewStateCopyWithImpl<OnboardingViewState>(this as OnboardingViewState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is OnboardingViewState&&(identical(other.cardIndex, cardIndex) || other.cardIndex == cardIndex)&&(identical(other.error, error) || other.error == error));
}
@override
int get hashCode => Object.hash(runtimeType,cardIndex,error);
@override
String toString() {
return 'OnboardingViewState(cardIndex: $cardIndex, error: $error)';
}
}
/// @nodoc
abstract mixin class $OnboardingViewStateCopyWith<$Res> {
factory $OnboardingViewStateCopyWith(OnboardingViewState value, $Res Function(OnboardingViewState) _then) = _$OnboardingViewStateCopyWithImpl;
@useResult
$Res call({
int cardIndex, String? error
});
}
/// @nodoc
class _$OnboardingViewStateCopyWithImpl<$Res>
implements $OnboardingViewStateCopyWith<$Res> {
_$OnboardingViewStateCopyWithImpl(this._self, this._then);
final OnboardingViewState _self;
final $Res Function(OnboardingViewState) _then;
/// Create a copy of OnboardingViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? cardIndex = null,Object? error = freezed,}) {
return _then(_self.copyWith(
cardIndex: null == cardIndex ? _self.cardIndex : cardIndex // ignore: cast_nullable_to_non_nullable
as int,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [OnboardingViewState].
extension OnboardingViewStatePatterns on OnboardingViewState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _OnboardingViewState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _OnboardingViewState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _OnboardingViewState value) $default,){
final _that = this;
switch (_that) {
case _OnboardingViewState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _OnboardingViewState value)? $default,){
final _that = this;
switch (_that) {
case _OnboardingViewState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int cardIndex, String? error)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _OnboardingViewState() when $default != null:
return $default(_that.cardIndex,_that.error);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int cardIndex, String? error) $default,) {final _that = this;
switch (_that) {
case _OnboardingViewState():
return $default(_that.cardIndex,_that.error);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int cardIndex, String? error)? $default,) {final _that = this;
switch (_that) {
case _OnboardingViewState() when $default != null:
return $default(_that.cardIndex,_that.error);case _:
return null;
}
}
}
/// @nodoc
class _OnboardingViewState implements OnboardingViewState {
const _OnboardingViewState({this.cardIndex = 0, this.error});
@override@JsonKey() final int cardIndex;
@override final String? error;
/// Create a copy of OnboardingViewState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$OnboardingViewStateCopyWith<_OnboardingViewState> get copyWith => __$OnboardingViewStateCopyWithImpl<_OnboardingViewState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _OnboardingViewState&&(identical(other.cardIndex, cardIndex) || other.cardIndex == cardIndex)&&(identical(other.error, error) || other.error == error));
}
@override
int get hashCode => Object.hash(runtimeType,cardIndex,error);
@override
String toString() {
return 'OnboardingViewState(cardIndex: $cardIndex, error: $error)';
}
}
/// @nodoc
abstract mixin class _$OnboardingViewStateCopyWith<$Res> implements $OnboardingViewStateCopyWith<$Res> {
factory _$OnboardingViewStateCopyWith(_OnboardingViewState value, $Res Function(_OnboardingViewState) _then) = __$OnboardingViewStateCopyWithImpl;
@override @useResult
$Res call({
int cardIndex, String? error
});
}
/// @nodoc
class __$OnboardingViewStateCopyWithImpl<$Res>
implements _$OnboardingViewStateCopyWith<$Res> {
__$OnboardingViewStateCopyWithImpl(this._self, this._then);
final _OnboardingViewState _self;
final $Res Function(_OnboardingViewState) _then;
/// Create a copy of OnboardingViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? cardIndex = null,Object? error = freezed,}) {
return _then(_OnboardingViewState(
cardIndex: null == cardIndex ? _self.cardIndex : cardIndex // ignore: cast_nullable_to_non_nullable
as int,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -1,160 +0,0 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:navigation/navigation.dart';
class WelcomeScreen extends ConsumerStatefulWidget {
final NavigationContract navigationContract;
const WelcomeScreen({super.key, required this.navigationContract});
@override
ConsumerState<ConsumerStatefulWidget> createState() => WelcomeScreenState(navigationContract: navigationContract);
}
class WelcomeScreenState extends ConsumerState{
late int currentStep;
final NavigationContract navigationContract;
WelcomeScreenState({required this.navigationContract});
@override
void initState() {
super.initState();
currentStep = 0;
}
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Container(
padding: EdgeInsets.only(top: 24),
child: Center(
child: Column(
spacing: 48,
children: [
Spacer(),
generateSteps()[currentStep],
Column(
spacing: 24,
children: [
StepIndicator(
max: 3,
current: currentStep+1,
color: theme.getColorFor(ThemeCode.buttonSecondary)
),
generateButtons(theme, 3, currentStep+1)
]
),
Spacer()
]
)
)
)
);
}
Widget generateButtons(ThemePort theme, int max, int step){
if (step==max) {
return PrimaryButton(
onPressed: () => navigationContract.goTo(AppRoutes.linkPhone),
text: "Continuar",
color: theme.getColorFor(ThemeCode.buttonPrimary),
width: 324,
);
} else {
return Column(
spacing: 16,
children: [
PrimaryButton(
onPressed: ()=>setState(() {
currentStep++;
}),
text: "Siguiente",
color: theme.getColorFor(ThemeCode.buttonSecondary),
width: 324,
),
CustomTextButton(
onPressed: ()=>navigationContract.goTo(AppRoutes.linkPhone),
text: "Omitir",
size: 18,
weight: FontWeight.w500,
)
],
);
}
}
List<Widget> generateSteps() {
return [
Column(
spacing: 48,
children: [
SvgPicture.asset("assets/images/ui/bienvenida_paso1.svg"),
Column(
spacing: 16,
children: [
Text(
"Aprende a gestionar su dinero",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0)
),
Text(
"Tu peque crea hábitos y se divierte mientras lo hace",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, letterSpacing: 0)
)
]
)
]
),
Column(
spacing: 48,
children: [
SvgPicture.asset("assets/images/ui/bienvenida_paso2.svg"),
Column(
spacing: 16,
children: [
Text(
"Tranquilidad en cada pago que hace",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0)
),
Text(
"Supervisa sus gastos, fija límites y acompáñale en cada paso",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, letterSpacing: 0)
),
],
)
],
),
Column(
spacing: 48,
children: [
SvgPicture.asset("assets/images/ui/bienvenida_paso3.svg"),
Column(
spacing: 16,
children: [
Text(
"Pagos fáciles y seguros, en sus manos",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0)
),
Text(
"Podrá pagar desde su reloj.\n Sin móvil ni efectivo",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, letterSpacing: 0)
),
],
)
],
),
];
}
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:sf_localizations/sf_localizations.dart';
class OnboardingContent extends StatelessWidget {
final String image;
final String title;
final String subtitle;
const OnboardingContent({
super.key,
required this.image,
required this.title,
required this.subtitle,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(flex: 3, child: SvgPicture.asset(image)),
const SizedBox(height: 48),
Text(
context.translate(title),
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 28,
height: 1.4,
letterSpacing: 0.3,
color: Color(0xFF4A4A4A),
),
),
const SizedBox(height: 16),
Text(
context.translate(subtitle),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 18,
height: 1.4,
letterSpacing: 0.3,
color: Color(0xFF9B9B9B),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
class OnboardingDotsIndicator extends StatelessWidget {
final int currentIndex;
final int total;
const OnboardingDotsIndicator({
super.key,
required this.currentIndex,
required this.total,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(total, (index) {
final bool isActive = index == currentIndex;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
margin: const EdgeInsets.symmetric(horizontal: 6),
width: 16.0,
height: 16.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isActive ? const Color(0xFF4A4A4A) : Colors.white,
border: Border.all(color: const Color(0xFF4A4A4A), width: 2),
),
);
}),
);
}
}

View File

@@ -27,12 +27,16 @@ dependencies:
get_it: ^9.0.5
go_router: ^17.0.0
flutter_riverpod: ^3.0.3
freezed_annotation: ^3.1.0
freezed: ^3.2.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
riverpod_generator: ^3.0.3
build_runner: ^2.7.1
riverpod_lint: ^3.0.3
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

@@ -1,3 +1,12 @@
{
"example": "Beispiel"
"example": "Beispiel",
"start": "Starten",
"next": "Weiter",
"skip": "Überspringen",
"onboardingTitle1": "Lerne, ihr Geld zu verwalten",
"onboardingSubtitle1": "Dein Kind entwickelt Gewohnheiten und hat Spaß dabei",
"onboardingTitle2": "Gelassenheit bei jeder Zahlung",
"onboardingSubtitle2": "Überwache ihre Ausgaben, setze Limits und begleite sie bei jedem Schritt",
"onboardingTitle3": "Einfache und sichere Zahlungen in ihren Händen",
"onboardingSubtitle3": "Sie können mit ihrer Uhr bezahlen.\nGanz ohne Handy und Bargeld"
}

View File

@@ -1,3 +1,12 @@
{
"example": "example"
"example": "example",
"onboardingTitle1": "Learn to manage their money",
"onboardingSubtitle1": "Your kid builds habits and has fun while doing it",
"onboardingTitle2": "Peace of mind in every payment",
"onboardingSubtitle2": "Monitor their spending, set limits and guide them step by step",
"onboardingTitle3": "Easy and secure payments in their hands",
"onboardingSubtitle3": "They can pay from their watch.\nNo phone or cash needed",
"start": "Start",
"next": "Next",
"skip": "Skip"
}

View File

@@ -1,3 +1,12 @@
{
"example": "ejemplo"
"example": "ejemplo",
"onboardingTitle1": "Aprende a gestionar su dinero",
"onboardingSubtitle1": "Tu peque crea hábitos y se divierte mientras lo hace",
"onboardingTitle2": "Tranquilidad en cada pago que hacen",
"onboardingSubtitle2": "Supervisa sus gastos, fija límites y acompáñalos en cada paso",
"onboardingTitle3": "Pagos fáciles y seguros en sus manos",
"onboardingSubtitle3": "Podrá pagar desde su reloj.\nSin móvil ni efectivo",
"start": "Comenzar",
"next": "Siguiente",
"skip": "Omitir"
}

View File

@@ -1,3 +1,12 @@
{
"example": "exemple"
"example": "exemple",
"start": "Commencer",
"next": "Suivant",
"skip": "Passer",
"onboardingTitle1": "Apprenez à gérer leur argent",
"onboardingSubtitle1": "Votre enfant développe de bonnes habitudes et s'amuse en le faisant",
"onboardingTitle2": "La tranquillité à chaque paiement",
"onboardingSubtitle2": "Surveillez leurs dépenses, fixez des limites et accompagnez-les à chaque étape",
"onboardingTitle3": "Des paiements faciles et sécurisés entre leurs mains",
"onboardingSubtitle3": "Ils peuvent payer avec leur montre.\nSans téléphone ni espèces"
}

View File

@@ -1,3 +1,12 @@
{
"example": "esempio"
"example": "esempio",
"start": "Inizia",
"next": "Avanti",
"skip": "Salta",
"onboardingTitle1": "Impara a gestire il loro denaro",
"onboardingSubtitle1": "Tuo figlio crea abitudini e si diverte mentre lo fa",
"onboardingTitle2": "Tranquillità in ogni pagamento",
"onboardingSubtitle2": "Monitora le sue spese, imposta limiti e accompagnalo in ogni passo",
"onboardingTitle3": "Pagamenti facili e sicuri nelle sue mani",
"onboardingSubtitle3": "Potrà pagare dal suo orologio.\nSenza telefono né contanti"
}

View File

@@ -1,3 +1,12 @@
{
"example": "exemplo"
"example": "exemplo",
"start": "Começar",
"next": "Próximo",
"skip": "Pular",
"onboardingTitle1": "Aprenda a gerenciar o dinheiro deles",
"onboardingSubtitle1": "Seu filho cria hábitos e se diverte enquanto faz isso",
"onboardingTitle2": "Tranquilidade em cada pagamento",
"onboardingSubtitle2": "Monitore os gastos deles, defina limites e acompanhe cada passo",
"onboardingTitle3": "Pagamentos fáceis e seguros nas mãos deles",
"onboardingSubtitle3": "Eles poderão pagar pelo relógio.\nSem celular nem dinheiro em espécie"
}

View File

@@ -4,4 +4,13 @@ class I18n {
const I18n._();
static const String example = 'example';
static const String start = 'start';
static const String next = 'next';
static const String skip = 'skip';
static const String onboardingTitle1 = 'onboardingTitle1';
static const String onboardingSubtitle1 = 'onboardingSubtitle1';
static const String onboardingTitle2 = 'onboardingTitle2';
static const String onboardingSubtitle2 = 'onboardingSubtitle2';
static const String onboardingTitle3 = 'onboardingTitle3';
static const String onboardingSubtitle3 = 'onboardingSubtitle3';
}