feat(auth,sf_infrastructure): register QuestiaApi/Dio and connect link_phone remote datasource

This commit is contained in:
AlcalaJulian
2025-12-04 16:07:02 +01:00
parent 99f544e24c
commit 6b3776f618
88 changed files with 2300 additions and 393 deletions

View File

@@ -1,8 +1,8 @@
export 'src/device_sign_up/link_watch/create_profile_screen.dart';
export 'src/onboarding/onboarding_builder.dart';
export 'src/login/link_phone_builder.dart';
export 'src/login/phone_code_builder.dart';
export 'src/login/login_builder.dart';
export 'src/recover_password/recover_password_builder.dart';
export 'src/device_sign_up/device_signup_builder.dart';
export 'src/sign_up/signup_builder.dart';
export 'src/features/device_sign_up/link_watch/create_profile_screen.dart';
export 'src/features/onboarding/onboarding_builder.dart';
export 'src/features/link_phone/link_phone_builder.dart';
export 'src/features/login/phone_code_builder.dart';
export 'src/features/login/login_builder.dart';
export 'src/features/recover_password/recover_password_builder.dart';
export 'src/features/device_sign_up/device_signup_builder.dart';
export 'src/features/sign_up/signup_builder.dart';

View File

@@ -0,0 +1,5 @@
abstract class AuthRemoteDatasource {
Future<void> requestPhoneCode({required String phone});
Future<void> verifyPhoneCode({required String phone, required String code});
}

View File

@@ -0,0 +1,53 @@
import 'package:dio/dio.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import 'auth_remote_datasource.dart';
class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
AuthRemoteDatasourceImpl(this._repository);
final QuestiaRepository _repository;
@override
Future<void> requestPhoneCode({required String phone}) async {
try {
await _repository.post<void>(
'/auth/link-phone/request-code',
body: <String, dynamic>{'phone': phone},
);
} on DioException catch (error) {
throw _mapDioError(error, defaultMessage: 'Error al solicitar el código');
}
}
@override
Future<void> verifyPhoneCode({
required String phone,
required String code,
}) async {
try {
await _repository.post<void>(
'/auth/link-phone/verify-code',
body: <String, dynamic>{'phone': phone, 'code': code},
);
} on DioException catch (error) {
throw _mapDioError(error, defaultMessage: 'Error al verificar el código');
}
}
Exception _mapDioError(DioException error, {required String defaultMessage}) {
final responseData = error.response?.data;
String message = defaultMessage;
if (responseData is Map<String, dynamic>) {
final serverMessage = responseData['message'];
if (serverMessage is String && serverMessage.isNotEmpty) {
message = serverMessage;
}
} else if (error.message != null && error.message!.isNotEmpty) {
message = error.message!;
}
return Exception(message);
}
}

View File

@@ -0,0 +1,18 @@
import 'package:auth/src/core/data/datasource/auth_remote_datasource.dart';
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
class AuthRepositoryImpl implements AuthRepository {
const AuthRepositoryImpl(this._remote);
final AuthRemoteDatasource _remote;
@override
Future<void> requestPhoneCode({required String phone}) {
return _remote.requestPhoneCode(phone: phone);
}
@override
Future<void> verifyPhoneCode({required String phone, required String code}) {
return _remote.verifyPhoneCode(phone: phone, code: code);
}
}

View File

@@ -0,0 +1,5 @@
abstract class AuthRepository {
Future<void> requestPhoneCode({required String phone});
Future<void> verifyPhoneCode({required String phone, required String code});
}

View File

@@ -0,0 +1,9 @@
import 'package:auth/src/core/data/datasource/auth_remote_datasource.dart';
import 'package:auth/src/core/data/datasource/auth_remote_datasource_impl.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
final authRemoteDatasourceProvider = Provider<AuthRemoteDatasource>((ref) {
final questiaRepository = getIt<QuestiaRepository>();
return AuthRemoteDatasourceImpl(questiaRepository);
});

View File

@@ -0,0 +1,9 @@
import 'package:auth/src/core/data/repositories/auth_repository_impl.dart';
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
import 'package:auth/src/core/providers/auth_remote_datasource_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final authRepositoryProvider = Provider<AuthRepository>((ref) {
final remote = ref.read(authRemoteDatasourceProvider);
return AuthRepositoryImpl(remote);
});

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/device_sign_up/device_signup_screen.dart';
import 'package:auth/src/features/device_sign_up/device_signup_screen.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';

View File

