payment methods with HiPay, payment profile edited, allowance step in device setup, tio snackbar added and logout from profile settings
39
apps/mobile_app/assets/images/ui/mastercard.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg3409" inkscape:version="0.91 r13725" sodipodi:docname="MasterCard 2016.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="999.2px" height="776px"
|
||||
viewBox="0 0 999.2 776" enable-background="new 0 0 999.2 776" xml:space="preserve">
|
||||
<path id="XMLID_1775_" inkscape:connector-curvature="0" d="M181.1,774.3v-51.5c0-19.7-12-32.6-32.6-32.6
|
||||
c-10.3,0-21.5,3.4-29.2,14.6c-6-9.4-14.6-14.6-27.5-14.6c-8.6,0-17.2,2.6-24,12v-10.3h-18v82.4h18v-45.5c0-14.6,7.7-21.5,19.7-21.5
|
||||
s18,7.7,18,21.5v45.5h18v-45.5c0-14.6,8.6-21.5,19.7-21.5c12,0,18,7.7,18,21.5v45.5H181.1z M448.1,691.9h-29.2V667h-18v24.9h-16.3
|
||||
v16.3h16.3v37.8c0,18.9,7.7,30,28.3,30c7.7,0,16.3-2.6,22.3-6l-5.2-15.5c-5.2,3.4-11.2,4.3-15.5,4.3c-8.6,0-12-5.2-12-13.7v-36.9
|
||||
h29.2V691.9z M600.9,690.1c-10.3,0-17.2,5.2-21.5,12v-10.3h-18v82.4h18v-46.4c0-13.7,6-21.5,17.2-21.5c3.4,0,7.7,0.9,11.2,1.7
|
||||
l5.2-17.2C609.4,690.1,604.3,690.1,600.9,690.1L600.9,690.1z M370,698.7c-8.6-6-20.6-8.6-33.5-8.6c-20.6,0-34.3,10.3-34.3,26.6
|
||||
c0,13.7,10.3,21.5,28.3,24l8.6,0.9c9.4,1.7,14.6,4.3,14.6,8.6c0,6-6.9,10.3-18.9,10.3c-12,0-21.5-4.3-27.5-8.6l-8.6,13.7
|
||||
c9.4,6.9,22.3,10.3,35.2,10.3c24,0,37.8-11.2,37.8-26.6c0-14.6-11.2-22.3-28.3-24.9l-8.6-0.9c-7.7-0.9-13.7-2.6-13.7-7.7
|
||||
c0-6,6-9.4,15.5-9.4c10.3,0,20.6,4.3,25.8,6.9L370,698.7L370,698.7z M848.9,690.1c-10.3,0-17.2,5.2-21.5,12v-10.3h-18v82.4h18v-46.4
|
||||
c0-13.7,6-21.5,17.2-21.5c3.4,0,7.7,0.9,11.2,1.7L861,691C857.5,690.1,852.4,690.1,848.9,690.1L848.9,690.1z M618.9,733.1
|
||||
c0,24.9,17.2,42.9,43.8,42.9c12,0,20.6-2.6,29.2-9.4l-8.6-14.6c-6.9,5.2-13.7,7.7-21.5,7.7c-14.6,0-24.9-10.3-24.9-26.6
|
||||
c0-15.5,10.3-25.8,24.9-26.6c7.7,0,14.6,2.6,21.5,7.7l8.6-14.6c-8.6-6.9-17.2-9.4-29.2-9.4C636.1,690.1,618.9,708.2,618.9,733.1
|
||||
L618.9,733.1L618.9,733.1z M785.4,733.1v-41.2h-18v10.3c-6-7.7-14.6-12-25.8-12c-23.2,0-41.2,18-41.2,42.9c0,24.9,18,42.9,41.2,42.9
|
||||
c12,0,20.6-4.3,25.8-12v10.3h18V733.1L785.4,733.1z M719.3,733.1c0-14.6,9.4-26.6,24.9-26.6c14.6,0,24.9,11.2,24.9,26.6
|
||||
c0,14.6-10.3,26.6-24.9,26.6C728.8,758.8,719.3,747.6,719.3,733.1L719.3,733.1z M503.9,690.1c-24,0-41.2,17.2-41.2,42.9
|
||||
c0,25.8,17.2,42.9,42.1,42.9c12,0,24-3.4,33.5-11.2l-8.6-12.9c-6.9,5.2-15.5,8.6-24,8.6c-11.2,0-22.3-5.2-24.9-19.7h60.9
|
||||
c0-2.6,0-4.3,0-6.9C542.5,707.3,527,690.1,503.9,690.1L503.9,690.1L503.9,690.1z M503.9,705.6c11.2,0,18.9,6.9,20.6,19.7h-42.9
|
||||
C483.3,714.2,491,705.6,503.9,705.6L503.9,705.6z M951.1,733.1v-73.8h-18v42.9c-6-7.7-14.6-12-25.8-12c-23.2,0-41.2,18-41.2,42.9
|
||||
c0,24.9,18,42.9,41.2,42.9c12,0,20.6-4.3,25.8-12v10.3h18V733.1L951.1,733.1z M885,733.1c0-14.6,9.4-26.6,24.9-26.6
|
||||
c14.6,0,24.9,11.2,24.9,26.6c0,14.6-10.3,26.6-24.9,26.6C894.4,758.8,885,747.6,885,733.1L885,733.1z M282.4,733.1v-41.2h-18v10.3
|
||||
c-6-7.7-14.6-12-25.8-12c-23.2,0-41.2,18-41.2,42.9c0,24.9,18,42.9,41.2,42.9c12,0,20.6-4.3,25.8-12v10.3h18V733.1L282.4,733.1z
|
||||
M215.5,733.1c0-14.6,9.4-26.6,24.9-26.6c14.6,0,24.9,11.2,24.9,26.6c0,14.6-10.3,26.6-24.9,26.6
|
||||
C224.9,758.8,215.5,747.6,215.5,733.1z"/>
|
||||
<g>
|
||||
<rect id="rect19" x="364" y="66.1" fill="#FF5A00" width="270.4" height="485.8"/>
|
||||
<path id="XMLID_330_" inkscape:connector-curvature="0" fill="#EB001B" d="M382,309c0-98.7,46.4-186.3,117.6-242.9
|
||||
C447.2,24.9,381.1,0,309,0C138.2,0,0,138.2,0,309s138.2,309,309,309c72.1,0,138.2-24.9,190.6-66.1C428.3,496.1,382,407.7,382,309z"
|
||||
/>
|
||||
<path id="path22" inkscape:connector-curvature="0" fill="#F79E1B" d="M999.2,309c0,170.8-138.2,309-309,309
|
||||
c-72.1,0-138.2-24.9-190.6-66.1c72.1-56.7,117.6-144.2,117.6-242.9S570.8,122.7,499.6,66.1C551.9,24.9,618,0,690.1,0
|
||||
C861,0,999.2,139.1,999.2,309z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -1,7 +1 @@
|
||||
<svg width="87" height="16" viewBox="0 0 87 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0378 14.0889C11.6569 15.2806 9.86558 16 7.90822 16C3.54061 16 0 12.4183 0 8.00003C0 3.5817 3.54061 0 7.90822 0C9.86558 0 11.6569 0.7194 13.0378 1.91112C14.4188 0.7194 16.2101 0 18.1675 0C22.535 0 26.0756 3.5817 26.0756 8.00003C26.0756 12.4183 22.535 16 18.1675 16C16.2101 16 14.4188 15.2806 13.0378 14.0889Z" fill="#ED0006"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0381 14.0889C14.7384 12.6216 15.8166 10.4382 15.8166 8.00003C15.8166 5.56183 14.7384 3.37843 13.0381 1.91112C14.4191 0.7194 16.2103 0 18.1677 0C22.5353 0 26.0759 3.5817 26.0759 8.00003C26.0759 12.4183 22.5353 16 18.1677 16C16.2103 16 14.4191 15.2806 13.0381 14.0889Z" fill="#F9A000"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0383 1.91016C14.7386 3.37752 15.8168 5.56087 15.8168 7.99907C15.8168 10.4372 14.7386 12.6206 13.0383 14.0879C11.338 12.6206 10.2598 10.4372 10.2598 7.99907C10.2598 5.56087 11.338 3.37752 13.0383 1.91016Z" fill="#FF5E00"/>
|
||||
<path d="M52.5256 15.8173L55.167 0.31688H59.3919L56.7485 15.8173H52.5256ZM72.0127 0.651022C71.1759 0.336992 69.8642 0 68.2262 0C64.0514 0 61.1108 2.10232 61.0857 5.11538C61.0621 7.34264 63.1851 8.58522 64.7877 9.32659C66.4323 10.0866 66.9851 10.571 66.9772 11.2493C66.9666 12.2884 65.6639 12.7629 64.4494 12.7629C62.7585 12.7629 61.8601 12.5279 60.4726 11.9494L59.928 11.7031L59.3351 15.173C60.322 15.6058 62.1467 15.9805 64.0415 16C68.4827 16 71.3658 13.9217 71.3986 10.704C71.4144 8.94067 70.2888 7.59895 67.8512 6.49256C66.3745 5.7755 65.4701 5.29702 65.4797 4.57093C65.4797 3.92664 66.2452 3.23769 67.8995 3.23769C69.2808 3.21623 70.2818 3.51751 71.0616 3.83154L71.4402 4.01041L72.0127 0.651022ZM82.885 0.316642H79.6204C78.6091 0.316642 77.8521 0.592666 77.4081 1.60206L71.1334 15.807H75.57C75.57 15.807 76.2952 13.8971 76.4593 13.4777C76.9441 13.4777 81.254 13.4844 81.8701 13.4844C81.9965 14.0271 82.3842 15.807 82.3842 15.807H86.3046L82.885 0.316167V0.316642ZM77.7052 10.3258C78.0547 9.43277 79.3886 5.99277 79.3886 5.99277C79.3638 6.03403 79.7355 5.09534 79.9487 4.51337L80.2343 5.84977C80.2343 5.84977 81.0433 9.5498 81.2124 10.3256H77.7052V10.3258ZM48.939 0.316642L44.8027 10.8872L44.362 8.73915C43.5919 6.26286 41.1928 3.57999 38.5107 2.23685L42.293 15.7925L46.7631 15.7876L53.4146 0.316484L48.939 0.316405" fill="#0E4595"/>
|
||||
<path d="M40.9429 0.316406H34.1302L34.0762 0.638908C39.3764 1.92195 42.8834 5.02258 44.3395 8.74857L42.8582 1.6255C42.6025 0.643975 41.8607 0.351007 40.9431 0.316801" fill="#0E4595"/>
|
||||
</svg>
|
||||
<svg height="812" viewBox="0.5 0.5 999 323.684" width="2500" xmlns="http://www.w3.org/2000/svg"><path d="M651.185.5c-70.933 0-134.322 36.766-134.322 104.694 0 77.9 112.423 83.28 112.423 122.415 0 16.478-18.884 31.229-51.137 31.229-45.773 0-79.984-20.611-79.984-20.611l-14.638 68.547s39.41 17.41 91.734 17.41c77.552 0 138.576-38.572 138.576-107.66 0-82.316-112.89-87.537-112.89-123.86 0-12.91 15.501-27.053 47.662-27.053 36.286 0 65.892 14.99 65.892 14.99l14.326-66.204S696.614.5 651.185.5zM2.218 5.497L.5 15.49s29.842 5.461 56.719 16.356c34.606 12.492 37.072 19.765 42.9 42.353l63.51 244.832h85.138L379.927 5.497h-84.942L210.707 218.67l-34.39-180.696c-3.154-20.68-19.13-32.477-38.685-32.477H2.218zm411.865 0L347.449 319.03h80.999l66.4-313.534h-80.765zm451.759 0c-19.532 0-29.88 10.457-37.474 28.73L709.699 319.03h84.942l16.434-47.468h103.483l9.994 47.468H999.5L934.115 5.497h-68.273zm11.047 84.707l25.178 117.653h-67.454z" fill="#1434cb"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 945 B |
@@ -114,6 +114,13 @@ void configureAppRouter() {
|
||||
path: 'settings',
|
||||
name: 'profile_settings',
|
||||
pageBuilder: const ProfileSettingsBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'payment-methods',
|
||||
name: 'profile_payment_methods',
|
||||
pageBuilder: const PaymentMethodsBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1187,6 +1187,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
top_snackbar_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: top_snackbar_flutter
|
||||
sha256: ad3f93062450e8c7db97b271d405c180536408cc2be4380a59da7022eb1d750c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -1,57 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:top_snackbar_flutter/custom_snack_bar.dart';
|
||||
import 'package:top_snackbar_flutter/top_snack_bar.dart';
|
||||
|
||||
enum MessageType { info, error, warning, success }
|
||||
|
||||
class CustomSnackBar extends StatelessWidget {
|
||||
final MessageType? type;
|
||||
final String message;
|
||||
void showTopSnackbar(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
MessageType type = MessageType.info,
|
||||
}) {
|
||||
final Widget child = switch (type) {
|
||||
MessageType.success => CustomSnackBar.success(message: message),
|
||||
MessageType.error => CustomSnackBar.error(message: message),
|
||||
MessageType.warning => CustomSnackBar.info(
|
||||
message: message,
|
||||
backgroundColor: Color(0xFFE34B04),
|
||||
icon: Icon(Icons.warning_outlined, color: Color(0x15000000), size: 120),
|
||||
),
|
||||
MessageType.info => CustomSnackBar.info(message: message),
|
||||
};
|
||||
|
||||
const CustomSnackBar({super.key, this.type, required this.message});
|
||||
|
||||
@override
|
||||
SnackBar build(BuildContext context) {
|
||||
late final Color foregroundColor;
|
||||
late final Color backgroundColor;
|
||||
late final IconData icon;
|
||||
|
||||
switch (type ?? MessageType.info) {
|
||||
case MessageType.info:
|
||||
backgroundColor = Color(0xFFE3EFFD);
|
||||
foregroundColor = Color(0xFF1F4ECF);
|
||||
icon = Icons.info;
|
||||
case MessageType.error:
|
||||
backgroundColor = Color(0xFFFBEDE9);
|
||||
foregroundColor = Color(0xFFD12D00);
|
||||
icon = Icons.cancel;
|
||||
case MessageType.warning:
|
||||
backgroundColor = Color(0xFFFBF3E2);
|
||||
foregroundColor = Color(0xFFE34B04);
|
||||
icon = Icons.warning_outlined;
|
||||
case MessageType.success:
|
||||
backgroundColor = Color(0xFFE2F4E8);
|
||||
foregroundColor = Color(0xFF00713D);
|
||||
icon = Icons.check_circle;
|
||||
}
|
||||
|
||||
return SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: backgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(color: foregroundColor, width: 1),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
content: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(icon, color: foregroundColor),
|
||||
Expanded(
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(color: Color(0xFF4B4B4B), fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
showTopSnackBar(Overlay.of(context), child);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ dependencies:
|
||||
country_code_picker: ^3.4.1
|
||||
fonts:
|
||||
path: ../../packages/fonts
|
||||
top_snackbar_flutter: ^3.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 9.3 KiB |
@@ -326,7 +326,7 @@ void main() {
|
||||
|
||||
void snackbarTest({
|
||||
required String message,
|
||||
required MessageType? type,
|
||||
required MessageType type,
|
||||
required String testName,
|
||||
}) {
|
||||
testGoldens('Snackbar $testName', (tester) async {
|
||||
@@ -341,11 +341,10 @@ void main() {
|
||||
builder: (BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
CustomSnackBar(
|
||||
message: message,
|
||||
type: type,
|
||||
).build(context),
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: message,
|
||||
type: type,
|
||||
);
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
@@ -375,9 +374,13 @@ void main() {
|
||||
const longText =
|
||||
"Mensaje de prueba largo para comprobar los casos en los que el texto ocupa varias líneas";
|
||||
|
||||
snackbarTest(message: "", type: null, testName: "default_empty");
|
||||
snackbarTest(message: shortText, type: null, testName: "default");
|
||||
snackbarTest(message: longText, type: null, testName: "default_long_text");
|
||||
snackbarTest(message: "", type: MessageType.info, testName: "default_empty");
|
||||
snackbarTest(message: shortText, type: MessageType.info, testName: "default");
|
||||
snackbarTest(
|
||||
message: longText,
|
||||
type: MessageType.info,
|
||||
testName: "default_long_text",
|
||||
);
|
||||
snackbarTest(message: "", type: MessageType.info, testName: "info_empty");
|
||||
snackbarTest(message: shortText, type: MessageType.info, testName: "info");
|
||||
snackbarTest(
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
<versions>
|
||||
<version>2.6.4</version>
|
||||
</versions>
|
||||
<lastUpdated>20260212000000</lastUpdated>
|
||||
<lastUpdated>20260213000000</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
|
||||
@@ -1 +1 @@
|
||||
299dd618be3c19b3aced93d75bb14a01
|
||||
65d633d19b3c2e2a43021f9b7dfc1f1b
|
||||
@@ -1 +1 @@
|
||||
9967947368e66f19dfcef5b3d016f0fdfd89f20e
|
||||
67557269553157d868efb3d16b678b8f3e8fca26
|
||||
@@ -16,6 +16,7 @@ class AppRoutes {
|
||||
static const dashboardNotifications = '$dashboard/notifications';
|
||||
static const dashboardProfile = '$dashboard/profile';
|
||||
static const dashboardProfileSettings = '$dashboardProfile/settings';
|
||||
static const dashboardProfilePaymentMethods = '$dashboardProfileSettings/payment-methods';
|
||||
|
||||
static const hipayWebView = '/hipay_webview';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_PaymentCardModel _$PaymentCardModelFromJson(Map<String, dynamic> json) =>
|
||||
_PaymentCardModel(
|
||||
topupCardId: json['topupCardId'] as String,
|
||||
token: json['token'] as String,
|
||||
userId: json['userId'] as String,
|
||||
profile: json['profile'] as String,
|
||||
brand: json['brand'] as String,
|
||||
maskedPan: json['maskedPan'] as String,
|
||||
cardHolder: json['cardHolder'] as String,
|
||||
cardExpiryMonth: json['cardExpiryMonth'] as String,
|
||||
cardExpiryYear: json['cardExpiryYear'] as String,
|
||||
issuer: json['issuer'] as String,
|
||||
country: json['country'] as String,
|
||||
domesticNetwork: json['domesticNetwork'] as String,
|
||||
cardType: json['cardType'] as String,
|
||||
createdDate: json['createdDate'] as String,
|
||||
updatedDate: json['updatedDate'] as String,
|
||||
status: json['status'] as String,
|
||||
providerName: json['providerName'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PaymentCardModelToJson(_PaymentCardModel instance) =>
|
||||
<String, dynamic>{
|
||||
'topupCardId': instance.topupCardId,
|
||||
'token': instance.token,
|
||||
'userId': instance.userId,
|
||||
'profile': instance.profile,
|
||||
'brand': instance.brand,
|
||||
'maskedPan': instance.maskedPan,
|
||||
'cardHolder': instance.cardHolder,
|
||||
'cardExpiryMonth': instance.cardExpiryMonth,
|
||||
'cardExpiryYear': instance.cardExpiryYear,
|
||||
'issuer': instance.issuer,
|
||||
'country': instance.country,
|
||||
'domesticNetwork': instance.domesticNetwork,
|
||||
'cardType': instance.cardType,
|
||||
'createdDate': instance.createdDate,
|
||||
'updatedDate': instance.updatedDate,
|
||||
'status': instance.status,
|
||||
'providerName': instance.providerName,
|
||||
};
|
||||
@@ -685,6 +685,12 @@
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "top_snackbar_flutter",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/top_snackbar_flutter-3.3.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.15"
|
||||
},
|
||||
{
|
||||
"name": "typed_data",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/typed_data-1.4.0",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"flutter_riverpod",
|
||||
"fonts",
|
||||
"get_it",
|
||||
"top_snackbar_flutter",
|
||||
"utils"
|
||||
]
|
||||
},
|
||||
@@ -313,6 +314,13 @@
|
||||
"dio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "top_snackbar_flutter",
|
||||
"version": "3.3.0",
|
||||
"dependencies": [
|
||||
"flutter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "country_code_picker",
|
||||
"version": "3.4.1",
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export 'src/features/topup_cards/presentation/hipay_webview_builder.dart';
|
||||
export 'src/features/topup_cards/presentation/hipay_webview_screen.dart';
|
||||
export 'src/core/domain/entities/hipay_result.dart';
|
||||
export 'src/core/domain/entities/payment_card_entity.dart';
|
||||
export 'src/core/domain/repositories/hipay_repository.dart';
|
||||
export 'src/core/providers/hipay_repository_provider.dart';
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:payments/src/core/data/models/payment_card_model.dart';
|
||||
import 'package:payments/src/core/data/models/topup_cards_response_model.dart';
|
||||
|
||||
abstract class HiPayRemoteDatasource {
|
||||
Future<TopupCardsResponseModel> topupCards();
|
||||
Future<bool> getProcessCard(String url);
|
||||
Future<List<PaymentCardModel>> getTopupCards();
|
||||
Future<void> deleteTopupCard(String topupCardId);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:payments/src/core/data/models/payment_card_model.dart';
|
||||
import 'package:payments/src/core/data/models/topup_cards_response_model.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
|
||||
@@ -27,4 +28,48 @@ class HiPayRemoteDatasourceImpl implements HiPayRemoteDatasource {
|
||||
throw Exception(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> getProcessCard(String url) async {
|
||||
try {
|
||||
final response = await _repository.get<dynamic>(
|
||||
'/payments/topup-cards/webhook?orderid=$url',
|
||||
);
|
||||
return response.statusCode == 200;
|
||||
} on DioException catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PaymentCardModel>> getTopupCards() async {
|
||||
try {
|
||||
final response = await _repository.get<List<dynamic>>(
|
||||
'/payments/topup-cards',
|
||||
);
|
||||
|
||||
final data = response.data;
|
||||
if (data == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(PaymentCardModel.fromJson)
|
||||
.toList();
|
||||
} on DioException catch (error) {
|
||||
final msg = error.message ?? 'Error in getTopupCards';
|
||||
throw Exception(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteTopupCard(String topupCardId) async {
|
||||
try {
|
||||
await _repository.delete<void>('/payments/topup-cards/$topupCardId');
|
||||
} on DioException catch (error) {
|
||||
final msg = error.message ?? 'Error in deleteTopupCard';
|
||||
throw Exception(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:payments/src/core/domain/entities/payment_card_entity.dart';
|
||||
|
||||
part 'payment_card_model.freezed.dart';
|
||||
part 'payment_card_model.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class PaymentCardModel with _$PaymentCardModel {
|
||||
const factory PaymentCardModel({
|
||||
required String topupCardId,
|
||||
required String token,
|
||||
required String userId,
|
||||
required String profile,
|
||||
required String brand,
|
||||
required String maskedPan,
|
||||
required String cardHolder,
|
||||
required String cardExpiryMonth,
|
||||
required String cardExpiryYear,
|
||||
required String issuer,
|
||||
required String country,
|
||||
required String domesticNetwork,
|
||||
required String cardType,
|
||||
required String createdDate,
|
||||
required String updatedDate,
|
||||
required String status,
|
||||
required String providerName,
|
||||
}) = _PaymentCardModel;
|
||||
|
||||
factory PaymentCardModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$PaymentCardModelFromJson(json);
|
||||
}
|
||||
|
||||
extension PaymentCardModelMapper on PaymentCardModel {
|
||||
PaymentCardEntity toEntity() {
|
||||
return PaymentCardEntity(
|
||||
topupCardId: topupCardId,
|
||||
brand: brand,
|
||||
maskedPan: maskedPan,
|
||||
cardHolder: cardHolder,
|
||||
cardExpiryMonth: cardExpiryMonth,
|
||||
cardExpiryYear: cardExpiryYear,
|
||||
status: status,
|
||||
cardType: cardType,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
// 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_card_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PaymentCardModel {
|
||||
|
||||
String get topupCardId; String get token; String get userId; String get profile; String get brand; String get maskedPan; String get cardHolder; String get cardExpiryMonth; String get cardExpiryYear; String get issuer; String get country; String get domesticNetwork; String get cardType; String get createdDate; String get updatedDate; String get status; String get providerName;
|
||||
/// Create a copy of PaymentCardModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PaymentCardModelCopyWith<PaymentCardModel> get copyWith => _$PaymentCardModelCopyWithImpl<PaymentCardModel>(this as PaymentCardModel, _$identity);
|
||||
|
||||
/// Serializes this PaymentCardModel to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentCardModel&&(identical(other.topupCardId, topupCardId) || other.topupCardId == topupCardId)&&(identical(other.token, token) || other.token == token)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.brand, brand) || other.brand == brand)&&(identical(other.maskedPan, maskedPan) || other.maskedPan == maskedPan)&&(identical(other.cardHolder, cardHolder) || other.cardHolder == cardHolder)&&(identical(other.cardExpiryMonth, cardExpiryMonth) || other.cardExpiryMonth == cardExpiryMonth)&&(identical(other.cardExpiryYear, cardExpiryYear) || other.cardExpiryYear == cardExpiryYear)&&(identical(other.issuer, issuer) || other.issuer == issuer)&&(identical(other.country, country) || other.country == country)&&(identical(other.domesticNetwork, domesticNetwork) || other.domesticNetwork == domesticNetwork)&&(identical(other.cardType, cardType) || other.cardType == cardType)&&(identical(other.createdDate, createdDate) || other.createdDate == createdDate)&&(identical(other.updatedDate, updatedDate) || other.updatedDate == updatedDate)&&(identical(other.status, status) || other.status == status)&&(identical(other.providerName, providerName) || other.providerName == providerName));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,topupCardId,token,userId,profile,brand,maskedPan,cardHolder,cardExpiryMonth,cardExpiryYear,issuer,country,domesticNetwork,cardType,createdDate,updatedDate,status,providerName);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PaymentCardModel(topupCardId: $topupCardId, token: $token, userId: $userId, profile: $profile, brand: $brand, maskedPan: $maskedPan, cardHolder: $cardHolder, cardExpiryMonth: $cardExpiryMonth, cardExpiryYear: $cardExpiryYear, issuer: $issuer, country: $country, domesticNetwork: $domesticNetwork, cardType: $cardType, createdDate: $createdDate, updatedDate: $updatedDate, status: $status, providerName: $providerName)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PaymentCardModelCopyWith<$Res> {
|
||||
factory $PaymentCardModelCopyWith(PaymentCardModel value, $Res Function(PaymentCardModel) _then) = _$PaymentCardModelCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String topupCardId, String token, String userId, String profile, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String issuer, String country, String domesticNetwork, String cardType, String createdDate, String updatedDate, String status, String providerName
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$PaymentCardModelCopyWithImpl<$Res>
|
||||
implements $PaymentCardModelCopyWith<$Res> {
|
||||
_$PaymentCardModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PaymentCardModel _self;
|
||||
final $Res Function(PaymentCardModel) _then;
|
||||
|
||||
/// Create a copy of PaymentCardModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? topupCardId = null,Object? token = null,Object? userId = null,Object? profile = null,Object? brand = null,Object? maskedPan = null,Object? cardHolder = null,Object? cardExpiryMonth = null,Object? cardExpiryYear = null,Object? issuer = null,Object? country = null,Object? domesticNetwork = null,Object? cardType = null,Object? createdDate = null,Object? updatedDate = null,Object? status = null,Object? providerName = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
topupCardId: null == topupCardId ? _self.topupCardId : topupCardId // ignore: cast_nullable_to_non_nullable
|
||||
as String,token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable
|
||||
as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
||||
as String,brand: null == brand ? _self.brand : brand // ignore: cast_nullable_to_non_nullable
|
||||
as String,maskedPan: null == maskedPan ? _self.maskedPan : maskedPan // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardHolder: null == cardHolder ? _self.cardHolder : cardHolder // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardExpiryMonth: null == cardExpiryMonth ? _self.cardExpiryMonth : cardExpiryMonth // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardExpiryYear: null == cardExpiryYear ? _self.cardExpiryYear : cardExpiryYear // ignore: cast_nullable_to_non_nullable
|
||||
as String,issuer: null == issuer ? _self.issuer : issuer // ignore: cast_nullable_to_non_nullable
|
||||
as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
|
||||
as String,domesticNetwork: null == domesticNetwork ? _self.domesticNetwork : domesticNetwork // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardType: null == cardType ? _self.cardType : cardType // ignore: cast_nullable_to_non_nullable
|
||||
as String,createdDate: null == createdDate ? _self.createdDate : createdDate // ignore: cast_nullable_to_non_nullable
|
||||
as String,updatedDate: null == updatedDate ? _self.updatedDate : updatedDate // ignore: cast_nullable_to_non_nullable
|
||||
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as String,providerName: null == providerName ? _self.providerName : providerName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [PaymentCardModel].
|
||||
extension PaymentCardModelPatterns on PaymentCardModel {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PaymentCardModel value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardModel() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PaymentCardModel value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardModel():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PaymentCardModel value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardModel() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String topupCardId, String token, String userId, String profile, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String issuer, String country, String domesticNetwork, String cardType, String createdDate, String updatedDate, String status, String providerName)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardModel() when $default != null:
|
||||
return $default(_that.topupCardId,_that.token,_that.userId,_that.profile,_that.brand,_that.maskedPan,_that.cardHolder,_that.cardExpiryMonth,_that.cardExpiryYear,_that.issuer,_that.country,_that.domesticNetwork,_that.cardType,_that.createdDate,_that.updatedDate,_that.status,_that.providerName);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String topupCardId, String token, String userId, String profile, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String issuer, String country, String domesticNetwork, String cardType, String createdDate, String updatedDate, String status, String providerName) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardModel():
|
||||
return $default(_that.topupCardId,_that.token,_that.userId,_that.profile,_that.brand,_that.maskedPan,_that.cardHolder,_that.cardExpiryMonth,_that.cardExpiryYear,_that.issuer,_that.country,_that.domesticNetwork,_that.cardType,_that.createdDate,_that.updatedDate,_that.status,_that.providerName);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String topupCardId, String token, String userId, String profile, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String issuer, String country, String domesticNetwork, String cardType, String createdDate, String updatedDate, String status, String providerName)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardModel() when $default != null:
|
||||
return $default(_that.topupCardId,_that.token,_that.userId,_that.profile,_that.brand,_that.maskedPan,_that.cardHolder,_that.cardExpiryMonth,_that.cardExpiryYear,_that.issuer,_that.country,_that.domesticNetwork,_that.cardType,_that.createdDate,_that.updatedDate,_that.status,_that.providerName);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _PaymentCardModel implements PaymentCardModel {
|
||||
const _PaymentCardModel({required this.topupCardId, required this.token, required this.userId, required this.profile, required this.brand, required this.maskedPan, required this.cardHolder, required this.cardExpiryMonth, required this.cardExpiryYear, required this.issuer, required this.country, required this.domesticNetwork, required this.cardType, required this.createdDate, required this.updatedDate, required this.status, required this.providerName});
|
||||
factory _PaymentCardModel.fromJson(Map<String, dynamic> json) => _$PaymentCardModelFromJson(json);
|
||||
|
||||
@override final String topupCardId;
|
||||
@override final String token;
|
||||
@override final String userId;
|
||||
@override final String profile;
|
||||
@override final String brand;
|
||||
@override final String maskedPan;
|
||||
@override final String cardHolder;
|
||||
@override final String cardExpiryMonth;
|
||||
@override final String cardExpiryYear;
|
||||
@override final String issuer;
|
||||
@override final String country;
|
||||
@override final String domesticNetwork;
|
||||
@override final String cardType;
|
||||
@override final String createdDate;
|
||||
@override final String updatedDate;
|
||||
@override final String status;
|
||||
@override final String providerName;
|
||||
|
||||
/// Create a copy of PaymentCardModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PaymentCardModelCopyWith<_PaymentCardModel> get copyWith => __$PaymentCardModelCopyWithImpl<_PaymentCardModel>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$PaymentCardModelToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentCardModel&&(identical(other.topupCardId, topupCardId) || other.topupCardId == topupCardId)&&(identical(other.token, token) || other.token == token)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.brand, brand) || other.brand == brand)&&(identical(other.maskedPan, maskedPan) || other.maskedPan == maskedPan)&&(identical(other.cardHolder, cardHolder) || other.cardHolder == cardHolder)&&(identical(other.cardExpiryMonth, cardExpiryMonth) || other.cardExpiryMonth == cardExpiryMonth)&&(identical(other.cardExpiryYear, cardExpiryYear) || other.cardExpiryYear == cardExpiryYear)&&(identical(other.issuer, issuer) || other.issuer == issuer)&&(identical(other.country, country) || other.country == country)&&(identical(other.domesticNetwork, domesticNetwork) || other.domesticNetwork == domesticNetwork)&&(identical(other.cardType, cardType) || other.cardType == cardType)&&(identical(other.createdDate, createdDate) || other.createdDate == createdDate)&&(identical(other.updatedDate, updatedDate) || other.updatedDate == updatedDate)&&(identical(other.status, status) || other.status == status)&&(identical(other.providerName, providerName) || other.providerName == providerName));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,topupCardId,token,userId,profile,brand,maskedPan,cardHolder,cardExpiryMonth,cardExpiryYear,issuer,country,domesticNetwork,cardType,createdDate,updatedDate,status,providerName);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PaymentCardModel(topupCardId: $topupCardId, token: $token, userId: $userId, profile: $profile, brand: $brand, maskedPan: $maskedPan, cardHolder: $cardHolder, cardExpiryMonth: $cardExpiryMonth, cardExpiryYear: $cardExpiryYear, issuer: $issuer, country: $country, domesticNetwork: $domesticNetwork, cardType: $cardType, createdDate: $createdDate, updatedDate: $updatedDate, status: $status, providerName: $providerName)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PaymentCardModelCopyWith<$Res> implements $PaymentCardModelCopyWith<$Res> {
|
||||
factory _$PaymentCardModelCopyWith(_PaymentCardModel value, $Res Function(_PaymentCardModel) _then) = __$PaymentCardModelCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String topupCardId, String token, String userId, String profile, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String issuer, String country, String domesticNetwork, String cardType, String createdDate, String updatedDate, String status, String providerName
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$PaymentCardModelCopyWithImpl<$Res>
|
||||
implements _$PaymentCardModelCopyWith<$Res> {
|
||||
__$PaymentCardModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PaymentCardModel _self;
|
||||
final $Res Function(_PaymentCardModel) _then;
|
||||
|
||||
/// Create a copy of PaymentCardModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? topupCardId = null,Object? token = null,Object? userId = null,Object? profile = null,Object? brand = null,Object? maskedPan = null,Object? cardHolder = null,Object? cardExpiryMonth = null,Object? cardExpiryYear = null,Object? issuer = null,Object? country = null,Object? domesticNetwork = null,Object? cardType = null,Object? createdDate = null,Object? updatedDate = null,Object? status = null,Object? providerName = null,}) {
|
||||
return _then(_PaymentCardModel(
|
||||
topupCardId: null == topupCardId ? _self.topupCardId : topupCardId // ignore: cast_nullable_to_non_nullable
|
||||
as String,token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable
|
||||
as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
||||
as String,brand: null == brand ? _self.brand : brand // ignore: cast_nullable_to_non_nullable
|
||||
as String,maskedPan: null == maskedPan ? _self.maskedPan : maskedPan // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardHolder: null == cardHolder ? _self.cardHolder : cardHolder // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardExpiryMonth: null == cardExpiryMonth ? _self.cardExpiryMonth : cardExpiryMonth // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardExpiryYear: null == cardExpiryYear ? _self.cardExpiryYear : cardExpiryYear // ignore: cast_nullable_to_non_nullable
|
||||
as String,issuer: null == issuer ? _self.issuer : issuer // ignore: cast_nullable_to_non_nullable
|
||||
as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
|
||||
as String,domesticNetwork: null == domesticNetwork ? _self.domesticNetwork : domesticNetwork // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardType: null == cardType ? _self.cardType : cardType // ignore: cast_nullable_to_non_nullable
|
||||
as String,createdDate: null == createdDate ? _self.createdDate : createdDate // ignore: cast_nullable_to_non_nullable
|
||||
as String,updatedDate: null == updatedDate ? _self.updatedDate : updatedDate // ignore: cast_nullable_to_non_nullable
|
||||
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as String,providerName: null == providerName ? _self.providerName : providerName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -0,0 +1,49 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'payment_card_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_PaymentCardModel _$PaymentCardModelFromJson(Map<String, dynamic> json) =>
|
||||
_PaymentCardModel(
|
||||
topupCardId: json['topupCardId'] as String,
|
||||
token: json['token'] as String,
|
||||
userId: json['userId'] as String,
|
||||
profile: json['profile'] as String,
|
||||
brand: json['brand'] as String,
|
||||
maskedPan: json['maskedPan'] as String,
|
||||
cardHolder: json['cardHolder'] as String,
|
||||
cardExpiryMonth: json['cardExpiryMonth'] as String,
|
||||
cardExpiryYear: json['cardExpiryYear'] as String,
|
||||
issuer: json['issuer'] as String,
|
||||
country: json['country'] as String,
|
||||
domesticNetwork: json['domesticNetwork'] as String,
|
||||
cardType: json['cardType'] as String,
|
||||
createdDate: json['createdDate'] as String,
|
||||
updatedDate: json['updatedDate'] as String,
|
||||
status: json['status'] as String,
|
||||
providerName: json['providerName'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PaymentCardModelToJson(_PaymentCardModel instance) =>
|
||||
<String, dynamic>{
|
||||
'topupCardId': instance.topupCardId,
|
||||
'token': instance.token,
|
||||
'userId': instance.userId,
|
||||
'profile': instance.profile,
|
||||
'brand': instance.brand,
|
||||
'maskedPan': instance.maskedPan,
|
||||
'cardHolder': instance.cardHolder,
|
||||
'cardExpiryMonth': instance.cardExpiryMonth,
|
||||
'cardExpiryYear': instance.cardExpiryYear,
|
||||
'issuer': instance.issuer,
|
||||
'country': instance.country,
|
||||
'domesticNetwork': instance.domesticNetwork,
|
||||
'cardType': instance.cardType,
|
||||
'createdDate': instance.createdDate,
|
||||
'updatedDate': instance.updatedDate,
|
||||
'status': instance.status,
|
||||
'providerName': instance.providerName,
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:payments/src/core/data/datasource/hipay_remote_datasource.dart';
|
||||
import 'package:payments/src/core/data/models/payment_card_model.dart';
|
||||
import 'package:payments/src/core/data/models/topup_cards_response_model.dart';
|
||||
import 'package:payments/src/core/domain/entities/payment_card_entity.dart';
|
||||
import 'package:payments/src/core/domain/entities/topup_cards_entity.dart';
|
||||
import 'package:payments/src/core/domain/repositories/hipay_repository.dart';
|
||||
|
||||
@@ -13,4 +15,20 @@ class HiPayRepositoryImpl implements HiPayRepository {
|
||||
final model = await _remote.topupCards();
|
||||
return model.toEntity();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> getProcessCard(String url) {
|
||||
return _remote.getProcessCard(url);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PaymentCardEntity>> getTopupCards() async {
|
||||
final models = await _remote.getTopupCards();
|
||||
return models.map((m) => m.toEntity()).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteTopupCard(String topupCardId) {
|
||||
return _remote.deleteTopupCard(topupCardId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'payment_card_entity.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class PaymentCardEntity with _$PaymentCardEntity {
|
||||
const factory PaymentCardEntity({
|
||||
required String topupCardId,
|
||||
required String brand,
|
||||
required String maskedPan,
|
||||
required String cardHolder,
|
||||
required String cardExpiryMonth,
|
||||
required String cardExpiryYear,
|
||||
required String status,
|
||||
required String cardType,
|
||||
}) = _PaymentCardEntity;
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
// 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_card_entity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$PaymentCardEntity {
|
||||
|
||||
String get topupCardId; String get brand; String get maskedPan; String get cardHolder; String get cardExpiryMonth; String get cardExpiryYear; String get status; String get cardType;
|
||||
/// Create a copy of PaymentCardEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PaymentCardEntityCopyWith<PaymentCardEntity> get copyWith => _$PaymentCardEntityCopyWithImpl<PaymentCardEntity>(this as PaymentCardEntity, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentCardEntity&&(identical(other.topupCardId, topupCardId) || other.topupCardId == topupCardId)&&(identical(other.brand, brand) || other.brand == brand)&&(identical(other.maskedPan, maskedPan) || other.maskedPan == maskedPan)&&(identical(other.cardHolder, cardHolder) || other.cardHolder == cardHolder)&&(identical(other.cardExpiryMonth, cardExpiryMonth) || other.cardExpiryMonth == cardExpiryMonth)&&(identical(other.cardExpiryYear, cardExpiryYear) || other.cardExpiryYear == cardExpiryYear)&&(identical(other.status, status) || other.status == status)&&(identical(other.cardType, cardType) || other.cardType == cardType));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,topupCardId,brand,maskedPan,cardHolder,cardExpiryMonth,cardExpiryYear,status,cardType);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PaymentCardEntity(topupCardId: $topupCardId, brand: $brand, maskedPan: $maskedPan, cardHolder: $cardHolder, cardExpiryMonth: $cardExpiryMonth, cardExpiryYear: $cardExpiryYear, status: $status, cardType: $cardType)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PaymentCardEntityCopyWith<$Res> {
|
||||
factory $PaymentCardEntityCopyWith(PaymentCardEntity value, $Res Function(PaymentCardEntity) _then) = _$PaymentCardEntityCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String topupCardId, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String status, String cardType
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$PaymentCardEntityCopyWithImpl<$Res>
|
||||
implements $PaymentCardEntityCopyWith<$Res> {
|
||||
_$PaymentCardEntityCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PaymentCardEntity _self;
|
||||
final $Res Function(PaymentCardEntity) _then;
|
||||
|
||||
/// Create a copy of PaymentCardEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? topupCardId = null,Object? brand = null,Object? maskedPan = null,Object? cardHolder = null,Object? cardExpiryMonth = null,Object? cardExpiryYear = null,Object? status = null,Object? cardType = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
topupCardId: null == topupCardId ? _self.topupCardId : topupCardId // ignore: cast_nullable_to_non_nullable
|
||||
as String,brand: null == brand ? _self.brand : brand // ignore: cast_nullable_to_non_nullable
|
||||
as String,maskedPan: null == maskedPan ? _self.maskedPan : maskedPan // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardHolder: null == cardHolder ? _self.cardHolder : cardHolder // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardExpiryMonth: null == cardExpiryMonth ? _self.cardExpiryMonth : cardExpiryMonth // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardExpiryYear: null == cardExpiryYear ? _self.cardExpiryYear : cardExpiryYear // ignore: cast_nullable_to_non_nullable
|
||||
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardType: null == cardType ? _self.cardType : cardType // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [PaymentCardEntity].
|
||||
extension PaymentCardEntityPatterns on PaymentCardEntity {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PaymentCardEntity value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardEntity() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PaymentCardEntity value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardEntity():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PaymentCardEntity value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardEntity() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String topupCardId, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String status, String cardType)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardEntity() when $default != null:
|
||||
return $default(_that.topupCardId,_that.brand,_that.maskedPan,_that.cardHolder,_that.cardExpiryMonth,_that.cardExpiryYear,_that.status,_that.cardType);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String topupCardId, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String status, String cardType) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardEntity():
|
||||
return $default(_that.topupCardId,_that.brand,_that.maskedPan,_that.cardHolder,_that.cardExpiryMonth,_that.cardExpiryYear,_that.status,_that.cardType);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String topupCardId, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String status, String cardType)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PaymentCardEntity() when $default != null:
|
||||
return $default(_that.topupCardId,_that.brand,_that.maskedPan,_that.cardHolder,_that.cardExpiryMonth,_that.cardExpiryYear,_that.status,_that.cardType);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _PaymentCardEntity implements PaymentCardEntity {
|
||||
const _PaymentCardEntity({required this.topupCardId, required this.brand, required this.maskedPan, required this.cardHolder, required this.cardExpiryMonth, required this.cardExpiryYear, required this.status, required this.cardType});
|
||||
|
||||
|
||||
@override final String topupCardId;
|
||||
@override final String brand;
|
||||
@override final String maskedPan;
|
||||
@override final String cardHolder;
|
||||
@override final String cardExpiryMonth;
|
||||
@override final String cardExpiryYear;
|
||||
@override final String status;
|
||||
@override final String cardType;
|
||||
|
||||
/// Create a copy of PaymentCardEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PaymentCardEntityCopyWith<_PaymentCardEntity> get copyWith => __$PaymentCardEntityCopyWithImpl<_PaymentCardEntity>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentCardEntity&&(identical(other.topupCardId, topupCardId) || other.topupCardId == topupCardId)&&(identical(other.brand, brand) || other.brand == brand)&&(identical(other.maskedPan, maskedPan) || other.maskedPan == maskedPan)&&(identical(other.cardHolder, cardHolder) || other.cardHolder == cardHolder)&&(identical(other.cardExpiryMonth, cardExpiryMonth) || other.cardExpiryMonth == cardExpiryMonth)&&(identical(other.cardExpiryYear, cardExpiryYear) || other.cardExpiryYear == cardExpiryYear)&&(identical(other.status, status) || other.status == status)&&(identical(other.cardType, cardType) || other.cardType == cardType));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,topupCardId,brand,maskedPan,cardHolder,cardExpiryMonth,cardExpiryYear,status,cardType);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PaymentCardEntity(topupCardId: $topupCardId, brand: $brand, maskedPan: $maskedPan, cardHolder: $cardHolder, cardExpiryMonth: $cardExpiryMonth, cardExpiryYear: $cardExpiryYear, status: $status, cardType: $cardType)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PaymentCardEntityCopyWith<$Res> implements $PaymentCardEntityCopyWith<$Res> {
|
||||
factory _$PaymentCardEntityCopyWith(_PaymentCardEntity value, $Res Function(_PaymentCardEntity) _then) = __$PaymentCardEntityCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String topupCardId, String brand, String maskedPan, String cardHolder, String cardExpiryMonth, String cardExpiryYear, String status, String cardType
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$PaymentCardEntityCopyWithImpl<$Res>
|
||||
implements _$PaymentCardEntityCopyWith<$Res> {
|
||||
__$PaymentCardEntityCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PaymentCardEntity _self;
|
||||
final $Res Function(_PaymentCardEntity) _then;
|
||||
|
||||
/// Create a copy of PaymentCardEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? topupCardId = null,Object? brand = null,Object? maskedPan = null,Object? cardHolder = null,Object? cardExpiryMonth = null,Object? cardExpiryYear = null,Object? status = null,Object? cardType = null,}) {
|
||||
return _then(_PaymentCardEntity(
|
||||
topupCardId: null == topupCardId ? _self.topupCardId : topupCardId // ignore: cast_nullable_to_non_nullable
|
||||
as String,brand: null == brand ? _self.brand : brand // ignore: cast_nullable_to_non_nullable
|
||||
as String,maskedPan: null == maskedPan ? _self.maskedPan : maskedPan // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardHolder: null == cardHolder ? _self.cardHolder : cardHolder // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardExpiryMonth: null == cardExpiryMonth ? _self.cardExpiryMonth : cardExpiryMonth // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardExpiryYear: null == cardExpiryYear ? _self.cardExpiryYear : cardExpiryYear // ignore: cast_nullable_to_non_nullable
|
||||
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as String,cardType: null == cardType ? _self.cardType : cardType // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:payments/src/core/domain/entities/payment_card_entity.dart';
|
||||
import 'package:payments/src/core/domain/entities/topup_cards_entity.dart';
|
||||
|
||||
abstract class HiPayRepository {
|
||||
Future<TopupCardsEntity> topupCards();
|
||||
Future<bool> getProcessCard(String url);
|
||||
Future<List<PaymentCardEntity>> getTopupCards();
|
||||
Future<void> deleteTopupCard(String topupCardId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
abstract class GetProcessCardUseCase {
|
||||
Future<bool> getProcessCard(String url);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:payments/src/core/domain/repositories/hipay_repository.dart';
|
||||
import 'package:payments/src/features/topup_cards/domain/use_cases/get_process_card_use_case.dart';
|
||||
|
||||
class GetProcessCardUseCaseImpl implements GetProcessCardUseCase {
|
||||
GetProcessCardUseCaseImpl(this._repository);
|
||||
|
||||
final HiPayRepository _repository;
|
||||
|
||||
@override
|
||||
Future<bool> getProcessCard(String url) {
|
||||
return _repository.getProcessCard(url);
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,12 @@ class _HiPayWebViewScreenState extends ConsumerState<HiPayWebViewScreen> {
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onNavigationRequest: (request) {
|
||||
onNavigationRequest: (request) async {
|
||||
if (request.url.contains('/payments/topup-cards/webhook') &&
|
||||
request.url.contains('orderid=$orderId')) {
|
||||
await ref
|
||||
.read(hipayWebViewViewModelProvider.notifier)
|
||||
.getProcessCard(orderId);
|
||||
Navigator.of(context).pop(HiPayResult.success);
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
@@ -64,7 +67,10 @@ class _HiPayWebViewScreenState extends ConsumerState<HiPayWebViewScreen> {
|
||||
onPressed: () => Navigator.of(context).pop(HiPayResult.cancelled),
|
||||
),
|
||||
),
|
||||
body: _buildBody(state, theme),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
child: _buildBody(state, theme),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:payments/src/features/topup_cards/domain/use_cases/get_process_card_use_case.dart';
|
||||
import 'package:payments/src/features/topup_cards/domain/use_cases/topup_cards_use_case.dart';
|
||||
import 'package:payments/src/features/topup_cards/presentation/hipay_webview_view_state.dart';
|
||||
import 'package:payments/src/features/topup_cards/providers/get_process_card_use_case_provider.dart';
|
||||
import 'package:payments/src/features/topup_cards/providers/topup_cards_use_case_provider.dart';
|
||||
|
||||
final hipayWebViewViewModelProvider =
|
||||
@@ -10,10 +12,12 @@ final hipayWebViewViewModelProvider =
|
||||
|
||||
class HiPayWebViewViewModel extends Notifier<HiPayWebViewViewState> {
|
||||
late final TopupCardsUseCase _topupCardsUseCase;
|
||||
late final GetProcessCardUseCase _getProcessCardUseCase;
|
||||
|
||||
@override
|
||||
HiPayWebViewViewState build() {
|
||||
_topupCardsUseCase = ref.read(topupCardsUseCaseProvider);
|
||||
_getProcessCardUseCase = ref.read(getProcessCardUseCaseProvider);
|
||||
return const HiPayWebViewViewState();
|
||||
}
|
||||
|
||||
@@ -38,6 +42,10 @@ class HiPayWebViewViewModel extends Notifier<HiPayWebViewViewState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> getProcessCard(String url) {
|
||||
return _getProcessCardUseCase.getProcessCard(url);
|
||||
}
|
||||
|
||||
void setWebViewReady(bool ready) {
|
||||
state = state.copyWith(isWebViewReady: ready);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:payments/src/core/providers/hipay_repository_provider.dart';
|
||||
import 'package:payments/src/features/topup_cards/domain/use_cases/get_process_card_use_case.dart';
|
||||
import 'package:payments/src/features/topup_cards/domain/use_cases/get_process_card_use_case_impl.dart';
|
||||
|
||||
final getProcessCardUseCaseProvider =
|
||||
Provider.autoDispose<GetProcessCardUseCase>((ref) {
|
||||
final repository = ref.read(hipayRepositoryProvider);
|
||||
return GetProcessCardUseCaseImpl(repository);
|
||||
});
|
||||
@@ -897,6 +897,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
top_snackbar_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: top_snackbar_flutter
|
||||
sha256: ad3f93062450e8c7db97b271d405c180536408cc2be4380a59da7022eb1d750c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'src/network/dio_client.dart';
|
||||
import 'src/env/env_contract.dart';
|
||||
@@ -12,15 +14,27 @@ export 'src/repositories/questia_repository.dart';
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
Future<void> configureDependencies(EnvConfig env, {bool log = false}) async {
|
||||
final cookieJar = await buildPersistCookieJar();
|
||||
|
||||
final dio = await buildDioClient(
|
||||
baseUrl: env.apiBaseUrl,
|
||||
origin: env.apiOrigin,
|
||||
log: log,
|
||||
cookieJar: cookieJar,
|
||||
);
|
||||
|
||||
getIt.registerLazySingleton<CookieJar>(() => cookieJar);
|
||||
getIt.registerLazySingleton<Dio>(() => dio);
|
||||
getIt.registerLazySingleton<QuestiaApi>(() => QuestiaApi(getIt<Dio>()));
|
||||
getIt.registerLazySingleton<QuestiaRepository>(
|
||||
() => QuestiaRepositoryImpl(getIt<QuestiaApi>()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> clearSessionData() async {
|
||||
final cookieJar = getIt<CookieJar>();
|
||||
await cookieJar.deleteAll();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.clear();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ Future<Dio> buildDioClient({
|
||||
),
|
||||
);
|
||||
|
||||
final jar = cookieJar ?? await _buildPersistCookieJar();
|
||||
final jar = cookieJar ?? await buildPersistCookieJar();
|
||||
dio.interceptors.add(CookieManager(jar));
|
||||
|
||||
if (log) {
|
||||
@@ -42,7 +42,7 @@ Future<Dio> buildDioClient({
|
||||
return dio;
|
||||
}
|
||||
|
||||
Future<PersistCookieJar> _buildPersistCookieJar() async {
|
||||
Future<PersistCookieJar> buildPersistCookieJar() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
return PersistCookieJar(storage: FileStorage('${dir.path}/.cookies/'));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ dependencies:
|
||||
sdk: flutter
|
||||
get_it: ^9.0.5
|
||||
dio: ^5.9.0
|
||||
shared_preferences: ^2.5.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -158,6 +158,9 @@
|
||||
"deviceSetup_addAnotherKid": "Ein weiteres Kind hinzufügen",
|
||||
"deviceSetup_start": "Los geht's!",
|
||||
"deviceSetup_giveFirstAllowance": "Gib das erste Taschengeld",
|
||||
"deviceSetup_paymentCancelled": "Die Zahlung wurde abgebrochen. Bitte versuche es erneut.",
|
||||
"deviceSetup_skipAndConfigureLater": "Überspringen und später konfigurieren",
|
||||
"deviceSetup_paymentSuccess": "Zahlung erfolgreich abgeschlossen!",
|
||||
"deviceSetup_scanQr": "QR scannen",
|
||||
"deviceSetup_scanQr_hint": "Richte den QR-Code innerhalb des Rahmens aus",
|
||||
"errorScanStrapRequired": "Scanne das Armband oder gib den Code ein, um fortzufahren",
|
||||
|
||||
@@ -158,6 +158,9 @@
|
||||
"deviceSetup_addAnotherKid": "Add another child",
|
||||
"deviceSetup_start": "Start!",
|
||||
"deviceSetup_giveFirstAllowance": "Give their first allowance",
|
||||
"deviceSetup_paymentCancelled": "Payment was cancelled. Please try again.",
|
||||
"deviceSetup_skipAndConfigureLater": "Skip and configure later",
|
||||
"deviceSetup_paymentSuccess": "Payment completed successfully!",
|
||||
"deviceSetup_scanQr": "Scan QR",
|
||||
"deviceSetup_scanQr_hint": "Center the QR inside the frame",
|
||||
"errorScanStrapRequired": "Scan the band or enter the code to continue",
|
||||
|
||||
@@ -158,6 +158,9 @@
|
||||
"deviceSetup_addAnotherKid": "Añadir otro peque",
|
||||
"deviceSetup_start": "¡Empezar!",
|
||||
"deviceSetup_giveFirstAllowance": "Dale su primera paga",
|
||||
"deviceSetup_paymentCancelled": "El pago fue cancelado. Inténtalo de nuevo.",
|
||||
"deviceSetup_skipAndConfigureLater": "Saltar y configurar más tarde",
|
||||
"deviceSetup_paymentSuccess": "¡Pago realizado con éxito!",
|
||||
"deviceSetup_scanQr": "Escanear QR",
|
||||
"deviceSetup_scanQr_hint": "Centra el QR dentro del recuadro",
|
||||
"errorScanStrapRequired": "Escanea la correa o introduce el código para continuar",
|
||||
|
||||
@@ -158,6 +158,9 @@
|
||||
"deviceSetup_addAnotherKid": "Ajouter un autre enfant",
|
||||
"deviceSetup_start": "Commencer!",
|
||||
"deviceSetup_giveFirstAllowance": "Donner sa première allocation",
|
||||
"deviceSetup_paymentCancelled": "Le paiement a été annulé. Veuillez réessayer.",
|
||||
"deviceSetup_skipAndConfigureLater": "Passer et configurer plus tard",
|
||||
"deviceSetup_paymentSuccess": "Paiement effectué avec succès !",
|
||||
"deviceSetup_scanQr": "Scanner le QR",
|
||||
"deviceSetup_scanQr_hint": "Place le QR au centre du cadre",
|
||||
"errorScanStrapRequired": "Scannez le bracelet ou saisissez le code pour continuer",
|
||||
|
||||
@@ -158,6 +158,9 @@
|
||||
"deviceSetup_addAnotherKid": "Aggiungi un altro bambino",
|
||||
"deviceSetup_start": "Inizia!",
|
||||
"deviceSetup_giveFirstAllowance": "Dagli la sua prima paghetta",
|
||||
"deviceSetup_paymentCancelled": "Il pagamento è stato annullato. Riprova.",
|
||||
"deviceSetup_skipAndConfigureLater": "Salta e configura più tardi",
|
||||
"deviceSetup_paymentSuccess": "Pagamento completato con successo!",
|
||||
"deviceSetup_scanQr": "Scansiona QR",
|
||||
"deviceSetup_scanQr_hint": "Centra il QR all'interno del riquadro",
|
||||
"errorScanStrapRequired": "Scansiona il cinturino o inserisci il codice per continuare",
|
||||
|
||||
@@ -158,6 +158,9 @@
|
||||
"deviceSetup_addAnotherKid": "Adicionar outra criança",
|
||||
"deviceSetup_start": "Começar!",
|
||||
"deviceSetup_giveFirstAllowance": "Dá-lhe a primeira mesada",
|
||||
"deviceSetup_paymentCancelled": "O pagamento foi cancelado. Tente novamente.",
|
||||
"deviceSetup_skipAndConfigureLater": "Pular e configurar mais tarde",
|
||||
"deviceSetup_paymentSuccess": "Pagamento realizado com sucesso!",
|
||||
"deviceSetup_scanQr": "Digitalizar QR",
|
||||
"deviceSetup_scanQr_hint": "Centraliza o QR dentro da moldura",
|
||||
"errorScanStrapRequired": "Digitaliza a pulseira ou introduz o código para continuar",
|
||||
|
||||
@@ -205,6 +205,12 @@ class I18n {
|
||||
static const String deviceSetup_start = 'deviceSetup_start';
|
||||
static const String deviceSetup_giveFirstAllowance =
|
||||
'deviceSetup_giveFirstAllowance';
|
||||
static const String deviceSetup_paymentCancelled =
|
||||
'deviceSetup_paymentCancelled';
|
||||
static const String deviceSetup_skipAndConfigureLater =
|
||||
'deviceSetup_skipAndConfigureLater';
|
||||
static const String deviceSetup_paymentSuccess =
|
||||
'deviceSetup_paymentSuccess';
|
||||
static const String deviceSetup_scanQr = 'deviceSetup_scanQr';
|
||||
static const String deviceSetup_scanQr_hint = 'deviceSetup_scanQr_hint';
|
||||
static const String errorScanStrapRequired = 'errorScanStrapRequired';
|
||||
|
||||