From 417f0021ad4988f129c041d430762cfcf1602435 Mon Sep 17 00:00:00 2001 From: JulianAlcala Date: Thu, 12 Feb 2026 09:02:43 +0100 Subject: [PATCH] create wallet, treezor session heartbeat, get payment profile, device setup refactor, flow management from splash screen, sca pin dynamic --- .dart_tool/extension_discovery/devtools.json | 1 + apps/mobile_app/lib/main.dart | 9 + .../mobile_app/lib/navigation/app_router.dart | 2 +- .../providers/wallet_heartbeat_service.dart | 56 ++ modules/auth/lib/auth.dart | 2 + .../datasource/auth_remote_datasource.dart | 5 + .../auth_remote_datasource_impl.dart | 36 + .../datasource/session_local_datasource.dart | 5 + .../session_local_datasource_impl.dart | 25 + .../payment_profile_response_model.dart | 107 +++ ...ayment_profile_response_model.freezed.dart | 905 ++++++++++++++++++ .../payment_profile_response_model.g.dart | 93 ++ .../repositories/auth_repository_impl.dart | 15 + .../domain/repositories/auth_repository.dart | 5 + .../session_local_datasource_provider.dart | 7 + .../presentation/add_kid_step_mapper.dart | 2 - .../presentation/device_setup_screen.dart | 61 +- .../presentation/enums/add_kid_step.dart | 2 +- .../presentation/qr_scanner_screen.dart | 2 +- .../state/device_setup_view_model.dart | 73 +- .../device_setup/presentation/step_body.dart | 3 - .../presentation/steps/sca_step.dart | 212 ---- .../entities/payment_profile_entity.dart | 40 + .../payment_profile_entity.freezed.dart | 606 ++++++++++++ .../presentation/state/login_view_model.dart | 2 - .../features/sca_treezor/sca_pin_view.dart | 242 +++++ .../sca_treezor/sca_treezor_screen.dart | 332 +------ .../sca_treezor/sca_treezor_view_model.dart | 46 +- .../sign_up/presentation/sign_up_screen.dart | 3 +- .../state/sign_up_view_model.dart | 20 +- .../src/data/splash_remote_datasource.dart | 3 + .../data/splash_remote_datasource_impl.dart | 19 + .../src/domain/check_session_use_case.dart | 5 + .../domain/check_session_use_case_impl.dart | 22 + .../splash/lib/src/domain/initial_route.dart | 1 + .../lib/src/presentation/splash_screen.dart | 27 +- modules/splash/lib/src/splash_builder.dart | 12 +- modules/splash/pubspec.yaml | 2 + .../antelop/antelop/maven-metadata.xml | 2 +- .../antelop/antelop/maven-metadata.xml.md5 | 2 +- .../antelop/antelop/maven-metadata.xml.sha1 | 2 +- .../lib/configure_dependencies.dart | 13 +- .../lib/src/network/dio_client.dart | 12 +- packages/sf_localizations/assets/l10n/de.json | 23 +- packages/sf_localizations/assets/l10n/en.json | 23 +- packages/sf_localizations/assets/l10n/es.json | 23 +- packages/sf_localizations/assets/l10n/fr.json | 23 +- packages/sf_localizations/assets/l10n/it.json | 23 +- packages/sf_localizations/assets/l10n/pt.json | 23 +- .../lib/src/generated/i18n.dart | 20 + 50 files changed, 2588 insertions(+), 611 deletions(-) create mode 100644 .dart_tool/extension_discovery/devtools.json create mode 100644 apps/mobile_app/lib/providers/wallet_heartbeat_service.dart create mode 100644 modules/auth/lib/src/core/data/datasource/session_local_datasource.dart create mode 100644 modules/auth/lib/src/core/data/datasource/session_local_datasource_impl.dart create mode 100644 modules/auth/lib/src/core/data/models/payment_profile_response_model.dart create mode 100644 modules/auth/lib/src/core/data/models/payment_profile_response_model.freezed.dart create mode 100644 modules/auth/lib/src/core/data/models/payment_profile_response_model.g.dart create mode 100644 modules/auth/lib/src/core/providers/session_local_datasource_provider.dart delete mode 100644 modules/auth/lib/src/features/device_setup/presentation/steps/sca_step.dart create mode 100644 modules/auth/lib/src/features/login/domain/entities/payment_profile_entity.dart create mode 100644 modules/auth/lib/src/features/login/domain/entities/payment_profile_entity.freezed.dart create mode 100644 modules/auth/lib/src/features/sca_treezor/sca_pin_view.dart create mode 100644 modules/splash/lib/src/data/splash_remote_datasource.dart create mode 100644 modules/splash/lib/src/data/splash_remote_datasource_impl.dart create mode 100644 modules/splash/lib/src/domain/check_session_use_case.dart create mode 100644 modules/splash/lib/src/domain/check_session_use_case_impl.dart create mode 100644 modules/splash/lib/src/domain/initial_route.dart diff --git a/.dart_tool/extension_discovery/devtools.json b/.dart_tool/extension_discovery/devtools.json new file mode 100644 index 00000000..ae1a36a6 --- /dev/null +++ b/.dart_tool/extension_discovery/devtools.json @@ -0,0 +1 @@ +{"version":2,"entries":[{"package":"sf_app_platform_mono_repo","rootUri":"../","packageUri":"lib/"}]} \ No newline at end of file diff --git a/apps/mobile_app/lib/main.dart b/apps/mobile_app/lib/main.dart index 82f7f8b2..a4f34335 100644 --- a/apps/mobile_app/lib/main.dart +++ b/apps/mobile_app/lib/main.dart @@ -10,6 +10,7 @@ import 'package:sf_app_platform/navigation/app_router.dart'; import 'package:navigation/navigation_module.dart'; import 'package:sf_app_platform/providers/app_state_provider.dart'; import 'package:sf_app_platform/providers/permissions/permissions_provider.dart'; +import 'package:sf_app_platform/providers/wallet_heartbeat_service.dart'; import 'package:sf_infrastructure/sf_infrastructure.dart'; import 'package:sf_localizations/sf_localizations.dart'; import 'package:utils/utils.dart'; @@ -36,14 +37,19 @@ class PlatformApp extends ConsumerStatefulWidget { class PlatformAppState extends ConsumerState with WidgetsBindingObserver { + late final WalletHeartbeatService walletHeartbeat; + @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); + walletHeartbeat = WalletHeartbeatService.create(); + walletHeartbeat.start(); } @override void dispose() { + walletHeartbeat.stop(); WidgetsBinding.instance.removeObserver(this); super.dispose(); } @@ -53,7 +59,10 @@ class PlatformAppState extends ConsumerState debugPrint('State: $state'); ref.read(appLifecycleStateProvider.notifier).setState(state); if (state == AppLifecycleState.resumed) { + walletHeartbeat.start(); ref.read(permissionsProvider.notifier).checkPermissions(); + } else if (state == AppLifecycleState.paused) { + walletHeartbeat.stop(); } super.didChangeAppLifecycleState(state); } diff --git a/apps/mobile_app/lib/navigation/app_router.dart b/apps/mobile_app/lib/navigation/app_router.dart index 67fe3365..85b8aaa2 100644 --- a/apps/mobile_app/lib/navigation/app_router.dart +++ b/apps/mobile_app/lib/navigation/app_router.dart @@ -16,7 +16,7 @@ late final GoRouter appRouter; void configureAppRouter() { appRouter = GoRouter( navigatorKey: rootNavigatorKey, - initialLocation: AppRoutes.login, + initialLocation: AppRoutes.splash, debugLogDiagnostics: true, routes: [ GoRoute( diff --git a/apps/mobile_app/lib/providers/wallet_heartbeat_service.dart b/apps/mobile_app/lib/providers/wallet_heartbeat_service.dart new file mode 100644 index 00000000..79ab6ec2 --- /dev/null +++ b/apps/mobile_app/lib/providers/wallet_heartbeat_service.dart @@ -0,0 +1,56 @@ +import 'dart:async'; + +import 'package:auth/auth.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:sf_infrastructure/sf_infrastructure.dart'; + +class WalletHeartbeatService { + WalletHeartbeatService({ + required QuestiaRepository repository, + required SessionLocalDatasource sessionLocal, + }) : _repository = repository, + _sessionLocal = sessionLocal; + + final QuestiaRepository _repository; + final SessionLocalDatasource _sessionLocal; + Timer? _timer; + + static const _interval = Duration(minutes: 4); + + factory WalletHeartbeatService.create() { + return WalletHeartbeatService( + repository: GetIt.I(), + sessionLocal: SessionLocalDatasourceImpl(), + ); + } + + void start() { + if (_timer != null) return; + _beat(); + _timer = Timer.periodic(_interval, (_) => _beat()); + debugPrint('[WalletHeartbeat] started'); + } + + void stop() { + _timer?.cancel(); + _timer = null; + debugPrint('[WalletHeartbeat] stopped'); + } + + Future _beat() async { + final walletId = await _sessionLocal.getPaymentProfileId(); + if (walletId == null || walletId.isEmpty) return; + + try { + final response = await _repository.get>( + '/wallets/$walletId', + ); + debugPrint( + '[WalletHeartbeat] /wallets/$walletId => ${response.statusCode}', + ); + } catch (e) { + debugPrint('[WalletHeartbeat] error: $e'); + } + } +} diff --git a/modules/auth/lib/auth.dart b/modules/auth/lib/auth.dart index 095f61cd..06af03f4 100644 --- a/modules/auth/lib/auth.dart +++ b/modules/auth/lib/auth.dart @@ -6,3 +6,5 @@ export 'src/features/recover_password/presentation/request_recovery/request_reco export 'src/features/device_setup/device_setup_builder.dart'; export 'src/features/sign_up/sign_up_builder.dart'; export 'src/features/sca_treezor/sca_treezor_builder.dart'; +export 'src/core/data/datasource/session_local_datasource.dart'; +export 'src/core/data/datasource/session_local_datasource_impl.dart'; diff --git a/modules/auth/lib/src/core/data/datasource/auth_remote_datasource.dart b/modules/auth/lib/src/core/data/datasource/auth_remote_datasource.dart index 05cd937f..7511b89c 100644 --- a/modules/auth/lib/src/core/data/datasource/auth_remote_datasource.dart +++ b/modules/auth/lib/src/core/data/datasource/auth_remote_datasource.dart @@ -1,4 +1,5 @@ import 'package:auth/src/core/data/models/child_profile_response_model.dart'; +import 'package:auth/src/core/data/models/payment_profile_response_model.dart'; import 'package:auth/src/core/data/models/user_response_model.dart'; import 'package:auth/src/core/data/models/sign_up_request_model.dart'; import 'package:auth/src/core/data/models/sign_up_response_model.dart'; @@ -41,6 +42,10 @@ abstract class AuthRemoteDatasource { Future recoverPassword({required newPassword, required token}); + Future createWallet(); + + Future getPaymentProfile({required String userId}); + Future createChildProfile({ required String id, required String parentId, diff --git a/modules/auth/lib/src/core/data/datasource/auth_remote_datasource_impl.dart b/modules/auth/lib/src/core/data/datasource/auth_remote_datasource_impl.dart index bf309170..1d2019f8 100644 --- a/modules/auth/lib/src/core/data/datasource/auth_remote_datasource_impl.dart +++ b/modules/auth/lib/src/core/data/datasource/auth_remote_datasource_impl.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:auth/src/core/data/models/child_profile_response_model.dart'; +import 'package:auth/src/core/data/models/payment_profile_response_model.dart'; import 'package:auth/src/core/data/models/user_response_model.dart'; import 'package:auth/src/core/data/models/sign_up_request_model.dart'; import 'package:auth/src/core/data/models/sign_up_response_model.dart'; @@ -270,6 +271,41 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource { } } + @override + Future createWallet() async { + try { + await _repository.post( + '/wallets', + body: {'walletName': 'default'}, + ); + } on DioException catch (error) { + throw _mapDioError(error, defaultMessage: 'Error in /wallets'); + } + } + + @override + Future getPaymentProfile({ + required String userId, + }) async { + try { + final response = await _repository.get>( + '/users/$userId/payment-profile', + ); + + final data = response.data; + if (data == null || data.isEmpty) { + throw Exception('Empty response from /users/$userId/payment-profile'); + } + + return PaymentProfileResponseModel.fromJson(data); + } on DioException catch (error) { + throw _mapDioError( + error, + defaultMessage: 'Error in /users/$userId/payment-profile', + ); + } + } + @override Future createChildProfile({ required String id, diff --git a/modules/auth/lib/src/core/data/datasource/session_local_datasource.dart b/modules/auth/lib/src/core/data/datasource/session_local_datasource.dart new file mode 100644 index 00000000..72d4d284 --- /dev/null +++ b/modules/auth/lib/src/core/data/datasource/session_local_datasource.dart @@ -0,0 +1,5 @@ +abstract class SessionLocalDatasource { + Future savePaymentProfileId(String id); + Future getPaymentProfileId(); + Future clearSession(); +} diff --git a/modules/auth/lib/src/core/data/datasource/session_local_datasource_impl.dart b/modules/auth/lib/src/core/data/datasource/session_local_datasource_impl.dart new file mode 100644 index 00000000..ffd3c42c --- /dev/null +++ b/modules/auth/lib/src/core/data/datasource/session_local_datasource_impl.dart @@ -0,0 +1,25 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +import 'session_local_datasource.dart'; + +class SessionLocalDatasourceImpl implements SessionLocalDatasource { + static const _paymentProfileIdKey = 'session_payment_profile_id'; + + @override + Future savePaymentProfileId(String id) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_paymentProfileIdKey, id); + } + + @override + Future getPaymentProfileId() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(_paymentProfileIdKey); + } + + @override + Future clearSession() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_paymentProfileIdKey); + } +} diff --git a/modules/auth/lib/src/core/data/models/payment_profile_response_model.dart b/modules/auth/lib/src/core/data/models/payment_profile_response_model.dart new file mode 100644 index 00000000..ac9d295b --- /dev/null +++ b/modules/auth/lib/src/core/data/models/payment_profile_response_model.dart @@ -0,0 +1,107 @@ +import 'package:auth/src/features/login/domain/entities/payment_profile_entity.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'payment_profile_response_model.freezed.dart'; +part 'payment_profile_response_model.g.dart'; + +@freezed +abstract class PaymentProfileResponseModel with _$PaymentProfileResponseModel { + const factory PaymentProfileResponseModel({ + required PaymentProfileItemModel item, + }) = _PaymentProfileResponseModel; + + factory PaymentProfileResponseModel.fromJson(Map json) => + _$PaymentProfileResponseModelFromJson(json); +} + +@freezed +abstract class PaymentProfileItemModel with _$PaymentProfileItemModel { + const factory PaymentProfileItemModel({ + required String id, + required String userId, + required String paymentProfileId, + String? jwt, + required int bornAt, + required String phone, + required List taxResidences, + required List addresses, + required String status, + //talk with Hector to change those names in the backend to be more consistent with the rest of the fields + // ignore: non_constant_identifier_names + required String KYCStatus, + // ignore: non_constant_identifier_names + required String KYCLevel, + required String placeOfBirth, + required String birthCountry, + required String nationality, + required String documentType, + required String document, + String? paymentWalletId, + required int createdAt, + }) = _PaymentProfileItemModel; + + factory PaymentProfileItemModel.fromJson(Map json) => + _$PaymentProfileItemModelFromJson(json); +} + +@freezed +abstract class PaymentProfileAddressModel with _$PaymentProfileAddressModel { + const factory PaymentProfileAddressModel({ + required String street, + required String city, + required String province, + required String state, + required String country, + required int postCode, + }) = _PaymentProfileAddressModel; + + factory PaymentProfileAddressModel.fromJson(Map json) => + _$PaymentProfileAddressModelFromJson(json); +} + +extension PaymentProfileResponseModelMapper on PaymentProfileResponseModel { + PaymentProfileEntity toEntity() { + return PaymentProfileEntity( + id: item.id, + userId: item.userId, + paymentProfileId: item.paymentProfileId, + jwt: item.jwt, + bornAt: item.bornAt, + phone: item.phone, + taxResidences: item.taxResidences + .map( + (a) => PaymentProfileAddressEntity( + street: a.street, + city: a.city, + province: a.province, + state: a.state, + country: a.country, + postCode: a.postCode, + ), + ) + .toList(), + addresses: item.addresses + .map( + (a) => PaymentProfileAddressEntity( + street: a.street, + city: a.city, + province: a.province, + state: a.state, + country: a.country, + postCode: a.postCode, + ), + ) + .toList(), + status: item.status, + kycStatus: item.KYCStatus, + kycLevel: item.KYCLevel, + placeOfBirth: item.placeOfBirth, + birthCountry: item.birthCountry, + nationality: item.nationality, + documentType: item.documentType, + document: item.document, + paymentWalletId: item.paymentWalletId, + createdAt: item.createdAt, + ); + } +} diff --git a/modules/auth/lib/src/core/data/models/payment_profile_response_model.freezed.dart b/modules/auth/lib/src/core/data/models/payment_profile_response_model.freezed.dart new file mode 100644 index 00000000..a1f5314d --- /dev/null +++ b/modules/auth/lib/src/core/data/models/payment_profile_response_model.freezed.dart @@ -0,0 +1,905 @@ +// 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 'payment_profile_response_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$PaymentProfileResponseModel { + + PaymentProfileItemModel get item; +/// Create a copy of PaymentProfileResponseModel +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PaymentProfileResponseModelCopyWith get copyWith => _$PaymentProfileResponseModelCopyWithImpl(this as PaymentProfileResponseModel, _$identity); + + /// Serializes this PaymentProfileResponseModel to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentProfileResponseModel&&(identical(other.item, item) || other.item == item)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,item); + +@override +String toString() { + return 'PaymentProfileResponseModel(item: $item)'; +} + + +} + +/// @nodoc +abstract mixin class $PaymentProfileResponseModelCopyWith<$Res> { + factory $PaymentProfileResponseModelCopyWith(PaymentProfileResponseModel value, $Res Function(PaymentProfileResponseModel) _then) = _$PaymentProfileResponseModelCopyWithImpl; +@useResult +$Res call({ + PaymentProfileItemModel item +}); + + +$PaymentProfileItemModelCopyWith<$Res> get item; + +} +/// @nodoc +class _$PaymentProfileResponseModelCopyWithImpl<$Res> + implements $PaymentProfileResponseModelCopyWith<$Res> { + _$PaymentProfileResponseModelCopyWithImpl(this._self, this._then); + + final PaymentProfileResponseModel _self; + final $Res Function(PaymentProfileResponseModel) _then; + +/// Create a copy of PaymentProfileResponseModel +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? item = null,}) { + return _then(_self.copyWith( +item: null == item ? _self.item : item // ignore: cast_nullable_to_non_nullable +as PaymentProfileItemModel, + )); +} +/// Create a copy of PaymentProfileResponseModel +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PaymentProfileItemModelCopyWith<$Res> get item { + + return $PaymentProfileItemModelCopyWith<$Res>(_self.item, (value) { + return _then(_self.copyWith(item: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [PaymentProfileResponseModel]. +extension PaymentProfileResponseModelPatterns on PaymentProfileResponseModel { +/// 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 Function( _PaymentProfileResponseModel value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PaymentProfileResponseModel() 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 Function( _PaymentProfileResponseModel value) $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileResponseModel(): +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? Function( _PaymentProfileResponseModel value)? $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileResponseModel() 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 Function( PaymentProfileItemModel item)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PaymentProfileResponseModel() when $default != null: +return $default(_that.item);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 Function( PaymentProfileItemModel item) $default,) {final _that = this; +switch (_that) { +case _PaymentProfileResponseModel(): +return $default(_that.item);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? Function( PaymentProfileItemModel item)? $default,) {final _that = this; +switch (_that) { +case _PaymentProfileResponseModel() when $default != null: +return $default(_that.item);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _PaymentProfileResponseModel implements PaymentProfileResponseModel { + const _PaymentProfileResponseModel({required this.item}); + factory _PaymentProfileResponseModel.fromJson(Map json) => _$PaymentProfileResponseModelFromJson(json); + +@override final PaymentProfileItemModel item; + +/// Create a copy of PaymentProfileResponseModel +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PaymentProfileResponseModelCopyWith<_PaymentProfileResponseModel> get copyWith => __$PaymentProfileResponseModelCopyWithImpl<_PaymentProfileResponseModel>(this, _$identity); + +@override +Map toJson() { + return _$PaymentProfileResponseModelToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentProfileResponseModel&&(identical(other.item, item) || other.item == item)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,item); + +@override +String toString() { + return 'PaymentProfileResponseModel(item: $item)'; +} + + +} + +/// @nodoc +abstract mixin class _$PaymentProfileResponseModelCopyWith<$Res> implements $PaymentProfileResponseModelCopyWith<$Res> { + factory _$PaymentProfileResponseModelCopyWith(_PaymentProfileResponseModel value, $Res Function(_PaymentProfileResponseModel) _then) = __$PaymentProfileResponseModelCopyWithImpl; +@override @useResult +$Res call({ + PaymentProfileItemModel item +}); + + +@override $PaymentProfileItemModelCopyWith<$Res> get item; + +} +/// @nodoc +class __$PaymentProfileResponseModelCopyWithImpl<$Res> + implements _$PaymentProfileResponseModelCopyWith<$Res> { + __$PaymentProfileResponseModelCopyWithImpl(this._self, this._then); + + final _PaymentProfileResponseModel _self; + final $Res Function(_PaymentProfileResponseModel) _then; + +/// Create a copy of PaymentProfileResponseModel +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? item = null,}) { + return _then(_PaymentProfileResponseModel( +item: null == item ? _self.item : item // ignore: cast_nullable_to_non_nullable +as PaymentProfileItemModel, + )); +} + +/// Create a copy of PaymentProfileResponseModel +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PaymentProfileItemModelCopyWith<$Res> get item { + + return $PaymentProfileItemModelCopyWith<$Res>(_self.item, (value) { + return _then(_self.copyWith(item: value)); + }); +} +} + + +/// @nodoc +mixin _$PaymentProfileItemModel { + + String get id; String get userId; String get paymentProfileId; String? get jwt; int get bornAt; String get phone; List get taxResidences; List get addresses; String get status;//talk with Hector to change those names in the backend to be more consistent with the rest of the fields +// ignore: non_constant_identifier_names + String get KYCStatus;// ignore: non_constant_identifier_names + String get KYCLevel; String get placeOfBirth; String get birthCountry; String get nationality; String get documentType; String get document; String? get paymentWalletId; int get createdAt; +/// Create a copy of PaymentProfileItemModel +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PaymentProfileItemModelCopyWith get copyWith => _$PaymentProfileItemModelCopyWithImpl(this as PaymentProfileItemModel, _$identity); + + /// Serializes this PaymentProfileItemModel to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentProfileItemModel&&(identical(other.id, id) || other.id == id)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.paymentProfileId, paymentProfileId) || other.paymentProfileId == paymentProfileId)&&(identical(other.jwt, jwt) || other.jwt == jwt)&&(identical(other.bornAt, bornAt) || other.bornAt == bornAt)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other.taxResidences, taxResidences)&&const DeepCollectionEquality().equals(other.addresses, addresses)&&(identical(other.status, status) || other.status == status)&&(identical(other.KYCStatus, KYCStatus) || other.KYCStatus == KYCStatus)&&(identical(other.KYCLevel, KYCLevel) || other.KYCLevel == KYCLevel)&&(identical(other.placeOfBirth, placeOfBirth) || other.placeOfBirth == placeOfBirth)&&(identical(other.birthCountry, birthCountry) || other.birthCountry == birthCountry)&&(identical(other.nationality, nationality) || other.nationality == nationality)&&(identical(other.documentType, documentType) || other.documentType == documentType)&&(identical(other.document, document) || other.document == document)&&(identical(other.paymentWalletId, paymentWalletId) || other.paymentWalletId == paymentWalletId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,userId,paymentProfileId,jwt,bornAt,phone,const DeepCollectionEquality().hash(taxResidences),const DeepCollectionEquality().hash(addresses),status,KYCStatus,KYCLevel,placeOfBirth,birthCountry,nationality,documentType,document,paymentWalletId,createdAt); + +@override +String toString() { + return 'PaymentProfileItemModel(id: $id, userId: $userId, paymentProfileId: $paymentProfileId, jwt: $jwt, bornAt: $bornAt, phone: $phone, taxResidences: $taxResidences, addresses: $addresses, status: $status, KYCStatus: $KYCStatus, KYCLevel: $KYCLevel, placeOfBirth: $placeOfBirth, birthCountry: $birthCountry, nationality: $nationality, documentType: $documentType, document: $document, paymentWalletId: $paymentWalletId, createdAt: $createdAt)'; +} + + +} + +/// @nodoc +abstract mixin class $PaymentProfileItemModelCopyWith<$Res> { + factory $PaymentProfileItemModelCopyWith(PaymentProfileItemModel value, $Res Function(PaymentProfileItemModel) _then) = _$PaymentProfileItemModelCopyWithImpl; +@useResult +$Res call({ + String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String KYCStatus, String KYCLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt +}); + + + + +} +/// @nodoc +class _$PaymentProfileItemModelCopyWithImpl<$Res> + implements $PaymentProfileItemModelCopyWith<$Res> { + _$PaymentProfileItemModelCopyWithImpl(this._self, this._then); + + final PaymentProfileItemModel _self; + final $Res Function(PaymentProfileItemModel) _then; + +/// Create a copy of PaymentProfileItemModel +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? userId = null,Object? paymentProfileId = null,Object? jwt = freezed,Object? bornAt = null,Object? phone = null,Object? taxResidences = null,Object? addresses = null,Object? status = null,Object? KYCStatus = null,Object? KYCLevel = null,Object? placeOfBirth = null,Object? birthCountry = null,Object? nationality = null,Object? documentType = null,Object? document = null,Object? paymentWalletId = freezed,Object? createdAt = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable +as String,paymentProfileId: null == paymentProfileId ? _self.paymentProfileId : paymentProfileId // ignore: cast_nullable_to_non_nullable +as String,jwt: freezed == jwt ? _self.jwt : jwt // ignore: cast_nullable_to_non_nullable +as String?,bornAt: null == bornAt ? _self.bornAt : bornAt // ignore: cast_nullable_to_non_nullable +as int,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable +as String,taxResidences: null == taxResidences ? _self.taxResidences : taxResidences // ignore: cast_nullable_to_non_nullable +as List,addresses: null == addresses ? _self.addresses : addresses // ignore: cast_nullable_to_non_nullable +as List,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as String,KYCStatus: null == KYCStatus ? _self.KYCStatus : KYCStatus // ignore: cast_nullable_to_non_nullable +as String,KYCLevel: null == KYCLevel ? _self.KYCLevel : KYCLevel // ignore: cast_nullable_to_non_nullable +as String,placeOfBirth: null == placeOfBirth ? _self.placeOfBirth : placeOfBirth // ignore: cast_nullable_to_non_nullable +as String,birthCountry: null == birthCountry ? _self.birthCountry : birthCountry // ignore: cast_nullable_to_non_nullable +as String,nationality: null == nationality ? _self.nationality : nationality // ignore: cast_nullable_to_non_nullable +as String,documentType: null == documentType ? _self.documentType : documentType // ignore: cast_nullable_to_non_nullable +as String,document: null == document ? _self.document : document // ignore: cast_nullable_to_non_nullable +as String,paymentWalletId: freezed == paymentWalletId ? _self.paymentWalletId : paymentWalletId // ignore: cast_nullable_to_non_nullable +as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PaymentProfileItemModel]. +extension PaymentProfileItemModelPatterns on PaymentProfileItemModel { +/// 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 Function( _PaymentProfileItemModel value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PaymentProfileItemModel() 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 Function( _PaymentProfileItemModel value) $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileItemModel(): +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? Function( _PaymentProfileItemModel value)? $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileItemModel() 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 Function( String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String KYCStatus, String KYCLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PaymentProfileItemModel() when $default != null: +return $default(_that.id,_that.userId,_that.paymentProfileId,_that.jwt,_that.bornAt,_that.phone,_that.taxResidences,_that.addresses,_that.status,_that.KYCStatus,_that.KYCLevel,_that.placeOfBirth,_that.birthCountry,_that.nationality,_that.documentType,_that.document,_that.paymentWalletId,_that.createdAt);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 Function( String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String KYCStatus, String KYCLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt) $default,) {final _that = this; +switch (_that) { +case _PaymentProfileItemModel(): +return $default(_that.id,_that.userId,_that.paymentProfileId,_that.jwt,_that.bornAt,_that.phone,_that.taxResidences,_that.addresses,_that.status,_that.KYCStatus,_that.KYCLevel,_that.placeOfBirth,_that.birthCountry,_that.nationality,_that.documentType,_that.document,_that.paymentWalletId,_that.createdAt);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? Function( String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String KYCStatus, String KYCLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt)? $default,) {final _that = this; +switch (_that) { +case _PaymentProfileItemModel() when $default != null: +return $default(_that.id,_that.userId,_that.paymentProfileId,_that.jwt,_that.bornAt,_that.phone,_that.taxResidences,_that.addresses,_that.status,_that.KYCStatus,_that.KYCLevel,_that.placeOfBirth,_that.birthCountry,_that.nationality,_that.documentType,_that.document,_that.paymentWalletId,_that.createdAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _PaymentProfileItemModel implements PaymentProfileItemModel { + const _PaymentProfileItemModel({required this.id, required this.userId, required this.paymentProfileId, this.jwt, required this.bornAt, required this.phone, required final List taxResidences, required final List addresses, required this.status, required this.KYCStatus, required this.KYCLevel, required this.placeOfBirth, required this.birthCountry, required this.nationality, required this.documentType, required this.document, this.paymentWalletId, required this.createdAt}): _taxResidences = taxResidences,_addresses = addresses; + factory _PaymentProfileItemModel.fromJson(Map json) => _$PaymentProfileItemModelFromJson(json); + +@override final String id; +@override final String userId; +@override final String paymentProfileId; +@override final String? jwt; +@override final int bornAt; +@override final String phone; + final List _taxResidences; +@override List get taxResidences { + if (_taxResidences is EqualUnmodifiableListView) return _taxResidences; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_taxResidences); +} + + final List _addresses; +@override List get addresses { + if (_addresses is EqualUnmodifiableListView) return _addresses; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_addresses); +} + +@override final String status; +//talk with Hector to change those names in the backend to be more consistent with the rest of the fields +// ignore: non_constant_identifier_names +@override final String KYCStatus; +// ignore: non_constant_identifier_names +@override final String KYCLevel; +@override final String placeOfBirth; +@override final String birthCountry; +@override final String nationality; +@override final String documentType; +@override final String document; +@override final String? paymentWalletId; +@override final int createdAt; + +/// Create a copy of PaymentProfileItemModel +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PaymentProfileItemModelCopyWith<_PaymentProfileItemModel> get copyWith => __$PaymentProfileItemModelCopyWithImpl<_PaymentProfileItemModel>(this, _$identity); + +@override +Map toJson() { + return _$PaymentProfileItemModelToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentProfileItemModel&&(identical(other.id, id) || other.id == id)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.paymentProfileId, paymentProfileId) || other.paymentProfileId == paymentProfileId)&&(identical(other.jwt, jwt) || other.jwt == jwt)&&(identical(other.bornAt, bornAt) || other.bornAt == bornAt)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other._taxResidences, _taxResidences)&&const DeepCollectionEquality().equals(other._addresses, _addresses)&&(identical(other.status, status) || other.status == status)&&(identical(other.KYCStatus, KYCStatus) || other.KYCStatus == KYCStatus)&&(identical(other.KYCLevel, KYCLevel) || other.KYCLevel == KYCLevel)&&(identical(other.placeOfBirth, placeOfBirth) || other.placeOfBirth == placeOfBirth)&&(identical(other.birthCountry, birthCountry) || other.birthCountry == birthCountry)&&(identical(other.nationality, nationality) || other.nationality == nationality)&&(identical(other.documentType, documentType) || other.documentType == documentType)&&(identical(other.document, document) || other.document == document)&&(identical(other.paymentWalletId, paymentWalletId) || other.paymentWalletId == paymentWalletId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,userId,paymentProfileId,jwt,bornAt,phone,const DeepCollectionEquality().hash(_taxResidences),const DeepCollectionEquality().hash(_addresses),status,KYCStatus,KYCLevel,placeOfBirth,birthCountry,nationality,documentType,document,paymentWalletId,createdAt); + +@override +String toString() { + return 'PaymentProfileItemModel(id: $id, userId: $userId, paymentProfileId: $paymentProfileId, jwt: $jwt, bornAt: $bornAt, phone: $phone, taxResidences: $taxResidences, addresses: $addresses, status: $status, KYCStatus: $KYCStatus, KYCLevel: $KYCLevel, placeOfBirth: $placeOfBirth, birthCountry: $birthCountry, nationality: $nationality, documentType: $documentType, document: $document, paymentWalletId: $paymentWalletId, createdAt: $createdAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$PaymentProfileItemModelCopyWith<$Res> implements $PaymentProfileItemModelCopyWith<$Res> { + factory _$PaymentProfileItemModelCopyWith(_PaymentProfileItemModel value, $Res Function(_PaymentProfileItemModel) _then) = __$PaymentProfileItemModelCopyWithImpl; +@override @useResult +$Res call({ + String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String KYCStatus, String KYCLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt +}); + + + + +} +/// @nodoc +class __$PaymentProfileItemModelCopyWithImpl<$Res> + implements _$PaymentProfileItemModelCopyWith<$Res> { + __$PaymentProfileItemModelCopyWithImpl(this._self, this._then); + + final _PaymentProfileItemModel _self; + final $Res Function(_PaymentProfileItemModel) _then; + +/// Create a copy of PaymentProfileItemModel +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? userId = null,Object? paymentProfileId = null,Object? jwt = freezed,Object? bornAt = null,Object? phone = null,Object? taxResidences = null,Object? addresses = null,Object? status = null,Object? KYCStatus = null,Object? KYCLevel = null,Object? placeOfBirth = null,Object? birthCountry = null,Object? nationality = null,Object? documentType = null,Object? document = null,Object? paymentWalletId = freezed,Object? createdAt = null,}) { + return _then(_PaymentProfileItemModel( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable +as String,paymentProfileId: null == paymentProfileId ? _self.paymentProfileId : paymentProfileId // ignore: cast_nullable_to_non_nullable +as String,jwt: freezed == jwt ? _self.jwt : jwt // ignore: cast_nullable_to_non_nullable +as String?,bornAt: null == bornAt ? _self.bornAt : bornAt // ignore: cast_nullable_to_non_nullable +as int,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable +as String,taxResidences: null == taxResidences ? _self._taxResidences : taxResidences // ignore: cast_nullable_to_non_nullable +as List,addresses: null == addresses ? _self._addresses : addresses // ignore: cast_nullable_to_non_nullable +as List,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as String,KYCStatus: null == KYCStatus ? _self.KYCStatus : KYCStatus // ignore: cast_nullable_to_non_nullable +as String,KYCLevel: null == KYCLevel ? _self.KYCLevel : KYCLevel // ignore: cast_nullable_to_non_nullable +as String,placeOfBirth: null == placeOfBirth ? _self.placeOfBirth : placeOfBirth // ignore: cast_nullable_to_non_nullable +as String,birthCountry: null == birthCountry ? _self.birthCountry : birthCountry // ignore: cast_nullable_to_non_nullable +as String,nationality: null == nationality ? _self.nationality : nationality // ignore: cast_nullable_to_non_nullable +as String,documentType: null == documentType ? _self.documentType : documentType // ignore: cast_nullable_to_non_nullable +as String,document: null == document ? _self.document : document // ignore: cast_nullable_to_non_nullable +as String,paymentWalletId: freezed == paymentWalletId ? _self.paymentWalletId : paymentWalletId // ignore: cast_nullable_to_non_nullable +as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + + +/// @nodoc +mixin _$PaymentProfileAddressModel { + + String get street; String get city; String get province; String get state; String get country; int get postCode; +/// Create a copy of PaymentProfileAddressModel +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PaymentProfileAddressModelCopyWith get copyWith => _$PaymentProfileAddressModelCopyWithImpl(this as PaymentProfileAddressModel, _$identity); + + /// Serializes this PaymentProfileAddressModel to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentProfileAddressModel&&(identical(other.street, street) || other.street == street)&&(identical(other.city, city) || other.city == city)&&(identical(other.province, province) || other.province == province)&&(identical(other.state, state) || other.state == state)&&(identical(other.country, country) || other.country == country)&&(identical(other.postCode, postCode) || other.postCode == postCode)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,street,city,province,state,country,postCode); + +@override +String toString() { + return 'PaymentProfileAddressModel(street: $street, city: $city, province: $province, state: $state, country: $country, postCode: $postCode)'; +} + + +} + +/// @nodoc +abstract mixin class $PaymentProfileAddressModelCopyWith<$Res> { + factory $PaymentProfileAddressModelCopyWith(PaymentProfileAddressModel value, $Res Function(PaymentProfileAddressModel) _then) = _$PaymentProfileAddressModelCopyWithImpl; +@useResult +$Res call({ + String street, String city, String province, String state, String country, int postCode +}); + + + + +} +/// @nodoc +class _$PaymentProfileAddressModelCopyWithImpl<$Res> + implements $PaymentProfileAddressModelCopyWith<$Res> { + _$PaymentProfileAddressModelCopyWithImpl(this._self, this._then); + + final PaymentProfileAddressModel _self; + final $Res Function(PaymentProfileAddressModel) _then; + +/// Create a copy of PaymentProfileAddressModel +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? street = null,Object? city = null,Object? province = null,Object? state = null,Object? country = null,Object? postCode = null,}) { + return _then(_self.copyWith( +street: null == street ? _self.street : street // ignore: cast_nullable_to_non_nullable +as String,city: null == city ? _self.city : city // ignore: cast_nullable_to_non_nullable +as String,province: null == province ? _self.province : province // ignore: cast_nullable_to_non_nullable +as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable +as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable +as String,postCode: null == postCode ? _self.postCode : postCode // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PaymentProfileAddressModel]. +extension PaymentProfileAddressModelPatterns on PaymentProfileAddressModel { +/// 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 Function( _PaymentProfileAddressModel value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PaymentProfileAddressModel() 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 Function( _PaymentProfileAddressModel value) $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileAddressModel(): +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? Function( _PaymentProfileAddressModel value)? $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileAddressModel() 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 Function( String street, String city, String province, String state, String country, int postCode)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PaymentProfileAddressModel() when $default != null: +return $default(_that.street,_that.city,_that.province,_that.state,_that.country,_that.postCode);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 Function( String street, String city, String province, String state, String country, int postCode) $default,) {final _that = this; +switch (_that) { +case _PaymentProfileAddressModel(): +return $default(_that.street,_that.city,_that.province,_that.state,_that.country,_that.postCode);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? Function( String street, String city, String province, String state, String country, int postCode)? $default,) {final _that = this; +switch (_that) { +case _PaymentProfileAddressModel() when $default != null: +return $default(_that.street,_that.city,_that.province,_that.state,_that.country,_that.postCode);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _PaymentProfileAddressModel implements PaymentProfileAddressModel { + const _PaymentProfileAddressModel({required this.street, required this.city, required this.province, required this.state, required this.country, required this.postCode}); + factory _PaymentProfileAddressModel.fromJson(Map json) => _$PaymentProfileAddressModelFromJson(json); + +@override final String street; +@override final String city; +@override final String province; +@override final String state; +@override final String country; +@override final int postCode; + +/// Create a copy of PaymentProfileAddressModel +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PaymentProfileAddressModelCopyWith<_PaymentProfileAddressModel> get copyWith => __$PaymentProfileAddressModelCopyWithImpl<_PaymentProfileAddressModel>(this, _$identity); + +@override +Map toJson() { + return _$PaymentProfileAddressModelToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentProfileAddressModel&&(identical(other.street, street) || other.street == street)&&(identical(other.city, city) || other.city == city)&&(identical(other.province, province) || other.province == province)&&(identical(other.state, state) || other.state == state)&&(identical(other.country, country) || other.country == country)&&(identical(other.postCode, postCode) || other.postCode == postCode)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,street,city,province,state,country,postCode); + +@override +String toString() { + return 'PaymentProfileAddressModel(street: $street, city: $city, province: $province, state: $state, country: $country, postCode: $postCode)'; +} + + +} + +/// @nodoc +abstract mixin class _$PaymentProfileAddressModelCopyWith<$Res> implements $PaymentProfileAddressModelCopyWith<$Res> { + factory _$PaymentProfileAddressModelCopyWith(_PaymentProfileAddressModel value, $Res Function(_PaymentProfileAddressModel) _then) = __$PaymentProfileAddressModelCopyWithImpl; +@override @useResult +$Res call({ + String street, String city, String province, String state, String country, int postCode +}); + + + + +} +/// @nodoc +class __$PaymentProfileAddressModelCopyWithImpl<$Res> + implements _$PaymentProfileAddressModelCopyWith<$Res> { + __$PaymentProfileAddressModelCopyWithImpl(this._self, this._then); + + final _PaymentProfileAddressModel _self; + final $Res Function(_PaymentProfileAddressModel) _then; + +/// Create a copy of PaymentProfileAddressModel +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? street = null,Object? city = null,Object? province = null,Object? state = null,Object? country = null,Object? postCode = null,}) { + return _then(_PaymentProfileAddressModel( +street: null == street ? _self.street : street // ignore: cast_nullable_to_non_nullable +as String,city: null == city ? _self.city : city // ignore: cast_nullable_to_non_nullable +as String,province: null == province ? _self.province : province // ignore: cast_nullable_to_non_nullable +as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable +as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable +as String,postCode: null == postCode ? _self.postCode : postCode // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/modules/auth/lib/src/core/data/models/payment_profile_response_model.g.dart b/modules/auth/lib/src/core/data/models/payment_profile_response_model.g.dart new file mode 100644 index 00000000..20b93e24 --- /dev/null +++ b/modules/auth/lib/src/core/data/models/payment_profile_response_model.g.dart @@ -0,0 +1,93 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'payment_profile_response_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_PaymentProfileResponseModel _$PaymentProfileResponseModelFromJson( + Map json, +) => _PaymentProfileResponseModel( + item: PaymentProfileItemModel.fromJson(json['item'] as Map), +); + +Map _$PaymentProfileResponseModelToJson( + _PaymentProfileResponseModel instance, +) => {'item': instance.item}; + +_PaymentProfileItemModel _$PaymentProfileItemModelFromJson( + Map json, +) => _PaymentProfileItemModel( + id: json['id'] as String, + userId: json['userId'] as String, + paymentProfileId: json['paymentProfileId'] as String, + jwt: json['jwt'] as String?, + bornAt: (json['bornAt'] as num).toInt(), + phone: json['phone'] as String, + taxResidences: (json['taxResidences'] as List) + .map( + (e) => PaymentProfileAddressModel.fromJson(e as Map), + ) + .toList(), + addresses: (json['addresses'] as List) + .map( + (e) => PaymentProfileAddressModel.fromJson(e as Map), + ) + .toList(), + status: json['status'] as String, + KYCStatus: json['KYCStatus'] as String, + KYCLevel: json['KYCLevel'] as String, + placeOfBirth: json['placeOfBirth'] as String, + birthCountry: json['birthCountry'] as String, + nationality: json['nationality'] as String, + documentType: json['documentType'] as String, + document: json['document'] as String, + paymentWalletId: json['paymentWalletId'] as String?, + createdAt: (json['createdAt'] as num).toInt(), +); + +Map _$PaymentProfileItemModelToJson( + _PaymentProfileItemModel instance, +) => { + 'id': instance.id, + 'userId': instance.userId, + 'paymentProfileId': instance.paymentProfileId, + 'jwt': instance.jwt, + 'bornAt': instance.bornAt, + 'phone': instance.phone, + 'taxResidences': instance.taxResidences, + 'addresses': instance.addresses, + 'status': instance.status, + 'KYCStatus': instance.KYCStatus, + 'KYCLevel': instance.KYCLevel, + 'placeOfBirth': instance.placeOfBirth, + 'birthCountry': instance.birthCountry, + 'nationality': instance.nationality, + 'documentType': instance.documentType, + 'document': instance.document, + 'paymentWalletId': instance.paymentWalletId, + 'createdAt': instance.createdAt, +}; + +_PaymentProfileAddressModel _$PaymentProfileAddressModelFromJson( + Map json, +) => _PaymentProfileAddressModel( + street: json['street'] as String, + city: json['city'] as String, + province: json['province'] as String, + state: json['state'] as String, + country: json['country'] as String, + postCode: (json['postCode'] as num).toInt(), +); + +Map _$PaymentProfileAddressModelToJson( + _PaymentProfileAddressModel instance, +) => { + 'street': instance.street, + 'city': instance.city, + 'province': instance.province, + 'state': instance.state, + 'country': instance.country, + 'postCode': instance.postCode, +}; diff --git a/modules/auth/lib/src/core/data/repositories/auth_repository_impl.dart b/modules/auth/lib/src/core/data/repositories/auth_repository_impl.dart index d6d410e1..6f9e09ce 100644 --- a/modules/auth/lib/src/core/data/repositories/auth_repository_impl.dart +++ b/modules/auth/lib/src/core/data/repositories/auth_repository_impl.dart @@ -1,12 +1,14 @@ import 'package:auth/src/core/data/datasource/auth_remote_datasource.dart'; import 'package:auth/src/core/data/mappers/user_model_mapper.dart'; import 'package:auth/src/core/data/models/child_profile_response_model.dart'; +import 'package:auth/src/core/data/models/payment_profile_response_model.dart'; import 'package:auth/src/core/data/models/sign_up_request_model.dart'; import 'package:auth/src/core/data/models/sign_up_response_model.dart'; import 'package:auth/src/core/data/models/two_fa_secret_response_model.dart'; import 'package:auth/src/core/domain/repositories/auth_repository.dart'; import 'package:auth/src/features/device_setup/domain/entities/child_profile_entity.dart'; import 'package:auth/src/features/login/domain/entities/login_response_entity.dart'; +import 'package:auth/src/features/login/domain/entities/payment_profile_entity.dart'; import 'package:auth/src/features/login/domain/entities/user_entity.dart'; import 'package:auth/src/features/sign_up/domain/entities/sign_up_request_entity.dart'; import 'package:auth/src/features/sign_up/domain/entities/sign_up_response_entity.dart'; @@ -64,6 +66,19 @@ class AuthRepositoryImpl implements AuthRepository { return model.toEntity(); } + @override + Future createWallet() { + return _remote.createWallet(); + } + + @override + Future getPaymentProfile({ + required String userId, + }) async { + final model = await _remote.getPaymentProfile(userId: userId); + return model.toEntity(); + } + // @override // Future totpLogin({required String token, required String code}) { // return _remote.totpLogin(token: token, code: code); diff --git a/modules/auth/lib/src/core/domain/repositories/auth_repository.dart b/modules/auth/lib/src/core/domain/repositories/auth_repository.dart index ceaf00e9..0041a5ad 100644 --- a/modules/auth/lib/src/core/domain/repositories/auth_repository.dart +++ b/modules/auth/lib/src/core/domain/repositories/auth_repository.dart @@ -2,6 +2,7 @@ import 'package:auth/src/core/data/models/child_profile_response_model.dart'; import 'package:auth/src/core/data/models/two_fa_secret_response_model.dart'; import 'package:auth/src/features/device_setup/domain/entities/child_profile_entity.dart'; import 'package:auth/src/features/login/domain/entities/login_response_entity.dart'; +import 'package:auth/src/features/login/domain/entities/payment_profile_entity.dart'; import 'package:auth/src/features/login/domain/entities/user_entity.dart'; import 'package:auth/src/features/sign_up/domain/entities/sign_up_request_entity.dart'; import 'package:auth/src/features/sign_up/domain/entities/sign_up_response_entity.dart'; @@ -26,6 +27,10 @@ abstract class AuthRepository { }); Future getUserInfo(); + Future createWallet(); + + Future getPaymentProfile({required String userId}); + // Future totpLogin({required String token, required String code}); Future signUp({required SignUpRequestEntity request}); diff --git a/modules/auth/lib/src/core/providers/session_local_datasource_provider.dart b/modules/auth/lib/src/core/providers/session_local_datasource_provider.dart new file mode 100644 index 00000000..3526db59 --- /dev/null +++ b/modules/auth/lib/src/core/providers/session_local_datasource_provider.dart @@ -0,0 +1,7 @@ +import 'package:auth/src/core/data/datasource/session_local_datasource.dart'; +import 'package:auth/src/core/data/datasource/session_local_datasource_impl.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final sessionLocalDatasourceProvider = Provider((ref) { + return SessionLocalDatasourceImpl(); +}); diff --git a/modules/auth/lib/src/features/device_setup/presentation/add_kid_step_mapper.dart b/modules/auth/lib/src/features/device_setup/presentation/add_kid_step_mapper.dart index 75b424e9..71887b4c 100644 --- a/modules/auth/lib/src/features/device_setup/presentation/add_kid_step_mapper.dart +++ b/modules/auth/lib/src/features/device_setup/presentation/add_kid_step_mapper.dart @@ -14,8 +14,6 @@ extension AddKidStepMapper on AddKidStep { return AddKidMainStep.success; case AddKidStep.intro: return AddKidMainStep.linkDevice; - case AddKidStep.sca: - return AddKidMainStep.success; } } } diff --git a/modules/auth/lib/src/features/device_setup/presentation/device_setup_screen.dart b/modules/auth/lib/src/features/device_setup/presentation/device_setup_screen.dart index 89638de3..a917e2a0 100644 --- a/modules/auth/lib/src/features/device_setup/presentation/device_setup_screen.dart +++ b/modules/auth/lib/src/features/device_setup/presentation/device_setup_screen.dart @@ -4,6 +4,7 @@ import 'package:auth/src/features/device_setup/presentation/enums/add_kid_main_s import 'package:auth/src/features/device_setup/presentation/enums/add_kid_step.dart'; import 'package:auth/src/features/device_setup/presentation/step_body.dart'; import 'package:auth/src/features/device_setup/presentation/widgets/flow_footer.dart'; +import 'package:auth/src/features/sca_treezor/sca_pin_view.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -74,23 +75,24 @@ class DeviceSetupScreen extends ConsumerWidget { ), FlowFooter( - error: state.errorMessage, + error: context.translate(state.errorMessage), primaryText: context.translate(primaryButtonText(state.step)), secondaryText: state.step == AddKidStep.success ? context.translate(I18n.deviceSetup_addAnotherKid) : null, - onPrimary: () async { + onPrimary: () { if (state.step == AddKidStep.success) { navigationContract.pushTo(AppRoutes.dashboardHome); return; } - if (state.step == AddKidStep.sca) { - await vm.createChildProfile(); + if (state.step == AddKidStep.profile) { + if (!vm.validateProfile()) return; + _pushScaPinScreen(context, ref); return; } vm.next(); }, - onSecondary: state.step == AddKidStep.success ? () {} : null, + onSecondary: state.step == AddKidStep.success ? vm.resetForNewKid : null, theme: theme, ), ], @@ -100,6 +102,14 @@ class DeviceSetupScreen extends ConsumerWidget { ); } + void _pushScaPinScreen(BuildContext context, WidgetRef ref) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => _ScaPinScreen(navigationContract: navigationContract), + ), + ); + } + String primaryButtonText(AddKidStep step) { switch (step) { case AddKidStep.intro: @@ -111,3 +121,44 @@ class DeviceSetupScreen extends ConsumerWidget { } } } + +class _ScaPinScreen extends ConsumerWidget { + final NavigationContract navigationContract; + + const _ScaPinScreen({required this.navigationContract}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(deviceSetupViewModelProvider); + final vm = ref.read(deviceSetupViewModelProvider.notifier); + + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: Center( + child: ScaPinView( + title: 'Introduce tu PIN para firmar', + pin: state.pin, + isProcessing: state.isSigning || state.isLoading, + processingText: + state.isSigning ? 'Firmando...' : 'Creando perfil...', + canSubmit: + vm.canSubmitPin && !state.isSigning && !state.isLoading, + submitText: 'Confirmar', + errorMessage: state.errorMessage.isNotEmpty + ? context.translate(state.errorMessage) + : null, + onDigitPressed: vm.onDigitPressed, + onBackspacePressed: vm.onBackspacePressed, + onClearPin: vm.clearPin, + onSubmit: () async { + await vm.createChildProfile(pin: state.pin); + if (!context.mounted) return; + Navigator.of(context).pop(); + }, + ), + ), + ), + ); + } +} diff --git a/modules/auth/lib/src/features/device_setup/presentation/enums/add_kid_step.dart b/modules/auth/lib/src/features/device_setup/presentation/enums/add_kid_step.dart index 3e43e889..e17ff405 100644 --- a/modules/auth/lib/src/features/device_setup/presentation/enums/add_kid_step.dart +++ b/modules/auth/lib/src/features/device_setup/presentation/enums/add_kid_step.dart @@ -1 +1 @@ -enum AddKidStep { intro, linkInfo, scanStrap, scanWatch, profile, success, sca } +enum AddKidStep { intro, linkInfo, scanStrap, scanWatch, profile, success } diff --git a/modules/auth/lib/src/features/device_setup/presentation/qr_scanner_screen.dart b/modules/auth/lib/src/features/device_setup/presentation/qr_scanner_screen.dart index 075758db..81a89528 100644 --- a/modules/auth/lib/src/features/device_setup/presentation/qr_scanner_screen.dart +++ b/modules/auth/lib/src/features/device_setup/presentation/qr_scanner_screen.dart @@ -75,7 +75,7 @@ class _QrScannerScreenState extends State { Positioned( left: 0, right: 0, - bottom: 26, + bottom: 50, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( diff --git a/modules/auth/lib/src/features/device_setup/presentation/state/device_setup_view_model.dart b/modules/auth/lib/src/features/device_setup/presentation/state/device_setup_view_model.dart index 3a953d55..a7a966c0 100644 --- a/modules/auth/lib/src/features/device_setup/presentation/state/device_setup_view_model.dart +++ b/modules/auth/lib/src/features/device_setup/presentation/state/device_setup_view_model.dart @@ -12,6 +12,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get_it/get_it.dart'; import 'package:sca_treezor/sca_treezor.dart'; +import 'package:sf_localizations/sf_localizations.dart'; import 'package:uuid/uuid.dart'; final deviceSetupViewModelProvider = @@ -77,34 +78,27 @@ class DeviceSetupViewModel extends Notifier { state = state.copyWith(step: AddKidStep.scanStrap); return; case AddKidStep.scanStrap: - // if (state.strapQr.isEmpty) { - // state = state.copyWith( - // errorMessage: 'Escanea la correa para continuar', - // ); - // return; - // } - state = state.copyWith(step: AddKidStep.scanWatch); + final hasStrap = state.strapQr.isNotEmpty || state.strapCode.isNotEmpty; + if (!hasStrap) { + state = state.copyWith( + errorMessage: I18n.errorScanStrapRequired, + ); + return; + } + state = state.copyWith(step: AddKidStep.scanWatch, errorMessage: ''); return; case AddKidStep.scanWatch: - // final hasWatch = state.watchQr.isNotEmpty || state.watchCode.isNotEmpty; - // if (!hasWatch) { - // state = state.copyWith( - // errorMessage: 'Escanea el reloj o introduce el código', - // ); - // return; - // } - state = state.copyWith(step: AddKidStep.profile); + final hasWatch = state.watchQr.isNotEmpty || state.watchCode.isNotEmpty; + if (!hasWatch) { + state = state.copyWith( + errorMessage: I18n.errorScanWatchRequired, + ); + return; + } + state = state.copyWith(step: AddKidStep.profile, errorMessage: ''); return; case AddKidStep.profile: - final isValid = _validateProfile(); - if (!isValid) return; - state = state.copyWith(step: AddKidStep.sca); return; - - case AddKidStep.sca: - state = state.copyWith(step: AddKidStep.success); - return; - case AddKidStep.success: return; } @@ -127,9 +121,6 @@ class DeviceSetupViewModel extends Notifier { case AddKidStep.profile: state = state.copyWith(step: AddKidStep.scanWatch); return; - case AddKidStep.sca: - state = state.copyWith(step: AddKidStep.profile); - return; case AddKidStep.success: state = state.copyWith(step: AddKidStep.profile); return; @@ -174,9 +165,10 @@ class DeviceSetupViewModel extends Notifier { state = state.copyWith(bornAt: date); } - Future createChildProfile() async { + Future createChildProfile({required String pin}) async { + state = state.copyWith(pin: pin); await generateJwsWithPin(); - // if (!_validateProfile()) return; + if (state.lastSignature.isEmpty) return false; await getUserInfo(); final firstName = state.firstName.trim(); final lastName = state.lastName.trim(); @@ -202,17 +194,19 @@ class DeviceSetupViewModel extends Notifier { scaProof: state.lastSignature, ); - if (!ref.mounted) return; + if (!ref.mounted) return false; state = state.copyWith( isLoading: false, isSuccess: true, step: AddKidStep.success, ); + return true; } catch (e) { - if (!ref.mounted) return; + if (!ref.mounted) return false; state = state.copyWith(isLoading: false, errorMessage: e.toString()); + return false; } } @@ -238,7 +232,7 @@ class DeviceSetupViewModel extends Notifier { } } - bool _validateProfile() { + bool validateProfile() { final isInvalid = state.firstName.trim().isEmpty || state.lastName.trim().isEmpty || @@ -248,7 +242,7 @@ class DeviceSetupViewModel extends Notifier { state.relationType.trim().isEmpty; if (isInvalid) { - state = state.copyWith(errorMessage: 'Completa todos los campos'); + state = state.copyWith(errorMessage: I18n.errorAllFieldsRequired); return false; } return true; @@ -367,7 +361,7 @@ class DeviceSetupViewModel extends Notifier { if (state.isSigning) return; if (!canSubmitPin) { - state = state.copyWith(errorMessage: '⚠️ PIN de 6 dígitos'); + state = state.copyWith(errorMessage: I18n.errorPinRequired); return; } @@ -402,12 +396,23 @@ class DeviceSetupViewModel extends Notifier { state = state.copyWith( isSigning: false, isSuccess: false, - errorMessage: ' Error firmando operación sensible: $e', + errorMessage: '${I18n.errorSigningOperation}: $e', pin: '', ); } } + void resetForNewKid() { + firstNameController.clear(); + lastNameController.clear(); + bornAtController.clear(); + addressController.clear(); + strapCodeController.clear(); + watchCodeController.clear(); + + state = DeviceSetupViewState(id: const Uuid().v4()); + } + void disposeControllers() { // firstNameController.dispose(); // lastNameController.dispose(); diff --git a/modules/auth/lib/src/features/device_setup/presentation/step_body.dart b/modules/auth/lib/src/features/device_setup/presentation/step_body.dart index dcaf09e7..48024328 100644 --- a/modules/auth/lib/src/features/device_setup/presentation/step_body.dart +++ b/modules/auth/lib/src/features/device_setup/presentation/step_body.dart @@ -4,7 +4,6 @@ import 'package:auth/src/features/device_setup/presentation/enums/scan_link_step import 'package:auth/src/features/device_setup/presentation/steps/intro_step.dart'; import 'package:auth/src/features/device_setup/presentation/steps/link_info_step.dart'; import 'package:auth/src/features/device_setup/presentation/steps/profile_step.dart'; -import 'package:auth/src/features/device_setup/presentation/steps/sca_step.dart'; import 'package:auth/src/features/device_setup/presentation/steps/scan_strap_and_watch_step.dart'; import 'package:auth/src/features/device_setup/presentation/steps/success_step.dart'; import 'package:flutter/material.dart'; @@ -26,8 +25,6 @@ class StepBody extends StatelessWidget { return ScanStrapAndWatchStepScreen(step: ScanLinkStep.watch); case AddKidStep.profile: return ProfileStepScreen(); - case AddKidStep.sca: - return SCAScreenStep(); case AddKidStep.success: return SuccessStepScreen(); } diff --git a/modules/auth/lib/src/features/device_setup/presentation/steps/sca_step.dart b/modules/auth/lib/src/features/device_setup/presentation/steps/sca_step.dart deleted file mode 100644 index 2793b50b..00000000 --- a/modules/auth/lib/src/features/device_setup/presentation/steps/sca_step.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'package:auth/src/features/device_setup/presentation/state/device_setup_view_model.dart'; -import 'package:auth/src/features/device_setup/presentation/state/device_setup_view_state.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class SCAScreenStep extends ConsumerWidget { - const SCAScreenStep({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final state = ref.watch(deviceSetupViewModelProvider); - final vm = ref.read(deviceSetupViewModelProvider.notifier); - return Scaffold( - backgroundColor: Colors.white, - body: Center( - child: Padding( - padding: const EdgeInsets.all(16), - child: _SignatureBody(state: state, vm: vm), - ), - ), - ); - } -} - -class _PinDots extends StatelessWidget { - final int length; - final int max; - - const _PinDots({required this.length, required this.max}); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: List.generate(max, (i) { - final filled = i < length; - - return AnimatedContainer( - duration: const Duration(milliseconds: 120), - curve: Curves.easeOut, - width: 12, - height: 12, - margin: const EdgeInsets.symmetric(horizontal: 7), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: filled ? Colors.black : const Color(0xFFD1D1D6), - ), - ); - }), - ); - } -} - -class _DialPad extends StatelessWidget { - final void Function(String digit) onDigitPressed; - - const _DialPad({ - required this.onDigitPressed, - required void Function() onBackspacePressed, - }); - - static const _keys = <_DialKey>[ - _DialKey('1'), - _DialKey('2'), - _DialKey('3'), - _DialKey('4'), - _DialKey('5'), - _DialKey('6'), - _DialKey('7'), - _DialKey('8'), - _DialKey('9'), - _DialKey(''), - _DialKey('0'), - ]; - - @override - Widget build(BuildContext context) { - return GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: _keys.length, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 30, - ), - itemBuilder: (_, index) { - final key = _keys[index]; - - if (key.digit.isEmpty) { - return const SizedBox.shrink(); - } - - return Center( - child: _DialButton( - digit: key.digit, - onTap: () => onDigitPressed(key.digit), - ), - ); - }, - ); - } -} - -class _DialButton extends StatefulWidget { - final String digit; - final VoidCallback onTap; - - const _DialButton({required this.digit, required this.onTap}); - - @override - State<_DialButton> createState() => _DialButtonState(); -} - -class _DialButtonState extends State<_DialButton> { - bool _pressed = false; - - void _setPressed(bool value) { - if (_pressed == value) return; - setState(() => _pressed = value); - } - - @override - Widget build(BuildContext context) { - const double size = 56; - - final bg = _pressed ? Colors.grey : Colors.white; - - return GestureDetector( - onTapDown: (_) => _setPressed(true), - onTapCancel: () => _setPressed(false), - onTapUp: (_) => _setPressed(false), - onTap: () { - HapticFeedback.lightImpact(); - widget.onTap(); - }, - child: SizedBox( - width: size, - height: size, - child: AnimatedContainer( - duration: const Duration(milliseconds: 120), - curve: Curves.easeOut, - decoration: BoxDecoration(shape: BoxShape.circle, color: bg), - child: Center( - child: Text( - widget.digit, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - ), - ), - ), - ); - } -} - -class _DialKey { - final String digit; - - const _DialKey(this.digit); -} - -class _SignatureBody extends StatelessWidget { - final DeviceSetupViewState state; - final DeviceSetupViewModel vm; - - const _SignatureBody({required this.state, required this.vm}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 18), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Introduce tu PIN para firmar:'), - const SizedBox(height: 12), - - _PinDots(length: state.pin.length, max: 6), - const SizedBox(height: 12), - - _DialPad( - onDigitPressed: vm.onDigitPressed, - onBackspacePressed: vm.onBackspacePressed, - ), - - const SizedBox(height: 16), - - if (state.isSigning) ...[ - const CircularProgressIndicator(), - const SizedBox(height: 8), - const Text('Firmando...'), - ] else ...[ - // ElevatedButton( - // onPressed: vm.canSubmitPin ? vm.generateJwsWithPin : null, - // child: const Text('Firmar SCA para JWT'), - // ), - // const SizedBox(height: 8), - ], - - const SizedBox(height: 16), - - const SizedBox(height: 8), - TextButton(onPressed: vm.clearPin, child: const Text('Borrar PIN')), - ], - ), - ); - } -} diff --git a/modules/auth/lib/src/features/login/domain/entities/payment_profile_entity.dart b/modules/auth/lib/src/features/login/domain/entities/payment_profile_entity.dart new file mode 100644 index 00000000..d3b3ca63 --- /dev/null +++ b/modules/auth/lib/src/features/login/domain/entities/payment_profile_entity.dart @@ -0,0 +1,40 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'payment_profile_entity.freezed.dart'; + +@freezed +abstract class PaymentProfileEntity with _$PaymentProfileEntity { + const factory PaymentProfileEntity({ + required String id, + required String userId, + required String paymentProfileId, + String? jwt, + required int bornAt, + required String phone, + required List taxResidences, + required List addresses, + required String status, + required String kycStatus, + required String kycLevel, + required String placeOfBirth, + required String birthCountry, + required String nationality, + required String documentType, + required String document, + String? paymentWalletId, + required int createdAt, + }) = _PaymentProfileEntity; +} + +@freezed +abstract class PaymentProfileAddressEntity + with _$PaymentProfileAddressEntity { + const factory PaymentProfileAddressEntity({ + required String street, + required String city, + required String province, + required String state, + required String country, + required int postCode, + }) = _PaymentProfileAddressEntity; +} diff --git a/modules/auth/lib/src/features/login/domain/entities/payment_profile_entity.freezed.dart b/modules/auth/lib/src/features/login/domain/entities/payment_profile_entity.freezed.dart new file mode 100644 index 00000000..e076e5e4 --- /dev/null +++ b/modules/auth/lib/src/features/login/domain/entities/payment_profile_entity.freezed.dart @@ -0,0 +1,606 @@ +// 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 'payment_profile_entity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$PaymentProfileEntity { + + String get id; String get userId; String get paymentProfileId; String? get jwt; int get bornAt; String get phone; List get taxResidences; List get addresses; String get status; String get kycStatus; String get kycLevel; String get placeOfBirth; String get birthCountry; String get nationality; String get documentType; String get document; String? get paymentWalletId; int get createdAt; +/// Create a copy of PaymentProfileEntity +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PaymentProfileEntityCopyWith get copyWith => _$PaymentProfileEntityCopyWithImpl(this as PaymentProfileEntity, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentProfileEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.paymentProfileId, paymentProfileId) || other.paymentProfileId == paymentProfileId)&&(identical(other.jwt, jwt) || other.jwt == jwt)&&(identical(other.bornAt, bornAt) || other.bornAt == bornAt)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other.taxResidences, taxResidences)&&const DeepCollectionEquality().equals(other.addresses, addresses)&&(identical(other.status, status) || other.status == status)&&(identical(other.kycStatus, kycStatus) || other.kycStatus == kycStatus)&&(identical(other.kycLevel, kycLevel) || other.kycLevel == kycLevel)&&(identical(other.placeOfBirth, placeOfBirth) || other.placeOfBirth == placeOfBirth)&&(identical(other.birthCountry, birthCountry) || other.birthCountry == birthCountry)&&(identical(other.nationality, nationality) || other.nationality == nationality)&&(identical(other.documentType, documentType) || other.documentType == documentType)&&(identical(other.document, document) || other.document == document)&&(identical(other.paymentWalletId, paymentWalletId) || other.paymentWalletId == paymentWalletId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)); +} + + +@override +int get hashCode => Object.hash(runtimeType,id,userId,paymentProfileId,jwt,bornAt,phone,const DeepCollectionEquality().hash(taxResidences),const DeepCollectionEquality().hash(addresses),status,kycStatus,kycLevel,placeOfBirth,birthCountry,nationality,documentType,document,paymentWalletId,createdAt); + +@override +String toString() { + return 'PaymentProfileEntity(id: $id, userId: $userId, paymentProfileId: $paymentProfileId, jwt: $jwt, bornAt: $bornAt, phone: $phone, taxResidences: $taxResidences, addresses: $addresses, status: $status, kycStatus: $kycStatus, kycLevel: $kycLevel, placeOfBirth: $placeOfBirth, birthCountry: $birthCountry, nationality: $nationality, documentType: $documentType, document: $document, paymentWalletId: $paymentWalletId, createdAt: $createdAt)'; +} + + +} + +/// @nodoc +abstract mixin class $PaymentProfileEntityCopyWith<$Res> { + factory $PaymentProfileEntityCopyWith(PaymentProfileEntity value, $Res Function(PaymentProfileEntity) _then) = _$PaymentProfileEntityCopyWithImpl; +@useResult +$Res call({ + String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String kycStatus, String kycLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt +}); + + + + +} +/// @nodoc +class _$PaymentProfileEntityCopyWithImpl<$Res> + implements $PaymentProfileEntityCopyWith<$Res> { + _$PaymentProfileEntityCopyWithImpl(this._self, this._then); + + final PaymentProfileEntity _self; + final $Res Function(PaymentProfileEntity) _then; + +/// Create a copy of PaymentProfileEntity +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? userId = null,Object? paymentProfileId = null,Object? jwt = freezed,Object? bornAt = null,Object? phone = null,Object? taxResidences = null,Object? addresses = null,Object? status = null,Object? kycStatus = null,Object? kycLevel = null,Object? placeOfBirth = null,Object? birthCountry = null,Object? nationality = null,Object? documentType = null,Object? document = null,Object? paymentWalletId = freezed,Object? createdAt = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable +as String,paymentProfileId: null == paymentProfileId ? _self.paymentProfileId : paymentProfileId // ignore: cast_nullable_to_non_nullable +as String,jwt: freezed == jwt ? _self.jwt : jwt // ignore: cast_nullable_to_non_nullable +as String?,bornAt: null == bornAt ? _self.bornAt : bornAt // ignore: cast_nullable_to_non_nullable +as int,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable +as String,taxResidences: null == taxResidences ? _self.taxResidences : taxResidences // ignore: cast_nullable_to_non_nullable +as List,addresses: null == addresses ? _self.addresses : addresses // ignore: cast_nullable_to_non_nullable +as List,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as String,kycStatus: null == kycStatus ? _self.kycStatus : kycStatus // ignore: cast_nullable_to_non_nullable +as String,kycLevel: null == kycLevel ? _self.kycLevel : kycLevel // ignore: cast_nullable_to_non_nullable +as String,placeOfBirth: null == placeOfBirth ? _self.placeOfBirth : placeOfBirth // ignore: cast_nullable_to_non_nullable +as String,birthCountry: null == birthCountry ? _self.birthCountry : birthCountry // ignore: cast_nullable_to_non_nullable +as String,nationality: null == nationality ? _self.nationality : nationality // ignore: cast_nullable_to_non_nullable +as String,documentType: null == documentType ? _self.documentType : documentType // ignore: cast_nullable_to_non_nullable +as String,document: null == document ? _self.document : document // ignore: cast_nullable_to_non_nullable +as String,paymentWalletId: freezed == paymentWalletId ? _self.paymentWalletId : paymentWalletId // ignore: cast_nullable_to_non_nullable +as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PaymentProfileEntity]. +extension PaymentProfileEntityPatterns on PaymentProfileEntity { +/// 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 Function( _PaymentProfileEntity value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PaymentProfileEntity() 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 Function( _PaymentProfileEntity value) $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileEntity(): +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? Function( _PaymentProfileEntity value)? $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileEntity() 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 Function( String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String kycStatus, String kycLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PaymentProfileEntity() when $default != null: +return $default(_that.id,_that.userId,_that.paymentProfileId,_that.jwt,_that.bornAt,_that.phone,_that.taxResidences,_that.addresses,_that.status,_that.kycStatus,_that.kycLevel,_that.placeOfBirth,_that.birthCountry,_that.nationality,_that.documentType,_that.document,_that.paymentWalletId,_that.createdAt);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 Function( String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String kycStatus, String kycLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt) $default,) {final _that = this; +switch (_that) { +case _PaymentProfileEntity(): +return $default(_that.id,_that.userId,_that.paymentProfileId,_that.jwt,_that.bornAt,_that.phone,_that.taxResidences,_that.addresses,_that.status,_that.kycStatus,_that.kycLevel,_that.placeOfBirth,_that.birthCountry,_that.nationality,_that.documentType,_that.document,_that.paymentWalletId,_that.createdAt);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? Function( String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String kycStatus, String kycLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt)? $default,) {final _that = this; +switch (_that) { +case _PaymentProfileEntity() when $default != null: +return $default(_that.id,_that.userId,_that.paymentProfileId,_that.jwt,_that.bornAt,_that.phone,_that.taxResidences,_that.addresses,_that.status,_that.kycStatus,_that.kycLevel,_that.placeOfBirth,_that.birthCountry,_that.nationality,_that.documentType,_that.document,_that.paymentWalletId,_that.createdAt);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _PaymentProfileEntity implements PaymentProfileEntity { + const _PaymentProfileEntity({required this.id, required this.userId, required this.paymentProfileId, this.jwt, required this.bornAt, required this.phone, required final List taxResidences, required final List addresses, required this.status, required this.kycStatus, required this.kycLevel, required this.placeOfBirth, required this.birthCountry, required this.nationality, required this.documentType, required this.document, this.paymentWalletId, required this.createdAt}): _taxResidences = taxResidences,_addresses = addresses; + + +@override final String id; +@override final String userId; +@override final String paymentProfileId; +@override final String? jwt; +@override final int bornAt; +@override final String phone; + final List _taxResidences; +@override List get taxResidences { + if (_taxResidences is EqualUnmodifiableListView) return _taxResidences; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_taxResidences); +} + + final List _addresses; +@override List get addresses { + if (_addresses is EqualUnmodifiableListView) return _addresses; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_addresses); +} + +@override final String status; +@override final String kycStatus; +@override final String kycLevel; +@override final String placeOfBirth; +@override final String birthCountry; +@override final String nationality; +@override final String documentType; +@override final String document; +@override final String? paymentWalletId; +@override final int createdAt; + +/// Create a copy of PaymentProfileEntity +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PaymentProfileEntityCopyWith<_PaymentProfileEntity> get copyWith => __$PaymentProfileEntityCopyWithImpl<_PaymentProfileEntity>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentProfileEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.paymentProfileId, paymentProfileId) || other.paymentProfileId == paymentProfileId)&&(identical(other.jwt, jwt) || other.jwt == jwt)&&(identical(other.bornAt, bornAt) || other.bornAt == bornAt)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other._taxResidences, _taxResidences)&&const DeepCollectionEquality().equals(other._addresses, _addresses)&&(identical(other.status, status) || other.status == status)&&(identical(other.kycStatus, kycStatus) || other.kycStatus == kycStatus)&&(identical(other.kycLevel, kycLevel) || other.kycLevel == kycLevel)&&(identical(other.placeOfBirth, placeOfBirth) || other.placeOfBirth == placeOfBirth)&&(identical(other.birthCountry, birthCountry) || other.birthCountry == birthCountry)&&(identical(other.nationality, nationality) || other.nationality == nationality)&&(identical(other.documentType, documentType) || other.documentType == documentType)&&(identical(other.document, document) || other.document == document)&&(identical(other.paymentWalletId, paymentWalletId) || other.paymentWalletId == paymentWalletId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)); +} + + +@override +int get hashCode => Object.hash(runtimeType,id,userId,paymentProfileId,jwt,bornAt,phone,const DeepCollectionEquality().hash(_taxResidences),const DeepCollectionEquality().hash(_addresses),status,kycStatus,kycLevel,placeOfBirth,birthCountry,nationality,documentType,document,paymentWalletId,createdAt); + +@override +String toString() { + return 'PaymentProfileEntity(id: $id, userId: $userId, paymentProfileId: $paymentProfileId, jwt: $jwt, bornAt: $bornAt, phone: $phone, taxResidences: $taxResidences, addresses: $addresses, status: $status, kycStatus: $kycStatus, kycLevel: $kycLevel, placeOfBirth: $placeOfBirth, birthCountry: $birthCountry, nationality: $nationality, documentType: $documentType, document: $document, paymentWalletId: $paymentWalletId, createdAt: $createdAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$PaymentProfileEntityCopyWith<$Res> implements $PaymentProfileEntityCopyWith<$Res> { + factory _$PaymentProfileEntityCopyWith(_PaymentProfileEntity value, $Res Function(_PaymentProfileEntity) _then) = __$PaymentProfileEntityCopyWithImpl; +@override @useResult +$Res call({ + String id, String userId, String paymentProfileId, String? jwt, int bornAt, String phone, List taxResidences, List addresses, String status, String kycStatus, String kycLevel, String placeOfBirth, String birthCountry, String nationality, String documentType, String document, String? paymentWalletId, int createdAt +}); + + + + +} +/// @nodoc +class __$PaymentProfileEntityCopyWithImpl<$Res> + implements _$PaymentProfileEntityCopyWith<$Res> { + __$PaymentProfileEntityCopyWithImpl(this._self, this._then); + + final _PaymentProfileEntity _self; + final $Res Function(_PaymentProfileEntity) _then; + +/// Create a copy of PaymentProfileEntity +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? userId = null,Object? paymentProfileId = null,Object? jwt = freezed,Object? bornAt = null,Object? phone = null,Object? taxResidences = null,Object? addresses = null,Object? status = null,Object? kycStatus = null,Object? kycLevel = null,Object? placeOfBirth = null,Object? birthCountry = null,Object? nationality = null,Object? documentType = null,Object? document = null,Object? paymentWalletId = freezed,Object? createdAt = null,}) { + return _then(_PaymentProfileEntity( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable +as String,paymentProfileId: null == paymentProfileId ? _self.paymentProfileId : paymentProfileId // ignore: cast_nullable_to_non_nullable +as String,jwt: freezed == jwt ? _self.jwt : jwt // ignore: cast_nullable_to_non_nullable +as String?,bornAt: null == bornAt ? _self.bornAt : bornAt // ignore: cast_nullable_to_non_nullable +as int,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable +as String,taxResidences: null == taxResidences ? _self._taxResidences : taxResidences // ignore: cast_nullable_to_non_nullable +as List,addresses: null == addresses ? _self._addresses : addresses // ignore: cast_nullable_to_non_nullable +as List,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as String,kycStatus: null == kycStatus ? _self.kycStatus : kycStatus // ignore: cast_nullable_to_non_nullable +as String,kycLevel: null == kycLevel ? _self.kycLevel : kycLevel // ignore: cast_nullable_to_non_nullable +as String,placeOfBirth: null == placeOfBirth ? _self.placeOfBirth : placeOfBirth // ignore: cast_nullable_to_non_nullable +as String,birthCountry: null == birthCountry ? _self.birthCountry : birthCountry // ignore: cast_nullable_to_non_nullable +as String,nationality: null == nationality ? _self.nationality : nationality // ignore: cast_nullable_to_non_nullable +as String,documentType: null == documentType ? _self.documentType : documentType // ignore: cast_nullable_to_non_nullable +as String,document: null == document ? _self.document : document // ignore: cast_nullable_to_non_nullable +as String,paymentWalletId: freezed == paymentWalletId ? _self.paymentWalletId : paymentWalletId // ignore: cast_nullable_to_non_nullable +as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +/// @nodoc +mixin _$PaymentProfileAddressEntity { + + String get street; String get city; String get province; String get state; String get country; int get postCode; +/// Create a copy of PaymentProfileAddressEntity +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PaymentProfileAddressEntityCopyWith get copyWith => _$PaymentProfileAddressEntityCopyWithImpl(this as PaymentProfileAddressEntity, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentProfileAddressEntity&&(identical(other.street, street) || other.street == street)&&(identical(other.city, city) || other.city == city)&&(identical(other.province, province) || other.province == province)&&(identical(other.state, state) || other.state == state)&&(identical(other.country, country) || other.country == country)&&(identical(other.postCode, postCode) || other.postCode == postCode)); +} + + +@override +int get hashCode => Object.hash(runtimeType,street,city,province,state,country,postCode); + +@override +String toString() { + return 'PaymentProfileAddressEntity(street: $street, city: $city, province: $province, state: $state, country: $country, postCode: $postCode)'; +} + + +} + +/// @nodoc +abstract mixin class $PaymentProfileAddressEntityCopyWith<$Res> { + factory $PaymentProfileAddressEntityCopyWith(PaymentProfileAddressEntity value, $Res Function(PaymentProfileAddressEntity) _then) = _$PaymentProfileAddressEntityCopyWithImpl; +@useResult +$Res call({ + String street, String city, String province, String state, String country, int postCode +}); + + + + +} +/// @nodoc +class _$PaymentProfileAddressEntityCopyWithImpl<$Res> + implements $PaymentProfileAddressEntityCopyWith<$Res> { + _$PaymentProfileAddressEntityCopyWithImpl(this._self, this._then); + + final PaymentProfileAddressEntity _self; + final $Res Function(PaymentProfileAddressEntity) _then; + +/// Create a copy of PaymentProfileAddressEntity +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? street = null,Object? city = null,Object? province = null,Object? state = null,Object? country = null,Object? postCode = null,}) { + return _then(_self.copyWith( +street: null == street ? _self.street : street // ignore: cast_nullable_to_non_nullable +as String,city: null == city ? _self.city : city // ignore: cast_nullable_to_non_nullable +as String,province: null == province ? _self.province : province // ignore: cast_nullable_to_non_nullable +as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable +as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable +as String,postCode: null == postCode ? _self.postCode : postCode // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PaymentProfileAddressEntity]. +extension PaymentProfileAddressEntityPatterns on PaymentProfileAddressEntity { +/// 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 Function( _PaymentProfileAddressEntity value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PaymentProfileAddressEntity() 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 Function( _PaymentProfileAddressEntity value) $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileAddressEntity(): +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? Function( _PaymentProfileAddressEntity value)? $default,){ +final _that = this; +switch (_that) { +case _PaymentProfileAddressEntity() 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 Function( String street, String city, String province, String state, String country, int postCode)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PaymentProfileAddressEntity() when $default != null: +return $default(_that.street,_that.city,_that.province,_that.state,_that.country,_that.postCode);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 Function( String street, String city, String province, String state, String country, int postCode) $default,) {final _that = this; +switch (_that) { +case _PaymentProfileAddressEntity(): +return $default(_that.street,_that.city,_that.province,_that.state,_that.country,_that.postCode);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? Function( String street, String city, String province, String state, String country, int postCode)? $default,) {final _that = this; +switch (_that) { +case _PaymentProfileAddressEntity() when $default != null: +return $default(_that.street,_that.city,_that.province,_that.state,_that.country,_that.postCode);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _PaymentProfileAddressEntity implements PaymentProfileAddressEntity { + const _PaymentProfileAddressEntity({required this.street, required this.city, required this.province, required this.state, required this.country, required this.postCode}); + + +@override final String street; +@override final String city; +@override final String province; +@override final String state; +@override final String country; +@override final int postCode; + +/// Create a copy of PaymentProfileAddressEntity +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PaymentProfileAddressEntityCopyWith<_PaymentProfileAddressEntity> get copyWith => __$PaymentProfileAddressEntityCopyWithImpl<_PaymentProfileAddressEntity>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentProfileAddressEntity&&(identical(other.street, street) || other.street == street)&&(identical(other.city, city) || other.city == city)&&(identical(other.province, province) || other.province == province)&&(identical(other.state, state) || other.state == state)&&(identical(other.country, country) || other.country == country)&&(identical(other.postCode, postCode) || other.postCode == postCode)); +} + + +@override +int get hashCode => Object.hash(runtimeType,street,city,province,state,country,postCode); + +@override +String toString() { + return 'PaymentProfileAddressEntity(street: $street, city: $city, province: $province, state: $state, country: $country, postCode: $postCode)'; +} + + +} + +/// @nodoc +abstract mixin class _$PaymentProfileAddressEntityCopyWith<$Res> implements $PaymentProfileAddressEntityCopyWith<$Res> { + factory _$PaymentProfileAddressEntityCopyWith(_PaymentProfileAddressEntity value, $Res Function(_PaymentProfileAddressEntity) _then) = __$PaymentProfileAddressEntityCopyWithImpl; +@override @useResult +$Res call({ + String street, String city, String province, String state, String country, int postCode +}); + + + + +} +/// @nodoc +class __$PaymentProfileAddressEntityCopyWithImpl<$Res> + implements _$PaymentProfileAddressEntityCopyWith<$Res> { + __$PaymentProfileAddressEntityCopyWithImpl(this._self, this._then); + + final _PaymentProfileAddressEntity _self; + final $Res Function(_PaymentProfileAddressEntity) _then; + +/// Create a copy of PaymentProfileAddressEntity +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? street = null,Object? city = null,Object? province = null,Object? state = null,Object? country = null,Object? postCode = null,}) { + return _then(_PaymentProfileAddressEntity( +street: null == street ? _self.street : street // ignore: cast_nullable_to_non_nullable +as String,city: null == city ? _self.city : city // ignore: cast_nullable_to_non_nullable +as String,province: null == province ? _self.province : province // ignore: cast_nullable_to_non_nullable +as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable +as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable +as String,postCode: null == postCode ? _self.postCode : postCode // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/modules/auth/lib/src/features/login/presentation/state/login_view_model.dart b/modules/auth/lib/src/features/login/presentation/state/login_view_model.dart index 498167c1..3ba393bc 100644 --- a/modules/auth/lib/src/features/login/presentation/state/login_view_model.dart +++ b/modules/auth/lib/src/features/login/presentation/state/login_view_model.dart @@ -226,12 +226,10 @@ class LoginViewModel extends Notifier { try { final user = await _getUserInfoUseCase.getUserInfo(); - if (ref.mounted) { state = state.copyWith(isLoading: false); } - debugPrint('[getUserInfo] userId => ${user.id}'); return user; } catch (e) { if (ref.mounted) { diff --git a/modules/auth/lib/src/features/sca_treezor/sca_pin_view.dart b/modules/auth/lib/src/features/sca_treezor/sca_pin_view.dart new file mode 100644 index 00000000..0dd04d3f --- /dev/null +++ b/modules/auth/lib/src/features/sca_treezor/sca_pin_view.dart @@ -0,0 +1,242 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ScaPinView extends StatelessWidget { + final String title; + final String pin; + final bool isProcessing; + final String processingText; + final bool canSubmit; + final String submitText; + final void Function(String digit) onDigitPressed; + final VoidCallback onBackspacePressed; + final VoidCallback onClearPin; + final VoidCallback onSubmit; + final String? errorMessage; + + const ScaPinView({ + super.key, + required this.title, + required this.pin, + required this.isProcessing, + this.processingText = 'Procesando...', + required this.canSubmit, + this.submitText = 'Confirmar', + required this.onDigitPressed, + required this.onBackspacePressed, + required this.onClearPin, + required this.onSubmit, + this.errorMessage, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 18), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + textAlign: TextAlign.center, + style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 17), + ), + const SizedBox(height: 18), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(width: 44), + _PinDots(length: pin.length, max: 6), + const SizedBox(width: 12), + SizedBox( + width: 44, + height: 44, + child: IconButton( + onPressed: pin.isEmpty ? null : onBackspacePressed, + icon: const Icon(CupertinoIcons.delete_left), + splashRadius: 20, + color: Colors.black87, + ), + ), + ], + ), + _DialPad(onDigitPressed: onDigitPressed), + if (isProcessing) ...[ + const CupertinoActivityIndicator(radius: 12), + Text( + processingText, + style: const TextStyle(fontSize: 14, color: Colors.black54), + ), + ] else ...[ + IgnorePointer( + ignoring: !canSubmit, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 150), + opacity: canSubmit ? 1 : 0.35, + child: GestureDetector( + onTap: onSubmit, + child: Container( + width: 74, + height: 74, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF34C759), + boxShadow: [ + BoxShadow( + blurRadius: 18, + offset: Offset(0, 8), + color: Color(0x22000000), + ), + ], + ), + child: Center( + child: Text( + submitText, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ), + ), + ], + const SizedBox(height: 10), + TextButton( + onPressed: pin.isEmpty ? null : onClearPin, + child: const Text('Borrar PIN'), + ), + if (errorMessage != null && errorMessage!.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + errorMessage!, + style: const TextStyle(color: Colors.red, fontSize: 13), + textAlign: TextAlign.center, + ), + ], + ], + ), + ); + } +} + +class _PinDots extends StatelessWidget { + final int length; + final int max; + + const _PinDots({required this.length, required this.max}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(max, (i) { + final filled = i < length; + return AnimatedContainer( + duration: const Duration(milliseconds: 120), + curve: Curves.easeOut, + width: 12, + height: 12, + margin: const EdgeInsets.symmetric(horizontal: 7), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: filled ? Colors.black : const Color(0xFFD1D1D6), + ), + ); + }), + ); + } +} + +class _DialPad extends StatelessWidget { + final void Function(String digit) onDigitPressed; + + const _DialPad({required this.onDigitPressed}); + + static const _digits = [ + '1', '2', '3', + '4', '5', '6', + '7', '8', '9', + '', '0', '', + ]; + + @override + Widget build(BuildContext context) { + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _digits.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 30, + ), + itemBuilder: (_, index) { + final digit = _digits[index]; + if (digit.isEmpty) return const SizedBox.shrink(); + return Center( + child: _DialButton( + digit: digit, + onTap: () => onDigitPressed(digit), + ), + ); + }, + ); + } +} + +class _DialButton extends StatefulWidget { + final String digit; + final VoidCallback onTap; + + const _DialButton({required this.digit, required this.onTap}); + + @override + State<_DialButton> createState() => _DialButtonState(); +} + +class _DialButtonState extends State<_DialButton> { + bool _pressed = false; + + void _setPressed(bool value) { + if (_pressed == value) return; + setState(() => _pressed = value); + } + + @override + Widget build(BuildContext context) { + const double size = 56; + final bg = _pressed ? Colors.grey : Colors.white; + + return GestureDetector( + onTapDown: (_) => _setPressed(true), + onTapCancel: () => _setPressed(false), + onTapUp: (_) => _setPressed(false), + onTap: () { + HapticFeedback.lightImpact(); + widget.onTap(); + }, + child: SizedBox( + width: size, + height: size, + child: AnimatedContainer( + duration: const Duration(milliseconds: 120), + curve: Curves.easeOut, + decoration: BoxDecoration(shape: BoxShape.circle, color: bg), + child: Center( + child: Text( + widget.digit, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ), + ), + ), + ); + } +} diff --git a/modules/auth/lib/src/features/sca_treezor/sca_treezor_screen.dart b/modules/auth/lib/src/features/sca_treezor/sca_treezor_screen.dart index dc7e877f..f1a48a90 100644 --- a/modules/auth/lib/src/features/sca_treezor/sca_treezor_screen.dart +++ b/modules/auth/lib/src/features/sca_treezor/sca_treezor_screen.dart @@ -1,10 +1,10 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:navigation/app_routes.dart'; import 'package:navigation/navigation_contract.dart'; +import 'package:sf_localizations/sf_localizations.dart'; +import 'sca_pin_view.dart'; import 'sca_treezor_view_model.dart'; import 'sca_treezor_view_state.dart'; @@ -18,44 +18,6 @@ class SCATreezorScreen extends ConsumerWidget { final SCATreezorViewState state = ref.watch(scaTreezorViewModelProvider); final vm = ref.read(scaTreezorViewModelProvider.notifier); - ref.listen(scaTreezorViewModelProvider.select((s) => s.errorMessage), ( - prev, - next, - ) { - if (next.isEmpty) return; - if (prev == next) return; - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(next))); - }); - - ref.listen(scaTreezorViewModelProvider.select((s) => s.success), ( - prev, - next, - ) { - if (next == false) return; - if (prev == next) return; - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - state.isConnected - ? '✅ Wallet conectado' - : '✅ Wallet provisionado exitosamente', - ), - ), - ); - }); - ref.listen( - scaTreezorViewModelProvider.select((s) => s.lastSignature), - (prev, next) { - final hadSignature = (prev ?? '').isNotEmpty; - final hasSignature = next.isNotEmpty; - - if (!hadSignature && hasSignature) { - navigationContract.goTo(AppRoutes.deviceSetup); - } - }, - ); - return Scaffold( backgroundColor: Colors.white, body: Center( @@ -64,10 +26,30 @@ class SCATreezorScreen extends ConsumerWidget { child: state.isLoading ? const CircularProgressIndicator() : state.isProvisioned - ? _ConnectionBody( - state: state, - vm: vm, - navigationContract: navigationContract, + ? ScaPinView( + title: state.isFirstConnectionDone + ? 'Introduce tu PIN para conectar' + : 'Crea tu PIN para conectar tu wallet', + pin: state.pin, + isProcessing: state.isConnecting || state.isSigning, + processingText: state.isConnecting + ? 'Conectando...' + : 'Firmando...', + canSubmit: vm.canSubmitPin && !state.isConnecting, + submitText: 'Conectar', + errorMessage: state.errorMessage.isNotEmpty + ? context.translate(state.errorMessage) + : null, + onDigitPressed: vm.onDigitPressed, + onBackspacePressed: vm.onBackspacePressed, + onClearPin: vm.clearPin, + onSubmit: () async { + final success = await vm.connectAndSignJwtSca(); + if (!context.mounted) return; + if (success) { + navigationContract.goTo(AppRoutes.deviceSetup); + } + }, ) : _ProvisioningBody(state: state, vm: vm), ), @@ -88,6 +70,14 @@ class _ProvisioningBody extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 16), + if (state.errorMessage.isNotEmpty) ...[ + Text( + context.translate(state.errorMessage), + style: const TextStyle(color: Colors.red, fontSize: 13), + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + ], if (state.isProvisioning) ...[ const CircularProgressIndicator(), const SizedBox(height: 8), @@ -102,257 +92,3 @@ class _ProvisioningBody extends StatelessWidget { ); } } - -class _ConnectionBody extends StatelessWidget { - final SCATreezorViewState state; - final SCATreezorViewModel vm; - - const _ConnectionBody({ - required this.state, - required this.vm, - required NavigationContract navigationContract, - }); - - @override - Widget build(BuildContext context) { - final title = state.isFirstConnectionDone - ? 'Introduce tu PIN para conectar' - : 'Crea tu PIN para conectar tu wallet'; - - final canSubmit = vm.canSubmitPin && !state.isConnecting; - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 18), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - title, - textAlign: TextAlign.center, - style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 17), - ), - - const SizedBox(height: 18), - - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(width: 44), - _PinDots(length: state.pin.length, max: 6), - const SizedBox(width: 12), - SizedBox( - width: 44, - height: 44, - child: IconButton( - onPressed: state.pin.isEmpty ? null : vm.onBackspacePressed, - icon: const Icon(CupertinoIcons.delete_left), - splashRadius: 20, - color: Colors.black87, - ), - ), - ], - ), - - _DialPad( - onDigitPressed: vm.onDigitPressed, - onBackspacePressed: vm.onBackspacePressed, - ), - - if (state.isConnecting) ...[ - const CupertinoActivityIndicator(radius: 12), - const Text( - 'Conectando...', - style: TextStyle(fontSize: 14, color: Colors.black54), - ), - ] else if (state.isSigning) ...[ - const CircularProgressIndicator(), - const SizedBox(height: 8), - const Text('Firmando...'), - ] else ...[ - IgnorePointer( - ignoring: !canSubmit, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 150), - opacity: canSubmit ? 1 : 0.35, - child: GestureDetector( - onTap: vm.connectAndSignJwtSca, - child: Container( - width: 74, - height: 74, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Color(0xFF34C759), - boxShadow: [ - BoxShadow( - blurRadius: 18, - offset: Offset(0, 8), - color: Color(0x22000000), - ), - ], - ), - child: Center( - child: const Text( - 'Conectar', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ), - ), - ), - ], - - const SizedBox(height: 10), - - TextButton( - onPressed: state.pin.isEmpty ? null : vm.clearPin, - child: const Text('Borrar PIN'), - ), - ], - ), - ); - } -} - -class _PinDots extends StatelessWidget { - final int length; - final int max; - - const _PinDots({required this.length, required this.max}); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: List.generate(max, (i) { - final filled = i < length; - - return AnimatedContainer( - duration: const Duration(milliseconds: 120), - curve: Curves.easeOut, - width: 12, - height: 12, - margin: const EdgeInsets.symmetric(horizontal: 7), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: filled ? Colors.black : const Color(0xFFD1D1D6), - ), - ); - }), - ); - } -} - -class _DialPad extends StatelessWidget { - final void Function(String digit) onDigitPressed; - - const _DialPad({ - required this.onDigitPressed, - required void Function() onBackspacePressed, - }); - - static const _keys = <_DialKey>[ - _DialKey('1'), - _DialKey('2'), - _DialKey('3'), - _DialKey('4'), - _DialKey('5'), - _DialKey('6'), - _DialKey('7'), - _DialKey('8'), - _DialKey('9'), - _DialKey(''), - _DialKey('0'), - ]; - - @override - Widget build(BuildContext context) { - return GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: _keys.length, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 30, - ), - itemBuilder: (_, index) { - final key = _keys[index]; - - if (key.digit.isEmpty) { - return const SizedBox.shrink(); - } - - return Center( - child: _DialButton( - digit: key.digit, - onTap: () => onDigitPressed(key.digit), - ), - ); - }, - ); - } -} - -class _DialButton extends StatefulWidget { - final String digit; - final VoidCallback onTap; - - const _DialButton({required this.digit, required this.onTap}); - - @override - State<_DialButton> createState() => _DialButtonState(); -} - -class _DialButtonState extends State<_DialButton> { - bool _pressed = false; - - void _setPressed(bool value) { - if (_pressed == value) return; - setState(() => _pressed = value); - } - - @override - Widget build(BuildContext context) { - const double size = 56; - - final bg = _pressed ? Colors.grey : Colors.white; - - return GestureDetector( - onTapDown: (_) => _setPressed(true), - onTapCancel: () => _setPressed(false), - onTapUp: (_) => _setPressed(false), - onTap: () { - HapticFeedback.lightImpact(); - widget.onTap(); - }, - child: SizedBox( - width: size, - height: size, - child: AnimatedContainer( - duration: const Duration(milliseconds: 120), - curve: Curves.easeOut, - decoration: BoxDecoration(shape: BoxShape.circle, color: bg), - child: Center( - child: Text( - widget.digit, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - ), - ), - ), - ); - } -} - -class _DialKey { - final String digit; - - const _DialKey(this.digit); -} diff --git a/modules/auth/lib/src/features/sca_treezor/sca_treezor_view_model.dart b/modules/auth/lib/src/features/sca_treezor/sca_treezor_view_model.dart index abdb1efc..c4f4f390 100644 --- a/modules/auth/lib/src/features/sca_treezor/sca_treezor_view_model.dart +++ b/modules/auth/lib/src/features/sca_treezor/sca_treezor_view_model.dart @@ -4,8 +4,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get_it/get_it.dart'; import 'package:sca_treezor/sca_treezor.dart'; +import 'package:sf_localizations/sf_localizations.dart'; import 'package:sf_shared/sf_shared.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:auth/src/core/data/datasource/session_local_datasource.dart'; +import 'package:auth/src/core/providers/session_local_datasource_provider.dart'; + +import '../../core/domain/repositories/auth_repository.dart'; +import '../../core/providers/auth_repository_provider.dart'; import 'sca_treezor_view_state.dart'; @@ -25,6 +31,8 @@ class SCATreezorViewModel extends Notifier { late final TreezorWalletProvisioningService _provisioningService; late final TreezorWalletConnectionService _connectionService; late final TreezorWalletSignatureService _signatureService; + late final AuthRepository _authRepository; + late final SessionLocalDatasource _sessionLocal; late final TextEditingController codeController; @@ -43,7 +51,8 @@ class SCATreezorViewModel extends Notifier { _provisioningService = GetIt.I(); _connectionService = GetIt.I(); _signatureService = GetIt.I(); - + _authRepository = ref.read(authRepositoryProvider); + _sessionLocal = ref.read(sessionLocalDatasourceProvider); codeController = TextEditingController(); codeController.addListener(_onCodeChanged); @@ -133,7 +142,7 @@ class SCATreezorViewModel extends Notifier { final activationCode = state.activationCode.trim(); if (activationCode.isEmpty) { - state = state.copyWith(errorMessage: '⚠️ Activation code vacío'); + state = state.copyWith(errorMessage: I18n.errorActivationCodeEmpty); return; } @@ -203,14 +212,12 @@ class SCATreezorViewModel extends Notifier { if (state.isConnecting) return false; if (!state.isProvisioned) { - state = state.copyWith(errorMessage: '⚠️ No está provisionado aún'); + state = state.copyWith(errorMessage: I18n.errorWalletNotProvisioned); return false; } if (!canSubmitPin) { - state = state.copyWith( - errorMessage: '⚠️ El PIN debe tener $_pinLength dígitos', - ); + state = state.copyWith(errorMessage: I18n.errorPinRequired); return false; } @@ -262,12 +269,12 @@ class SCATreezorViewModel extends Notifier { if (state.isSigning) return; if (state.isConnected == false) { - state = state.copyWith(errorMessage: '⚠️ Conecta la wallet primero'); + state = state.copyWith(errorMessage: I18n.errorWalletConnectFirst); return; } if (!canSubmitPin) { - state = state.copyWith(errorMessage: '⚠️ PIN de $_pinLength dígitos'); + state = state.copyWith(errorMessage: I18n.errorPinRequired); return; } @@ -303,13 +310,30 @@ class SCATreezorViewModel extends Notifier { } } - Future connectAndSignJwtSca() async { - if (state.isConnecting || state.isSigning) return; + Future connectAndSignJwtSca() async { + if (state.isConnecting || state.isSigning) return false; final connected = await connectWithPin(); - if (!connected) return; + if (!connected) return false; await signJwtSca(); + final user = await _authRepository.getUserInfo(); + final paymentProfile = await _authRepository.getPaymentProfile( + userId: user.id, + ); + debugPrint( + '[connectWithPin] paymentWalletId => ${paymentProfile.paymentWalletId}', + ); + + if (paymentProfile.paymentWalletId == null || + paymentProfile.paymentWalletId!.isEmpty) { + await _authRepository.createWallet(); + await _sessionLocal.savePaymentProfileId(paymentProfile.paymentWalletId!); + } + // remove this when the backend starts returning the walletId on getUserInfo or getPaymentProfile + await _sessionLocal.savePaymentProfileId(paymentProfile.paymentWalletId!); + + return state.lastSignature.isNotEmpty; } String _sanitizeActivationCode(String code) { diff --git a/modules/auth/lib/src/features/sign_up/presentation/sign_up_screen.dart b/modules/auth/lib/src/features/sign_up/presentation/sign_up_screen.dart index dcd1bd51..80b6b5af 100644 --- a/modules/auth/lib/src/features/sign_up/presentation/sign_up_screen.dart +++ b/modules/auth/lib/src/features/sign_up/presentation/sign_up_screen.dart @@ -7,6 +7,7 @@ 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 SignupScreen extends ConsumerWidget { final NavigationContract navigationContract; @@ -54,7 +55,7 @@ class SignupScreen extends ConsumerWidget { currentStep: index + 1, numSteps: steps.length, body: step.bodyBuilder(context, ref), - errorMessage: state.errorMessage, + errorMessage: context.translate(state.errorMessage), onBackPressed: state.currentIndex == 0 ? navigationContract.goBack : vm.back, diff --git a/modules/auth/lib/src/features/sign_up/presentation/state/sign_up_view_model.dart b/modules/auth/lib/src/features/sign_up/presentation/state/sign_up_view_model.dart index bbb6573a..ff84ceb6 100644 --- a/modules/auth/lib/src/features/sign_up/presentation/state/sign_up_view_model.dart +++ b/modules/auth/lib/src/features/sign_up/presentation/state/sign_up_view_model.dart @@ -401,22 +401,22 @@ class SignUpViewModel extends Notifier { errorMessage: '', ); if (state.firstName.trim().isEmpty) { - state = state.copyWith(errorMessage: 'El nombre es obligatorio'); + state = state.copyWith(errorMessage: I18n.errorFirstNameRequired); return false; } if (state.lastName.trim().isEmpty) { - state = state.copyWith(errorMessage: 'El apellido es obligatorio'); + state = state.copyWith(errorMessage: I18n.errorLastNameRequired); return false; } if (state.documentType.trim().isEmpty) { state = state.copyWith( - errorMessage: 'El tipo de documento es obligatorio', + errorMessage: I18n.errorDocumentTypeRequired, ); return false; } if (state.document.trim().isEmpty) { state = state.copyWith( - errorMessage: 'El número de documento es obligatorio', + errorMessage: I18n.errorDocumentNumberRequired, ); return false; } @@ -430,7 +430,7 @@ class SignUpViewModel extends Notifier { } if (!state.acceptTerms) { state = state.copyWith( - errorMessage: 'Debes aceptar los términos y condiciones', + errorMessage: I18n.errorAcceptTerms, ); return false; } @@ -445,27 +445,27 @@ class SignUpViewModel extends Notifier { } if (state.bornAt == null) { state = state.copyWith( - errorMessage: 'Selecciona una fecha de nacimiento válida (DD/MM/AAAA)', + errorMessage: I18n.errorBirthDateRequired, ); return false; } if (state.relationType.trim().isEmpty) { - state = state.copyWith(errorMessage: 'La relación es obligatoria'); + state = state.copyWith(errorMessage: I18n.errorRelationshipRequired); return false; } if (state.placeOfBirth.trim().isEmpty) { - state = state.copyWith(errorMessage: 'Falta el lugar de nacimiento'); + state = state.copyWith(errorMessage: I18n.errorPlaceOfBirthRequired); return false; } if (state.birthCountry.trim().isEmpty) { - state = state.copyWith(errorMessage: 'Falta el país de nacimiento'); + state = state.copyWith(errorMessage: I18n.errorBirthCountryRequired); return false; } if (!_isAddressValid(state.address)) { - state = state.copyWith(errorMessage: 'Completa la dirección'); + state = state.copyWith(errorMessage: I18n.errorAddressRequired); return false; } diff --git a/modules/splash/lib/src/data/splash_remote_datasource.dart b/modules/splash/lib/src/data/splash_remote_datasource.dart new file mode 100644 index 00000000..11edec49 --- /dev/null +++ b/modules/splash/lib/src/data/splash_remote_datasource.dart @@ -0,0 +1,3 @@ +abstract class SplashRemoteDatasource { + Future> getChildProfiles(); +} diff --git a/modules/splash/lib/src/data/splash_remote_datasource_impl.dart b/modules/splash/lib/src/data/splash_remote_datasource_impl.dart new file mode 100644 index 00000000..8157af77 --- /dev/null +++ b/modules/splash/lib/src/data/splash_remote_datasource_impl.dart @@ -0,0 +1,19 @@ +import 'package:sf_infrastructure/sf_infrastructure.dart'; + +import 'splash_remote_datasource.dart'; + +class SplashRemoteDatasourceImpl implements SplashRemoteDatasource { + SplashRemoteDatasourceImpl(this._repository); + + final QuestiaRepository _repository; + + @override + Future> getChildProfiles() async { + final response = + await _repository.get>('/child-profiles'); + final data = response.data; + if (data == null) return []; + final items = data['items'] ?? data['data'] ?? []; + return items is List ? items : []; + } +} diff --git a/modules/splash/lib/src/domain/check_session_use_case.dart b/modules/splash/lib/src/domain/check_session_use_case.dart new file mode 100644 index 00000000..f32ab5f8 --- /dev/null +++ b/modules/splash/lib/src/domain/check_session_use_case.dart @@ -0,0 +1,5 @@ +import 'initial_route.dart'; + +abstract class CheckSessionUseCase { + Future execute(); +} diff --git a/modules/splash/lib/src/domain/check_session_use_case_impl.dart b/modules/splash/lib/src/domain/check_session_use_case_impl.dart new file mode 100644 index 00000000..4aea4666 --- /dev/null +++ b/modules/splash/lib/src/domain/check_session_use_case_impl.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +import '../data/splash_remote_datasource.dart'; +import 'check_session_use_case.dart'; +import 'initial_route.dart'; + +class CheckSessionUseCaseImpl implements CheckSessionUseCase { + CheckSessionUseCaseImpl(this._datasource); + + final SplashRemoteDatasource _datasource; + + @override + Future execute() async { + try { + final profiles = await _datasource.getChildProfiles(); + return profiles.isEmpty ? InitialRoute.deviceSetup : InitialRoute.home; + } catch (e) { + debugPrint('[CheckSessionUseCase] error: $e'); + return InitialRoute.login; + } + } +} diff --git a/modules/splash/lib/src/domain/initial_route.dart b/modules/splash/lib/src/domain/initial_route.dart new file mode 100644 index 00000000..363643bb --- /dev/null +++ b/modules/splash/lib/src/domain/initial_route.dart @@ -0,0 +1 @@ +enum InitialRoute { login, deviceSetup, home } diff --git a/modules/splash/lib/src/presentation/splash_screen.dart b/modules/splash/lib/src/presentation/splash_screen.dart index d91647f7..eac9e52c 100644 --- a/modules/splash/lib/src/presentation/splash_screen.dart +++ b/modules/splash/lib/src/presentation/splash_screen.dart @@ -4,10 +4,18 @@ import 'package:flutter/material.dart'; import 'package:navigation/app_routes.dart'; import 'package:navigation/navigation_contract.dart'; import 'package:page_transition/page_transition.dart'; +import 'package:splash/src/domain/check_session_use_case.dart'; +import 'package:splash/src/domain/initial_route.dart'; class SplashScreen extends StatefulWidget { - const SplashScreen({super.key, required this.navigationContract}); + const SplashScreen({ + super.key, + required this.navigationContract, + required this.checkSessionUseCase, + }); + final NavigationContract navigationContract; + final CheckSessionUseCase checkSessionUseCase; @override State createState() => _SplashScreenState(); @@ -21,9 +29,22 @@ class _SplashScreenState extends State { } Future nextRoute() async { - await Future.delayed(const Duration(milliseconds: 3500)); + final results = await Future.wait([ + widget.checkSessionUseCase.execute(), + Future.delayed(const Duration(milliseconds: 3500)), + ]); + if (!mounted) return; - widget.navigationContract.goTo(AppRoutes.onboarding); + + final route = results[0] as InitialRoute; + switch (route) { + case InitialRoute.login: + widget.navigationContract.goTo(AppRoutes.login); + case InitialRoute.deviceSetup: + widget.navigationContract.goTo(AppRoutes.deviceSetup); + case InitialRoute.home: + widget.navigationContract.goTo(AppRoutes.dashboardHome); + } } @override diff --git a/modules/splash/lib/src/splash_builder.dart b/modules/splash/lib/src/splash_builder.dart index be9903a2..01fe1d22 100644 --- a/modules/splash/lib/src/splash_builder.dart +++ b/modules/splash/lib/src/splash_builder.dart @@ -2,16 +2,24 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; import 'package:navigation/navigation_contract.dart'; +import 'package:sf_infrastructure/sf_infrastructure.dart'; +import 'package:splash/src/data/splash_remote_datasource_impl.dart'; +import 'package:splash/src/domain/check_session_use_case_impl.dart'; import 'package:splash/src/presentation/splash_screen.dart'; class SplashBuilder { const SplashBuilder(); Page buildPage(BuildContext context, GoRouterState state) { - final NavigationContract navigationContract = GetIt.I(); + final navigationContract = GetIt.I(); + final datasource = SplashRemoteDatasourceImpl(GetIt.I()); + final checkSessionUseCase = CheckSessionUseCaseImpl(datasource); return NoTransitionPage( - child: SplashScreen(navigationContract: navigationContract), + child: SplashScreen( + navigationContract: navigationContract, + checkSessionUseCase: checkSessionUseCase, + ), ); } } diff --git a/modules/splash/pubspec.yaml b/modules/splash/pubspec.yaml index 5ef4a316..ad2d1da7 100644 --- a/modules/splash/pubspec.yaml +++ b/modules/splash/pubspec.yaml @@ -17,6 +17,8 @@ dependencies: page_transition: ^2.2.1 navigation: path: ../../packages/navigation + sf_infrastructure: + path: ../../packages/sf_infrastructure dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml index a24c7f62..c6999cf5 100644 --- a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml +++ b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml @@ -7,6 +7,6 @@ 2.6.4 - 20260127000000 + 20260210000000 diff --git a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.md5 b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.md5 index 0dfaf2cc..4cf0bb31 100644 --- a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.md5 +++ b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.md5 @@ -1 +1 @@ -d3de74b51e78f073e96d4f8f7e37e091 \ No newline at end of file +d77d08495d6aeaec19114b634957c84c \ No newline at end of file diff --git a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.sha1 b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.sha1 index 0e025363..dca21762 100644 --- a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.sha1 +++ b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.sha1 @@ -1 +1 @@ -7bc6215d7d14b8165f428c6cffca46f51c260d44 \ No newline at end of file +bdcbc45dfb9b2a6ccb485f2e1114aeef96a11ce7 \ No newline at end of file diff --git a/packages/sf_infrastructure/lib/configure_dependencies.dart b/packages/sf_infrastructure/lib/configure_dependencies.dart index 65497681..077e5b4c 100644 --- a/packages/sf_infrastructure/lib/configure_dependencies.dart +++ b/packages/sf_infrastructure/lib/configure_dependencies.dart @@ -12,14 +12,13 @@ export 'src/repositories/questia_repository.dart'; final getIt = GetIt.instance; Future configureDependencies(EnvConfig env, {bool log = false}) async { - getIt.registerLazySingleton( - () => buildDioClient( - baseUrl: env.apiBaseUrl, - origin: env.apiOrigin, - // apiKey: env.apiKey, - log: log, - ), + final dio = await buildDioClient( + baseUrl: env.apiBaseUrl, + origin: env.apiOrigin, + log: log, ); + + getIt.registerLazySingleton(() => dio); getIt.registerLazySingleton(() => QuestiaApi(getIt())); getIt.registerLazySingleton( () => QuestiaRepositoryImpl(getIt()), diff --git a/packages/sf_infrastructure/lib/src/network/dio_client.dart b/packages/sf_infrastructure/lib/src/network/dio_client.dart index 3348f460..ab48bf4d 100644 --- a/packages/sf_infrastructure/lib/src/network/dio_client.dart +++ b/packages/sf_infrastructure/lib/src/network/dio_client.dart @@ -1,13 +1,14 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; +import 'package:path_provider/path_provider.dart'; -Dio buildDioClient({ +Future buildDioClient({ required String baseUrl, required String origin, bool log = false, CookieJar? cookieJar, -}) { +}) async { final dio = Dio( BaseOptions( baseUrl: baseUrl, @@ -22,7 +23,7 @@ Dio buildDioClient({ ), ); - final jar = cookieJar ?? CookieJar(); + final jar = cookieJar ?? await _buildPersistCookieJar(); dio.interceptors.add(CookieManager(jar)); if (log) { @@ -40,3 +41,8 @@ Dio buildDioClient({ return dio; } + +Future _buildPersistCookieJar() async { + final dir = await getApplicationDocumentsDirectory(); + return PersistCookieJar(storage: FileStorage('${dir.path}/.cookies/')); +} diff --git a/packages/sf_localizations/assets/l10n/de.json b/packages/sf_localizations/assets/l10n/de.json index 8a07ee50..b910f0c2 100644 --- a/packages/sf_localizations/assets/l10n/de.json +++ b/packages/sf_localizations/assets/l10n/de.json @@ -148,7 +148,7 @@ "deviceSetup_linkInfo_item2_prefix": "Scanne die ", "deviceSetup_linkInfo_item2_boldWord": "Uhr", "deviceSetup_linkInfo_item2_subtitle": "Du kannst die getätigten Ausgaben sehen", - "deviceSetup_watchCode_orInsert": "Oder gib den Code der Uhr ein", + "deviceSetup_watchCode_orInsert": "Oder gib den Code", "deviceSetup_watchCode_continueWithCode": "Mit Code fortfahren", "deviceSetup_linkTroubleshoot_title": "Wenn du das Armband oder die Uhr nicht verbinden kannst", "deviceSetup_contactUs": "Kontaktiere uns", @@ -159,5 +159,24 @@ "deviceSetup_start": "Los geht's!", "deviceSetup_giveFirstAllowance": "Gib das erste Taschengeld", "deviceSetup_scanQr": "QR scannen", - "deviceSetup_scanQr_hint": "Richte den QR-Code innerhalb des Rahmens aus" + "deviceSetup_scanQr_hint": "Richte den QR-Code innerhalb des Rahmens aus", + "errorScanStrapRequired": "Scanne das Armband oder gib den Code ein, um fortzufahren", + "errorScanWatchRequired": "Scanne die Uhr oder gib den Code ein, um fortzufahren", + "errorAllFieldsRequired": "Bitte fülle alle Felder aus", + "errorPinRequired": "Die PIN muss 6 Ziffern haben", + "errorSigningOperation": "Fehler beim Signieren des Vorgangs", + "errorFirstNameRequired": "Vorname ist erforderlich", + "errorLastNameRequired": "Nachname ist erforderlich", + "errorDocumentTypeRequired": "Dokumenttyp ist erforderlich", + "errorDocumentNumberRequired": "Dokumentnummer ist erforderlich", + "errorAcceptTerms": "Du musst die Allgemeinen Geschäftsbedingungen akzeptieren", + "errorBirthDateRequired": "Wähle ein gültiges Geburtsdatum (TT/MM/JJJJ)", + "errorRelationshipRequired": "Die Beziehung ist erforderlich", + "errorPlaceOfBirthRequired": "Der Geburtsort fehlt", + "errorBirthCountryRequired": "Das Geburtsland fehlt", + "errorAddressRequired": "Bitte vervollständige die Adresse", + "errorActivationCodeEmpty": "Aktivierungscode ist leer", + "errorWalletNotProvisioned": "Wallet ist noch nicht provisioniert", + "errorPinLength": "Die PIN muss {length} Ziffern haben", + "errorWalletConnectFirst": "Verbinde zuerst die Wallet" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/en.json b/packages/sf_localizations/assets/l10n/en.json index 584432c6..d151164d 100755 --- a/packages/sf_localizations/assets/l10n/en.json +++ b/packages/sf_localizations/assets/l10n/en.json @@ -148,7 +148,7 @@ "deviceSetup_linkInfo_item2_prefix": "Scan the ", "deviceSetup_linkInfo_item2_boldWord": "watch", "deviceSetup_linkInfo_item2_subtitle": "You'll be able to see the expenses made", - "deviceSetup_watchCode_orInsert": "Or enter the watch code", + "deviceSetup_watchCode_orInsert": "Or enter code", "deviceSetup_watchCode_continueWithCode": "Continue with code", "deviceSetup_linkTroubleshoot_title": "If you can't link their band or watch", "deviceSetup_contactUs": "Contact us", @@ -159,5 +159,24 @@ "deviceSetup_start": "Start!", "deviceSetup_giveFirstAllowance": "Give their first allowance", "deviceSetup_scanQr": "Scan QR", - "deviceSetup_scanQr_hint": "Center the QR inside the frame" + "deviceSetup_scanQr_hint": "Center the QR inside the frame", + "errorScanStrapRequired": "Scan the band or enter the code to continue", + "errorScanWatchRequired": "Scan the watch or enter the code to continue", + "errorAllFieldsRequired": "Please fill in all fields", + "errorPinRequired": "PIN must be 6 digits", + "errorSigningOperation": "Error signing operation", + "errorFirstNameRequired": "First name is required", + "errorLastNameRequired": "Last name is required", + "errorDocumentTypeRequired": "Document type is required", + "errorDocumentNumberRequired": "Document number is required", + "errorAcceptTerms": "You must accept the terms and conditions", + "errorBirthDateRequired": "Select a valid date of birth (DD/MM/YYYY)", + "errorRelationshipRequired": "Relationship is required", + "errorPlaceOfBirthRequired": "Place of birth is required", + "errorBirthCountryRequired": "Country of birth is required", + "errorAddressRequired": "Please complete the address", + "errorActivationCodeEmpty": "Activation code is empty", + "errorWalletNotProvisioned": "Wallet is not provisioned yet", + "errorPinLength": "PIN must be {length} digits", + "errorWalletConnectFirst": "Connect the wallet first" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/es.json b/packages/sf_localizations/assets/l10n/es.json index 76a7dc45..82b98820 100644 --- a/packages/sf_localizations/assets/l10n/es.json +++ b/packages/sf_localizations/assets/l10n/es.json @@ -148,7 +148,7 @@ "deviceSetup_linkInfo_item2_prefix": "Escanea el ", "deviceSetup_linkInfo_item2_boldWord": "reloj", "deviceSetup_linkInfo_item2_subtitle": "Visualizarás los gastos que se hagan", - "deviceSetup_watchCode_orInsert": "O inserta el código del reloj", + "deviceSetup_watchCode_orInsert": "O inserta el código", "deviceSetup_watchCode_continueWithCode": "Continuar con código", "deviceSetup_linkTroubleshoot_title": "Si no consigues vincular su correa o reloj", "deviceSetup_contactUs": "Contáctanos", @@ -159,5 +159,24 @@ "deviceSetup_start": "¡Empezar!", "deviceSetup_giveFirstAllowance": "Dale su primera paga", "deviceSetup_scanQr": "Escanear QR", - "deviceSetup_scanQr_hint": "Centra el QR dentro del recuadro" + "deviceSetup_scanQr_hint": "Centra el QR dentro del recuadro", + "errorScanStrapRequired": "Escanea la correa o introduce el código para continuar", + "errorScanWatchRequired": "Escanea el reloj o introduce el código para continuar", + "errorAllFieldsRequired": "Completa todos los campos", + "errorPinRequired": "El PIN debe tener 6 dígitos", + "errorSigningOperation": "Error firmando operación sensible", + "errorFirstNameRequired": "El nombre es obligatorio", + "errorLastNameRequired": "El apellido es obligatorio", + "errorDocumentTypeRequired": "El tipo de documento es obligatorio", + "errorDocumentNumberRequired": "El número de documento es obligatorio", + "errorAcceptTerms": "Debes aceptar los términos y condiciones", + "errorBirthDateRequired": "Selecciona una fecha de nacimiento válida (DD/MM/AAAA)", + "errorRelationshipRequired": "La relación es obligatoria", + "errorPlaceOfBirthRequired": "Falta el lugar de nacimiento", + "errorBirthCountryRequired": "Falta el país de nacimiento", + "errorAddressRequired": "Completa la dirección", + "errorActivationCodeEmpty": "El código de activación está vacío", + "errorWalletNotProvisioned": "No está provisionado aún", + "errorPinLength": "El PIN debe tener {length} dígitos", + "errorWalletConnectFirst": "Conecta la wallet primero" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/fr.json b/packages/sf_localizations/assets/l10n/fr.json index e25a04f0..67d7c251 100644 --- a/packages/sf_localizations/assets/l10n/fr.json +++ b/packages/sf_localizations/assets/l10n/fr.json @@ -148,7 +148,7 @@ "deviceSetup_linkInfo_item2_prefix": "Scanne la ", "deviceSetup_linkInfo_item2_boldWord": "montre", "deviceSetup_linkInfo_item2_subtitle": "Vous verrez les dépenses effectuées", - "deviceSetup_watchCode_orInsert": "Ou saisissez le code de la montre", + "deviceSetup_watchCode_orInsert": "Ou saisissez le code", "deviceSetup_watchCode_continueWithCode": "Continuer avec un code", "deviceSetup_linkTroubleshoot_title": "Si vous n'arrivez pas à associer son bracelet ou sa montre", "deviceSetup_contactUs": "Contactez-nous", @@ -159,5 +159,24 @@ "deviceSetup_start": "Commencer!", "deviceSetup_giveFirstAllowance": "Donner sa première allocation", "deviceSetup_scanQr": "Scanner le QR", - "deviceSetup_scanQr_hint": "Place le QR au centre du cadre" + "deviceSetup_scanQr_hint": "Place le QR au centre du cadre", + "errorScanStrapRequired": "Scannez le bracelet ou saisissez le code pour continuer", + "errorScanWatchRequired": "Scannez la montre ou saisissez le code pour continuer", + "errorAllFieldsRequired": "Veuillez remplir tous les champs", + "errorPinRequired": "Le PIN doit contenir 6 chiffres", + "errorSigningOperation": "Erreur lors de la signature de l'opération", + "errorFirstNameRequired": "Le prénom est obligatoire", + "errorLastNameRequired": "Le nom est obligatoire", + "errorDocumentTypeRequired": "Le type de document est obligatoire", + "errorDocumentNumberRequired": "Le numéro de document est obligatoire", + "errorAcceptTerms": "Vous devez accepter les conditions générales", + "errorBirthDateRequired": "Sélectionnez une date de naissance valide (JJ/MM/AAAA)", + "errorRelationshipRequired": "Le lien de parenté est obligatoire", + "errorPlaceOfBirthRequired": "Le lieu de naissance est requis", + "errorBirthCountryRequired": "Le pays de naissance est requis", + "errorAddressRequired": "Veuillez compléter l'adresse", + "errorActivationCodeEmpty": "Le code d'activation est vide", + "errorWalletNotProvisioned": "Le portefeuille n'est pas encore provisionné", + "errorPinLength": "Le PIN doit contenir {length} chiffres", + "errorWalletConnectFirst": "Connectez d'abord le portefeuille" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/it.json b/packages/sf_localizations/assets/l10n/it.json index 939a1f52..dbc611b0 100644 --- a/packages/sf_localizations/assets/l10n/it.json +++ b/packages/sf_localizations/assets/l10n/it.json @@ -148,7 +148,7 @@ "deviceSetup_linkInfo_item2_prefix": "Scansiona l'", "deviceSetup_linkInfo_item2_boldWord": "orologio", "deviceSetup_linkInfo_item2_subtitle": "Potrai visualizzare le spese effettuate", - "deviceSetup_watchCode_orInsert": "Oppure inserisci il codice dell'orologio", + "deviceSetup_watchCode_orInsert": "Oppure inserisci il codice", "deviceSetup_watchCode_continueWithCode": "Continua con il codice", "deviceSetup_linkTroubleshoot_title": "Se non riesci a collegare il cinturino o l'orologio", "deviceSetup_contactUs": "Contactez-nous", @@ -159,5 +159,24 @@ "deviceSetup_start": "Inizia!", "deviceSetup_giveFirstAllowance": "Dagli la sua prima paghetta", "deviceSetup_scanQr": "Scansiona QR", - "deviceSetup_scanQr_hint": "Centra il QR all’interno del riquadro" + "deviceSetup_scanQr_hint": "Centra il QR all'interno del riquadro", + "errorScanStrapRequired": "Scansiona il cinturino o inserisci il codice per continuare", + "errorScanWatchRequired": "Scansiona l'orologio o inserisci il codice per continuare", + "errorAllFieldsRequired": "Compila tutti i campi", + "errorPinRequired": "Il PIN deve avere 6 cifre", + "errorSigningOperation": "Errore durante la firma dell'operazione", + "errorFirstNameRequired": "Il nome è obbligatorio", + "errorLastNameRequired": "Il cognome è obbligatorio", + "errorDocumentTypeRequired": "Il tipo di documento è obbligatorio", + "errorDocumentNumberRequired": "Il numero di documento è obbligatorio", + "errorAcceptTerms": "Devi accettare i termini e condizioni", + "errorBirthDateRequired": "Seleziona una data di nascita valida (GG/MM/AAAA)", + "errorRelationshipRequired": "Il rapporto di parentela è obbligatorio", + "errorPlaceOfBirthRequired": "Manca il luogo di nascita", + "errorBirthCountryRequired": "Manca il paese di nascita", + "errorAddressRequired": "Completa l'indirizzo", + "errorActivationCodeEmpty": "Il codice di attivazione è vuoto", + "errorWalletNotProvisioned": "Il portafoglio non è ancora provisionato", + "errorPinLength": "Il PIN deve avere {length} cifre", + "errorWalletConnectFirst": "Connetti prima il portafoglio" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/pt.json b/packages/sf_localizations/assets/l10n/pt.json index 8dbae710..e5356ace 100644 --- a/packages/sf_localizations/assets/l10n/pt.json +++ b/packages/sf_localizations/assets/l10n/pt.json @@ -148,7 +148,7 @@ "deviceSetup_linkInfo_item2_prefix": "Digitaliza o ", "deviceSetup_linkInfo_item2_boldWord": "relógio", "deviceSetup_linkInfo_item2_subtitle": "Poderás visualizar os gastos efetuados", - "deviceSetup_watchCode_orInsert": "Ou introduz o código do relógio", + "deviceSetup_watchCode_orInsert": "Ou introduz o código", "deviceSetup_watchCode_continueWithCode": "Continuar com código", "deviceSetup_linkTroubleshoot_title": "Se não conseguires vincular a pulseira ou o relógio", "deviceSetup_contactUs": "Contacta-nos", @@ -159,5 +159,24 @@ "deviceSetup_start": "Começar!", "deviceSetup_giveFirstAllowance": "Dá-lhe a primeira mesada", "deviceSetup_scanQr": "Digitalizar QR", - "deviceSetup_scanQr_hint": "Centraliza o QR dentro da moldura" + "deviceSetup_scanQr_hint": "Centraliza o QR dentro da moldura", + "errorScanStrapRequired": "Digitaliza a pulseira ou introduz o código para continuar", + "errorScanWatchRequired": "Digitaliza o relógio ou introduz o código para continuar", + "errorAllFieldsRequired": "Preenche todos os campos", + "errorPinRequired": "O PIN deve ter 6 dígitos", + "errorSigningOperation": "Erro ao assinar a operação", + "errorFirstNameRequired": "O nome é obrigatório", + "errorLastNameRequired": "O apelido é obrigatório", + "errorDocumentTypeRequired": "O tipo de documento é obrigatório", + "errorDocumentNumberRequired": "O número de documento é obrigatório", + "errorAcceptTerms": "Deves aceitar os termos e condições", + "errorBirthDateRequired": "Seleciona uma data de nascimento válida (DD/MM/AAAA)", + "errorRelationshipRequired": "O grau de parentesco é obrigatório", + "errorPlaceOfBirthRequired": "Falta o local de nascimento", + "errorBirthCountryRequired": "Falta o país de nascimento", + "errorAddressRequired": "Completa a morada", + "errorActivationCodeEmpty": "O código de ativação está vazio", + "errorWalletNotProvisioned": "A carteira ainda não está provisionada", + "errorPinLength": "O PIN deve ter {length} dígitos", + "errorWalletConnectFirst": "Conecta a carteira primeiro" } \ No newline at end of file diff --git a/packages/sf_localizations/lib/src/generated/i18n.dart b/packages/sf_localizations/lib/src/generated/i18n.dart index 91243e52..abb01534 100755 --- a/packages/sf_localizations/lib/src/generated/i18n.dart +++ b/packages/sf_localizations/lib/src/generated/i18n.dart @@ -207,4 +207,24 @@ class I18n { 'deviceSetup_giveFirstAllowance'; static const String deviceSetup_scanQr = 'deviceSetup_scanQr'; static const String deviceSetup_scanQr_hint = 'deviceSetup_scanQr_hint'; + static const String errorScanStrapRequired = 'errorScanStrapRequired'; + static const String errorScanWatchRequired = 'errorScanWatchRequired'; + static const String errorAllFieldsRequired = 'errorAllFieldsRequired'; + static const String errorPinRequired = 'errorPinRequired'; + static const String errorSigningOperation = 'errorSigningOperation'; + static const String errorFirstNameRequired = 'errorFirstNameRequired'; + static const String errorLastNameRequired = 'errorLastNameRequired'; + static const String errorDocumentTypeRequired = 'errorDocumentTypeRequired'; + static const String errorDocumentNumberRequired = + 'errorDocumentNumberRequired'; + static const String errorAcceptTerms = 'errorAcceptTerms'; + static const String errorBirthDateRequired = 'errorBirthDateRequired'; + static const String errorRelationshipRequired = 'errorRelationshipRequired'; + static const String errorPlaceOfBirthRequired = 'errorPlaceOfBirthRequired'; + static const String errorBirthCountryRequired = 'errorBirthCountryRequired'; + static const String errorAddressRequired = 'errorAddressRequired'; + static const String errorActivationCodeEmpty = 'errorActivationCodeEmpty'; + static const String errorWalletNotProvisioned = 'errorWalletNotProvisioned'; + static const String errorPinLength = 'errorPinLength'; + static const String errorWalletConnectFirst = 'errorWalletConnectFirst'; }