@@ -1,24 +1,25 @@
import 'package:auth/auth.dart';
import 'package:auth/src/device_sign_up/add_kid_screen.dart';
import 'package:auth/src/device_sign_up/link_watch/link_watch_screen.dart';
import 'package:auth/src/device_sign_up/link_watch/link_watch_previous_screen.dart';
import 'package:auth/src/sign_up/account_created_screen.dart';
import 'package:auth/src/features/device_sign_up/add_kid_screen.dart';
import 'package:auth/src/features/device_sign_up/link_watch/link_watch_screen.dart';
import 'package:auth/src/features/device_sign_up/link_watch/link_watch_previous_screen.dart';
import 'package:auth/src/features/sign_up/account_created_screen.dart';
import 'package:auth/src/widgets/layouts/form_step_layout.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:navigation/navigation.dart';
class DeviceSignupScreen extends ConsumerStatefulWidget{
class DeviceSignupScreen extends ConsumerStatefulWidget {
final NavigationContract navigationContract;
const DeviceSignupScreen({super.key, required this.navigationContract});
@override
ConsumerState<DeviceSignupScreen> createState() => DeviceSignupScreenState(navigationContract);
ConsumerState<DeviceSignupScreen> createState() =>
DeviceSignupScreenState(navigationContract);
}
class DeviceSignupScreenState extends ConsumerState<DeviceSignupScreen>{
class DeviceSignupScreenState extends ConsumerState<DeviceSignupScreen> {
late int currentStep;
final NavigationContract navigationContract;
@@ -34,37 +35,46 @@ class DeviceSignupScreenState extends ConsumerState<DeviceSignupScreen>{
return getSteps()[currentStep];
}
List<Widget> getSteps(){
List<Widget> getSteps() {
final theme = ref.watch(themePortProvider);
final continueBtn = Container(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
child: PrimaryButton(
onPressed: ()=>{setState(() {
currentStep++;
})},
onPressed: () => {
setState(() {
currentStep++;
}),
},
text: "Continuar",
color: theme.getColorFor(ThemeCode.buttonPrimary)
)
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
);
return [
AddKidScreen(nextStep: ()=>{setState(() {
currentStep++;
})}),
AddKidScreen(
nextStep: () => {
setState(() {
currentStep++;
}),
},
),
FormStepLayout(
title: "Crea su perfil",
subtitle: "Necesitamos estos datos para crear su cuenta y gestionar sus pagos y gastos",
subtitle:
"Necesitamos estos datos para crear su cuenta y gestionar sus pagos y gastos",
currentStep: 1,
numSteps: 3,
body: [CreateProfileScreen()],
footer: [Container(
padding: EdgeInsets.all(24),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
child: continueBtn
)],
nextStep: ()=>{},
previousStep: ()=>{}
footer: [
Container(
padding: EdgeInsets.all(24),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
child: continueBtn,
),
],
nextStep: () => {},
previousStep: () => {},
),
FormStepLayout(
title: "Vincula su correa y su reloj",
@@ -72,28 +82,31 @@ class DeviceSignupScreenState extends ConsumerState<DeviceSignupScreen>{
numSteps: 3,
body: [LinkWatchPreviousScreen()],
footer: [continueBtn],
nextStep: ()=>{},
previousStep: ()=>{}
nextStep: () => {},
previousStep: () => {},
),
FormStepLayout(
title: "Vincula su correa\ny su reloj",
currentStep: 2,
numSteps: 3,
body: [LinkWatchScreen(step:1)],
body: [LinkWatchScreen(step: 1)],
footer: [continueBtn],
nextStep: ()=>{},
previousStep: ()=>{}
nextStep: () => {},
previousStep: () => {},
),
FormStepLayout(
title: "Vincula su correa\ny su reloj",
currentStep: 2,
numSteps: 3,
body: [LinkWatchScreen(step:2)],
body: [LinkWatchScreen(step: 2)],
footer: [continueBtn],
nextStep: ()=>{},
previousStep: ()=>{}
nextStep: () => {},
previousStep: () => {},
),
AccountCreatedScreen(
navigationContract: navigationContract,
kidAccount: true,
),
AccountCreatedScreen(navigationContract: navigationContract, kidAccount: true)
];
}
}
}

View File

@@ -0,0 +1,5 @@
abstract class LinkPhoneUseCase {
Future<void> requestCode({required String phone});
Future<void> verifyCode({required String phone, required String code});
}

View File

@@ -0,0 +1,18 @@
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
import 'package:auth/src/features/link_phone/domain/use_cases/link_phone_use_case.dart';
class LinkPhoneUseCaseImpl implements LinkPhoneUseCase {
LinkPhoneUseCaseImpl(this._repository);
final AuthRepository _repository;
@override
Future<void> requestCode({required String phone}) {
return _repository.requestPhoneCode(phone: phone);
}
@override
Future<void> verifyCode({required String phone, required String code}) {
return _repository.verifyPhoneCode(phone: phone, code: code);
}
}

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/login/presentation/link_phone_screen.dart';
import 'package:auth/src/features/link_phone/presentation/link_phone_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:get_it/get_it.dart';

View File

@@ -0,0 +1,114 @@
import 'package:auth/src/features/link_phone/presentation/link_phone_view_model.dart';
import 'package:design_system/design_system.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';
class LinkPhoneScreen extends ConsumerWidget {
final NavigationContract navigationContract;
const LinkPhoneScreen({super.key, required this.navigationContract});
void _onCountryChanged(int? value) {}
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final viewModel = ref.watch(linkPhoneViewModelProvider.notifier);
final viewState = ref.watch(linkPhoneViewModelProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
context.translate(I18n.linkPhoneTitle),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
const SizedBox(height: 24),
Text(
context.translate(I18n.linkPhoneSubtitle),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16, letterSpacing: 0),
),
const SizedBox(height: 48),
Column(
spacing: 8,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Text(
context.translate(I18n.mobilePhone),
style: const TextStyle(fontSize: 14, letterSpacing: 0),
),
),
Row(
spacing: 10,
children: [
CustomDropdown(
value: 0,
items: const [
Icon(Icons.outlined_flag),
Icon(Icons.outlined_flag),
Icon(Icons.outlined_flag),
],
onChanged: _onCountryChanged,
width: 80,
),
Expanded(
child: CustomTextField(
// controller: viewModel.phoneNumberController,
hint: context.translate(I18n.phoneNumber),
numeric: true,
),
),
],
),
],
),
const SizedBox(height: 16),
if (viewState.errorMessage?.isNotEmpty ?? false) ...[
const SizedBox(height: 4),
Text(
// context.translate(viewState.errorMessage
'',
textAlign: TextAlign.center,
style: const TextStyle(
color: Color.fromRGBO(239, 17, 17, 1),
fontSize: 12,
),
),
],
const SizedBox(height: 24),
PrimaryButton(
onPressed: () async {
await viewModel.requestCode();
final updatedState = ref.read(linkPhoneViewModelProvider);
if (updatedState.errorMessage!.isEmpty) {
navigationContract.pushTo(AppRoutes.phoneCode);
}
},
text: context.translate(I18n.next),
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,61 @@
import 'package:auth/src/features/link_phone/presentation/providers/link_phone_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:auth/src/features/link_phone/domain/use_cases/link_phone_use_case.dart';
import 'package:auth/src/features/link_phone/presentation/link_phone_view_state.dart';
final linkPhoneViewModelProvider =
NotifierProvider.autoDispose<LinkPhoneViewModel, LinkPhoneViewState>(
LinkPhoneViewModel.new,
);
class LinkPhoneViewModel extends Notifier<LinkPhoneViewState> {
late final LinkPhoneUseCase _linkPhoneUseCase;
late final TextEditingController phoneNumberController;
@override
LinkPhoneViewState build() {
_linkPhoneUseCase = ref.read(linkPhoneUseCaseProvider);
phoneNumberController = TextEditingController();
phoneNumberController.addListener(_onPhoneNumberChanged);
ref.onDispose(disposeControllers);
return const LinkPhoneViewState();
}
void _onPhoneNumberChanged() {
final raw = phoneNumberController.text;
state = state.copyWith(phoneNumber: raw, errorMessage: '');
}
Future<void> requestCode() async {
final phone = phoneNumberController.text.trim();
if (phone.isEmpty) {
state = state.copyWith(errorMessage: 'El teléfono no puede estar vacío');
return;
}
state = state.copyWith(isLoading: true, errorMessage: '');
try {
await _linkPhoneUseCase.requestCode(phone: phone);
if (!ref.mounted) return;
state = state.copyWith(
isLoading: false,
errorMessage: '',
codeRequested: true,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(isLoading: false, errorMessage: e.toString());
}
}
void disposeControllers() {
phoneNumberController.removeListener(_onPhoneNumberChanged);
phoneNumberController.dispose();
}
}

View File

@@ -0,0 +1,13 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'link_phone_view_state.freezed.dart';
@freezed
abstract class LinkPhoneViewState with _$LinkPhoneViewState {
const factory LinkPhoneViewState({
@Default('') String phoneNumber,
String? errorMessage,
@Default(false) bool isLoading,
@Default(false) bool codeRequested,
}) = _LinkPhoneViewState;
}

View File

@@ -0,0 +1,280 @@
// 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 'link_phone_view_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LinkPhoneViewState {
String get phoneNumber; String? get errorMessage; bool get isLoading; bool get codeRequested;
/// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LinkPhoneViewStateCopyWith<LinkPhoneViewState> get copyWith => _$LinkPhoneViewStateCopyWithImpl<LinkPhoneViewState>(this as LinkPhoneViewState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested));
}
@override
int get hashCode => Object.hash(runtimeType,phoneNumber,errorMessage,isLoading,codeRequested);
@override
String toString() {
return 'LinkPhoneViewState(phoneNumber: $phoneNumber, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested)';
}
}
/// @nodoc
abstract mixin class $LinkPhoneViewStateCopyWith<$Res> {
factory $LinkPhoneViewStateCopyWith(LinkPhoneViewState value, $Res Function(LinkPhoneViewState) _then) = _$LinkPhoneViewStateCopyWithImpl;
@useResult
$Res call({
String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested
});
}
/// @nodoc
class _$LinkPhoneViewStateCopyWithImpl<$Res>
implements $LinkPhoneViewStateCopyWith<$Res> {
_$LinkPhoneViewStateCopyWithImpl(this._self, this._then);
final LinkPhoneViewState _self;
final $Res Function(LinkPhoneViewState) _then;
/// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? phoneNumber = null,Object? errorMessage = freezed,Object? isLoading = null,Object? codeRequested = null,}) {
return _then(_self.copyWith(
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable
as String,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,codeRequested: null == codeRequested ? _self.codeRequested : codeRequested // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [LinkPhoneViewState].
extension LinkPhoneViewStatePatterns on LinkPhoneViewState {
/// 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( _LinkPhoneViewState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LinkPhoneViewState() 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( _LinkPhoneViewState value) $default,){
final _that = this;
switch (_that) {
case _LinkPhoneViewState():
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( _LinkPhoneViewState value)? $default,){
final _that = this;
switch (_that) {
case _LinkPhoneViewState() 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( String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LinkPhoneViewState() when $default != null:
return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeRequested);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( String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested) $default,) {final _that = this;
switch (_that) {
case _LinkPhoneViewState():
return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeRequested);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( String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested)? $default,) {final _that = this;
switch (_that) {
case _LinkPhoneViewState() when $default != null:
return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
return null;
}
}
}
/// @nodoc
class _LinkPhoneViewState implements LinkPhoneViewState {
const _LinkPhoneViewState({this.phoneNumber = '', this.errorMessage, this.isLoading = false, this.codeRequested = false});
@override@JsonKey() final String phoneNumber;
@override final String? errorMessage;
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool codeRequested;
/// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LinkPhoneViewStateCopyWith<_LinkPhoneViewState> get copyWith => __$LinkPhoneViewStateCopyWithImpl<_LinkPhoneViewState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested));
}
@override
int get hashCode => Object.hash(runtimeType,phoneNumber,errorMessage,isLoading,codeRequested);
@override
String toString() {
return 'LinkPhoneViewState(phoneNumber: $phoneNumber, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested)';
}
}
/// @nodoc
abstract mixin class _$LinkPhoneViewStateCopyWith<$Res> implements $LinkPhoneViewStateCopyWith<$Res> {
factory _$LinkPhoneViewStateCopyWith(_LinkPhoneViewState value, $Res Function(_LinkPhoneViewState) _then) = __$LinkPhoneViewStateCopyWithImpl;
@override @useResult
$Res call({
String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested
});
}
/// @nodoc
class __$LinkPhoneViewStateCopyWithImpl<$Res>
implements _$LinkPhoneViewStateCopyWith<$Res> {
__$LinkPhoneViewStateCopyWithImpl(this._self, this._then);
final _LinkPhoneViewState _self;
final $Res Function(_LinkPhoneViewState) _then;
/// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? phoneNumber = null,Object? errorMessage = freezed,Object? isLoading = null,Object? codeRequested = null,}) {
return _then(_LinkPhoneViewState(
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable
as String,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,codeRequested: null == codeRequested ? _self.codeRequested : codeRequested // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -0,0 +1,9 @@
import 'package:auth/src/core/providers/auth_repository_provider.dart';
import 'package:auth/src/features/link_phone/domain/use_cases/link_phone_use_case.dart';
import 'package:auth/src/features/link_phone/domain/use_cases/link_phone_use_case_impl.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final linkPhoneUseCaseProvider = Provider.autoDispose<LinkPhoneUseCase>((ref) {
final authRepository = ref.read(authRepositoryProvider);
return LinkPhoneUseCaseImpl(authRepository);
});

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/login/presentation/login_screen.dart';
import 'package:auth/src/features/login/presentation/login_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:get_it/get_it.dart';

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/login/presentation/phone_code_screen.dart';
import 'package:auth/src/features/login/presentation/phone_code_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:get_it/get_it.dart';

View File

@@ -1,5 +1,5 @@
import 'package:auth/src/login/presentation/loading_google_screen.dart';
import 'package:auth/src/sign_up/signup_screen.dart';
import 'package:auth/src/features/login/presentation/loading_google_screen.dart';
import 'package:auth/src/features/sign_up/signup_screen.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -15,7 +15,7 @@ class LoginScreen extends ConsumerStatefulWidget {
ConsumerState<ConsumerStatefulWidget> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen>{
class _LoginScreenState extends ConsumerState<LoginScreen> {
bool passwordVisible = false;
@override
@@ -28,7 +28,11 @@ class _LoginScreenState extends ConsumerState<LoginScreen>{
Column(
spacing: 8,
children: [
Icon(Icons.check, color: theme.getColorFor(ThemeCode.buttonPrimary), size: 50),
Icon(
Icons.check,
color: theme.getColorFor(ThemeCode.buttonPrimary),
size: 50,
),
Text(
// context.translate(I18n.example)
"¡Te damos la bienvenida!",
@@ -51,43 +55,48 @@ class _LoginScreenState extends ConsumerState<LoginScreen>{
spacing: 12,
children: [
CustomTextField(
showPassword: passwordVisible,
label: "Contraseña",
hint: "********"
showPassword: passwordVisible,
label: "Contraseña",
hint: "********",
),
Align(
alignment: Alignment.topLeft,
child: CustomTextButton(
text: "¿Has olvidado la contraseña?",
onPressed: () =>
widget.navigationContract.pushTo(AppRoutes.recoverPassword),
onPressed: () => widget.navigationContract.pushTo(
AppRoutes.recoverPassword,
),
size: 16,
)),
),
),
],
)
),
],
),
PrimaryButton(
onPressed: () => widget.navigationContract.goTo(AppRoutes.dashboardHome),
text: "Iniciar sesión",
color: theme.getColorFor(ThemeCode.buttonPrimary)
onPressed: () =>
widget.navigationContract.goTo(AppRoutes.dashboardHome),
text: "Iniciar sesión",
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
Container(
padding: EdgeInsets.only(top: 24),
child: Column(
spacing: 24,
children: [
Stack(children: [
Divider(endIndent: 74, indent: 74),
Align(
Stack(
children: [
Divider(endIndent: 74, indent: 74),
Align(
alignment: Alignment.center,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
child: Text("o continúa con"),
)
)
]),
),
),
],
),
Row(
spacing: 20,
children: [
@@ -105,7 +114,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>{
label: "Google",
),
SecondaryButton(
onPressed: ()=>{},
onPressed: () => {},
radius: 16,
padding: 44,
icon: Icons.apple,
@@ -121,18 +130,23 @@ class _LoginScreenState extends ConsumerState<LoginScreen>{
spacing: 8,
children: [
Text(
"¿No tienes cuenta?",
style: TextStyle(fontSize: 18, letterSpacing: 0)
"¿No tienes cuenta?",
style: TextStyle(fontSize: 18, letterSpacing: 0),
),
TextButton(
onPressed: () => widget.navigationContract.goTo(AppRoutes.signup),
onPressed: () =>
widget.navigationContract.goTo(AppRoutes.signup),
child: Text(
"Crear una ahora",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0)
)
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
),
],
)
),
],
),
];
@@ -153,8 +167,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>{
itemCount: content.length,
),
),
)
)
),
),
);
}
}

