payment methods with HiPay, payment profile edited, allowance step in device setup, tio snackbar added and logout from profile settings
This commit is contained in:
@@ -40,6 +40,8 @@ abstract class AuthRemoteDatasource {
|
||||
|
||||
Future<void> createWallet();
|
||||
|
||||
Future<void> logout();
|
||||
|
||||
Future<ChildProfileResponseModel> createChildProfile({
|
||||
required String id,
|
||||
required String parentId,
|
||||
|
||||
@@ -265,6 +265,15 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
await _repository.post<void>('/auth/logout');
|
||||
} on DioException catch (error) {
|
||||
throw _mapDioError(error, defaultMessage: 'Error in logout');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ChildProfileResponseModel> createChildProfile({
|
||||
required String id,
|
||||
|
||||
@@ -103,6 +103,11 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
return _remote.recoverPassword(newPassword: newPassword, token: token);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout() {
|
||||
return _remote.logout();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ChildProfileEntity> createChildProfile({
|
||||
required String id,
|
||||
|
||||
@@ -43,6 +43,8 @@ abstract class AuthRepository {
|
||||
required String token,
|
||||
});
|
||||
|
||||
Future<void> logout();
|
||||
|
||||
Future<ChildProfileEntity> createChildProfile({
|
||||
required String id,
|
||||
required String parentId,
|
||||
|
||||
@@ -10,8 +10,8 @@ extension AddKidStepMapper on AddKidStep {
|
||||
return AddKidMainStep.linkDevice;
|
||||
case AddKidStep.profile:
|
||||
return AddKidMainStep.profile;
|
||||
case AddKidStep.success:
|
||||
return AddKidMainStep.success;
|
||||
case AddKidStep.allowance:
|
||||
return AddKidMainStep.allowance;
|
||||
case AddKidStep.intro:
|
||||
return AddKidMainStep.linkDevice;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:auth/src/features/device_setup/presentation/state/device_setup_v
|
||||
import 'package:auth/src/features/device_setup/presentation/enums/add_kid_main_step.dart';
|
||||
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/success_screen.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';
|
||||
@@ -10,6 +11,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/app_routes.dart';
|
||||
import 'package:navigation/navigation_contract.dart';
|
||||
import 'package:payments/payments.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class DeviceSetupScreen extends ConsumerWidget {
|
||||
@@ -24,15 +26,15 @@ class DeviceSetupScreen extends ConsumerWidget {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final mainStep = state.step.mainStep;
|
||||
|
||||
final isIntroOrSuccess =
|
||||
state.step == AddKidStep.intro || state.step == AddKidStep.success;
|
||||
final isIntro = state.step == AddKidStep.intro;
|
||||
final isAllowance = state.step == AddKidStep.allowance;
|
||||
|
||||
final canPopRoute = state.step == AddKidStep.intro;
|
||||
|
||||
return PopScope(
|
||||
canPop: canPopRoute,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (didPop) return;
|
||||
if (didPop || isAllowance) return;
|
||||
vm.back();
|
||||
},
|
||||
child: Scaffold(
|
||||
@@ -40,21 +42,24 @@ class DeviceSetupScreen extends ConsumerWidget {
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
if (isIntroOrSuccess)
|
||||
if (isIntro)
|
||||
const SizedBox(height: 24)
|
||||
else
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12, left: 8, right: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: vm.back,
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
tooltip: MaterialLocalizations.of(
|
||||
context,
|
||||
).backButtonTooltip,
|
||||
),
|
||||
if (isAllowance)
|
||||
const SizedBox(width: 48)
|
||||
else
|
||||
IconButton(
|
||||
onPressed: vm.back,
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
tooltip: MaterialLocalizations.of(
|
||||
context,
|
||||
).backButtonTooltip,
|
||||
),
|
||||
Expanded(
|
||||
child: StepIndicator(
|
||||
total: AddKidMainStep.values.length,
|
||||
@@ -75,24 +80,25 @@ class DeviceSetupScreen extends ConsumerWidget {
|
||||
),
|
||||
|
||||
FlowFooter(
|
||||
error: context.translate(state.errorMessage),
|
||||
primaryText: context.translate(primaryButtonText(state.step)),
|
||||
secondaryText: state.step == AddKidStep.success
|
||||
? context.translate(I18n.deviceSetup_addAnotherKid)
|
||||
: null,
|
||||
onPrimary: () {
|
||||
if (state.step == AddKidStep.success) {
|
||||
navigationContract.pushTo(AppRoutes.dashboardHome);
|
||||
return;
|
||||
}
|
||||
if (state.step == AddKidStep.profile) {
|
||||
if (!vm.validateProfile()) return;
|
||||
_pushScaPinScreen(context, ref);
|
||||
return;
|
||||
}
|
||||
if (state.step == AddKidStep.allowance) {
|
||||
_pushHiPayScreen(context, ref);
|
||||
return;
|
||||
}
|
||||
vm.next();
|
||||
},
|
||||
onSecondary: state.step == AddKidStep.success ? vm.resetForNewKid : null,
|
||||
secondaryText: isAllowance
|
||||
? context.translate(I18n.deviceSetup_skipAndConfigureLater)
|
||||
: null,
|
||||
onSecondary: isAllowance
|
||||
? () => navigationContract.pushTo(AppRoutes.dashboardHome)
|
||||
: null,
|
||||
theme: theme,
|
||||
),
|
||||
],
|
||||
@@ -102,6 +108,31 @@ class DeviceSetupScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pushHiPayScreen(BuildContext context, WidgetRef ref) async {
|
||||
final vm = ref.read(deviceSetupViewModelProvider.notifier);
|
||||
final result = await Navigator.of(context).push<HiPayResult>(
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
HiPayWebViewScreen(navigationContract: navigationContract),
|
||||
),
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
if (result == HiPayResult.success) {
|
||||
vm.setError('');
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: context.translate(I18n.deviceSetup_paymentSuccess),
|
||||
type: MessageType.success,
|
||||
);
|
||||
} else {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: context.translate(I18n.deviceSetup_paymentCancelled),
|
||||
type: MessageType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _pushScaPinScreen(BuildContext context, WidgetRef ref) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
@@ -114,7 +145,7 @@ class DeviceSetupScreen extends ConsumerWidget {
|
||||
switch (step) {
|
||||
case AddKidStep.intro:
|
||||
return I18n.deviceSetup_start;
|
||||
case AddKidStep.success:
|
||||
case AddKidStep.allowance:
|
||||
return I18n.deviceSetup_giveFirstAllowance;
|
||||
default:
|
||||
return I18n.continueKey;
|
||||
@@ -132,6 +163,19 @@ class _ScaPinScreen extends ConsumerWidget {
|
||||
final state = ref.watch(deviceSetupViewModelProvider);
|
||||
final vm = ref.read(deviceSetupViewModelProvider.notifier);
|
||||
|
||||
ref.listen(
|
||||
deviceSetupViewModelProvider.select((s) => s.errorMessage),
|
||||
(previous, next) {
|
||||
if (next.isNotEmpty) {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: context.translate(next),
|
||||
type: MessageType.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
@@ -140,21 +184,22 @@ class _ScaPinScreen extends ConsumerWidget {
|
||||
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,
|
||||
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);
|
||||
final ok = await vm.createChildProfile(pin: state.pin);
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
if (ok) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => SuccessScreen()),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1 +1 @@
|
||||
enum AddKidMainStep { linkDevice, profile, success }
|
||||
enum AddKidMainStep { linkDevice, profile, allowance }
|
||||
|
||||
@@ -1 +1 @@
|
||||
enum AddKidStep { intro, linkInfo, scanStrap, scanWatch, profile, success }
|
||||
enum AddKidStep { intro, linkInfo, scanStrap, scanWatch, profile, allowance }
|
||||
|
||||
@@ -29,10 +29,11 @@ class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
late final TextEditingController addressController;
|
||||
late final TextEditingController strapCodeController;
|
||||
late final TextEditingController watchCodeController;
|
||||
late final TextEditingController allowanceAmountController;
|
||||
|
||||
@override
|
||||
DeviceSetupViewState build() {
|
||||
final initial = DeviceSetupViewState(id: const Uuid().v4());
|
||||
final initial = DeviceSetupViewState(id: const Uuid().v4(), step: AddKidStep.allowance); // TODO: revert to default (intro)
|
||||
_initControllers(initial);
|
||||
_addListeners();
|
||||
|
||||
@@ -55,6 +56,7 @@ class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
addressController = TextEditingController(text: s.address);
|
||||
strapCodeController = TextEditingController(text: s.strapCode);
|
||||
watchCodeController = TextEditingController(text: s.watchCode);
|
||||
allowanceAmountController = TextEditingController(text: s.allowanceAmount);
|
||||
}
|
||||
|
||||
void _addListeners() {
|
||||
@@ -65,6 +67,7 @@ class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
|
||||
strapCodeController.addListener(_onStrapCodeChanged);
|
||||
watchCodeController.addListener(_onWatchCodeChanged);
|
||||
allowanceAmountController.addListener(_onAllowanceAmountChanged);
|
||||
}
|
||||
|
||||
void next() {
|
||||
@@ -93,7 +96,7 @@ class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
return;
|
||||
case AddKidStep.profile:
|
||||
return;
|
||||
case AddKidStep.success:
|
||||
case AddKidStep.allowance:
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -115,7 +118,7 @@ class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
case AddKidStep.profile:
|
||||
state = state.copyWith(step: AddKidStep.scanWatch);
|
||||
return;
|
||||
case AddKidStep.success:
|
||||
case AddKidStep.allowance:
|
||||
state = state.copyWith(step: AddKidStep.profile);
|
||||
return;
|
||||
}
|
||||
@@ -193,7 +196,6 @@ class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
step: AddKidStep.success,
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -295,6 +297,20 @@ class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
state = state.copyWith(watchCode: text, errorMessage: '');
|
||||
}
|
||||
|
||||
void _onAllowanceAmountChanged() {
|
||||
final text = allowanceAmountController.text;
|
||||
if (text == state.allowanceAmount) return;
|
||||
state = state.copyWith(allowanceAmount: text, errorMessage: '');
|
||||
}
|
||||
|
||||
void setError(String message) {
|
||||
state = state.copyWith(errorMessage: message);
|
||||
}
|
||||
|
||||
void goToAllowance() {
|
||||
state = state.copyWith(step: AddKidStep.allowance, errorMessage: '');
|
||||
}
|
||||
|
||||
void onGenrerChanged(String? value) {
|
||||
final v = value ?? '';
|
||||
if (v == state.genrer) return;
|
||||
@@ -403,6 +419,7 @@ class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
addressController.clear();
|
||||
strapCodeController.clear();
|
||||
watchCodeController.clear();
|
||||
allowanceAmountController.clear();
|
||||
|
||||
state = DeviceSetupViewState(id: const Uuid().v4());
|
||||
}
|
||||
|
||||
@@ -28,5 +28,7 @@ abstract class DeviceSetupViewState with _$DeviceSetupViewState {
|
||||
@Default('') String pin,
|
||||
@Default(false) bool isSigning,
|
||||
@Default('') String lastSignature,
|
||||
|
||||
@Default('') String allowanceAmount,
|
||||
}) = _AddKidFlowState;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$DeviceSetupViewState {
|
||||
|
||||
AddKidStep get step; String get id; String get parentId; String get firstName; String get lastName; DateTime? get bornAt; String get address; String get genrer; String get relationType; String get strapQr; String get strapCode; String get watchQr; String get watchCode; bool get isLoading; String get errorMessage; bool get isSuccess; String get pin; bool get isSigning; String get lastSignature;
|
||||
AddKidStep get step; String get id; String get parentId; String get firstName; String get lastName; DateTime? get bornAt; String get address; String get genrer; String get relationType; String get strapQr; String get strapCode; String get watchQr; String get watchCode; bool get isLoading; String get errorMessage; bool get isSuccess; String get pin; bool get isSigning; String get lastSignature; String get allowanceAmount;
|
||||
/// Create a copy of DeviceSetupViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -25,16 +25,16 @@ $DeviceSetupViewStateCopyWith<DeviceSetupViewState> get copyWith => _$DeviceSetu
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is DeviceSetupViewState&&(identical(other.step, step) || other.step == step)&&(identical(other.id, id) || other.id == id)&&(identical(other.parentId, parentId) || other.parentId == parentId)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bornAt, bornAt) || other.bornAt == bornAt)&&(identical(other.address, address) || other.address == address)&&(identical(other.genrer, genrer) || other.genrer == genrer)&&(identical(other.relationType, relationType) || other.relationType == relationType)&&(identical(other.strapQr, strapQr) || other.strapQr == strapQr)&&(identical(other.strapCode, strapCode) || other.strapCode == strapCode)&&(identical(other.watchQr, watchQr) || other.watchQr == watchQr)&&(identical(other.watchCode, watchCode) || other.watchCode == watchCode)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isSuccess, isSuccess) || other.isSuccess == isSuccess)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.lastSignature, lastSignature) || other.lastSignature == lastSignature));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is DeviceSetupViewState&&(identical(other.step, step) || other.step == step)&&(identical(other.id, id) || other.id == id)&&(identical(other.parentId, parentId) || other.parentId == parentId)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bornAt, bornAt) || other.bornAt == bornAt)&&(identical(other.address, address) || other.address == address)&&(identical(other.genrer, genrer) || other.genrer == genrer)&&(identical(other.relationType, relationType) || other.relationType == relationType)&&(identical(other.strapQr, strapQr) || other.strapQr == strapQr)&&(identical(other.strapCode, strapCode) || other.strapCode == strapCode)&&(identical(other.watchQr, watchQr) || other.watchQr == watchQr)&&(identical(other.watchCode, watchCode) || other.watchCode == watchCode)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isSuccess, isSuccess) || other.isSuccess == isSuccess)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.lastSignature, lastSignature) || other.lastSignature == lastSignature)&&(identical(other.allowanceAmount, allowanceAmount) || other.allowanceAmount == allowanceAmount));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,step,id,parentId,firstName,lastName,bornAt,address,genrer,relationType,strapQr,strapCode,watchQr,watchCode,isLoading,errorMessage,isSuccess,pin,isSigning,lastSignature]);
|
||||
int get hashCode => Object.hashAll([runtimeType,step,id,parentId,firstName,lastName,bornAt,address,genrer,relationType,strapQr,strapCode,watchQr,watchCode,isLoading,errorMessage,isSuccess,pin,isSigning,lastSignature,allowanceAmount]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DeviceSetupViewState(step: $step, id: $id, parentId: $parentId, firstName: $firstName, lastName: $lastName, bornAt: $bornAt, address: $address, genrer: $genrer, relationType: $relationType, strapQr: $strapQr, strapCode: $strapCode, watchQr: $watchQr, watchCode: $watchCode, isLoading: $isLoading, errorMessage: $errorMessage, isSuccess: $isSuccess, pin: $pin, isSigning: $isSigning, lastSignature: $lastSignature)';
|
||||
return 'DeviceSetupViewState(step: $step, id: $id, parentId: $parentId, firstName: $firstName, lastName: $lastName, bornAt: $bornAt, address: $address, genrer: $genrer, relationType: $relationType, strapQr: $strapQr, strapCode: $strapCode, watchQr: $watchQr, watchCode: $watchCode, isLoading: $isLoading, errorMessage: $errorMessage, isSuccess: $isSuccess, pin: $pin, isSigning: $isSigning, lastSignature: $lastSignature, allowanceAmount: $allowanceAmount)';
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ abstract mixin class $DeviceSetupViewStateCopyWith<$Res> {
|
||||
factory $DeviceSetupViewStateCopyWith(DeviceSetupViewState value, $Res Function(DeviceSetupViewState) _then) = _$DeviceSetupViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature
|
||||
AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature, String allowanceAmount
|
||||
});
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class _$DeviceSetupViewStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of DeviceSetupViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? step = null,Object? id = null,Object? parentId = null,Object? firstName = null,Object? lastName = null,Object? bornAt = freezed,Object? address = null,Object? genrer = null,Object? relationType = null,Object? strapQr = null,Object? strapCode = null,Object? watchQr = null,Object? watchCode = null,Object? isLoading = null,Object? errorMessage = null,Object? isSuccess = null,Object? pin = null,Object? isSigning = null,Object? lastSignature = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? step = null,Object? id = null,Object? parentId = null,Object? firstName = null,Object? lastName = null,Object? bornAt = freezed,Object? address = null,Object? genrer = null,Object? relationType = null,Object? strapQr = null,Object? strapCode = null,Object? watchQr = null,Object? watchCode = null,Object? isLoading = null,Object? errorMessage = null,Object? isSuccess = null,Object? pin = null,Object? isSigning = null,Object? lastSignature = null,Object? allowanceAmount = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable
|
||||
as AddKidStep,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
@@ -83,6 +83,7 @@ as String,isSuccess: null == isSuccess ? _self.isSuccess : isSuccess // ignore:
|
||||
as bool,pin: null == pin ? _self.pin : pin // ignore: cast_nullable_to_non_nullable
|
||||
as String,isSigning: null == isSigning ? _self.isSigning : isSigning // ignore: cast_nullable_to_non_nullable
|
||||
as bool,lastSignature: null == lastSignature ? _self.lastSignature : lastSignature // ignore: cast_nullable_to_non_nullable
|
||||
as String,allowanceAmount: null == allowanceAmount ? _self.allowanceAmount : allowanceAmount // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
@@ -168,10 +169,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature, String allowanceAmount)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState() when $default != null:
|
||||
return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.genrer,_that.relationType,_that.strapQr,_that.strapCode,_that.watchQr,_that.watchCode,_that.isLoading,_that.errorMessage,_that.isSuccess,_that.pin,_that.isSigning,_that.lastSignature);case _:
|
||||
return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.genrer,_that.relationType,_that.strapQr,_that.strapCode,_that.watchQr,_that.watchCode,_that.isLoading,_that.errorMessage,_that.isSuccess,_that.pin,_that.isSigning,_that.lastSignature,_that.allowanceAmount);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -189,10 +190,10 @@ return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastNam
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature, String allowanceAmount) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState():
|
||||
return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.genrer,_that.relationType,_that.strapQr,_that.strapCode,_that.watchQr,_that.watchCode,_that.isLoading,_that.errorMessage,_that.isSuccess,_that.pin,_that.isSigning,_that.lastSignature);case _:
|
||||
return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.genrer,_that.relationType,_that.strapQr,_that.strapCode,_that.watchQr,_that.watchCode,_that.isLoading,_that.errorMessage,_that.isSuccess,_that.pin,_that.isSigning,_that.lastSignature,_that.allowanceAmount);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -209,10 +210,10 @@ return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastNam
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature, String allowanceAmount)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState() when $default != null:
|
||||
return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.genrer,_that.relationType,_that.strapQr,_that.strapCode,_that.watchQr,_that.watchCode,_that.isLoading,_that.errorMessage,_that.isSuccess,_that.pin,_that.isSigning,_that.lastSignature);case _:
|
||||
return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.genrer,_that.relationType,_that.strapQr,_that.strapCode,_that.watchQr,_that.watchCode,_that.isLoading,_that.errorMessage,_that.isSuccess,_that.pin,_that.isSigning,_that.lastSignature,_that.allowanceAmount);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -224,7 +225,7 @@ return $default(_that.step,_that.id,_that.parentId,_that.firstName,_that.lastNam
|
||||
|
||||
|
||||
class _AddKidFlowState implements DeviceSetupViewState {
|
||||
const _AddKidFlowState({this.step = AddKidStep.intro, this.id = '', this.parentId = '', this.firstName = '', this.lastName = '', this.bornAt, this.address = '', this.genrer = '', this.relationType = '', this.strapQr = '', this.strapCode = '', this.watchQr = '', this.watchCode = '', this.isLoading = false, this.errorMessage = '', this.isSuccess = false, this.pin = '', this.isSigning = false, this.lastSignature = ''});
|
||||
const _AddKidFlowState({this.step = AddKidStep.intro, this.id = '', this.parentId = '', this.firstName = '', this.lastName = '', this.bornAt, this.address = '', this.genrer = '', this.relationType = '', this.strapQr = '', this.strapCode = '', this.watchQr = '', this.watchCode = '', this.isLoading = false, this.errorMessage = '', this.isSuccess = false, this.pin = '', this.isSigning = false, this.lastSignature = '', this.allowanceAmount = ''});
|
||||
|
||||
|
||||
@override@JsonKey() final AddKidStep step;
|
||||
@@ -246,6 +247,7 @@ class _AddKidFlowState implements DeviceSetupViewState {
|
||||
@override@JsonKey() final String pin;
|
||||
@override@JsonKey() final bool isSigning;
|
||||
@override@JsonKey() final String lastSignature;
|
||||
@override@JsonKey() final String allowanceAmount;
|
||||
|
||||
/// Create a copy of DeviceSetupViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -257,16 +259,16 @@ _$AddKidFlowStateCopyWith<_AddKidFlowState> get copyWith => __$AddKidFlowStateCo
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AddKidFlowState&&(identical(other.step, step) || other.step == step)&&(identical(other.id, id) || other.id == id)&&(identical(other.parentId, parentId) || other.parentId == parentId)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bornAt, bornAt) || other.bornAt == bornAt)&&(identical(other.address, address) || other.address == address)&&(identical(other.genrer, genrer) || other.genrer == genrer)&&(identical(other.relationType, relationType) || other.relationType == relationType)&&(identical(other.strapQr, strapQr) || other.strapQr == strapQr)&&(identical(other.strapCode, strapCode) || other.strapCode == strapCode)&&(identical(other.watchQr, watchQr) || other.watchQr == watchQr)&&(identical(other.watchCode, watchCode) || other.watchCode == watchCode)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isSuccess, isSuccess) || other.isSuccess == isSuccess)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.lastSignature, lastSignature) || other.lastSignature == lastSignature));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AddKidFlowState&&(identical(other.step, step) || other.step == step)&&(identical(other.id, id) || other.id == id)&&(identical(other.parentId, parentId) || other.parentId == parentId)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bornAt, bornAt) || other.bornAt == bornAt)&&(identical(other.address, address) || other.address == address)&&(identical(other.genrer, genrer) || other.genrer == genrer)&&(identical(other.relationType, relationType) || other.relationType == relationType)&&(identical(other.strapQr, strapQr) || other.strapQr == strapQr)&&(identical(other.strapCode, strapCode) || other.strapCode == strapCode)&&(identical(other.watchQr, watchQr) || other.watchQr == watchQr)&&(identical(other.watchCode, watchCode) || other.watchCode == watchCode)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isSuccess, isSuccess) || other.isSuccess == isSuccess)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.lastSignature, lastSignature) || other.lastSignature == lastSignature)&&(identical(other.allowanceAmount, allowanceAmount) || other.allowanceAmount == allowanceAmount));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,step,id,parentId,firstName,lastName,bornAt,address,genrer,relationType,strapQr,strapCode,watchQr,watchCode,isLoading,errorMessage,isSuccess,pin,isSigning,lastSignature]);
|
||||
int get hashCode => Object.hashAll([runtimeType,step,id,parentId,firstName,lastName,bornAt,address,genrer,relationType,strapQr,strapCode,watchQr,watchCode,isLoading,errorMessage,isSuccess,pin,isSigning,lastSignature,allowanceAmount]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DeviceSetupViewState(step: $step, id: $id, parentId: $parentId, firstName: $firstName, lastName: $lastName, bornAt: $bornAt, address: $address, genrer: $genrer, relationType: $relationType, strapQr: $strapQr, strapCode: $strapCode, watchQr: $watchQr, watchCode: $watchCode, isLoading: $isLoading, errorMessage: $errorMessage, isSuccess: $isSuccess, pin: $pin, isSigning: $isSigning, lastSignature: $lastSignature)';
|
||||
return 'DeviceSetupViewState(step: $step, id: $id, parentId: $parentId, firstName: $firstName, lastName: $lastName, bornAt: $bornAt, address: $address, genrer: $genrer, relationType: $relationType, strapQr: $strapQr, strapCode: $strapCode, watchQr: $watchQr, watchCode: $watchCode, isLoading: $isLoading, errorMessage: $errorMessage, isSuccess: $isSuccess, pin: $pin, isSigning: $isSigning, lastSignature: $lastSignature, allowanceAmount: $allowanceAmount)';
|
||||
}
|
||||
|
||||
|
||||
@@ -277,7 +279,7 @@ abstract mixin class _$AddKidFlowStateCopyWith<$Res> implements $DeviceSetupView
|
||||
factory _$AddKidFlowStateCopyWith(_AddKidFlowState value, $Res Function(_AddKidFlowState) _then) = __$AddKidFlowStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature
|
||||
AddKidStep step, String id, String parentId, String firstName, String lastName, DateTime? bornAt, String address, String genrer, String relationType, String strapQr, String strapCode, String watchQr, String watchCode, bool isLoading, String errorMessage, bool isSuccess, String pin, bool isSigning, String lastSignature, String allowanceAmount
|
||||
});
|
||||
|
||||
|
||||
@@ -294,7 +296,7 @@ class __$AddKidFlowStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of DeviceSetupViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? step = null,Object? id = null,Object? parentId = null,Object? firstName = null,Object? lastName = null,Object? bornAt = freezed,Object? address = null,Object? genrer = null,Object? relationType = null,Object? strapQr = null,Object? strapCode = null,Object? watchQr = null,Object? watchCode = null,Object? isLoading = null,Object? errorMessage = null,Object? isSuccess = null,Object? pin = null,Object? isSigning = null,Object? lastSignature = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? step = null,Object? id = null,Object? parentId = null,Object? firstName = null,Object? lastName = null,Object? bornAt = freezed,Object? address = null,Object? genrer = null,Object? relationType = null,Object? strapQr = null,Object? strapCode = null,Object? watchQr = null,Object? watchCode = null,Object? isLoading = null,Object? errorMessage = null,Object? isSuccess = null,Object? pin = null,Object? isSigning = null,Object? lastSignature = null,Object? allowanceAmount = null,}) {
|
||||
return _then(_AddKidFlowState(
|
||||
step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable
|
||||
as AddKidStep,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
@@ -315,6 +317,7 @@ as String,isSuccess: null == isSuccess ? _self.isSuccess : isSuccess // ignore:
|
||||
as bool,pin: null == pin ? _self.pin : pin // ignore: cast_nullable_to_non_nullable
|
||||
as String,isSigning: null == isSigning ? _self.isSigning : isSigning // ignore: cast_nullable_to_non_nullable
|
||||
as bool,lastSignature: null == lastSignature ? _self.lastSignature : lastSignature // ignore: cast_nullable_to_non_nullable
|
||||
as String,allowanceAmount: null == allowanceAmount ? _self.allowanceAmount : allowanceAmount // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'package:auth/src/features/device_setup/presentation/enums/add_kid_step.d
|
||||
import 'package:auth/src/features/device_setup/presentation/enums/scan_link_step.dart';
|
||||
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/allowance_step.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/steps/profile_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';
|
||||
|
||||
class StepBody extends StatelessWidget {
|
||||
@@ -25,8 +25,8 @@ class StepBody extends StatelessWidget {
|
||||
return ScanStrapAndWatchStepScreen(step: ScanLinkStep.watch);
|
||||
case AddKidStep.profile:
|
||||
return ProfileStepScreen();
|
||||
case AddKidStep.success:
|
||||
return SuccessStepScreen();
|
||||
case AddKidStep.allowance:
|
||||
return AllowanceStepScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/state/device_setup_view_model.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class AllowanceStepScreen extends ConsumerWidget {
|
||||
const AllowanceStepScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final vm = ref.read(deviceSetupViewModelProvider.notifier);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
'¡Dale su primera paga!',
|
||||
style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'Enséñales a gestionar su dinero recargando su reloj',
|
||||
style: const TextStyle(fontSize: 18),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
CustomTextField(
|
||||
label: 'Cantidad de dinero de la paga',
|
||||
hint: '0',
|
||||
controller: vm.allowanceAmountController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Cantidad mínima: 10 euros',
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Por seguridad sólo se puede disponer de un máximo de 150€ por wallet',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ class ProfileStepScreen extends ConsumerWidget {
|
||||
'OTHER': 'Otro',
|
||||
};
|
||||
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final state = ref.watch(deviceSetupViewModelProvider);
|
||||
final vm = ref.read(deviceSetupViewModelProvider.notifier);
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/state/device_setup_view_model.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/steps/success_step.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/widgets/flow_footer.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class SuccessScreen extends ConsumerWidget {
|
||||
const SuccessScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final vm = ref.read(deviceSetupViewModelProvider.notifier);
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
Expanded(child: SuccessStepScreen()),
|
||||
FlowFooter(
|
||||
primaryText: context.translate(
|
||||
I18n.deviceSetup_giveFirstAllowance,
|
||||
),
|
||||
onPrimary: () {
|
||||
vm.goToAllowance();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
secondaryText: context.translate(I18n.deviceSetup_addAnotherKid),
|
||||
onSecondary: () {
|
||||
vm.resetForNewKid();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
theme: theme,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ class FlowFooter extends StatelessWidget {
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 10),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
||||
@@ -23,9 +23,11 @@ class LoginScreen extends ConsumerWidget {
|
||||
final state = ref.read(loginViewModelProvider);
|
||||
|
||||
if (state.errorMessage.isNotEmpty) {
|
||||
ScaffoldMessenger.of(
|
||||
showTopSnackbar(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(state.errorMessage)));
|
||||
message: state.errorMessage,
|
||||
type: MessageType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,9 +76,11 @@ class LoginScreen extends ConsumerWidget {
|
||||
if (!ok) {
|
||||
final state = ref.read(loginViewModelProvider);
|
||||
if (state.errorMessage.isNotEmpty) {
|
||||
ScaffoldMessenger.of(
|
||||
showTopSnackbar(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(state.errorMessage)));
|
||||
message: state.errorMessage,
|
||||
type: MessageType.error,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -87,14 +91,12 @@ class LoginScreen extends ConsumerWidget {
|
||||
|
||||
if (user == null) {
|
||||
final state = ref.read(loginViewModelProvider);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
state.errorMessage.isEmpty
|
||||
? 'Error getting user info'
|
||||
: state.errorMessage,
|
||||
),
|
||||
),
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: state.errorMessage.isEmpty
|
||||
? 'Error getting user info'
|
||||
: state.errorMessage,
|
||||
type: MessageType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -286,9 +288,6 @@ class _SignInSection extends ConsumerWidget {
|
||||
final bool isLoading = ref.watch(
|
||||
loginViewModelProvider.select((s) => s.isLoading),
|
||||
);
|
||||
final String errorMessage = ref.watch(
|
||||
loginViewModelProvider.select((s) => s.errorMessage),
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
@@ -308,18 +307,6 @@ class _SignInSection extends ConsumerWidget {
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Text(
|
||||
errorMessage,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/app_routes.dart';
|
||||
@@ -18,6 +19,19 @@ class SCATreezorScreen extends ConsumerWidget {
|
||||
final SCATreezorViewState state = ref.watch(scaTreezorViewModelProvider);
|
||||
final vm = ref.read(scaTreezorViewModelProvider.notifier);
|
||||
|
||||
ref.listen(
|
||||
scaTreezorViewModelProvider.select((s) => s.errorMessage),
|
||||
(previous, next) {
|
||||
if (next.isNotEmpty) {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: context.translate(next),
|
||||
type: MessageType.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
@@ -37,9 +51,6 @@ class SCATreezorScreen extends ConsumerWidget {
|
||||
: '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,
|
||||
@@ -70,14 +81,6 @@ 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),
|
||||
|
||||
@@ -40,6 +40,19 @@ class SignupScreen extends ConsumerWidget {
|
||||
final vm = ref.read(signUpViewModelProvider.notifier);
|
||||
final state = ref.watch(signUpViewModelProvider);
|
||||
|
||||
ref.listen(
|
||||
signUpViewModelProvider.select((s) => s.errorMessage),
|
||||
(previous, next) {
|
||||
if (next.isNotEmpty) {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: context.translate(next),
|
||||
type: MessageType.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
final steps = signUpSteps(context);
|
||||
final index = state.currentIndex.clamp(0, steps.length - 1);
|
||||
final step = steps[index];
|
||||
@@ -55,7 +68,6 @@ class SignupScreen extends ConsumerWidget {
|
||||
currentStep: index + 1,
|
||||
numSteps: steps.length,
|
||||
body: step.bodyBuilder(context, ref),
|
||||
errorMessage: context.translate(state.errorMessage),
|
||||
onBackPressed: state.currentIndex == 0
|
||||
? navigationContract.goBack
|
||||
: vm.back,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:auth/src/widgets/form_error_banner.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -15,8 +14,6 @@ class SignUpLayout extends StatelessWidget {
|
||||
final VoidCallback onBackPressed;
|
||||
final VoidCallback onNextPressed;
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
const SignUpLayout({
|
||||
super.key,
|
||||
required this.theme,
|
||||
@@ -28,7 +25,6 @@ class SignUpLayout extends StatelessWidget {
|
||||
required this.body,
|
||||
required this.onBackPressed,
|
||||
required this.onNextPressed,
|
||||
this.errorMessage = '',
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -75,12 +71,7 @@ class SignUpLayout extends StatelessWidget {
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
FormErrorBanner(message: errorMessage),
|
||||
body,
|
||||
],
|
||||
),
|
||||
child: body,
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ dependencies:
|
||||
path: ../../packages/sf_shared
|
||||
sca_treezor:
|
||||
path: ../../packages/sca_treezor
|
||||
payments:
|
||||
path: ../../packages/payments
|
||||
#dependencies go here
|
||||
flutter_svg: ^2.2.1
|
||||
get_it: ^9.0.5
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# melos_managed_dependency_overrides: dashboard_shell,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure,flutter_treezor_entrust_sdk_bridge,sca_treezor
|
||||
# melos_managed_dependency_overrides: dashboard_shell,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure,flutter_treezor_entrust_sdk_bridge,sca_treezor,payments
|
||||
dependency_overrides:
|
||||
dashboard_shell:
|
||||
path: ../dashboard_shell
|
||||
@@ -14,6 +14,8 @@ dependency_overrides:
|
||||
path: ../../packages/navigation
|
||||
notifications:
|
||||
path: ../notifications
|
||||
payments:
|
||||
path: ../../packages/payments
|
||||
profile:
|
||||
path: ../profile
|
||||
sca_treezor:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# melos_managed_dependency_overrides: auth,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure,flutter_treezor_entrust_sdk_bridge,sca_treezor
|
||||
# melos_managed_dependency_overrides: auth,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure,flutter_treezor_entrust_sdk_bridge,sca_treezor,payments
|
||||
dependency_overrides:
|
||||
auth:
|
||||
path: ../auth
|
||||
@@ -14,6 +14,8 @@ dependency_overrides:
|
||||
path: ../../packages/navigation
|
||||
notifications:
|
||||
path: ../notifications
|
||||
payments:
|
||||
path: ../../packages/payments
|
||||
profile:
|
||||
path: ../profile
|
||||
sca_treezor:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# melos_managed_dependency_overrides: auth,dashboard_shell,design_system,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure,flutter_treezor_entrust_sdk_bridge,sca_treezor
|
||||
# melos_managed_dependency_overrides: auth,dashboard_shell,design_system,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure,flutter_treezor_entrust_sdk_bridge,sca_treezor,payments
|
||||
dependency_overrides:
|
||||
auth:
|
||||
path: ../auth
|
||||
@@ -14,6 +14,8 @@ dependency_overrides:
|
||||
path: ../../packages/navigation
|
||||
notifications:
|
||||
path: ../notifications
|
||||
payments:
|
||||
path: ../../packages/payments
|
||||
profile:
|
||||
path: ../profile
|
||||
sca_treezor:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export 'src/presentation/profile_screen.dart';
|
||||
export 'src/profile_builder.dart';
|
||||
export 'src/profile_settings_builder.dart';
|
||||
export 'src/features/payment_methods/presentation/payment_methods_builder.dart';
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:payments/payments.dart';
|
||||
|
||||
abstract class DeletePaymentCardUseCase {
|
||||
Future<void> call(String topupCardId);
|
||||
}
|
||||
|
||||
class DeletePaymentCardUseCaseImpl implements DeletePaymentCardUseCase {
|
||||
DeletePaymentCardUseCaseImpl(this._repository);
|
||||
|
||||
final HiPayRepository _repository;
|
||||
|
||||
@override
|
||||
Future<void> call(String topupCardId) {
|
||||
return _repository.deleteTopupCard(topupCardId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:payments/payments.dart';
|
||||
|
||||
abstract class GetPaymentCardsUseCase {
|
||||
Future<List<PaymentCardEntity>> call();
|
||||
}
|
||||
|
||||
class GetPaymentCardsUseCaseImpl implements GetPaymentCardsUseCase {
|
||||
GetPaymentCardsUseCaseImpl(this._repository);
|
||||
|
||||
final HiPayRepository _repository;
|
||||
|
||||
@override
|
||||
Future<List<PaymentCardEntity>> call() {
|
||||
return _repository.getTopupCards();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:profile/src/features/payment_methods/presentation/payment_methods_screen.dart';
|
||||
|
||||
class PaymentMethodsBuilder {
|
||||
const PaymentMethodsBuilder();
|
||||
|
||||
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||
final navigationContract = GetIt.I<NavigationContract>();
|
||||
return MaterialPage(
|
||||
key: state.pageKey,
|
||||
child: PaymentMethodsScreen(navigationContract: navigationContract),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:payments/payments.dart';
|
||||
import 'package:profile/src/features/payment_methods/presentation/payment_methods_view_model.dart';
|
||||
import 'package:profile/src/features/payment_methods/presentation/payment_methods_view_state.dart';
|
||||
|
||||
class PaymentMethodsScreen extends ConsumerWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const PaymentMethodsScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final viewState = ref.watch(paymentMethodsViewModelProvider);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||
appBar: AppBar(
|
||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
title: Text(
|
||||
'Métodos de pago',
|
||||
style: TextStyle(
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
Icons.arrow_back,
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
),
|
||||
onPressed: () => navigationContract.goBack(),
|
||||
),
|
||||
),
|
||||
body: _buildBody(context, ref, theme, viewState),
|
||||
),
|
||||
if (viewState.isDeleting)
|
||||
Container(
|
||||
color: Colors.black26,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ThemePort theme,
|
||||
PaymentMethodsViewState viewState,
|
||||
) {
|
||||
if (viewState.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (viewState.errorMessage.isNotEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Error al cargar las tarjetas',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
viewState.errorMessage,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextButton(
|
||||
onPressed: () => ref
|
||||
.read(paymentMethodsViewModelProvider.notifier)
|
||||
.loadCards(),
|
||||
child: const Text('Reintentar'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return _buildContent(context, ref, theme, viewState.cards);
|
||||
}
|
||||
|
||||
Widget _buildContent(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ThemePort theme,
|
||||
List<PaymentCardEntity> cards,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: cards.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
'No tienes tarjetas registradas',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.separated(
|
||||
padding: const EdgeInsets.all(20),
|
||||
itemCount: cards.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) =>
|
||||
_buildCardTile(context, ref, theme, cards[index]),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
),
|
||||
child: PrimaryButton(
|
||||
onPressed: () => _addCard(context, ref),
|
||||
text: 'Agregar tarjeta',
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCardTile(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ThemePort theme,
|
||||
PaymentCardEntity card,
|
||||
) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_brandIcon(card.brand),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
card.maskedPan,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
card.cardHolder,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${card.cardExpiryMonth}/${card.cardExpiryYear}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.delete_outline,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
onPressed: () => _confirmDelete(context, ref, card),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _brandIcon(String brand) {
|
||||
switch (brand.toLowerCase()) {
|
||||
case 'visa':
|
||||
return SvgPicture.asset(
|
||||
'assets/images/ui/visa.svg',
|
||||
width: 28,
|
||||
height: 12,
|
||||
);
|
||||
case 'mastercard':
|
||||
return SvgPicture.asset(
|
||||
'assets/images/ui/mastercard.svg',
|
||||
width: 40,
|
||||
height: 28,
|
||||
);
|
||||
default:
|
||||
return const Icon(Icons.credit_card, size: 36);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _confirmDelete(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
PaymentCardEntity card,
|
||||
) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Eliminar tarjeta'),
|
||||
content: Text('¿Deseas eliminar la tarjeta ${card.maskedPan}?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Cancelar'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('Eliminar'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
if (!context.mounted) return;
|
||||
|
||||
final success = await ref
|
||||
.read(paymentMethodsViewModelProvider.notifier)
|
||||
.deleteCard(card.topupCardId);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (success) {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: 'Tarjeta eliminada correctamente',
|
||||
type: MessageType.success,
|
||||
);
|
||||
} else {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: 'Error al eliminar la tarjeta',
|
||||
type: MessageType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addCard(BuildContext context, WidgetRef ref) async {
|
||||
final result = await Navigator.of(context).push<HiPayResult>(
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
HiPayWebViewScreen(navigationContract: navigationContract),
|
||||
),
|
||||
);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (result == HiPayResult.success) {
|
||||
ref.read(paymentMethodsViewModelProvider.notifier).loadCards();
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: 'Tarjeta agregada correctamente',
|
||||
type: MessageType.success,
|
||||
);
|
||||
} else if (result == HiPayResult.cancelled) {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: 'Se canceló el registro de la tarjeta',
|
||||
type: MessageType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:profile/src/features/payment_methods/domain/use_cases/delete_payment_card_use_case.dart';
|
||||
import 'package:profile/src/features/payment_methods/domain/use_cases/get_payment_cards_use_case.dart';
|
||||
import 'package:profile/src/features/payment_methods/presentation/payment_methods_view_state.dart';
|
||||
import 'package:profile/src/features/payment_methods/providers/payment_methods_providers.dart';
|
||||
|
||||
final paymentMethodsViewModelProvider =
|
||||
NotifierProvider.autoDispose<
|
||||
PaymentMethodsViewModel,
|
||||
PaymentMethodsViewState
|
||||
>(PaymentMethodsViewModel.new);
|
||||
|
||||
class PaymentMethodsViewModel extends Notifier<PaymentMethodsViewState> {
|
||||
late final GetPaymentCardsUseCase _getPaymentCardsUseCase;
|
||||
late final DeletePaymentCardUseCase _deletePaymentCardUseCase;
|
||||
|
||||
@override
|
||||
PaymentMethodsViewState build() {
|
||||
_getPaymentCardsUseCase = ref.read(getPaymentCardsUseCaseProvider);
|
||||
_deletePaymentCardUseCase = ref.read(deletePaymentCardUseCaseProvider);
|
||||
Future.microtask(() => loadCards());
|
||||
return const PaymentMethodsViewState(isLoading: true);
|
||||
}
|
||||
|
||||
Future<void> loadCards() async {
|
||||
state = state.copyWith(isLoading: true, errorMessage: '');
|
||||
|
||||
try {
|
||||
final cards = await _getPaymentCardsUseCase();
|
||||
|
||||
if (!ref.mounted) return;
|
||||
|
||||
final validated = cards
|
||||
.where((c) => c.status.toLowerCase() == 'validated')
|
||||
.toList();
|
||||
|
||||
state = state.copyWith(isLoading: false, cards: validated);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
|
||||
state = state.copyWith(isLoading: false, errorMessage: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> deleteCard(String topupCardId) async {
|
||||
state = state.copyWith(isDeleting: true);
|
||||
|
||||
try {
|
||||
await _deletePaymentCardUseCase(topupCardId);
|
||||
|
||||
if (!ref.mounted) return false;
|
||||
|
||||
final updatedCards = state.cards
|
||||
.where((c) => c.topupCardId != topupCardId)
|
||||
.toList();
|
||||
|
||||
state = state.copyWith(isDeleting: false, cards: updatedCards);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return false;
|
||||
|
||||
state = state.copyWith(isDeleting: false, errorMessage: e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:payments/payments.dart';
|
||||
|
||||
class PaymentMethodsViewState {
|
||||
final bool isLoading;
|
||||
final bool isDeleting;
|
||||
final List<PaymentCardEntity> cards;
|
||||
final String errorMessage;
|
||||
|
||||
const PaymentMethodsViewState({
|
||||
this.isLoading = false,
|
||||
this.isDeleting = false,
|
||||
this.cards = const [],
|
||||
this.errorMessage = '',
|
||||
});
|
||||
|
||||
PaymentMethodsViewState copyWith({
|
||||
bool? isLoading,
|
||||
bool? isDeleting,
|
||||
List<PaymentCardEntity>? cards,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return PaymentMethodsViewState(
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
isDeleting: isDeleting ?? this.isDeleting,
|
||||
cards: cards ?? this.cards,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:payments/payments.dart';
|
||||
import 'package:profile/src/features/payment_methods/domain/use_cases/delete_payment_card_use_case.dart';
|
||||
import 'package:profile/src/features/payment_methods/domain/use_cases/get_payment_cards_use_case.dart';
|
||||
|
||||
final getPaymentCardsUseCaseProvider =
|
||||
Provider.autoDispose<GetPaymentCardsUseCase>((ref) {
|
||||
final repository = ref.read(hipayRepositoryProvider);
|
||||
return GetPaymentCardsUseCaseImpl(repository);
|
||||
});
|
||||
|
||||
final deletePaymentCardUseCaseProvider =
|
||||
Provider.autoDispose<DeletePaymentCardUseCase>((ref) {
|
||||
final repository = ref.read(hipayRepositoryProvider);
|
||||
return DeletePaymentCardUseCaseImpl(repository);
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sealed_countries/sealed_countries.dart';
|
||||
import '../providers/logout_provider.dart';
|
||||
import '../providers/payment_profile_provider.dart';
|
||||
|
||||
class ProfileSettingsScreen extends ConsumerWidget {
|
||||
@@ -143,10 +144,6 @@ class ProfileSettingsScreen extends ConsumerWidget {
|
||||
_labelValue("Fecha de nacimiento", birthDate),
|
||||
_labelValue("Nacionalidad", nationality),
|
||||
_labelValue("Lugar de nacimiento", profile.placeOfBirth),
|
||||
_labelValue(
|
||||
"Documento (${profile.documentType})",
|
||||
profile.document.toUpperCase(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -229,7 +226,10 @@ class ProfileSettingsScreen extends ConsumerWidget {
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
TextButton(onPressed: () => {}, child: Text("Editar")),
|
||||
TextButton(
|
||||
onPressed: () => navigationContract.goTo(AppRoutes.dashboardProfilePaymentMethods),
|
||||
child: Text("Editar"),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text("Puedes cambiar el método de pago en cualquier momento"),
|
||||
@@ -354,6 +354,25 @@ class ProfileSettingsScreen extends ConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStatePropertyAll(EdgeInsets.all(0)),
|
||||
),
|
||||
onPressed: () => _logout(context, ref),
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Icons.logout, size: 24, color: Colors.red),
|
||||
Text(
|
||||
"Cerrar sesión",
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return Stack(
|
||||
@@ -413,6 +432,41 @@ class ProfileSettingsScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _logout(BuildContext context, WidgetRef ref) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Cerrar sesión'),
|
||||
content: Text('¿Estás seguro de que deseas cerrar sesión?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text('Cancelar'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text('Cerrar sesión'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
await ref.read(logoutProvider.future);
|
||||
if (!context.mounted) return;
|
||||
navigationContract.goTo(AppRoutes.login);
|
||||
} catch (e) {
|
||||
if (!context.mounted) return;
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: 'Error al cerrar sesión',
|
||||
type: MessageType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _labelValue(String label, String value) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
|
||||
13
modules/profile/lib/src/providers/logout_provider.dart
Normal file
13
modules/profile/lib/src/providers/logout_provider.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
|
||||
final logoutProvider = FutureProvider.autoDispose<void>((ref) async {
|
||||
final repository = getIt<QuestiaRepository>();
|
||||
try {
|
||||
await repository.post<void>('/auth/logout');
|
||||
} on DioException catch (error) {
|
||||
throw Exception(error.message ?? 'Error in logout');
|
||||
}
|
||||
await clearSessionData();
|
||||
});
|
||||
@@ -22,7 +22,12 @@ dependencies:
|
||||
path: ../../packages/sf_shared
|
||||
navigation:
|
||||
path: ../../packages/navigation
|
||||
sf_infrastructure:
|
||||
path: ../../packages/sf_infrastructure
|
||||
payments:
|
||||
path: ../../packages/payments
|
||||
|
||||
dio: ^5.9.0
|
||||
#dependencies go here
|
||||
sealed_countries: ^2.8.0
|
||||
flutter_riverpod: ^3.0.3
|
||||
|
||||
Reference in New Issue
Block a user