- 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() {
|
void configureAppRouter() {
|
||||||
appRouter = GoRouter(
|
appRouter = GoRouter(
|
||||||
navigatorKey: rootNavigatorKey,
|
navigatorKey: rootNavigatorKey,
|
||||||
initialLocation: AppRoutes.login,
|
initialLocation: AppRoutes.onboarding,
|
||||||
debugLogDiagnostics: true,
|
debugLogDiagnostics: true,
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
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:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -12,7 +12,7 @@ class OnboardingBuilder {
|
|||||||
|
|
||||||
return MaterialPage<void>(
|
return MaterialPage<void>(
|
||||||
key: state.pageKey,
|
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
|
get_it: ^9.0.5
|
||||||
go_router: ^17.0.0
|
go_router: ^17.0.0
|
||||||
flutter_riverpod: ^3.0.3
|
flutter_riverpod: ^3.0.3
|
||||||
|
freezed_annotation: ^3.1.0
|
||||||
|
freezed: ^3.2.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
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
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# 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._();
|
const I18n._();
|
||||||
|
|
||||||
static const String example = 'example';
|
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