View File

@@ -5,12 +5,10 @@ import 'package:navigation/navigation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class PhoneCodeScreen extends ConsumerWidget {
// final String phone;
final NavigationContract navigationContract;
PhoneCodeScreen({super.key, required this.navigationContract});
// class PhoneCodeScreenState extends State<PhoneCodeScreen> {
final focusNodes = List<FocusNode>.generate(6, (int i) {
return FocusNode();
});
@@ -34,7 +32,10 @@ class PhoneCodeScreen extends ConsumerWidget {
children: [
Text(
"Conéctate",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 30,
),
),
Text.rich(
TextSpan(
@@ -55,7 +56,9 @@ class PhoneCodeScreen extends ConsumerWidget {
child: TextField(
focusNode: focusNodes[i],
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
textAlign: TextAlign.center,
decoration: InputDecoration(
hintText: "0",
@@ -78,7 +81,9 @@ class PhoneCodeScreen extends ConsumerWidget {
spacing: 24,
children: [
PrimaryButton(
onPressed: () => {navigationContract.pushTo(AppRoutes.login)},
onPressed: () => {
navigationContract.pushTo(AppRoutes.login),
},
text: "Entrar",
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
@@ -87,7 +92,11 @@ class PhoneCodeScreen extends ConsumerWidget {
children: [
Text(
"¿No lo has recibido?",
style: TextStyle(fontSize: 18, letterSpacing: 0, height: 1.5),
style: TextStyle(
fontSize: 18,
letterSpacing: 0,
height: 1.5,
),
),
CustomTextButton(
onPressed: () => {},
@@ -96,7 +105,7 @@ class PhoneCodeScreen extends ConsumerWidget {
weight: FontWeight.w500,
),
],
)
),
],
),
Spacer(flex: 10),

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/onboarding/presentation/onboarding_screen.dart';
import 'package:auth/src/features/onboarding/presentation/onboarding_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:get_it/get_it.dart';

View File

@@ -0,0 +1,126 @@
import 'package:auth/src/features/onboarding/domain/onboarding_page.dart';
import 'package:auth/src/features/onboarding/presentation/onboarding_view_model.dart';
import 'package:auth/src/features/onboarding/presentation/widgets/onboarding_content.dart';
import 'package:design_system/design_system.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: 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,
);
},
),
),
StepIndicator(
current: state.cardIndex + 1,
total: onboardingPages.length,
color: const Color(0xFF4A4A4A),
),
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: 48),
],
),
),
);
}
}

