- refactor to onboarding and add OnboardingContent, dots indicator and view model
- Integrate i18n for onboarding texts
This commit is contained in:
@@ -15,7 +15,7 @@ late final GoRouter appRouter;
|
||||
void configureAppRouter() {
|
||||
appRouter = GoRouter(
|
||||
navigatorKey: rootNavigatorKey,
|
||||
initialLocation: AppRoutes.login,
|
||||
initialLocation: AppRoutes.onboarding,
|
||||
debugLogDiagnostics: true,
|
||||
routes: [
|
||||
GoRoute(
|
||||
|
||||
31
modules/auth/lib/src/onboarding/domain/onboarding_page.dart
Normal file
31
modules/auth/lib/src/onboarding/domain/onboarding_page.dart
Normal 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,
|
||||
),
|
||||
];
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,75 +0,0 @@
|
||||
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 ConsumerWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const WelcomeScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: CarouselView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemExtent: double.infinity,
|
||||
itemSnapping: true,
|
||||
shrinkExtent: 400,
|
||||
children: generateSteps(),
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => navigationContract.goTo(AppRoutes.linkPhone),
|
||||
child: const Text('Continuar'),
|
||||
),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void jumpToNext(BuildContext context) {
|
||||
// Navigator.pushReplacement(
|
||||
// context,
|
||||
// MaterialPageRoute(builder: (_) => LinkPhoneScreen()),
|
||||
// );
|
||||
return;
|
||||
}
|
||||
|
||||
List<Widget> generateSteps() {
|
||||
return [
|
||||
Column(
|
||||
spacing: 30,
|
||||
children: [
|
||||
SvgPicture.asset("assets/images/ui/bienvenida_paso1.svg"),
|
||||
Text(
|
||||
"Aprende a gestionar su dinero",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
),
|
||||
Text("Tu peque crea hábitos y se divierte mientras lo hace"),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset("assets/images/ui/bienvenida_paso2.svg"),
|
||||
Text("Tranquilidad en cada pago que hacen"),
|
||||
Text("Supervisa gastos, fija límites y acompáñalos en cada paso"),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset("assets/images/ui/bienvenida_paso3.svg"),
|
||||
Text("Pagos fáciles y seguros en sus manos"),
|
||||
Text("Podrá pagar desde su reloj.\n Sin móvil ni efectivo"),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user