View File

@@ -1,6 +1,6 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:auth/src/onboarding/presentation/onboarding_view_state.dart';
import 'package:auth/src/features/onboarding/presentation/onboarding_view_state.dart';
final onBoardingViewModelProvider =
NotifierProvider.autoDispose<OnBoardingViewModel, OnboardingViewState>(

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/recover_password/presentation/sent_screen.dart';
import 'package:auth/src/features/recover_password/presentation/sent_screen.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:navigation/navigation.dart';
@@ -47,26 +47,30 @@ class RestorePasswordScreen extends ConsumerWidget {
spacing: 8,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Text(
"Teléfono móvil",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)
alignment: Alignment.bottomLeft,
child: Text(
"Teléfono móvil",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
),
Row(
spacing: 10,
children: [
CustomDropdown(
value: 0,
items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)],
onChanged: (value)=> {},
items: [
Icon(Icons.outlined_flag),
Icon(Icons.outlined_flag),
Icon(Icons.outlined_flag),
],
onChanged: (value) => {},
width: 80,
),
Expanded(
child: CustomTextField(
hint: "Teléfono",
numeric: true
)
child: CustomTextField(
hint: "Teléfono",
numeric: true,
),
),
],
),
@@ -75,11 +79,14 @@ class RestorePasswordScreen extends ConsumerWidget {
Row(
spacing: 20,
children: [
Expanded( child: SecondaryButton(
Expanded(
child: SecondaryButton(
onPressed: () => {Navigator.pop(context)},
text: "Volver"
)),
Expanded( child: PrimaryButton(
text: "Volver",
),
),
Expanded(
child: PrimaryButton(
onPressed: () => {
Navigator.push(
context,
@@ -90,8 +97,9 @@ class RestorePasswordScreen extends ConsumerWidget {
},
text: "Enviar",
size: 16,
color: theme.getColorFor(ThemeCode.buttonSecondary)
)),
color: theme.getColorFor(ThemeCode.buttonSecondary),
),
),
],
),
],

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/recover_password/presentation/new_password_screen.dart';
import 'package:auth/src/features/recover_password/presentation/new_password_screen.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -6,10 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
class SentScreen extends ConsumerWidget {
final String format;
const SentScreen({
super.key,
required this.format
});
const SentScreen({super.key, required this.format});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -27,7 +24,11 @@ class SentScreen extends ConsumerWidget {
Text(
"Recuperar contraseña",
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30, letterSpacing: 0),
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 30,
letterSpacing: 0,
),
),
Row(
spacing: 10,
@@ -38,9 +39,9 @@ class SentScreen extends ConsumerWidget {
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
Text(
format=="email"
?"Correo enviado correctamente"
:"SMS enviado correctamente",
format == "email"
? "Correo enviado correctamente"
: "SMS enviado correctamente",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Spacer(),
@@ -50,16 +51,16 @@ class SentScreen extends ConsumerWidget {
spacing: 16,
children: [
Text(
format=="email"
?"Revisa tu email y haz clic en el enlace para crear una nueva contraseña."
:"Revisa tu móvil y sigue las instrucciones para crear una nueva contraseña.",
format == "email"
? "Revisa tu email y haz clic en el enlace para crear una nueva contraseña."
: "Revisa tu móvil y sigue las instrucciones para crear una nueva contraseña.",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, letterSpacing: 0),
),
Text(
format=="email"
?"Si no recibes el correo en unos minutos, revisa tu carpeta de spam o pulsa \"Reenviar correo\"."
:"Si no recibes el SMS en unos minutos, asegúrate de tener cobertura o pulsa \"Reenviar SMS \".",
format == "email"
? "Si no recibes el correo en unos minutos, revisa tu carpeta de spam o pulsa \"Reenviar correo\"."
: "Si no recibes el SMS en unos minutos, asegúrate de tener cobertura o pulsa \"Reenviar SMS \".",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
@@ -68,22 +69,22 @@ class SentScreen extends ConsumerWidget {
Row(
spacing: 10,
children: [
Expanded( child: SecondaryButton(
onPressed: () => {},
text: format=="email"
?"Reenviar correo"
:"Reenviar SMS"
)),
Expanded(
child: SecondaryButton(
onPressed: () => {},
text: format == "email"
? "Reenviar correo"
: "Reenviar SMS",
),
),
Expanded(
child: PrimaryButton(
onPressed: ()=>Navigator.push(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => NewPasswordScreen(),
),
MaterialPageRoute(builder: (_) => NewPasswordScreen()),
),
text: "Continuar",
color: theme.getColorFor(ThemeCode.buttonSecondary)
color: theme.getColorFor(ThemeCode.buttonSecondary),
),
),
],

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/recover_password/presentation/restore_password_screen.dart';
import 'package:auth/src/features/recover_password/presentation/restore_password_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:get_it/get_it.dart';

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/sign_up/signup_screen.dart';
import 'package:auth/src/features/sign_up/signup_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:get_it/get_it.dart';

View File

@@ -1,9 +1,9 @@
import 'package:auth/src/widgets/layouts/form_step_layout.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:auth/src/sign_up/signup_address_screen.dart';
import 'package:auth/src/sign_up/signup_personal_screen.dart';
import 'package:auth/src/sign_up/signup_user_screen.dart';
import 'package:auth/src/features/sign_up/signup_address_screen.dart';
import 'package:auth/src/features/sign_up/signup_personal_screen.dart';
import 'package:auth/src/features/sign_up/signup_user_screen.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get_it/get_it.dart';
import 'package:navigation/navigation.dart';
@@ -13,10 +13,7 @@ import 'account_created_screen.dart';
class SignupScreen extends ConsumerStatefulWidget {
NavigationContract navigationContract;
SignupScreen({
super.key,
required this.navigationContract
});
SignupScreen({super.key, required this.navigationContract});
@override
ConsumerState<SignupScreen> createState() => _SignupScreenState();
@@ -37,8 +34,9 @@ class _SignupScreenState extends ConsumerState<SignupScreen> {
return [
FormStepLayout(
title: "Crea tu usuario",
subtitle: "Con tu email y tu número podremos mantenerte siempre informado",
supertitle: "Usuario y contacto",
subtitle:
"Con tu email y tu número podremos mantenerte siempre informado",
supertitle: "Usuario y contacto",
currentStep: 1,
numSteps: 3,
body: [SignupPersonalScreen()],
@@ -46,20 +44,26 @@ class _SignupScreenState extends ConsumerState<SignupScreen> {
Row(
spacing: 16,
children: [
Expanded(child: SecondaryButton(
onPressed: ()=>{},
text: "Atrás",
size: 16,
)),
Expanded(child: PrimaryButton(
onPressed: ()=>{setState(() {
currentStep++;
})},
text: "Siguiente",
size: 16,
color: theme.getColorFor(ThemeCode.buttonSecondary)
))
]
Expanded(
child: SecondaryButton(
onPressed: () => {},
text: "Atrás",
size: 16,
),
),
Expanded(
child: PrimaryButton(
onPressed: () => {
setState(() {
currentStep++;
}),
},
text: "Siguiente",
size: 16,
color: theme.getColorFor(ThemeCode.buttonSecondary),
),
),
],
),
CheckboxListTile(
value: acceptTerms,
@@ -74,42 +78,57 @@ class _SignupScreenState extends ConsumerState<SignupScreen> {
contentPadding: EdgeInsets.zero,
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
controlAffinity: ListTileControlAffinity.leading,
)
),
],
nextStep: ()=>{setState(() {
currentStep++;
})},
previousStep: ()=>{},
nextStep: () => {
setState(() {
currentStep++;
}),
},
previousStep: () => {},
),
FormStepLayout(
title: "Tu dirección",
subtitle: "Tu dirección nos ayudará a verificar y mantener la seguridad de tu cuenta",
subtitle:
"Tu dirección nos ayudará a verificar y mantener la seguridad de tu cuenta",
supertitle: "Domicilio",
currentStep: 2,
numSteps: 3,
body: [SignupAddressScreen()],
nextStep: ()=>{setState(() {
currentStep++;
})},
previousStep: ()=>{setState(() {
currentStep--;
})},
nextStep: () => {
setState(() {
currentStep++;
}),
},
previousStep: () => {
setState(() {
currentStep--;
}),
},
),
FormStepLayout(
title: "Identifícate",
subtitle: "Contraseña mínima de 8 caracteres, con una mayúscula, un número y un carácter especial",
subtitle:
"Contraseña mínima de 8 caracteres, con una mayúscula, un número y un carácter especial",
supertitle: "Usuario y contacto",
currentStep: 3,
numSteps: 3,
body: [SignupUserScreen()],
nextStep: ()=>{setState(() {
currentStep++;
})},
previousStep: ()=>{setState(() {
currentStep--;
})},
nextStep: () => {
setState(() {
currentStep++;
}),
},
previousStep: () => {
setState(() {
currentStep--;
}),
},
),
AccountCreatedScreen(
navigationContract: widget.navigationContract,
kidAccount: false,
),
AccountCreatedScreen(navigationContract: widget.navigationContract, kidAccount: false)
];
}
}

View File

@@ -1,78 +0,0 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:navigation/navigation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class LinkPhoneScreen extends ConsumerWidget {
final NavigationContract navigationContract;
const LinkPhoneScreen({super.key, required this.navigationContract});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
// TextEditingController phoneController = TextEditingController();
// String? phone;
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: SafeArea(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 24),
child: Expanded(
child: Center(
child: Column(
spacing: 48,
children: [
Spacer(flex: 8),
Text(
"¡Nos alegra mucho tenerte por aquí!",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0),
),
Text(
"Para poder entrar de forma segura, te vamos a enviar un código al teléfono",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
Column(
spacing: 8,
children: [
Align(alignment: Alignment.bottomLeft, child: Text(
"Teléfono móvil",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)),
Row(
spacing: 10,
children: [
CustomDropdown(
value: 0,
items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)],
onChanged: (value)=> {},
width: 80,
),
Expanded(
child: CustomTextField(
hint: "Teléfono",
numeric: true
)
),
],
),
],
),
PrimaryButton(
onPressed: () => navigationContract.pushTo(AppRoutes.phoneCode),
text: "Siguiente",
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
Spacer(flex: 10)
],
),
),
),
),
));
}
}

View File

@@ -1,128 +0,0 @@
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:design_system/design_system.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,
);
},
),
),
StepIndicator(
current: state.cardIndex,
total: onboardingPages.length,
color: const Color(0xFF4A4A4A),
),
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

@@ -22,6 +22,8 @@ dependencies:
path: ../../packages/navigation
sf_localizations:
path: ../../packages/sf_localizations
sf_infrastructure:
path: ../../packages/sf_infrastructure
#dependencies go here
flutter_svg: ^2.2.1
get_it: ^9.0.5
@@ -29,6 +31,7 @@ dependencies:
flutter_riverpod: ^3.0.3
freezed_annotation: ^3.1.0
freezed: ^3.2.3
dio: ^5.9.0
dev_dependencies:
flutter_test:

View File

@@ -1,4 +1,4 @@
# melos_managed_dependency_overrides: dashboard_shell,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts
# melos_managed_dependency_overrides: dashboard_shell,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure
dependency_overrides:
dashboard_shell:
path: ../dashboard_shell
@@ -14,6 +14,8 @@ dependency_overrides:
path: ../notifications
profile:
path: ../profile
sf_infrastructure:
path: ../../packages/sf_infrastructure
sf_localizations:
path: ../../packages/sf_localizations
sf_shared: