added device setup flow, qr reader, createChildProfile models and cookies packages
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<application
|
||||
android:label="SaveFamily"
|
||||
android:name="${applicationName}"
|
||||
|
||||
@@ -2,20 +2,40 @@ PODS:
|
||||
- Flutter (1.0.0)
|
||||
- flutter_native_splash (2.4.3):
|
||||
- Flutter
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
|
||||
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
||||
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
437F5EA1E5D92D7C421FD996 /* [CP] Embed Pods Frameworks */,
|
||||
791C3CA41F1AAEE1267769C8 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -325,6 +326,23 @@
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
791C3CA41F1AAEE1267769C8 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
<true/>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Necesitamos la cámara para escanear códigos QR</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
|
||||
@@ -7,6 +7,8 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:sf_app_platform/config/env/questia_env_config.dart';
|
||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||
import 'package:navigation/navigation_module.dart';
|
||||
import 'package:sf_app_platform/providers/app_state_provider.dart';
|
||||
import 'package:sf_app_platform/providers/permissions/permissions_provider.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
@@ -23,11 +25,39 @@ Future<void> main() async {
|
||||
runApp(const ProviderScope(child: PlatformApp()));
|
||||
}
|
||||
|
||||
class PlatformApp extends ConsumerWidget {
|
||||
class PlatformApp extends ConsumerStatefulWidget {
|
||||
const PlatformApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
PlatformAppState createState() => PlatformAppState();
|
||||
}
|
||||
|
||||
class PlatformAppState extends ConsumerState<PlatformApp>
|
||||
with WidgetsBindingObserver {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
debugPrint('State: $state');
|
||||
ref.read(appLifecycleStateProvider.notifier).setState(state);
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
ref.read(permissionsProvider.notifier).checkPermissions();
|
||||
}
|
||||
super.didChangeAppLifecycleState(state);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeUtils.init(context: context);
|
||||
|
||||
return MaterialApp.router(
|
||||
|
||||
@@ -55,9 +55,9 @@ void configureAppRouter() {
|
||||
pageBuilder: RequestRecoveryBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.deviceSignup,
|
||||
name: 'device_signup',
|
||||
pageBuilder: DeviceSignupBuilder().buildPage,
|
||||
path: AppRoutes.deviceSetup,
|
||||
name: 'device_setup',
|
||||
pageBuilder: DeviceSetupBuilder().buildPage,
|
||||
),
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: (context, state, navShell) {
|
||||
|
||||
18
apps/mobile_app/lib/providers/app_state_provider.dart
Normal file
18
apps/mobile_app/lib/providers/app_state_provider.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
final appLifecycleStateProvider =
|
||||
NotifierProvider<AppLifecycleNotifier, AppLifecycleState>(
|
||||
AppLifecycleNotifier.new,
|
||||
);
|
||||
|
||||
class AppLifecycleNotifier extends Notifier<AppLifecycleState> {
|
||||
@override
|
||||
AppLifecycleState build() {
|
||||
return AppLifecycleState.resumed;
|
||||
}
|
||||
|
||||
void setState(AppLifecycleState newState) {
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
final permissionsProvider =
|
||||
NotifierProvider<PermissionsNotifier, PermissionsState>(
|
||||
PermissionsNotifier.new,
|
||||
);
|
||||
|
||||
class PermissionsNotifier extends Notifier<PermissionsState> {
|
||||
@override
|
||||
PermissionsState build() {
|
||||
final initialState = PermissionsState();
|
||||
checkPermissions();
|
||||
return initialState;
|
||||
}
|
||||
|
||||
Future<void> checkPermissions() async {
|
||||
final permissionsArray = await Future.wait([
|
||||
Permission.camera.status,
|
||||
Permission.photos.status,
|
||||
]);
|
||||
|
||||
state = state.copyWith(
|
||||
camera: permissionsArray[0],
|
||||
photoLibrary: permissionsArray[1],
|
||||
);
|
||||
}
|
||||
|
||||
void openSettinsScreen() {
|
||||
openAppSettings();
|
||||
}
|
||||
|
||||
void _checkPerssionState(PermissionStatus status) {
|
||||
if (status == PermissionStatus.permanentlyDenied) {
|
||||
openSettinsScreen();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> requestCameraAccess() async {
|
||||
final status = await Permission.camera.request();
|
||||
state = state.copyWith(camera: status);
|
||||
_checkPerssionState(status);
|
||||
return status == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
Future<bool> requestPhotoLibraryAccess() async {
|
||||
final status = await Permission.photos.request();
|
||||
state = state.copyWith(photoLibrary: status);
|
||||
_checkPerssionState(status);
|
||||
return status == PermissionStatus.granted;
|
||||
}
|
||||
}
|
||||
|
||||
class PermissionsState {
|
||||
final PermissionStatus camera;
|
||||
final PermissionStatus photoLibrary;
|
||||
|
||||
PermissionsState({
|
||||
this.camera = PermissionStatus.denied,
|
||||
this.photoLibrary = PermissionStatus.denied,
|
||||
});
|
||||
|
||||
bool get cameraGranted => camera == PermissionStatus.granted;
|
||||
bool get photoLibraryGranted => photoLibrary == PermissionStatus.granted;
|
||||
|
||||
PermissionsState copyWith({
|
||||
PermissionStatus? camera,
|
||||
PermissionStatus? photoLibrary,
|
||||
}) {
|
||||
return PermissionsState(
|
||||
camera: camera ?? this.camera,
|
||||
photoLibrary: photoLibrary ?? this.photoLibrary,
|
||||
);
|
||||
}
|
||||
}
|
||||
2
apps/mobile_app/lib/providers/providers.dart
Normal file
2
apps/mobile_app/lib/providers/providers.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
export 'app_state_provider.dart';
|
||||
export 'permissions/permissions_provider.dart';
|
||||
@@ -200,6 +200,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
cookie_jar:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cookie_jar
|
||||
sha256: a6ac027d3ed6ed756bfce8f3ff60cb479e266f3b0fdabd6242b804b6765e52de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.8"
|
||||
country_code_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -278,6 +286,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.9.0"
|
||||
dio_cookie_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_cookie_manager
|
||||
sha256: d39c16abcc711c871b7b29bd51c6b5f3059ef39503916c6a9df7e22c4fc595e0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -632,6 +648,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mobile_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.4"
|
||||
navigation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -686,6 +710,102 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.22"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.1"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.7"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+5"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -694,6 +814,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1037,6 +1173,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1055,4 +1199,4 @@ packages:
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.9.2 <4.0.0"
|
||||
flutter: ">=3.32.0"
|
||||
flutter: ">=3.35.0"
|
||||
|
||||
@@ -70,6 +70,7 @@ dependencies:
|
||||
flutter_dotenv: ^6.0.0
|
||||
country_code_picker: ^3.4.1
|
||||
flutter_native_splash: ^2.4.7
|
||||
permission_handler: ^12.0.1
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
export 'src/features/device_sign_up/link_watch/create_profile_screen.dart';
|
||||
export 'src/features/onboarding/onboarding_builder.dart';
|
||||
export 'src/features/link_phone/presentation/request_phone/request_link_phone_builder.dart';
|
||||
export 'src/features/link_phone/presentation/verify_code/verify_link_phone_code_builder.dart';
|
||||
export 'src/features/login/login_builder.dart';
|
||||
export 'src/features/recover_password/presentation/request_recovery/request_recovery_builder.dart';
|
||||
export 'src/features/device_sign_up/device_signup_builder.dart';
|
||||
export 'src/features/device_setup/device_setup_builder.dart';
|
||||
export 'src/features/sign_up/sign_up_builder.dart';
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'package:auth/src/core/data/models/get_me_response_model.dart';
|
||||
import 'package:auth/src/core/data/models/two_fa_secret_response_model.dart';
|
||||
import 'package:auth/src/features/sign_up/domain/entities/sign_up_request_entity.dart';
|
||||
import 'package:auth/src/features/sign_up/domain/entities/two_fa_secret_entity.dart';
|
||||
|
||||
abstract class AuthRemoteDatasource {
|
||||
Future<MeUserModel> getMe();
|
||||
|
||||
Future<void> requestPhoneCode({required String phone});
|
||||
|
||||
Future<void> verifyPhoneCode({required String phone, required String code});
|
||||
@@ -11,12 +15,25 @@ abstract class AuthRemoteDatasource {
|
||||
Future<void> twoFALogin({required String token, required String code});
|
||||
Future<String> signUp({required SignUpRequestEntity request});
|
||||
|
||||
Future<TwoFASecretEntity> generateTwoFASignUp({required String token});
|
||||
Future<TwoFASecretResponseModel> generateTwoFASignUp({required String token});
|
||||
Future<void> verifyTwoFACodeSignUp({
|
||||
required String token,
|
||||
required String code,
|
||||
});
|
||||
Future<String> requestPasswordReset({String? phone, String? email});
|
||||
Future<String> requestPasswordReset({required String email});
|
||||
|
||||
Future<void> recoverPassword({required newPassword, required token});
|
||||
Future<String> createChildProfile({
|
||||
required String id,
|
||||
required String parentId,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required int bornAt,
|
||||
required String gender,
|
||||
required String relationType,
|
||||
required String address,
|
||||
required String cardPublicKey,
|
||||
required String deviceActivationCode,
|
||||
required String scaProof,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:auth/src/core/data/models/get_me_response_model.dart';
|
||||
import 'package:auth/src/core/data/models/sign_up_request_model.dart';
|
||||
import 'package:auth/src/core/data/models/sign_up_response_model.dart';
|
||||
import 'package:auth/src/core/data/models/two_fa_secret_response_model.dart';
|
||||
@@ -16,6 +17,23 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
|
||||
|
||||
final QuestiaRepository _repository;
|
||||
|
||||
@override
|
||||
Future<MeUserModel> getMe() async {
|
||||
try {
|
||||
final response = await _repository.get<Map<String, dynamic>>('/auth/me');
|
||||
|
||||
final data = response.data;
|
||||
if (data == null || data.isEmpty) {
|
||||
throw Exception('Empty response from /auth/me');
|
||||
}
|
||||
|
||||
final parsed = GetMeResponseModel.fromJson(data);
|
||||
return parsed.item;
|
||||
} on DioException catch (error) {
|
||||
throw _mapDioError(error, defaultMessage: 'Error in /auth/me');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> requestPhoneCode({required String phone}) async {
|
||||
try {
|
||||
@@ -127,7 +145,9 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TwoFASecretEntity> generateTwoFASignUp({required String token}) async {
|
||||
Future<TwoFASecretResponseModel> generateTwoFASignUp({
|
||||
required String token,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _repository.post<Map<String, dynamic>>(
|
||||
'/auth/totp/secret',
|
||||
@@ -140,7 +160,7 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
|
||||
}
|
||||
|
||||
final model = TwoFASecretResponseModel.fromJson(data);
|
||||
return model.toEntity();
|
||||
return model;
|
||||
} on DioException catch (error) {
|
||||
throw _mapDioError(error, defaultMessage: 'Error in twoFASignUp');
|
||||
}
|
||||
@@ -169,32 +189,26 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> requestPasswordReset({
|
||||
String? phone,
|
||||
String? email
|
||||
}) async {
|
||||
Future<String> requestPasswordReset({required String email}) async {
|
||||
try {
|
||||
if (phone == null && email == null) {
|
||||
throw FormatException("No phone or email address given");
|
||||
}
|
||||
late final Map<String, dynamic> body;
|
||||
body = {'email': email};
|
||||
|
||||
// late final Map<String, dynamic> body;
|
||||
if (email != null) {
|
||||
// body = {'email': email};
|
||||
return 'ec14b7e7-58dd-4a59-9f41-0da86eaabf14';
|
||||
} else {
|
||||
// body = {'phone': phone!};
|
||||
return 'ec14b7e7-58dd-4a59-9f41-0da86eaabf14';
|
||||
// throw Exception("reset by phone is not currently implemented");
|
||||
}
|
||||
/*final response = await _repository.put<Map<String, dynamic>>(
|
||||
final response = await _repository.put<String>(
|
||||
'/auth/reset-password',
|
||||
body: body,
|
||||
);
|
||||
final token = response.data!['token'];
|
||||
return token;*/
|
||||
final data = response.data;
|
||||
if (data == null || data.isEmpty) {
|
||||
throw Exception('Empty response from /auth/totp/code');
|
||||
}
|
||||
|
||||
return data;
|
||||
} on DioException catch (error) {
|
||||
throw _mapDioError(error, defaultMessage: 'Error to request password reset');
|
||||
throw _mapDioError(
|
||||
error,
|
||||
defaultMessage: 'Error to request password reset',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +220,52 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
|
||||
body: <String, dynamic>{'newPassword': newPassword, 'token': token},
|
||||
);
|
||||
} on DioException catch (error) {
|
||||
throw _mapDioError(error, defaultMessage: 'Error to request password recovery');
|
||||
throw _mapDioError(
|
||||
error,
|
||||
defaultMessage: 'Error to request password recovery',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> createChildProfile({
|
||||
required String id,
|
||||
required String parentId,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required int bornAt,
|
||||
required String gender,
|
||||
required String relationType,
|
||||
required String address,
|
||||
required String cardPublicKey,
|
||||
required String deviceActivationCode,
|
||||
required String scaProof,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _repository.post<Map<String, dynamic>>(
|
||||
'/auth/child-profiles',
|
||||
body: <String, dynamic>{
|
||||
'id': id,
|
||||
'parentId': parentId,
|
||||
'firstName': firstName,
|
||||
'lastName': lastName,
|
||||
'bornAt': bornAt,
|
||||
'gender': gender,
|
||||
'relationType': relationType,
|
||||
'address': address,
|
||||
'cardPublicKey': cardPublicKey,
|
||||
'deviceActivationCode': deviceActivationCode,
|
||||
'scaProof': scaProof,
|
||||
},
|
||||
);
|
||||
final data = response.data;
|
||||
if (data == null || data.isEmpty) {
|
||||
throw Exception('Empty response from /auth/child-profiles');
|
||||
} else {
|
||||
return data['id'];
|
||||
}
|
||||
} on DioException catch (error) {
|
||||
throw _mapDioError(error, defaultMessage: 'Error in createChildProfile');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'get_me_response_model.freezed.dart';
|
||||
part 'get_me_response_model.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class GetMeResponseModel with _$GetMeResponseModel {
|
||||
const factory GetMeResponseModel({required MeUserModel item}) =
|
||||
_GetMeResponseModel;
|
||||
|
||||
factory GetMeResponseModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$GetMeResponseModelFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class MeUserModel with _$MeUserModel {
|
||||
const factory MeUserModel({
|
||||
required String id,
|
||||
required String delegationId,
|
||||
required String email,
|
||||
required int createdAt,
|
||||
required int updatedAt,
|
||||
required String status,
|
||||
required String role,
|
||||
required int lastLogin,
|
||||
required int currentLogin,
|
||||
required String language,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required bool hasApiKey,
|
||||
required String phone,
|
||||
}) = _MeUserModel;
|
||||
|
||||
factory MeUserModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$MeUserModelFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
// 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 'get_me_response_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$GetMeResponseModel {
|
||||
|
||||
MeUserModel get item;
|
||||
/// Create a copy of GetMeResponseModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$GetMeResponseModelCopyWith<GetMeResponseModel> get copyWith => _$GetMeResponseModelCopyWithImpl<GetMeResponseModel>(this as GetMeResponseModel, _$identity);
|
||||
|
||||
/// Serializes this GetMeResponseModel to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetMeResponseModel&&(identical(other.item, item) || other.item == item));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,item);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GetMeResponseModel(item: $item)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $GetMeResponseModelCopyWith<$Res> {
|
||||
factory $GetMeResponseModelCopyWith(GetMeResponseModel value, $Res Function(GetMeResponseModel) _then) = _$GetMeResponseModelCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
MeUserModel item
|
||||
});
|
||||
|
||||
|
||||
$MeUserModelCopyWith<$Res> get item;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$GetMeResponseModelCopyWithImpl<$Res>
|
||||
implements $GetMeResponseModelCopyWith<$Res> {
|
||||
_$GetMeResponseModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final GetMeResponseModel _self;
|
||||
final $Res Function(GetMeResponseModel) _then;
|
||||
|
||||
/// Create a copy of GetMeResponseModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? item = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
item: null == item ? _self.item : item // ignore: cast_nullable_to_non_nullable
|
||||
as MeUserModel,
|
||||
));
|
||||
}
|
||||
/// Create a copy of GetMeResponseModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$MeUserModelCopyWith<$Res> get item {
|
||||
|
||||
return $MeUserModelCopyWith<$Res>(_self.item, (value) {
|
||||
return _then(_self.copyWith(item: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [GetMeResponseModel].
|
||||
extension GetMeResponseModelPatterns on GetMeResponseModel {
|
||||
/// 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( _GetMeResponseModel value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetMeResponseModel() 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( _GetMeResponseModel value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetMeResponseModel():
|
||||
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( _GetMeResponseModel value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetMeResponseModel() 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( MeUserModel item)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetMeResponseModel() when $default != null:
|
||||
return $default(_that.item);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( MeUserModel item) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetMeResponseModel():
|
||||
return $default(_that.item);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( MeUserModel item)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetMeResponseModel() when $default != null:
|
||||
return $default(_that.item);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _GetMeResponseModel implements GetMeResponseModel {
|
||||
const _GetMeResponseModel({required this.item});
|
||||
factory _GetMeResponseModel.fromJson(Map<String, dynamic> json) => _$GetMeResponseModelFromJson(json);
|
||||
|
||||
@override final MeUserModel item;
|
||||
|
||||
/// Create a copy of GetMeResponseModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$GetMeResponseModelCopyWith<_GetMeResponseModel> get copyWith => __$GetMeResponseModelCopyWithImpl<_GetMeResponseModel>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$GetMeResponseModelToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetMeResponseModel&&(identical(other.item, item) || other.item == item));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,item);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GetMeResponseModel(item: $item)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$GetMeResponseModelCopyWith<$Res> implements $GetMeResponseModelCopyWith<$Res> {
|
||||
factory _$GetMeResponseModelCopyWith(_GetMeResponseModel value, $Res Function(_GetMeResponseModel) _then) = __$GetMeResponseModelCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
MeUserModel item
|
||||
});
|
||||
|
||||
|
||||
@override $MeUserModelCopyWith<$Res> get item;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$GetMeResponseModelCopyWithImpl<$Res>
|
||||
implements _$GetMeResponseModelCopyWith<$Res> {
|
||||
__$GetMeResponseModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _GetMeResponseModel _self;
|
||||
final $Res Function(_GetMeResponseModel) _then;
|
||||
|
||||
/// Create a copy of GetMeResponseModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? item = null,}) {
|
||||
return _then(_GetMeResponseModel(
|
||||
item: null == item ? _self.item : item // ignore: cast_nullable_to_non_nullable
|
||||
as MeUserModel,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of GetMeResponseModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$MeUserModelCopyWith<$Res> get item {
|
||||
|
||||
return $MeUserModelCopyWith<$Res>(_self.item, (value) {
|
||||
return _then(_self.copyWith(item: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$MeUserModel {
|
||||
|
||||
String get id; String get delegationId; String get email; int get createdAt; int get updatedAt; String get status; String get role; int get lastLogin; int get currentLogin; String get language; String get firstName; String get lastName; bool get hasApiKey; String get phone;
|
||||
/// Create a copy of MeUserModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$MeUserModelCopyWith<MeUserModel> get copyWith => _$MeUserModelCopyWithImpl<MeUserModel>(this as MeUserModel, _$identity);
|
||||
|
||||
/// Serializes this MeUserModel to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is MeUserModel&&(identical(other.id, id) || other.id == id)&&(identical(other.delegationId, delegationId) || other.delegationId == delegationId)&&(identical(other.email, email) || other.email == email)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.status, status) || other.status == status)&&(identical(other.role, role) || other.role == role)&&(identical(other.lastLogin, lastLogin) || other.lastLogin == lastLogin)&&(identical(other.currentLogin, currentLogin) || other.currentLogin == currentLogin)&&(identical(other.language, language) || other.language == language)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.hasApiKey, hasApiKey) || other.hasApiKey == hasApiKey)&&(identical(other.phone, phone) || other.phone == phone));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,delegationId,email,createdAt,updatedAt,status,role,lastLogin,currentLogin,language,firstName,lastName,hasApiKey,phone);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MeUserModel(id: $id, delegationId: $delegationId, email: $email, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, role: $role, lastLogin: $lastLogin, currentLogin: $currentLogin, language: $language, firstName: $firstName, lastName: $lastName, hasApiKey: $hasApiKey, phone: $phone)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $MeUserModelCopyWith<$Res> {
|
||||
factory $MeUserModelCopyWith(MeUserModel value, $Res Function(MeUserModel) _then) = _$MeUserModelCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String delegationId, String email, int createdAt, int updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$MeUserModelCopyWithImpl<$Res>
|
||||
implements $MeUserModelCopyWith<$Res> {
|
||||
_$MeUserModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final MeUserModel _self;
|
||||
final $Res Function(MeUserModel) _then;
|
||||
|
||||
/// Create a copy of MeUserModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? delegationId = null,Object? email = null,Object? createdAt = null,Object? updatedAt = null,Object? status = null,Object? role = null,Object? lastLogin = null,Object? currentLogin = null,Object? language = null,Object? firstName = null,Object? lastName = null,Object? hasApiKey = null,Object? phone = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,delegationId: null == delegationId ? _self.delegationId : delegationId // ignore: cast_nullable_to_non_nullable
|
||||
as String,email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable
|
||||
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as int,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as int,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as String,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastLogin: null == lastLogin ? _self.lastLogin : lastLogin // ignore: cast_nullable_to_non_nullable
|
||||
as int,currentLogin: null == currentLogin ? _self.currentLogin : currentLogin // ignore: cast_nullable_to_non_nullable
|
||||
as int,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||
as String,hasApiKey: null == hasApiKey ? _self.hasApiKey : hasApiKey // ignore: cast_nullable_to_non_nullable
|
||||
as bool,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [MeUserModel].
|
||||
extension MeUserModelPatterns on MeUserModel {
|
||||
/// 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( _MeUserModel value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _MeUserModel() 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( _MeUserModel value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _MeUserModel():
|
||||
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( _MeUserModel value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _MeUserModel() 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 id, String delegationId, String email, int createdAt, int updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _MeUserModel() when $default != null:
|
||||
return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.updatedAt,_that.status,_that.role,_that.lastLogin,_that.currentLogin,_that.language,_that.firstName,_that.lastName,_that.hasApiKey,_that.phone);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 id, String delegationId, String email, int createdAt, int updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _MeUserModel():
|
||||
return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.updatedAt,_that.status,_that.role,_that.lastLogin,_that.currentLogin,_that.language,_that.firstName,_that.lastName,_that.hasApiKey,_that.phone);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 id, String delegationId, String email, int createdAt, int updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _MeUserModel() when $default != null:
|
||||
return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.updatedAt,_that.status,_that.role,_that.lastLogin,_that.currentLogin,_that.language,_that.firstName,_that.lastName,_that.hasApiKey,_that.phone);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _MeUserModel implements MeUserModel {
|
||||
const _MeUserModel({required this.id, required this.delegationId, required this.email, required this.createdAt, required this.updatedAt, required this.status, required this.role, required this.lastLogin, required this.currentLogin, required this.language, required this.firstName, required this.lastName, required this.hasApiKey, required this.phone});
|
||||
factory _MeUserModel.fromJson(Map<String, dynamic> json) => _$MeUserModelFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String delegationId;
|
||||
@override final String email;
|
||||
@override final int createdAt;
|
||||
@override final int updatedAt;
|
||||
@override final String status;
|
||||
@override final String role;
|
||||
@override final int lastLogin;
|
||||
@override final int currentLogin;
|
||||
@override final String language;
|
||||
@override final String firstName;
|
||||
@override final String lastName;
|
||||
@override final bool hasApiKey;
|
||||
@override final String phone;
|
||||
|
||||
/// Create a copy of MeUserModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$MeUserModelCopyWith<_MeUserModel> get copyWith => __$MeUserModelCopyWithImpl<_MeUserModel>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$MeUserModelToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MeUserModel&&(identical(other.id, id) || other.id == id)&&(identical(other.delegationId, delegationId) || other.delegationId == delegationId)&&(identical(other.email, email) || other.email == email)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.status, status) || other.status == status)&&(identical(other.role, role) || other.role == role)&&(identical(other.lastLogin, lastLogin) || other.lastLogin == lastLogin)&&(identical(other.currentLogin, currentLogin) || other.currentLogin == currentLogin)&&(identical(other.language, language) || other.language == language)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.hasApiKey, hasApiKey) || other.hasApiKey == hasApiKey)&&(identical(other.phone, phone) || other.phone == phone));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,delegationId,email,createdAt,updatedAt,status,role,lastLogin,currentLogin,language,firstName,lastName,hasApiKey,phone);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MeUserModel(id: $id, delegationId: $delegationId, email: $email, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, role: $role, lastLogin: $lastLogin, currentLogin: $currentLogin, language: $language, firstName: $firstName, lastName: $lastName, hasApiKey: $hasApiKey, phone: $phone)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$MeUserModelCopyWith<$Res> implements $MeUserModelCopyWith<$Res> {
|
||||
factory _$MeUserModelCopyWith(_MeUserModel value, $Res Function(_MeUserModel) _then) = __$MeUserModelCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String delegationId, String email, int createdAt, int updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$MeUserModelCopyWithImpl<$Res>
|
||||
implements _$MeUserModelCopyWith<$Res> {
|
||||
__$MeUserModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _MeUserModel _self;
|
||||
final $Res Function(_MeUserModel) _then;
|
||||
|
||||
/// Create a copy of MeUserModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? delegationId = null,Object? email = null,Object? createdAt = null,Object? updatedAt = null,Object? status = null,Object? role = null,Object? lastLogin = null,Object? currentLogin = null,Object? language = null,Object? firstName = null,Object? lastName = null,Object? hasApiKey = null,Object? phone = null,}) {
|
||||
return _then(_MeUserModel(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,delegationId: null == delegationId ? _self.delegationId : delegationId // ignore: cast_nullable_to_non_nullable
|
||||
as String,email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable
|
||||
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as int,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as int,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as String,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastLogin: null == lastLogin ? _self.lastLogin : lastLogin // ignore: cast_nullable_to_non_nullable
|
||||
as int,currentLogin: null == currentLogin ? _self.currentLogin : currentLogin // ignore: cast_nullable_to_non_nullable
|
||||
as int,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||
as String,hasApiKey: null == hasApiKey ? _self.hasApiKey : hasApiKey // ignore: cast_nullable_to_non_nullable
|
||||
as bool,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'get_me_response_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_GetMeResponseModel _$GetMeResponseModelFromJson(Map<String, dynamic> json) =>
|
||||
_GetMeResponseModel(
|
||||
item: MeUserModel.fromJson(json['item'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$GetMeResponseModelToJson(_GetMeResponseModel instance) =>
|
||||
<String, dynamic>{'item': instance.item};
|
||||
|
||||
_MeUserModel _$MeUserModelFromJson(Map<String, dynamic> json) => _MeUserModel(
|
||||
id: json['id'] as String,
|
||||
delegationId: json['delegationId'] as String,
|
||||
email: json['email'] as String,
|
||||
createdAt: (json['createdAt'] as num).toInt(),
|
||||
updatedAt: (json['updatedAt'] as num).toInt(),
|
||||
status: json['status'] as String,
|
||||
role: json['role'] as String,
|
||||
lastLogin: (json['lastLogin'] as num).toInt(),
|
||||
currentLogin: (json['currentLogin'] as num).toInt(),
|
||||
language: json['language'] as String,
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
hasApiKey: json['hasApiKey'] as bool,
|
||||
phone: json['phone'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$MeUserModelToJson(_MeUserModel instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'delegationId': instance.delegationId,
|
||||
'email': instance.email,
|
||||
'createdAt': instance.createdAt,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'status': instance.status,
|
||||
'role': instance.role,
|
||||
'lastLogin': instance.lastLogin,
|
||||
'currentLogin': instance.currentLogin,
|
||||
'language': instance.language,
|
||||
'firstName': instance.firstName,
|
||||
'lastName': instance.lastName,
|
||||
'hasApiKey': instance.hasApiKey,
|
||||
'phone': instance.phone,
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:auth/src/core/data/datasource/auth_remote_datasource.dart';
|
||||
import 'package:auth/src/core/data/models/two_fa_secret_response_model.dart';
|
||||
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
|
||||
import 'package:auth/src/features/sign_up/domain/entities/sign_up_request_entity.dart';
|
||||
import 'package:auth/src/features/sign_up/domain/entities/two_fa_secret_entity.dart';
|
||||
@@ -34,7 +35,9 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TwoFASecretEntity> generateTwoFASignUp({required String token}) {
|
||||
Future<TwoFASecretResponseModel> generateTwoFASignUp({
|
||||
required String token,
|
||||
}) {
|
||||
return _remote.generateTwoFASignUp(token: token);
|
||||
}
|
||||
|
||||
@@ -47,8 +50,8 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> requestPasswordReset({String? phone, String? email}) {
|
||||
return _remote.requestPasswordReset(phone: phone, email: email);
|
||||
Future<String> requestPasswordReset({required String email}) {
|
||||
return _remote.requestPasswordReset(email: email);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -58,4 +61,33 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
}) {
|
||||
return _remote.recoverPassword(newPassword: newPassword, token: token);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> createChildProfile({
|
||||
required String id,
|
||||
required String parentId,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required int bornAt,
|
||||
required String gender,
|
||||
required String relationType,
|
||||
required String address,
|
||||
required String cardPublicKey,
|
||||
required String deviceActivationCode,
|
||||
required String scaProof,
|
||||
}) {
|
||||
return _remote.createChildProfile(
|
||||
id: id,
|
||||
parentId: parentId,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
bornAt: bornAt,
|
||||
gender: gender,
|
||||
relationType: relationType,
|
||||
address: address,
|
||||
cardPublicKey: cardPublicKey,
|
||||
deviceActivationCode: deviceActivationCode,
|
||||
scaProof: scaProof,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:auth/src/core/data/models/two_fa_secret_response_model.dart';
|
||||
import 'package:auth/src/features/sign_up/domain/entities/sign_up_request_entity.dart';
|
||||
import 'package:auth/src/features/sign_up/domain/entities/two_fa_secret_entity.dart';
|
||||
|
||||
@@ -9,7 +10,7 @@ abstract class AuthRepository {
|
||||
Future<String> login({required String email, required String password});
|
||||
Future<void> twoFactor({required String token, required String code});
|
||||
|
||||
Future<String> requestPasswordReset({String phone, String email});
|
||||
Future<String> requestPasswordReset({required String email});
|
||||
|
||||
Future<void> recoverPassword({
|
||||
required String newPassword,
|
||||
@@ -18,9 +19,22 @@ abstract class AuthRepository {
|
||||
|
||||
Future<String> signUp({required SignUpRequestEntity request});
|
||||
|
||||
Future<TwoFASecretEntity> generateTwoFASignUp({required String token});
|
||||
Future<TwoFASecretResponseModel> generateTwoFASignUp({required String token});
|
||||
Future<void> verifyTwoFACodeSignUp({
|
||||
required String token,
|
||||
required String code,
|
||||
});
|
||||
Future<String> createChildProfile({
|
||||
required String id,
|
||||
required String parentId,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required int bornAt,
|
||||
required String gender,
|
||||
required String relationType,
|
||||
required String address,
|
||||
required String cardPublicKey,
|
||||
required String deviceActivationCode,
|
||||
required String scaProof,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:auth/src/features/device_sign_up/device_signup_screen.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/device_setup_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
|
||||
class DeviceSignupBuilder {
|
||||
const DeviceSignupBuilder();
|
||||
class DeviceSetupBuilder {
|
||||
const DeviceSetupBuilder();
|
||||
|
||||
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
|
||||
|
||||
return MaterialPage<void>(
|
||||
key: state.pageKey,
|
||||
child: DeviceSignupScreen(navigationContract: navigationContract),
|
||||
child: DeviceSetupScreen(navigationContract: navigationContract),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
abstract class CreateChildProfileUseCase {
|
||||
Future<String> createChildProfile({
|
||||
required String id,
|
||||
required String parentId,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required int bornAt,
|
||||
required String gender,
|
||||
required String relationType,
|
||||
required String address,
|
||||
required String cardPublicKey,
|
||||
required String deviceActivationCode,
|
||||
required String scaProof,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
|
||||
import 'package:auth/src/features/device_setup/domain/create_child_profile_use_case.dart';
|
||||
|
||||
class CreateChildProfileUseCaseImpl implements CreateChildProfileUseCase {
|
||||
CreateChildProfileUseCaseImpl(this._repository);
|
||||
|
||||
final AuthRepository _repository;
|
||||
|
||||
@override
|
||||
Future<String> createChildProfile({
|
||||
required String id,
|
||||
required String parentId,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required int bornAt,
|
||||
required String gender,
|
||||
required String relationType,
|
||||
required String address,
|
||||
required String cardPublicKey,
|
||||
required String deviceActivationCode,
|
||||
required String scaProof,
|
||||
}) {
|
||||
return _repository.createChildProfile(
|
||||
id: id,
|
||||
parentId: parentId,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
bornAt: bornAt,
|
||||
gender: gender,
|
||||
relationType: relationType,
|
||||
address: address,
|
||||
cardPublicKey: cardPublicKey,
|
||||
deviceActivationCode: deviceActivationCode,
|
||||
scaProof: scaProof,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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';
|
||||
|
||||
extension AddKidStepMapper on AddKidStep {
|
||||
AddKidMainStep get mainStep {
|
||||
switch (this) {
|
||||
case AddKidStep.linkInfo:
|
||||
case AddKidStep.scanStrap:
|
||||
case AddKidStep.scanWatch:
|
||||
return AddKidMainStep.linkDevice;
|
||||
case AddKidStep.profile:
|
||||
return AddKidMainStep.profile;
|
||||
case AddKidStep.success:
|
||||
return AddKidMainStep.success;
|
||||
case AddKidStep.intro:
|
||||
return AddKidMainStep.linkDevice;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/add_kid_step_mapper.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/state/device_setup_view_model.dart';
|
||||
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/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:navigation/navigation_contract.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class DeviceSetupScreen extends ConsumerWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const DeviceSetupScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(deviceSetupViewModelProvider);
|
||||
final vm = ref.read(deviceSetupViewModelProvider.notifier);
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final mainStep = state.step.mainStep;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
state.step == AddKidStep.intro || state.step == AddKidStep.success
|
||||
? const SizedBox(height: 24)
|
||||
: StepIndicator(
|
||||
total: AddKidMainStep.values.length,
|
||||
current: mainStep.index + 1,
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: StepBody(key: ValueKey(state.step), state: state),
|
||||
),
|
||||
),
|
||||
|
||||
FlowFooter(
|
||||
error: state.errorMessage,
|
||||
primaryText: context.translate(primaryButtonText(state.step)),
|
||||
secondaryText: state.step == AddKidStep.success
|
||||
? context.translate(I18n.deviceSetup_addAnotherKid)
|
||||
: null,
|
||||
onPrimary: vm.next,
|
||||
onSecondary: state.step == AddKidStep.success ? () {} : null,
|
||||
theme: theme,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String primaryButtonText(AddKidStep step) {
|
||||
switch (step) {
|
||||
case AddKidStep.intro:
|
||||
return I18n.deviceSetup_start;
|
||||
case AddKidStep.success:
|
||||
return I18n.deviceSetup_giveFirstAllowance;
|
||||
default:
|
||||
return I18n.continueKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
enum AddKidMainStep { linkDevice, profile, success }
|
||||
@@ -0,0 +1 @@
|
||||
enum AddKidStep { intro, linkInfo, scanStrap, scanWatch, profile, success }
|
||||
@@ -0,0 +1 @@
|
||||
enum ScanLinkStep { strap, watch }
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:auth/src/core/providers/auth_repository_provider.dart';
|
||||
import 'package:auth/src/features/device_setup/domain/create_child_profile_use_case.dart';
|
||||
import 'package:auth/src/features/device_setup/domain/create_child_profile_use_case_impl.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final createChildProfileUseCaseProvider =
|
||||
Provider.autoDispose<CreateChildProfileUseCase>((ref) {
|
||||
final authRepository = ref.read(authRepositoryProvider);
|
||||
return CreateChildProfileUseCaseImpl(authRepository);
|
||||
});
|
||||
@@ -0,0 +1,182 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class QrScannerScreen extends StatefulWidget {
|
||||
const QrScannerScreen({super.key});
|
||||
|
||||
@override
|
||||
State<QrScannerScreen> createState() => _QrScannerScreenState();
|
||||
}
|
||||
|
||||
class _QrScannerScreenState extends State<QrScannerScreen> {
|
||||
late final MobileScannerController _controller;
|
||||
|
||||
bool _alreadyReturned = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = MobileScannerController(
|
||||
detectionSpeed: DetectionSpeed.noDuplicates,
|
||||
formats: const [BarcodeFormat.qrCode],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _returnResult(String value) {
|
||||
if (_alreadyReturned) return;
|
||||
_alreadyReturned = true;
|
||||
Navigator.of(context).pop(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.black,
|
||||
foregroundColor: Colors.white,
|
||||
title: Text(context.translate(I18n.deviceSetup_scanQr)),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.flash_on),
|
||||
onPressed: () => _controller.toggleTorch(),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
MobileScanner(
|
||||
controller: _controller,
|
||||
onDetect: (capture) {
|
||||
if (capture.barcodes.isEmpty) return;
|
||||
|
||||
final rawValue = capture.barcodes.first.rawValue;
|
||||
if (rawValue == null || rawValue.isEmpty) return;
|
||||
|
||||
_returnResult(rawValue);
|
||||
},
|
||||
),
|
||||
|
||||
const Positioned.fill(
|
||||
child: QrScannerOverlay(
|
||||
cutOutSize: 260,
|
||||
borderRadius: 18,
|
||||
borderWidth: 3,
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 26,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.6),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Text(
|
||||
context.translate(I18n.deviceSetup_scanQr_hint),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.white, fontSize: 15),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class QrScannerOverlay extends StatelessWidget {
|
||||
const QrScannerOverlay({
|
||||
super.key,
|
||||
required this.cutOutSize,
|
||||
required this.borderRadius,
|
||||
required this.borderWidth,
|
||||
});
|
||||
|
||||
final double cutOutSize;
|
||||
final double borderRadius;
|
||||
final double borderWidth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: CustomPaint(
|
||||
painter: _QrScannerOverlayPainter(
|
||||
cutOutSize: cutOutSize,
|
||||
borderRadius: borderRadius,
|
||||
borderWidth: borderWidth,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QrScannerOverlayPainter extends CustomPainter {
|
||||
_QrScannerOverlayPainter({
|
||||
required this.cutOutSize,
|
||||
required this.borderRadius,
|
||||
required this.borderWidth,
|
||||
});
|
||||
|
||||
final double cutOutSize;
|
||||
final double borderRadius;
|
||||
final double borderWidth;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final screenRect = Rect.fromLTWH(0, 0, size.width, size.height);
|
||||
|
||||
final cutOutRect = Rect.fromCenter(
|
||||
center: screenRect.center,
|
||||
width: cutOutSize,
|
||||
height: cutOutSize,
|
||||
);
|
||||
|
||||
final cutOutRRect = RRect.fromRectXY(
|
||||
cutOutRect,
|
||||
borderRadius,
|
||||
borderRadius,
|
||||
);
|
||||
|
||||
final backgroundPath = Path()..addRect(screenRect);
|
||||
final cutOutPath = Path()..addRRect(cutOutRRect);
|
||||
|
||||
final overlayPath = Path.combine(
|
||||
PathOperation.difference,
|
||||
backgroundPath,
|
||||
cutOutPath,
|
||||
);
|
||||
|
||||
final overlayPaint = Paint()
|
||||
..color = Colors.black.withValues(alpha: 0.55)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawPath(overlayPath, overlayPaint);
|
||||
|
||||
final borderPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = borderWidth
|
||||
..color = Colors.white;
|
||||
|
||||
canvas.drawRRect(cutOutRRect, borderPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/enums/scan_link_step.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/state/device_setup_view_state.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/enums/add_kid_step.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final deviceSetupViewModelProvider =
|
||||
NotifierProvider<DeviceSetupViewModel, DeviceSetupViewState>(
|
||||
DeviceSetupViewModel.new,
|
||||
);
|
||||
|
||||
class DeviceSetupViewModel extends Notifier<DeviceSetupViewState> {
|
||||
late final TextEditingController bornAtController;
|
||||
late final TextEditingController firstNameController;
|
||||
late final TextEditingController lastNameController;
|
||||
|
||||
@override
|
||||
DeviceSetupViewState build() {
|
||||
// _signUpUseCase = ref.read(signUpUseCaseProvider);
|
||||
|
||||
final initial = DeviceSetupViewState();
|
||||
_initControllers(initial);
|
||||
_addListeners();
|
||||
|
||||
ref.onDispose(disposeControllers);
|
||||
|
||||
return initial;
|
||||
}
|
||||
|
||||
void _initControllers(DeviceSetupViewState s) {
|
||||
firstNameController = TextEditingController(text: s.firstName);
|
||||
lastNameController = TextEditingController(text: s.lastName);
|
||||
bornAtController = TextEditingController(
|
||||
text: s.bornAt == null ? '' : _formatDate(s.bornAt!),
|
||||
);
|
||||
}
|
||||
|
||||
void _addListeners() {
|
||||
firstNameController.addListener(_onFirstNameChanged);
|
||||
lastNameController.addListener(_onLastNameChanged);
|
||||
bornAtController.addListener(_onBornAtTextChanged);
|
||||
}
|
||||
|
||||
void next() {
|
||||
switch (state.step) {
|
||||
case AddKidStep.intro:
|
||||
state = state.copyWith(step: AddKidStep.linkInfo);
|
||||
return;
|
||||
case AddKidStep.linkInfo:
|
||||
state = state.copyWith(step: AddKidStep.scanStrap);
|
||||
return;
|
||||
case AddKidStep.scanStrap:
|
||||
if (state.strapQr.isEmpty) {
|
||||
state = state.copyWith(
|
||||
errorMessage: 'Escanea la correa para continuar',
|
||||
);
|
||||
return;
|
||||
}
|
||||
state = state.copyWith(step: AddKidStep.scanWatch);
|
||||
return;
|
||||
case AddKidStep.scanWatch:
|
||||
final hasWatch = state.watchQr.isNotEmpty || state.watchCode.isNotEmpty;
|
||||
if (!hasWatch) {
|
||||
state = state.copyWith(
|
||||
errorMessage: 'Escanea el reloj o introduce el código',
|
||||
);
|
||||
return;
|
||||
}
|
||||
state = state.copyWith(step: AddKidStep.profile);
|
||||
return;
|
||||
case AddKidStep.profile:
|
||||
// final isValid = _validateProfile();
|
||||
// if (!isValid) return;
|
||||
state = state.copyWith(step: AddKidStep.success);
|
||||
return;
|
||||
|
||||
case AddKidStep.success:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void back() {
|
||||
switch (state.step) {
|
||||
case AddKidStep.intro:
|
||||
return;
|
||||
|
||||
case AddKidStep.linkInfo:
|
||||
state = state.copyWith(step: AddKidStep.intro, errorMessage: '');
|
||||
return;
|
||||
case AddKidStep.scanStrap:
|
||||
state = state.copyWith(step: AddKidStep.linkInfo);
|
||||
return;
|
||||
case AddKidStep.scanWatch:
|
||||
state = state.copyWith(step: AddKidStep.scanStrap);
|
||||
return;
|
||||
case AddKidStep.profile:
|
||||
state = state.copyWith(step: AddKidStep.scanWatch);
|
||||
return;
|
||||
case AddKidStep.success:
|
||||
state = state.copyWith(step: AddKidStep.profile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void onQrScanned({required ScanLinkStep step, required String qr}) {
|
||||
switch (step) {
|
||||
case ScanLinkStep.strap:
|
||||
state = state.copyWith(strapQr: qr, step: AddKidStep.scanWatch);
|
||||
break;
|
||||
|
||||
case ScanLinkStep.watch:
|
||||
state = state.copyWith(watchQr: qr, step: AddKidStep.profile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> pickBornAt(BuildContext context) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
final now = DateTime.now();
|
||||
final initial = state.bornAt ?? DateTime(now.year - 18, now.month, now.day);
|
||||
|
||||
final safeInitial = initial.isAfter(now) ? now : initial;
|
||||
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: safeInitial,
|
||||
firstDate: DateTime(1900, 1, 1),
|
||||
lastDate: now,
|
||||
);
|
||||
|
||||
if (!ref.mounted) return;
|
||||
if (picked == null) return;
|
||||
|
||||
setBornAt(picked);
|
||||
}
|
||||
|
||||
void setBornAt(DateTime date) {
|
||||
bornAtController.text = _formatDate(date);
|
||||
state = state.copyWith(bornAt: date);
|
||||
}
|
||||
|
||||
bool _validateProfile() {
|
||||
if (state.firstName.trim().isEmpty ||
|
||||
state.lastName.trim().isEmpty ||
|
||||
state.bornAt == null ||
|
||||
state.address.trim().isEmpty) {
|
||||
state = state.copyWith(errorMessage: 'Completa todos los campos');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
final dd = date.day.toString().padLeft(2, '0');
|
||||
final mm = date.month.toString().padLeft(2, '0');
|
||||
final yyyy = date.year.toString();
|
||||
return '$dd/$mm/$yyyy';
|
||||
}
|
||||
|
||||
void _onFirstNameChanged() {
|
||||
final text = firstNameController.text;
|
||||
if (text == state.firstName) return;
|
||||
state = state.copyWith(firstName: text, errorMessage: '');
|
||||
}
|
||||
|
||||
void _onLastNameChanged() {
|
||||
final text = lastNameController.text;
|
||||
if (text == state.lastName) return;
|
||||
state = state.copyWith(lastName: text, errorMessage: '');
|
||||
}
|
||||
|
||||
void _onBornAtTextChanged() {
|
||||
final text = bornAtController.text;
|
||||
final parsed = _tryParseDate(text);
|
||||
|
||||
if (text.trim().isEmpty) {
|
||||
if (state.bornAt != null) {
|
||||
state = state.copyWith(bornAt: null, errorMessage: '');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed != null && parsed != state.bornAt) {
|
||||
state = state.copyWith(bornAt: parsed, errorMessage: '');
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? _tryParseDate(String value) {
|
||||
final v = value.trim();
|
||||
if (v.isEmpty) return null;
|
||||
|
||||
final parts = v.split('/');
|
||||
if (parts.length != 3) return null;
|
||||
|
||||
final d = int.tryParse(parts[0]);
|
||||
final m = int.tryParse(parts[1]);
|
||||
final y = int.tryParse(parts[2]);
|
||||
|
||||
if (d == null || m == null || y == null) return null;
|
||||
|
||||
final date = DateTime(y, m, d);
|
||||
if (date.year != y || date.month != m || date.day != d) return null;
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
void disposeControllers() {
|
||||
// firstNameController.dispose();
|
||||
// lastNameController.dispose();
|
||||
bornAtController.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/enums/add_kid_step.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'device_setup_view_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class DeviceSetupViewState with _$DeviceSetupViewState {
|
||||
const factory DeviceSetupViewState({
|
||||
@Default(AddKidStep.intro) AddKidStep step,
|
||||
|
||||
@Default('') String firstName,
|
||||
@Default('') String lastName,
|
||||
DateTime? bornAt,
|
||||
@Default('') String address,
|
||||
|
||||
@Default('') String strapQr,
|
||||
@Default('') String watchQr,
|
||||
@Default('') String watchCode,
|
||||
|
||||
@Default(false) bool loading,
|
||||
@Default('') String errorMessage,
|
||||
}) = _AddKidFlowState;
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
// 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 'device_setup_view_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$DeviceSetupViewState {
|
||||
|
||||
AddKidStep get step; String get firstName; String get lastName; DateTime? get bornAt; String get address; String get strapQr; String get watchQr; String get watchCode; bool get loading; String get errorMessage;
|
||||
/// Create a copy of DeviceSetupViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$DeviceSetupViewStateCopyWith<DeviceSetupViewState> get copyWith => _$DeviceSetupViewStateCopyWithImpl<DeviceSetupViewState>(this as DeviceSetupViewState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is DeviceSetupViewState&&(identical(other.step, step) || other.step == step)&&(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.strapQr, strapQr) || other.strapQr == strapQr)&&(identical(other.watchQr, watchQr) || other.watchQr == watchQr)&&(identical(other.watchCode, watchCode) || other.watchCode == watchCode)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,step,firstName,lastName,bornAt,address,strapQr,watchQr,watchCode,loading,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DeviceSetupViewState(step: $step, firstName: $firstName, lastName: $lastName, bornAt: $bornAt, address: $address, strapQr: $strapQr, watchQr: $watchQr, watchCode: $watchCode, loading: $loading, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $DeviceSetupViewStateCopyWith<$Res> {
|
||||
factory $DeviceSetupViewStateCopyWith(DeviceSetupViewState value, $Res Function(DeviceSetupViewState) _then) = _$DeviceSetupViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
AddKidStep step, String firstName, String lastName, DateTime? bornAt, String address, String strapQr, String watchQr, String watchCode, bool loading, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$DeviceSetupViewStateCopyWithImpl<$Res>
|
||||
implements $DeviceSetupViewStateCopyWith<$Res> {
|
||||
_$DeviceSetupViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final DeviceSetupViewState _self;
|
||||
final $Res Function(DeviceSetupViewState) _then;
|
||||
|
||||
/// 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? firstName = null,Object? lastName = null,Object? bornAt = freezed,Object? address = null,Object? strapQr = null,Object? watchQr = null,Object? watchCode = null,Object? loading = null,Object? errorMessage = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable
|
||||
as AddKidStep,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||
as String,bornAt: freezed == bornAt ? _self.bornAt : bornAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,address: null == address ? _self.address : address // ignore: cast_nullable_to_non_nullable
|
||||
as String,strapQr: null == strapQr ? _self.strapQr : strapQr // ignore: cast_nullable_to_non_nullable
|
||||
as String,watchQr: null == watchQr ? _self.watchQr : watchQr // ignore: cast_nullable_to_non_nullable
|
||||
as String,watchCode: null == watchCode ? _self.watchCode : watchCode // ignore: cast_nullable_to_non_nullable
|
||||
as String,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [DeviceSetupViewState].
|
||||
extension DeviceSetupViewStatePatterns on DeviceSetupViewState {
|
||||
/// 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( _AddKidFlowState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState() 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( _AddKidFlowState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState():
|
||||
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( _AddKidFlowState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState() 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( AddKidStep step, String firstName, String lastName, DateTime? bornAt, String address, String strapQr, String watchQr, String watchCode, bool loading, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState() when $default != null:
|
||||
return $default(_that.step,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.strapQr,_that.watchQr,_that.watchCode,_that.loading,_that.errorMessage);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( AddKidStep step, String firstName, String lastName, DateTime? bornAt, String address, String strapQr, String watchQr, String watchCode, bool loading, String errorMessage) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState():
|
||||
return $default(_that.step,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.strapQr,_that.watchQr,_that.watchCode,_that.loading,_that.errorMessage);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( AddKidStep step, String firstName, String lastName, DateTime? bornAt, String address, String strapQr, String watchQr, String watchCode, bool loading, String errorMessage)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AddKidFlowState() when $default != null:
|
||||
return $default(_that.step,_that.firstName,_that.lastName,_that.bornAt,_that.address,_that.strapQr,_that.watchQr,_that.watchCode,_that.loading,_that.errorMessage);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _AddKidFlowState implements DeviceSetupViewState {
|
||||
const _AddKidFlowState({this.step = AddKidStep.intro, this.firstName = '', this.lastName = '', this.bornAt, this.address = '', this.strapQr = '', this.watchQr = '', this.watchCode = '', this.loading = false, this.errorMessage = ''});
|
||||
|
||||
|
||||
@override@JsonKey() final AddKidStep step;
|
||||
@override@JsonKey() final String firstName;
|
||||
@override@JsonKey() final String lastName;
|
||||
@override final DateTime? bornAt;
|
||||
@override@JsonKey() final String address;
|
||||
@override@JsonKey() final String strapQr;
|
||||
@override@JsonKey() final String watchQr;
|
||||
@override@JsonKey() final String watchCode;
|
||||
@override@JsonKey() final bool loading;
|
||||
@override@JsonKey() final String errorMessage;
|
||||
|
||||
/// Create a copy of DeviceSetupViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$AddKidFlowStateCopyWith<_AddKidFlowState> get copyWith => __$AddKidFlowStateCopyWithImpl<_AddKidFlowState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AddKidFlowState&&(identical(other.step, step) || other.step == step)&&(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.strapQr, strapQr) || other.strapQr == strapQr)&&(identical(other.watchQr, watchQr) || other.watchQr == watchQr)&&(identical(other.watchCode, watchCode) || other.watchCode == watchCode)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,step,firstName,lastName,bornAt,address,strapQr,watchQr,watchCode,loading,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DeviceSetupViewState(step: $step, firstName: $firstName, lastName: $lastName, bornAt: $bornAt, address: $address, strapQr: $strapQr, watchQr: $watchQr, watchCode: $watchCode, loading: $loading, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$AddKidFlowStateCopyWith<$Res> implements $DeviceSetupViewStateCopyWith<$Res> {
|
||||
factory _$AddKidFlowStateCopyWith(_AddKidFlowState value, $Res Function(_AddKidFlowState) _then) = __$AddKidFlowStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
AddKidStep step, String firstName, String lastName, DateTime? bornAt, String address, String strapQr, String watchQr, String watchCode, bool loading, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$AddKidFlowStateCopyWithImpl<$Res>
|
||||
implements _$AddKidFlowStateCopyWith<$Res> {
|
||||
__$AddKidFlowStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _AddKidFlowState _self;
|
||||
final $Res Function(_AddKidFlowState) _then;
|
||||
|
||||
/// 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? firstName = null,Object? lastName = null,Object? bornAt = freezed,Object? address = null,Object? strapQr = null,Object? watchQr = null,Object? watchCode = null,Object? loading = null,Object? errorMessage = null,}) {
|
||||
return _then(_AddKidFlowState(
|
||||
step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable
|
||||
as AddKidStep,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||
as String,bornAt: freezed == bornAt ? _self.bornAt : bornAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,address: null == address ? _self.address : address // ignore: cast_nullable_to_non_nullable
|
||||
as String,strapQr: null == strapQr ? _self.strapQr : strapQr // ignore: cast_nullable_to_non_nullable
|
||||
as String,watchQr: null == watchQr ? _self.watchQr : watchQr // ignore: cast_nullable_to_non_nullable
|
||||
as String,watchCode: null == watchCode ? _self.watchCode : watchCode // ignore: cast_nullable_to_non_nullable
|
||||
as String,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/state/device_setup_view_state.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/enums/add_kid_step.dart';
|
||||
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/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 {
|
||||
const StepBody({super.key, required this.state});
|
||||
final DeviceSetupViewState state;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (state.step) {
|
||||
case AddKidStep.intro:
|
||||
return IntroStepScreen();
|
||||
case AddKidStep.linkInfo:
|
||||
return LinkInfoStepScreen();
|
||||
case AddKidStep.scanStrap:
|
||||
return ScanStrapAndWatchStepScreen(step: ScanLinkStep.strap);
|
||||
case AddKidStep.scanWatch:
|
||||
return ScanStrapAndWatchStepScreen(step: ScanLinkStep.watch);
|
||||
case AddKidStep.profile:
|
||||
return ProfileStepScreen();
|
||||
case AddKidStep.success:
|
||||
return SuccessStepScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/widgets/numbered_steps.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 IntroStepScreen extends ConsumerWidget {
|
||||
const IntroStepScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_intro_title),
|
||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_intro_subtitle),
|
||||
style: TextStyle(fontSize: 18),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
NumberedSteps(
|
||||
steps: [
|
||||
context.translate(I18n.deviceSetup_intro_step_1),
|
||||
context.translate(I18n.deviceSetup_intro_step_2),
|
||||
context.translate(I18n.deviceSetup_intro_step_3),
|
||||
],
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_intro_ready_title),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_intro_remember_prefix),
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_intro_plan_name),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50.0),
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: context.translate(I18n.deviceSetup_intro_web_prefix),
|
||||
style: TextStyle(fontSize: 16, color: Colors.black),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: context.translate(I18n.deviceSetup_intro_web_link),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/widgets/link_info_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class LinkInfoStepScreen extends ConsumerWidget {
|
||||
const LinkInfoStepScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 65),
|
||||
child: Text(
|
||||
context.translate(I18n.deviceSetup_linkInfo_title),
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.2,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 25),
|
||||
|
||||
SvgPicture.asset("assets/images/ui/formulario.svg"),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
LinkInfoItem(
|
||||
number: 1,
|
||||
boldWord: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item1_boldWord,
|
||||
),
|
||||
titlePrefix: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item1_prefix,
|
||||
),
|
||||
subtitle: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item1_subtitle,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
LinkInfoItem(
|
||||
number: 2,
|
||||
boldWord: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item2_boldWord,
|
||||
),
|
||||
titlePrefix: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item2_prefix,
|
||||
),
|
||||
subtitle: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item2_subtitle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class ProfileStepScreen extends ConsumerWidget {
|
||||
const ProfileStepScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
final vm = ref.read(deviceSetupViewModelProvider.notifier);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_intro_step_1),
|
||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_accountData_info),
|
||||
style: TextStyle(fontSize: 18),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_startWithOneKid_info),
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
CustomTextField(
|
||||
label: context.translate(I18n.firstNameLabel),
|
||||
hint: context.translate(I18n.firstNameHint),
|
||||
controller: vm.firstNameController,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
CustomTextField(
|
||||
label: context.translate(I18n.lastNameLabel),
|
||||
hint: context.translate(I18n.lastNameHint),
|
||||
controller: vm.lastNameController,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
GestureDetector(
|
||||
onTap: () => vm.pickBornAt(context),
|
||||
child: AbsorbPointer(
|
||||
child: CustomTextField(
|
||||
label: context.translate(I18n.birthDateLabel),
|
||||
hint: context.translate(I18n.birthDateHint),
|
||||
controller: vm.bornAtController,
|
||||
readOnly: true,
|
||||
keyboardType: TextInputType.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/enums/scan_link_step.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/qr_scanner_screen.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/state/device_setup_view_model.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/widgets/scan_link_steps_indicator.dart';
|
||||
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:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class ScanStrapAndWatchStepScreen extends ConsumerWidget {
|
||||
const ScanStrapAndWatchStepScreen({super.key, required this.step});
|
||||
|
||||
final ScanLinkStep step;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final activeColor = theme.getColorFor(ThemeCode.buttonPrimary);
|
||||
final inactiveCircleColor = theme.getColorFor(
|
||||
ThemeCode.backgroundSecondary,
|
||||
);
|
||||
final inactiveLineColor = Colors.grey.shade200;
|
||||
final textPrimary = theme.getColorFor(ThemeCode.textPrimary);
|
||||
|
||||
final vm = ref.read(deviceSetupViewModelProvider.notifier);
|
||||
final state = ref.watch(deviceSetupViewModelProvider);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 65),
|
||||
child: Text(
|
||||
context.translate(I18n.deviceSetup_linkInfo_title),
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.2,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 18),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 100),
|
||||
child: ScanLinkStepsIndicator(
|
||||
step: step,
|
||||
activeColor: activeColor,
|
||||
inactiveCircleColor: inactiveCircleColor,
|
||||
inactiveLineColor: inactiveLineColor,
|
||||
textPrimary: textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 35),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item1_prefix,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item1_boldWord,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.w800),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item2_prefix,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: context.translate(
|
||||
I18n.deviceSetup_linkInfo_item2_boldWord,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.w800),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 28),
|
||||
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: () async {
|
||||
final result = await Navigator.of(context).push<String>(
|
||||
MaterialPageRoute(builder: (_) => const QrScannerScreen()),
|
||||
);
|
||||
if (result == null || result.isEmpty) return;
|
||||
vm.onQrScanned(step: step, qr: result);
|
||||
},
|
||||
child: Container(
|
||||
width: 170,
|
||||
height: 170,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade500, width: 1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
"assets/images/ui/qr.svg",
|
||||
width: 90,
|
||||
height: 90,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 22),
|
||||
|
||||
if (step == ScanLinkStep.watch) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_watchCode_orInsert),
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: CustomTextField(hint: "XXXXXXXXXX")),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: () {},
|
||||
text: context.translate(
|
||||
I18n.deviceSetup_watchCode_continueWithCode,
|
||||
),
|
||||
size: 14,
|
||||
color: theme.getColorFor(ThemeCode.buttonSecondary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
context.translate(I18n.deviceSetup_linkTroubleshoot_title),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
CustomTextButton(
|
||||
onPressed: () {},
|
||||
text: context.translate(I18n.deviceSetup_contactUs),
|
||||
weight: FontWeight.w800,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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 SuccessStepScreen extends ConsumerWidget {
|
||||
const SuccessStepScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
size: 50,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
context.translate(I18n.accountCreatedTitle),
|
||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
context.translate(I18n.accountCreatedForLabel),
|
||||
style: TextStyle(fontSize: 18),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
'Julián Alcalá',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
|
||||
Text(
|
||||
'Reloj: SaveWatch Plus 2',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
'ID del reloj: 1106652524',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
context.translate(I18n.deviceSetup_firstAllowance_title),
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FlowFooter extends StatelessWidget {
|
||||
const FlowFooter({
|
||||
super.key,
|
||||
required this.primaryText,
|
||||
required this.onPrimary,
|
||||
required this.theme,
|
||||
this.secondaryText,
|
||||
this.onSecondary,
|
||||
this.error,
|
||||
});
|
||||
|
||||
final String primaryText;
|
||||
final VoidCallback onPrimary;
|
||||
final ThemePort theme;
|
||||
|
||||
final String? secondaryText;
|
||||
final VoidCallback? onSecondary;
|
||||
|
||||
final String? error;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
border: Border(top: BorderSide(color: Colors.grey.shade300, width: 1)),
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (error != null) ...[
|
||||
Text(
|
||||
error!,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 13),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
PrimaryButton(
|
||||
text: primaryText,
|
||||
onPressed: onPrimary,
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
if (secondaryText != null && onSecondary != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
Material(
|
||||
child: InkWell(
|
||||
onTap: onSecondary,
|
||||
child: Text(
|
||||
secondaryText ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/widgets/number_circle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LinkInfoItem extends StatelessWidget {
|
||||
const LinkInfoItem({
|
||||
super.key,
|
||||
required this.number,
|
||||
required this.titlePrefix,
|
||||
required this.boldWord,
|
||||
required this.subtitle,
|
||||
});
|
||||
|
||||
final int number;
|
||||
final String titlePrefix;
|
||||
final String boldWord;
|
||||
final String subtitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
NumberCircle(number: number),
|
||||
const SizedBox(width: 10),
|
||||
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: titlePrefix,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF4B4B4B),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: boldWord,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Color(0xFF4B4B4B),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(subtitle, style: const TextStyle(fontSize: 16)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NumberCircle extends StatelessWidget {
|
||||
const NumberCircle({super.key, required this.number});
|
||||
|
||||
final int number;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFF2F2F2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Text(
|
||||
number.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFF5A5A5A),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NumberedSteps extends StatelessWidget {
|
||||
const NumberedSteps({
|
||||
super.key,
|
||||
required this.steps,
|
||||
this.color,
|
||||
this.textColor,
|
||||
this.textStyle,
|
||||
});
|
||||
|
||||
final List<String> steps;
|
||||
|
||||
final Color? color;
|
||||
final Color? textColor;
|
||||
|
||||
final TextStyle? textStyle;
|
||||
|
||||
@override
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color resolvedColor =
|
||||
color ?? Theme.of(context).colorScheme.secondaryContainer;
|
||||
final Color resolvedTextColor = textColor ?? Colors.grey.shade800;
|
||||
final TextStyle resolvedTextStyle =
|
||||
textStyle ??
|
||||
TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: resolvedTextColor,
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: List.generate(steps.length, (index) {
|
||||
final isLast = index == steps.length - 1;
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 32,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_StepCircle(
|
||||
number: index + 1,
|
||||
size: 32,
|
||||
color: resolvedColor,
|
||||
),
|
||||
if (!isLast)
|
||||
Container(
|
||||
width: 4,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade400,
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 32 * 0.15),
|
||||
child: Text(steps[index], style: resolvedTextStyle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StepCircle extends StatelessWidget {
|
||||
const _StepCircle({
|
||||
required this.number,
|
||||
required this.size,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
final int number;
|
||||
final double size;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
child: Text(
|
||||
'$number',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: size * 0.55,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:auth/src/features/device_setup/presentation/enums/scan_link_step.dart';
|
||||
import 'package:auth/src/features/device_setup/presentation/widgets/step_circle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ScanLinkStepsIndicator extends StatelessWidget {
|
||||
const ScanLinkStepsIndicator({
|
||||
super.key,
|
||||
required this.step,
|
||||
required this.activeColor,
|
||||
required this.inactiveCircleColor,
|
||||
required this.inactiveLineColor,
|
||||
required this.textPrimary,
|
||||
});
|
||||
|
||||
final ScanLinkStep step;
|
||||
final Color activeColor;
|
||||
final Color inactiveCircleColor;
|
||||
final Color inactiveLineColor;
|
||||
final Color textPrimary;
|
||||
|
||||
bool get isWatch => step == ScanLinkStep.watch;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const circleSize = 48.0;
|
||||
const lineHeight = 4.0;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
StepCircle(
|
||||
label: "1",
|
||||
size: circleSize,
|
||||
background: activeColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
child: SizedBox(
|
||||
height: lineHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Container(color: activeColor)),
|
||||
if (!isWatch)
|
||||
Expanded(child: Container(color: inactiveLineColor)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
StepCircle(
|
||||
label: "2",
|
||||
size: circleSize,
|
||||
background: isWatch ? activeColor : inactiveCircleColor,
|
||||
textColor: isWatch ? Colors.white : textPrimary,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StepCircle extends StatelessWidget {
|
||||
const StepCircle({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.size,
|
||||
required this.background,
|
||||
required this.textColor,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final double size;
|
||||
final Color background;
|
||||
final Color textColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: background, shape: BoxShape.circle),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class AddKidScreen extends ConsumerWidget {
|
||||
final nextStep;
|
||||
|
||||
const AddKidScreen({super.key, required this.nextStep});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
body: Container(
|
||||
margin: EdgeInsets.all(30),
|
||||
child: Column(
|
||||
spacing: 15,
|
||||
children: [
|
||||
Spacer(flex: 6),
|
||||
Text("Añade a tu peque", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
"Controla su gasto a la vez que aprende hábitos financieros responsables",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 30, horizontal: 50),
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Column(
|
||||
spacing: 16,
|
||||
children: List<Widget>.generate(3, (int index) =>
|
||||
Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: CircleBorder(),
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||
),
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: Center(child: Text(
|
||||
(index + 1).toString(),
|
||||
style: TextStyle(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary)
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
),
|
||||
Divider(color: Colors.red, thickness: 4,),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Text("Crea su perfil"),
|
||||
Text("Vincula su correa y su reloj"),
|
||||
Text("Carga su hucha"),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"¡Y todo listo para que tenga su dinero!",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0
|
||||
)
|
||||
),
|
||||
Text(
|
||||
"Recuerda que necesitas tener un Plan SaveFamily",
|
||||
textAlign: TextAlign.center
|
||||
),
|
||||
Text(
|
||||
"Si aún no lo tienes, puedes conseguirlo a través de nuestra web",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Spacer(flex: 8),
|
||||
PrimaryButton(
|
||||
onPressed: nextStep,
|
||||
text: "¡Empezar!",
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
import 'package:auth/auth.dart';
|
||||
import 'package:auth/src/features/device_sign_up/add_kid_screen.dart';
|
||||
import 'package:auth/src/features/device_sign_up/link_watch/link_watch_screen.dart';
|
||||
import 'package:auth/src/features/device_sign_up/link_watch/link_watch_previous_screen.dart';
|
||||
import 'package:auth/src/features/sign_up/presentation/screens/account_created_screen.dart';
|
||||
import 'package:auth/src/widgets/layouts/form_step_layout.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
|
||||
class DeviceSignupScreen extends ConsumerStatefulWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const DeviceSignupScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
ConsumerState<DeviceSignupScreen> createState() =>
|
||||
DeviceSignupScreenState(navigationContract);
|
||||
}
|
||||
|
||||
class DeviceSignupScreenState extends ConsumerState<DeviceSignupScreen> {
|
||||
late int currentStep;
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
DeviceSignupScreenState(this.navigationContract);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
currentStep = 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return getSteps()[currentStep];
|
||||
}
|
||||
|
||||
List<Widget> getSteps() {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
final continueBtn = Container(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
child: PrimaryButton(
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
currentStep++;
|
||||
}),
|
||||
},
|
||||
text: "Continuar",
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
);
|
||||
|
||||
return [
|
||||
AddKidScreen(
|
||||
nextStep: () => {
|
||||
setState(() {
|
||||
currentStep++;
|
||||
}),
|
||||
},
|
||||
),
|
||||
FormStepLayout(
|
||||
title: "Crea su perfil",
|
||||
subtitle:
|
||||
"Necesitamos estos datos para crear su cuenta y gestionar sus pagos y gastos",
|
||||
currentStep: 1,
|
||||
numSteps: 3,
|
||||
body: [CreateProfileScreen()],
|
||||
footer: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(24),
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
child: continueBtn,
|
||||
),
|
||||
],
|
||||
nextStep: () => {},
|
||||
previousStep: () => {},
|
||||
),
|
||||
FormStepLayout(
|
||||
title: "Vincula su correa y su reloj",
|
||||
currentStep: 2,
|
||||
numSteps: 3,
|
||||
body: [LinkWatchPreviousScreen()],
|
||||
footer: [continueBtn],
|
||||
nextStep: () => {},
|
||||
previousStep: () => {},
|
||||
),
|
||||
FormStepLayout(
|
||||
title: "Vincula su correa\ny su reloj",
|
||||
currentStep: 2,
|
||||
numSteps: 3,
|
||||
body: [LinkWatchScreen(step: 1)],
|
||||
footer: [continueBtn],
|
||||
nextStep: () => {},
|
||||
previousStep: () => {},
|
||||
),
|
||||
FormStepLayout(
|
||||
title: "Vincula su correa\ny su reloj",
|
||||
currentStep: 2,
|
||||
numSteps: 3,
|
||||
body: [LinkWatchScreen(step: 2)],
|
||||
footer: [continueBtn],
|
||||
nextStep: () => {},
|
||||
previousStep: () => {},
|
||||
),
|
||||
AccountCreatedScreen(navigationContract: navigationContract),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class CreateProfileScreen extends ConsumerWidget {
|
||||
const CreateProfileScreen({super.key});
|
||||
|
||||
final bool firstTime = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return Column(
|
||||
spacing: 24,
|
||||
children: [
|
||||
Text(
|
||||
"Comienza con un peque; luego podrás agregar más",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
CustomTextField(label: "Nombre", hint: "Nombre"),
|
||||
CustomTextField(label: "Apellidos", hint: "Apellidos"),
|
||||
Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text(
|
||||
"Fecha de nacimiento",
|
||||
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
keyboardType: TextInputType.number,
|
||||
hint: "DD",
|
||||
length: 2,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
keyboardType: TextInputType.number,
|
||||
hint: "MM",
|
||||
length: 2,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
keyboardType: TextInputType.number,
|
||||
hint: "AAAA",
|
||||
length: 4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
CustomTextField(
|
||||
label: "Dirección completa",
|
||||
hint: "Nombre de la calle",
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: CustomTextButton(
|
||||
onPressed: () => {},
|
||||
text: "Cambiar dirección",
|
||||
size: 18,
|
||||
weight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class LinkWatchPreviousScreen extends ConsumerWidget{
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
SvgPicture.asset("assets/images/ui/formulario.svg"),
|
||||
Row(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: CircleBorder(side: BorderSide(color: theme.getColorFor(ThemeCode.backgroundSecondary))),
|
||||
color: theme.getColorFor(ThemeCode.backgroundSecondary)
|
||||
),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: Center(child: Text("1", style: TextStyle(fontSize: 24))),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text("Escanea la correa", textAlign: TextAlign.left, style: TextStyle(fontSize: 24)),
|
||||
Text("El peque podrá realizar pagos"),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: CircleBorder(side: BorderSide(color: theme.getColorFor(ThemeCode.backgroundSecondary))),
|
||||
color: theme.getColorFor(ThemeCode.backgroundSecondary)
|
||||
),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: Center(child: Text("2", style: TextStyle(fontSize: 24))),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text("Escanea el reloj", textAlign: TextAlign.left, style: TextStyle(fontSize: 24)),
|
||||
Text("Visualizarás los gastos que se hagan"),
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
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';
|
||||
|
||||
class LinkWatchScreen extends ConsumerStatefulWidget{
|
||||
final int step;
|
||||
|
||||
const LinkWatchScreen({super.key, required this.step});
|
||||
|
||||
@override
|
||||
ConsumerState<LinkWatchScreen> createState() => LinkWatchScreenState();
|
||||
}
|
||||
|
||||
class LinkWatchScreenState extends ConsumerState<LinkWatchScreen>{
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return Column(
|
||||
spacing: 32,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Divider(
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
thickness: 4,
|
||||
indent: 93,
|
||||
endIndent: 93,
|
||||
height: 48,
|
||||
),
|
||||
if (widget.step==1)Divider(
|
||||
color: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||
thickness: 4,
|
||||
indent: 186,
|
||||
endIndent: 93,
|
||||
height: 48,
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 69),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: CircleBorder(),
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||
),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: Center(child: Text(
|
||||
"1",
|
||||
style: TextStyle(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
fontSize: 24
|
||||
)
|
||||
))
|
||||
),
|
||||
Spacer(),
|
||||
Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: CircleBorder(),
|
||||
color: theme.getColorFor(widget.step==1 ? ThemeCode.backgroundSecondary : ThemeCode.buttonPrimary)
|
||||
),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: Center(child: Text(
|
||||
"2",
|
||||
style: TextStyle(
|
||||
color: theme.getColorFor(widget.step==1 ? ThemeCode.textPrimary : ThemeCode.backgroundSecondary),
|
||||
fontSize: 24
|
||||
)
|
||||
))
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(children: [
|
||||
Spacer(),
|
||||
Text("Escanea la correa"),
|
||||
Spacer(),
|
||||
Text("Escanea el reloj"),
|
||||
Spacer(),
|
||||
]),
|
||||
Container(
|
||||
padding: EdgeInsets.all(40),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: theme.getColorFor(ThemeCode.textPrimary)),
|
||||
borderRadius: BorderRadius.all(Radius.circular(16))
|
||||
),
|
||||
child: SvgPicture.asset("assets/images/ui/qr.svg")
|
||||
),
|
||||
if (widget.step == 2)Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text("O inserta el código del reloj"),
|
||||
),
|
||||
Row(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Expanded(child: CustomTextField(
|
||||
hint: "XXXXXXXXXX",
|
||||
)),
|
||||
Expanded(child: PrimaryButton(
|
||||
onPressed: ()=>{},
|
||||
text: "Continuar con código",
|
||||
size: 16,
|
||||
color: theme.getColorFor(ThemeCode.buttonSecondary)
|
||||
))
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text("Si no consigues vincular su correa o reloj"),
|
||||
CustomTextButton(onPressed: ()=>{}, text: "Contáctanos", weight: FontWeight.w500, size: 18)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import 'package:auth/src/core/data/models/get_me_response_model.dart';
|
||||
|
||||
abstract class GetMeUserUseCase {
|
||||
Future<MeUserModel> getMe();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:auth/src/core/data/models/get_me_response_model.dart';
|
||||
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
|
||||
import 'package:auth/src/features/login/domain/get_me_user_use_case.dart';
|
||||
|
||||
class GetMeUserUseCaseImpl implements GetMeUserUseCase {
|
||||
GetMeUserUseCaseImpl(this._repository);
|
||||
|
||||
final AuthRepository _repository;
|
||||
|
||||
@override
|
||||
Future<String> login({required String email, required String password}) {
|
||||
return _repository.login(email: email, password: password);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MeUserModel> getMe() {
|
||||
// TODO: implement getMe
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
abstract class RecoverPasswordUseCase {
|
||||
Future<String> requestEmail({required String email});
|
||||
|
||||
Future<String> requestSms({required String phone});
|
||||
|
||||
Future<void> recoverPassword({required String newPassword, required String token});
|
||||
}
|
||||
Future<void> recoverPassword({
|
||||
required String newPassword,
|
||||
required String token,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,12 +12,10 @@ class RecoverPasswordUseCaseImpl implements RecoverPasswordUseCase {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> requestSms({required String phone}) async {
|
||||
return await _repository.requestPasswordReset(phone: phone);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> recoverPassword({required String newPassword, required String token}) async {
|
||||
Future<void> recoverPassword({
|
||||
required String newPassword,
|
||||
required String token,
|
||||
}) async {
|
||||
await _repository.recoverPassword(newPassword: newPassword, token: token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ class SentScreen extends ConsumerWidget {
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 48, big: 48, xl: 40)),
|
||||
SizedBox(
|
||||
height: SizeUtils.getByScreen(small: 48, big: 48, xl: 40),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -45,22 +47,36 @@ class SentScreen extends ConsumerWidget {
|
||||
Icons.check,
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
SizedBox(width: SizeUtils.getByScreen(small: 10, big: 10, xl: 6)),
|
||||
SizedBox(
|
||||
width: SizeUtils.getByScreen(small: 10, big: 10, xl: 6),
|
||||
),
|
||||
Text(
|
||||
viewState.recoveryFormat == "email"
|
||||
? context.translate(I18n.emailSent)
|
||||
: context.translate(I18n.smsSent),
|
||||
style: TextStyle(fontSize: SizeUtils.getByScreen(small: 18, big: 18, xl: 15), fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(
|
||||
small: 18,
|
||||
big: 18,
|
||||
xl: 15,
|
||||
),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 48, big: 48, xl: 40)),
|
||||
SizedBox(
|
||||
height: SizeUtils.getByScreen(small: 48, big: 48, xl: 40),
|
||||
),
|
||||
Text(
|
||||
viewState.recoveryFormat == "email"
|
||||
? context.translate(I18n.checkEmail1)
|
||||
: context.translate(I18n.checkSms1),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: SizeUtils.getByScreen(small: 17, big: 17, xl: 15), letterSpacing: 0),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 17, big: 17, xl: 15),
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
@@ -68,18 +84,21 @@ class SentScreen extends ConsumerWidget {
|
||||
? context.translate(I18n.checkEmail2)
|
||||
: context.translate(I18n.checkSms2),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: SizeUtils.getByScreen(small: 14, big: 14, xl: 12), letterSpacing: 0),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 14, big: 14, xl: 12),
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: SizeUtils.getByScreen(small: 48, big: 48, xl: 40),
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 48, big: 48, xl: 40)),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
onPressed: () {
|
||||
if ( viewState.recoveryFormat == "email") {
|
||||
if (viewState.recoveryFormat == "email") {
|
||||
viewModel.requestEmail();
|
||||
} else {
|
||||
viewModel.requestSms();
|
||||
}
|
||||
},
|
||||
text: viewState.recoveryFormat == "email"
|
||||
@@ -93,7 +112,11 @@ class SentScreen extends ConsumerWidget {
|
||||
child: PrimaryButton(
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => NewPasswordScreen(navigationContract: navigationContract)),
|
||||
MaterialPageRoute(
|
||||
builder: (_) => NewPasswordScreen(
|
||||
navigationContract: navigationContract,
|
||||
),
|
||||
),
|
||||
),
|
||||
text: context.translate(I18n.continueKey),
|
||||
color: theme.getColorFor(ThemeCode.buttonSecondary),
|
||||
|
||||
@@ -6,9 +6,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../providers/recover_password_provider.dart';
|
||||
|
||||
final recoverPasswordViewModelProvider =
|
||||
NotifierProvider.autoDispose<RecoverPasswordViewModel, RecoverPasswordViewState>(
|
||||
RecoverPasswordViewModel.new,
|
||||
);
|
||||
NotifierProvider.autoDispose<
|
||||
RecoverPasswordViewModel,
|
||||
RecoverPasswordViewState
|
||||
>(RecoverPasswordViewModel.new);
|
||||
|
||||
class RecoverPasswordViewModel extends Notifier<RecoverPasswordViewState> {
|
||||
late final RecoverPasswordUseCase _recoverPasswordUseCase;
|
||||
@@ -104,27 +105,18 @@ class RecoverPasswordViewModel extends Notifier<RecoverPasswordViewState> {
|
||||
}
|
||||
|
||||
void updateDialCode(String dialCode) {
|
||||
state = state.copyWith(
|
||||
dialCode: dialCode,
|
||||
errorMessage: '',
|
||||
);
|
||||
state = state.copyWith(dialCode: dialCode, errorMessage: '');
|
||||
}
|
||||
|
||||
void updateNewDialCode(String dialCode) {
|
||||
state = state.copyWith(
|
||||
newDialCode: dialCode,
|
||||
errorMessage: '',
|
||||
);
|
||||
state = state.copyWith(newDialCode: dialCode, errorMessage: '');
|
||||
}
|
||||
|
||||
void togglePasswordVisible(){
|
||||
state = state.copyWith(
|
||||
passwordVisible: !state.passwordVisible,
|
||||
);
|
||||
void togglePasswordVisible() {
|
||||
state = state.copyWith(passwordVisible: !state.passwordVisible);
|
||||
}
|
||||
|
||||
Future<void> requestRecovery() async {
|
||||
final trimmedNumber = state.phoneNumber.trim();
|
||||
final email = state.email.trim();
|
||||
|
||||
state = state.copyWith(
|
||||
@@ -135,8 +127,6 @@ class RecoverPasswordViewModel extends Notifier<RecoverPasswordViewState> {
|
||||
|
||||
if (email.isNotEmpty) {
|
||||
await requestEmail();
|
||||
} else if (trimmedNumber.isNotEmpty) {
|
||||
await requestSms();
|
||||
} else {
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
@@ -150,7 +140,9 @@ class RecoverPasswordViewModel extends Notifier<RecoverPasswordViewState> {
|
||||
final email = state.email.trim();
|
||||
|
||||
try {
|
||||
final String token = await _recoverPasswordUseCase.requestEmail(email: email);
|
||||
final String token = await _recoverPasswordUseCase.requestEmail(
|
||||
email: email,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
state = state.copyWith(
|
||||
@@ -172,34 +164,6 @@ class RecoverPasswordViewModel extends Notifier<RecoverPasswordViewState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> requestSms() async {
|
||||
final trimmedNumber = state.phoneNumber.trim();
|
||||
|
||||
final fullPhone = '${state.dialCode}$trimmedNumber';
|
||||
|
||||
try {
|
||||
final String token = await _recoverPasswordUseCase.requestSms(phone: fullPhone);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
errorMessage: '',
|
||||
recoveryRequested: true,
|
||||
token: token,
|
||||
recoveryFormat: 'sms'
|
||||
);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
errorMessage: e.toString(),
|
||||
recoveryRequested: false,
|
||||
passwordChanged: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> recoverPassword() async {
|
||||
//final String fullPhone = state.newDialCode + state.newPhoneNumber;
|
||||
final String password = state.password;
|
||||
@@ -244,17 +208,13 @@ class RecoverPasswordViewModel extends Notifier<RecoverPasswordViewState> {
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
isLoading: true,
|
||||
passwordChanged: false,
|
||||
);
|
||||
state = state.copyWith(isLoading: true, passwordChanged: false);
|
||||
try {
|
||||
await _recoverPasswordUseCase.recoverPassword(
|
||||
newPassword: password, token: state.token);
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
passwordChanged: true,
|
||||
newPassword: password,
|
||||
token: state.token,
|
||||
);
|
||||
state = state.copyWith(isLoading: false, passwordChanged: true);
|
||||
} catch (error) {
|
||||
state = state.copyWith(
|
||||
errorMessage: error.toString(),
|
||||
@@ -276,4 +236,4 @@ class RecoverPasswordViewModel extends Notifier<RecoverPasswordViewState> {
|
||||
newPhoneNumberController.removeListener(_onNewPhoneNumberChanged);
|
||||
newPhoneNumberController.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:auth/src/core/data/models/two_fa_secret_response_model.dart';
|
||||
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
|
||||
import 'package:auth/src/features/sign_up/domain/entities/two_fa_secret_entity.dart';
|
||||
import 'package:auth/src/features/sign_up/domain/generate_two_fa_sign_up_use_case.dart';
|
||||
@@ -8,6 +9,8 @@ class GenerateTwoFASignUpUseCaseImpl implements GenerateTwoFASignUpUseCase {
|
||||
final AuthRepository _repository;
|
||||
@override
|
||||
Future<TwoFASecretEntity> generateTwoFASignUp({required String token}) {
|
||||
return _repository.generateTwoFASignUp(token: token);
|
||||
return _repository
|
||||
.generateTwoFASignUp(token: token)
|
||||
.then((model) => model.toEntity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ dependencies:
|
||||
json_annotation: ^4.9.0
|
||||
json_serializable: ^6.11.2
|
||||
uuid: ^4.5.2
|
||||
mobile_scanner: ^7.1.4
|
||||
dio_cookie_manager: ^3.3.0
|
||||
cookie_jar: ^4.0.8
|
||||
path_provider: ^2.1.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -53,7 +53,8 @@ class HomeScreen extends ConsumerWidget {
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TextButton(
|
||||
onPressed: () => navigationContract.pushTo(AppRoutes.deviceSignup),
|
||||
onPressed: () =>
|
||||
navigationContract.pushTo(AppRoutes.deviceSetup),
|
||||
child: Text(
|
||||
"+ Añadir otro peque",
|
||||
style: TextStyle(
|
||||
@@ -63,7 +64,11 @@ class HomeScreen extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
WalletBalanceBlock(max: total, value: total - available, savings: savings),
|
||||
WalletBalanceBlock(
|
||||
max: total,
|
||||
value: total - available,
|
||||
savings: savings,
|
||||
),
|
||||
DepositBlock(max: 150 - total),
|
||||
],
|
||||
),
|
||||
@@ -73,7 +78,6 @@ class HomeScreen extends ConsumerWidget {
|
||||
}
|
||||
|
||||
Widget walletsList(BuildContext context, List<Kid> kids, WidgetRef ref) {
|
||||
|
||||
return Column(
|
||||
spacing: 20,
|
||||
children: List<Widget>.generate(kids.length, (int index) {
|
||||
|
||||
@@ -30,7 +30,7 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedSplashScreen(
|
||||
splash: Image.asset('assets/images/logos/splash.gif'),
|
||||
splashIconSize: 2000.0,
|
||||
splashIconSize: 900.0,
|
||||
nextScreen: const SizedBox.shrink(),
|
||||
disableNavigation: true,
|
||||
backgroundColor: Colors.white,
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StepIndicator extends StatelessWidget{
|
||||
class StepIndicator extends StatelessWidget {
|
||||
final int total;
|
||||
final int current;
|
||||
final Color color;
|
||||
|
||||
const StepIndicator({super.key, required this.total, required this.current, required this.color});
|
||||
const StepIndicator({
|
||||
super.key,
|
||||
required this.total,
|
||||
required this.current,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
...List<Widget>.generate(total, (index){
|
||||
...List<Widget>.generate(total, (index) {
|
||||
final bool isActive = index < current;
|
||||
|
||||
return AnimatedContainer(
|
||||
@@ -24,11 +29,11 @@ class StepIndicator extends StatelessWidget{
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: isActive ? color : Colors.white,
|
||||
border: Border.all(color: color),
|
||||
border: Border.all(color: isActive ? color : Colors.black),
|
||||
),
|
||||
);
|
||||
}),
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ class AppRoutes {
|
||||
static const onboarding = '/onboarding';
|
||||
static const linkPhone = '/request_link_phone';
|
||||
static const phoneCode = '/verify_link_phone_code';
|
||||
static const deviceSignup = '/device_signup';
|
||||
static const deviceSetup = '/device_setup';
|
||||
static const recoverPassword = '/recover_password';
|
||||
|
||||
static const dashboard = '/dashboard';
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
|
||||
Dio buildDioClient({
|
||||
required String baseUrl,
|
||||
required String origin,
|
||||
// required String apiKey,
|
||||
bool log = false,
|
||||
CookieJar? cookieJar,
|
||||
}) {
|
||||
final dio = Dio(
|
||||
BaseOptions(
|
||||
@@ -13,7 +15,6 @@ Dio buildDioClient({
|
||||
receiveTimeout: const Duration(seconds: 20),
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
headers: {
|
||||
// if (apiKey.isNotEmpty) 'x-api-key': apiKey,
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
'origin': origin,
|
||||
@@ -21,13 +22,16 @@ Dio buildDioClient({
|
||||
),
|
||||
);
|
||||
|
||||
final jar = cookieJar ?? CookieJar();
|
||||
dio.interceptors.add(CookieManager(jar));
|
||||
|
||||
if (log) {
|
||||
dio.interceptors.add(
|
||||
LogInterceptor(
|
||||
request: true,
|
||||
requestHeader: false,
|
||||
requestBody: true,
|
||||
responseHeader: false,
|
||||
responseHeader: true,
|
||||
responseBody: true,
|
||||
error: true,
|
||||
),
|
||||
|
||||
@@ -8,10 +8,13 @@ environment:
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
dio: ^5.9.0
|
||||
dio_cookie_manager: ^3.3.0
|
||||
cookie_jar: ^4.0.8
|
||||
path_provider: ^2.1.5
|
||||
flutter:
|
||||
sdk: flutter
|
||||
get_it: ^9.0.5
|
||||
dio: ^5.9.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -130,5 +130,34 @@
|
||||
"secretCodeKeyCopied": "Schlüssel kopiert",
|
||||
"secretCodeStep3Title": "Generierten Code kopieren",
|
||||
"secretCodeStep3Body": "Nachdem du den QR-Code gescannt oder den Schlüssel in der Authenticator-App eingegeben hast, kopiere den generierten 6-stelligen Code und gib ihn im nächsten Bildschirm ein.",
|
||||
"secretCodeConfigure": "Einrichten"
|
||||
"secretCodeConfigure": "Einrichten",
|
||||
"deviceSetup_intro_title": "Füge dein Kind hinzu",
|
||||
"deviceSetup_intro_subtitle": "Behalte die Ausgaben im Blick, während es verantwortungsvolle Finanzgewohnheiten lernt",
|
||||
"deviceSetup_intro_step_1": "Erstelle sein Profil",
|
||||
"deviceSetup_intro_step_2": "Verbinde seine Uhr und das Armband",
|
||||
"deviceSetup_intro_step_3": "Lade sein Sparschwein auf",
|
||||
"deviceSetup_intro_ready_title": "Und fertig - alles ist bereit, damit es sein Geld hat!",
|
||||
"deviceSetup_intro_remember_prefix": "Denk daran, dass du einen",
|
||||
"deviceSetup_intro_plan_name": "SaveFamily-Plan",
|
||||
"deviceSetup_intro_web_prefix": "Wenn du ihn noch nicht hast, kannst du ihn über ",
|
||||
"deviceSetup_intro_web_link": "unsere Website bekommen",
|
||||
"deviceSetup_linkInfo_title": "Verbinde Armband und Uhr",
|
||||
"deviceSetup_linkInfo_item1_prefix": "Scanne das ",
|
||||
"deviceSetup_linkInfo_item1_boldWord": "Armband",
|
||||
"deviceSetup_linkInfo_item1_subtitle": "Dein Kind kann damit Zahlungen durchführen",
|
||||
"deviceSetup_linkInfo_item2_prefix": "Scanne die ",
|
||||
"deviceSetup_linkInfo_item2_boldWord": "Uhr",
|
||||
"deviceSetup_linkInfo_item2_subtitle": "Du kannst die getätigten Ausgaben sehen",
|
||||
"deviceSetup_watchCode_orInsert": "Oder gib den Code der Uhr ein",
|
||||
"deviceSetup_watchCode_continueWithCode": "Mit Code fortfahren",
|
||||
"deviceSetup_linkTroubleshoot_title": "Wenn du das Armband oder die Uhr nicht verbinden kannst",
|
||||
"deviceSetup_contactUs": "Kontaktiere uns",
|
||||
"deviceSetup_accountData_info": "Wir benötigen diese Angaben, um das Konto zu erstellen und Taschengeld sowie Ausgaben zu verwalten",
|
||||
"deviceSetup_startWithOneKid_info": "Starte mit einem Kind, später kannst du weitere hinzufügen",
|
||||
"deviceSetup_firstAllowance_title": "Du kannst ihnen jetzt das erste Taschengeld geben, damit sie es auf ihrer Uhr nutzen können",
|
||||
"deviceSetup_addAnotherKid": "Ein weiteres Kind hinzufügen",
|
||||
"deviceSetup_start": "Los geht's!",
|
||||
"deviceSetup_giveFirstAllowance": "Gib das erste Taschengeld",
|
||||
"deviceSetup_scanQr": "QR scannen",
|
||||
"deviceSetup_scanQr_hint": "Richte den QR-Code innerhalb des Rahmens aus"
|
||||
}
|
||||
@@ -119,8 +119,8 @@
|
||||
"passwordRulesSubtitle": "Minimum 8 characters, including an uppercase letter, a number, and a special character",
|
||||
"accountCreatedTitle": "Account created",
|
||||
"accountCreatedForLabel": "You created the account for:",
|
||||
"accountCreatedEmailVerificationSentLabel": "We’ve sent a verification email to:",
|
||||
"accountCreatedChildSetupHint": "Create your child’s account and enter their \nfirst allowance to use it with their watch",
|
||||
"accountCreatedEmailVerificationSentLabel": "We've sent a verification email to:",
|
||||
"accountCreatedChildSetupHint": "Create your child's account and enter their \nfirst allowance to use it with their watch",
|
||||
"accountCreatedContinue": "Continue",
|
||||
"secretCodeTitle": "Setup instructions",
|
||||
"secretCodeStep1Title": "Download an authenticator app",
|
||||
@@ -130,5 +130,34 @@
|
||||
"secretCodeKeyCopied": "Key copied",
|
||||
"secretCodeStep3Title": "Copy the generated code",
|
||||
"secretCodeStep3Body": "After scanning the QR code or entering the key in the authenticator app, copy the generated 6-digit code and enter it on the next screen.",
|
||||
"secretCodeConfigure": "Set up"
|
||||
"secretCodeConfigure": "Set up",
|
||||
"deviceSetup_intro_title": "Add your child",
|
||||
"deviceSetup_intro_subtitle": "Track their spending while they learn responsible financial habits",
|
||||
"deviceSetup_intro_step_1": "Create their profile",
|
||||
"deviceSetup_intro_step_2": "Link their watch and band",
|
||||
"deviceSetup_intro_step_3": "Top up their piggy bank",
|
||||
"deviceSetup_intro_ready_title": "And you're all set so they can have their money!",
|
||||
"deviceSetup_intro_remember_prefix": "Remember you need to have a",
|
||||
"deviceSetup_intro_plan_name": "SaveFamily Plan",
|
||||
"deviceSetup_intro_web_prefix": "If you don't have it yet, you can get it through ",
|
||||
"deviceSetup_intro_web_link": "our website",
|
||||
"deviceSetup_linkInfo_title": "Link their band and watch",
|
||||
"deviceSetup_linkInfo_item1_prefix": "Scan the ",
|
||||
"deviceSetup_linkInfo_item1_boldWord": "band",
|
||||
"deviceSetup_linkInfo_item1_subtitle": "Your child will be able to make payments",
|
||||
"deviceSetup_linkInfo_item2_prefix": "Scan the ",
|
||||
"deviceSetup_linkInfo_item2_boldWord": "watch",
|
||||
"deviceSetup_linkInfo_item2_subtitle": "You'll be able to see the expenses made",
|
||||
"deviceSetup_watchCode_orInsert": "Or enter the watch code",
|
||||
"deviceSetup_watchCode_continueWithCode": "Continue with code",
|
||||
"deviceSetup_linkTroubleshoot_title": "If you can't link their band or watch",
|
||||
"deviceSetup_contactUs": "Contact us",
|
||||
"deviceSetup_accountData_info": "We need this information to create their account and manage their allowances and spending",
|
||||
"deviceSetup_startWithOneKid_info": "Start with one child; you can add more later",
|
||||
"deviceSetup_firstAllowance_title": "You can now give them their first allowance so they can start enjoying it on their watch",
|
||||
"deviceSetup_addAnotherKid": "Add another child",
|
||||
"deviceSetup_start": "Start!",
|
||||
"deviceSetup_giveFirstAllowance": "Give their first allowance",
|
||||
"deviceSetup_scanQr": "Scan QR",
|
||||
"deviceSetup_scanQr_hint": "Center the QR inside the frame"
|
||||
}
|
||||
@@ -130,5 +130,34 @@
|
||||
"secretCodeKeyCopied": "Llave copiada",
|
||||
"secretCodeStep3Title": "Copia el código generado",
|
||||
"secretCodeStep3Body": "Después de escanear el código QR o introducir la llave en la aplicación de autenticación, copia el código generado de 6 dígitos e introdúcelo en la siguiente pantalla.",
|
||||
"secretCodeConfigure": "Configurar"
|
||||
"secretCodeConfigure": "Configurar",
|
||||
"deviceSetup_intro_title": "Añade a tu peque",
|
||||
"deviceSetup_intro_subtitle": "Controla su gasto a la vez que aprende hábitos financieros responsables",
|
||||
"deviceSetup_intro_step_1": "Crea su perfil",
|
||||
"deviceSetup_intro_step_2": "Vincula su reloj y su correa",
|
||||
"deviceSetup_intro_step_3": "Carga su hucha",
|
||||
"deviceSetup_intro_ready_title": "¡Y todo listo para que tenga su dinero!",
|
||||
"deviceSetup_intro_remember_prefix": "Recuerda que necesitas tener un",
|
||||
"deviceSetup_intro_plan_name": "Plan SaveFamily",
|
||||
"deviceSetup_intro_web_prefix": "Si aún no lo tienes, puedes conseguirlo a través de ",
|
||||
"deviceSetup_intro_web_link": "nuestra web",
|
||||
"deviceSetup_linkInfo_title": "Vincula su correa y su reloj",
|
||||
"deviceSetup_linkInfo_item1_prefix": "Escanea la ",
|
||||
"deviceSetup_linkInfo_item1_boldWord": "correa",
|
||||
"deviceSetup_linkInfo_item1_subtitle": "El peque podrá realizar pagos",
|
||||
"deviceSetup_linkInfo_item2_prefix": "Escanea el ",
|
||||
"deviceSetup_linkInfo_item2_boldWord": "reloj",
|
||||
"deviceSetup_linkInfo_item2_subtitle": "Visualizarás los gastos que se hagan",
|
||||
"deviceSetup_watchCode_orInsert": "O inserta el código del reloj",
|
||||
"deviceSetup_watchCode_continueWithCode": "Continuar con código",
|
||||
"deviceSetup_linkTroubleshoot_title": "Si no consigues vincular su correa o reloj",
|
||||
"deviceSetup_contactUs": "Contáctanos",
|
||||
"deviceSetup_accountData_info": "Necesitamos estos datos para crear su cuenta y gestionar sus pagas y gastos",
|
||||
"deviceSetup_startWithOneKid_info": "Comienza con un peque; luego podrás agregar más",
|
||||
"deviceSetup_firstAllowance_title": "Ya puedes darle su primera paga para que empiece a disfrutarla en su reloj",
|
||||
"deviceSetup_addAnotherKid": "Añadir otro peque",
|
||||
"deviceSetup_start": "¡Empezar!",
|
||||
"deviceSetup_giveFirstAllowance": "Dale su primera paga",
|
||||
"deviceSetup_scanQr": "Escanear QR",
|
||||
"deviceSetup_scanQr_hint": "Centra el QR dentro del recuadro"
|
||||
}
|
||||
@@ -120,15 +120,44 @@
|
||||
"accountCreatedTitle": "Compte créé",
|
||||
"accountCreatedForLabel": "Vous avez créé le compte pour :",
|
||||
"accountCreatedEmailVerificationSentLabel": "Nous avons envoyé un e-mail de vérification à :",
|
||||
"accountCreatedChildSetupHint": "Créez le compte de votre enfant et saisissez sa \npremière allocation pour l’utiliser avec sa montre",
|
||||
"accountCreatedChildSetupHint": "Créez le compte de votre enfant et saisissez sa \npremière allocation pour l'utiliser avec sa montre",
|
||||
"accountCreatedContinue": "Continuer",
|
||||
"secretCodeTitle": "Instructions de configuration",
|
||||
"secretCodeStep1Title": "Téléchargez une application d’authentification",
|
||||
"secretCodeStep1Body": "Assurez-vous d’avoir Google Authenticator sur votre appareil.",
|
||||
"secretCodeStep1Title": "Téléchargez une application d'authentification",
|
||||
"secretCodeStep1Body": "Assurez-vous d'avoir Google Authenticator sur votre appareil.",
|
||||
"secretCodeStep2Title": "Scannez le code QR ou copiez la clé",
|
||||
"secretCodeStep2Body": "Scannez le code QR ci-dessous avec l’application d’authentification pour vérifier l’appareil.\n\nOu copiez la clé et saisissez-la manuellement dans l’application d’authentification.",
|
||||
"secretCodeStep2Body": "Scannez le code QR ci-dessous avec l'application d'authentification pour vérifier l'appareil.\n\nOu copiez la clé et saisissez-la manuellement dans l'application d'authentification.",
|
||||
"secretCodeKeyCopied": "Clé copiée",
|
||||
"secretCodeStep3Title": "Copiez le code généré",
|
||||
"secretCodeStep3Body": "Après avoir scanné le code QR ou saisi la clé dans l’application d’authentification, copiez le code à 6 chiffres généré et saisissez-le sur l’écran suivant.",
|
||||
"secretCodeConfigure": "Configurer"
|
||||
"secretCodeStep3Body": "Après avoir scanné le code QR ou saisi la clé dans l'application d'authentification, copiez le code à 6 chiffres généré et saisissez-le sur l'écran suivant.",
|
||||
"secretCodeConfigure": "Configurer",
|
||||
"deviceSetup_intro_title": "Ajoutez votre enfant",
|
||||
"deviceSetup_intro_subtitle": "Suivez ses dépenses tout en l'aidant à adopter des habitudes financières responsables",
|
||||
"deviceSetup_intro_step_1": "Créez son profil",
|
||||
"deviceSetup_intro_step_2": "Associez sa montre et son bracelet",
|
||||
"deviceSetup_intro_step_3": "Alimentez sa cagnotte",
|
||||
"deviceSetup_intro_ready_title": "Et voilà, tout est prêt pour qu'il/elle ait son argent !",
|
||||
"deviceSetup_intro_remember_prefix": "N'oubliez pas que vous devez avoir un",
|
||||
"deviceSetup_intro_plan_name": "Plan SaveFamily",
|
||||
"deviceSetup_intro_web_prefix": "Si vous ne l'avez pas encore, vous pouvez l'obtenir via ",
|
||||
"deviceSetup_intro_web_link": "notre site web",
|
||||
"deviceSetup_linkInfo_title": "Associez son bracelet et sa montre",
|
||||
"deviceSetup_linkInfo_item1_prefix": "Scanne le ",
|
||||
"deviceSetup_linkInfo_item1_boldWord": "bracelet",
|
||||
"deviceSetup_linkInfo_item1_subtitle": "Votre enfant pourra effectuer des paiements",
|
||||
"deviceSetup_linkInfo_item2_prefix": "Scanne la ",
|
||||
"deviceSetup_linkInfo_item2_boldWord": "montre",
|
||||
"deviceSetup_linkInfo_item2_subtitle": "Vous verrez les dépenses effectuées",
|
||||
"deviceSetup_watchCode_orInsert": "Ou saisissez le code de la montre",
|
||||
"deviceSetup_watchCode_continueWithCode": "Continuer avec un code",
|
||||
"deviceSetup_linkTroubleshoot_title": "Si vous n'arrivez pas à associer son bracelet ou sa montre",
|
||||
"deviceSetup_contactUs": "Contactez-nous",
|
||||
"deviceSetup_accountData_info": "Nous avons besoin de ces informations pour créer son compte et gérer ses allocations et dépenses",
|
||||
"deviceSetup_startWithOneKid_info": "Commencez avec un enfant, vous pourrez en ajouter d'autres ensuite",
|
||||
"deviceSetup_firstAllowance_title": "Vous pouvez maintenant lui donner sa première allocation pour qu'il/elle commence à en profiter sur sa montre",
|
||||
"deviceSetup_addAnotherKid": "Ajouter un autre enfant",
|
||||
"deviceSetup_start": "Commencer!",
|
||||
"deviceSetup_giveFirstAllowance": "Donner sa première allocation",
|
||||
"deviceSetup_scanQr": "Scanner le QR",
|
||||
"deviceSetup_scanQr_hint": "Place le QR au centre du cadre"
|
||||
}
|
||||
@@ -118,17 +118,46 @@
|
||||
"stepAddressTitle": "Il tuo indirizzo",
|
||||
"passwordRulesSubtitle": "Password minima di 8 caratteri, con una maiuscola, un numero e un carattere speciale",
|
||||
"accountCreatedTitle": "Account creato",
|
||||
"accountCreatedForLabel": "Hai creato l’account per:",
|
||||
"accountCreatedEmailVerificationSentLabel": "Abbiamo inviato un’email di verifica a:",
|
||||
"accountCreatedChildSetupHint": "Crea l’account del tuo bambino e inserisci la sua \nprima paghetta per usarlo con il suo orologio",
|
||||
"accountCreatedForLabel": "Hai creato l'account per:",
|
||||
"accountCreatedEmailVerificationSentLabel": "Abbiamo inviato un'email di verifica a:",
|
||||
"accountCreatedChildSetupHint": "Crea l'account del tuo bambino e inserisci la sua \nprima paghetta per usarlo con il suo orologio",
|
||||
"accountCreatedContinue": "Continua",
|
||||
"secretCodeTitle": "Istruzioni di configurazione",
|
||||
"secretCodeStep1Title": "Scarica un’app di autenticazione",
|
||||
"secretCodeStep1Title": "Scarica un'app di autenticazione",
|
||||
"secretCodeStep1Body": "Assicurati di avere Google Authenticator sul tuo dispositivo.",
|
||||
"secretCodeStep2Title": "Scansiona il codice QR o copia la chiave",
|
||||
"secretCodeStep2Body": "Scansiona il codice QR qui sotto con l’app di autenticazione per verificare il dispositivo.\n\nOppure copia la chiave e inseriscila manualmente nell’app di autenticazione.",
|
||||
"secretCodeStep2Body": "Scansiona il codice QR qui sotto con l'app di autenticazione per verificare il dispositivo.\n\nOppure copia la chiave e inseriscila manualmente nell'app di autenticazione.",
|
||||
"secretCodeKeyCopied": "Chiave copiata",
|
||||
"secretCodeStep3Title": "Copia il codice generato",
|
||||
"secretCodeStep3Body": "Dopo aver scansionato il codice QR o inserito la chiave nell’app di autenticazione, copia il codice a 6 cifre generato e inseriscilo nella schermata successiva.",
|
||||
"secretCodeConfigure": "Configura"
|
||||
"secretCodeStep3Body": "Dopo aver scansionato il codice QR o inserito la chiave nell'app di autenticazione, copia il codice a 6 cifre generato e inseriscilo nella schermata successiva.",
|
||||
"secretCodeConfigure": "Configura",
|
||||
"deviceSetup_intro_title": "Aggiungi il tuo bambino",
|
||||
"deviceSetup_intro_subtitle": "Controlla le sue spese mentre impara abitudini finanziarie responsabili",
|
||||
"deviceSetup_intro_step_1": "Crea il suo profilo",
|
||||
"deviceSetup_intro_step_2": "Collega il suo orologio e il cinturino",
|
||||
"deviceSetup_intro_step_3": "Ricarica il suo salvadanaio",
|
||||
"deviceSetup_intro_ready_title": "E tutto è pronto perché abbia i suoi soldi!",
|
||||
"deviceSetup_intro_remember_prefix": "Ricorda che devi avere un",
|
||||
"deviceSetup_intro_plan_name": "Piano SaveFamily",
|
||||
"deviceSetup_intro_web_prefix": "Se non ce l'hai ancora, puoi ottenerlo tramite ",
|
||||
"deviceSetup_intro_web_link": "il nostro sito web",
|
||||
"deviceSetup_linkInfo_title": "Collega il cinturino e l'orologio",
|
||||
"deviceSetup_linkInfo_item1_prefix": "Scansiona il ",
|
||||
"deviceSetup_linkInfo_item1_boldWord": "cinturino",
|
||||
"deviceSetup_linkInfo_item1_subtitle": "Il bambino potrà effettuare pagamenti",
|
||||
"deviceSetup_linkInfo_item2_prefix": "Scansiona l'",
|
||||
"deviceSetup_linkInfo_item2_boldWord": "orologio",
|
||||
"deviceSetup_linkInfo_item2_subtitle": "Potrai visualizzare le spese effettuate",
|
||||
"deviceSetup_watchCode_orInsert": "Oppure inserisci il codice dell'orologio",
|
||||
"deviceSetup_watchCode_continueWithCode": "Continua con il codice",
|
||||
"deviceSetup_linkTroubleshoot_title": "Se non riesci a collegare il cinturino o l'orologio",
|
||||
"deviceSetup_contactUs": "Contactez-nous",
|
||||
"deviceSetup_accountData_info": "Abbiamo bisogno di questi dati per creare il suo conto e gestire paghette e spese",
|
||||
"deviceSetup_startWithOneKid_info": "Inizia con un bambino, poi potrai aggiungerne altri",
|
||||
"deviceSetup_firstAllowance_title": "Ora puoi dargli la sua prima paghetta così potrà iniziare a usarla sul suo orologio",
|
||||
"deviceSetup_addAnotherKid": "Aggiungi un altro bambino",
|
||||
"deviceSetup_start": "Inizia!",
|
||||
"deviceSetup_giveFirstAllowance": "Dagli la sua prima paghetta",
|
||||
"deviceSetup_scanQr": "Scansiona QR",
|
||||
"deviceSetup_scanQr_hint": "Centra il QR all’interno del riquadro"
|
||||
}
|
||||
@@ -130,5 +130,34 @@
|
||||
"secretCodeKeyCopied": "Chave copiada",
|
||||
"secretCodeStep3Title": "Copia o código gerado",
|
||||
"secretCodeStep3Body": "Depois de leres o código QR ou introduzires a chave na aplicação de autenticação, copia o código de 6 dígitos gerado e introduz-lo no ecrã seguinte.",
|
||||
"secretCodeConfigure": "Configurar"
|
||||
"secretCodeConfigure": "Configurar",
|
||||
"deviceSetup_intro_title": "Adicione o seu filho",
|
||||
"deviceSetup_intro_subtitle": "Acompanhe os gastos enquanto ele aprende hábitos financeiros responsáveis",
|
||||
"deviceSetup_intro_step_1": "Crie o perfil dele",
|
||||
"deviceSetup_intro_step_2": "Vincule o relógio e a pulseira",
|
||||
"deviceSetup_intro_step_3": "Carregue o cofrinho dele",
|
||||
"deviceSetup_intro_ready_title": "E pronto, tudo preparado para ele ter o dinheiro dele!",
|
||||
"deviceSetup_intro_remember_prefix": "Lembre-se de que precisa de um",
|
||||
"deviceSetup_intro_plan_name": "Plano SaveFamily",
|
||||
"deviceSetup_intro_web_prefix": "Se ainda não tem, pode conseguir através do ",
|
||||
"deviceSetup_intro_web_link": "nosso site",
|
||||
"deviceSetup_linkInfo_title": "Vincula a pulseira e o relógio",
|
||||
"deviceSetup_linkInfo_item1_prefix": "Digitaliza a ",
|
||||
"deviceSetup_linkInfo_item1_boldWord": "pulseira",
|
||||
"deviceSetup_linkInfo_item1_subtitle": "A criança poderá realizar pagamentos",
|
||||
"deviceSetup_linkInfo_item2_prefix": "Digitaliza o ",
|
||||
"deviceSetup_linkInfo_item2_boldWord": "relógio",
|
||||
"deviceSetup_linkInfo_item2_subtitle": "Poderás visualizar os gastos efetuados",
|
||||
"deviceSetup_watchCode_orInsert": "Ou introduz o código do relógio",
|
||||
"deviceSetup_watchCode_continueWithCode": "Continuar com código",
|
||||
"deviceSetup_linkTroubleshoot_title": "Se não conseguires vincular a pulseira ou o relógio",
|
||||
"deviceSetup_contactUs": "Contacta-nos",
|
||||
"deviceSetup_accountData_info": "Precisamos destes dados para criar a conta e gerir as mesadas e os gastos",
|
||||
"deviceSetup_startWithOneKid_info": "Começa com uma criança; depois podes adicionar mais",
|
||||
"deviceSetup_firstAllowance_title": "Agora já podes dar-lhe a primeira mesada para que comece a aproveitá-la no relógio",
|
||||
"deviceSetup_addAnotherKid": "Adicionar outra criança",
|
||||
"deviceSetup_start": "Começar!",
|
||||
"deviceSetup_giveFirstAllowance": "Dá-lhe a primeira mesada",
|
||||
"deviceSetup_scanQr": "Digitalizar QR",
|
||||
"deviceSetup_scanQr_hint": "Centraliza o QR dentro da moldura"
|
||||
}
|
||||
@@ -161,4 +161,50 @@ class I18n {
|
||||
static const String secretCodeStep3Title = 'secretCodeStep3Title';
|
||||
static const String secretCodeStep3Body = 'secretCodeStep3Body';
|
||||
static const String secretCodeConfigure = 'secretCodeConfigure';
|
||||
static const String deviceSetup_intro_title = 'deviceSetup_intro_title';
|
||||
static const String deviceSetup_intro_subtitle = 'deviceSetup_intro_subtitle';
|
||||
static const String deviceSetup_intro_step_1 = 'deviceSetup_intro_step_1';
|
||||
static const String deviceSetup_intro_step_2 = 'deviceSetup_intro_step_2';
|
||||
static const String deviceSetup_intro_step_3 = 'deviceSetup_intro_step_3';
|
||||
static const String deviceSetup_intro_ready_title =
|
||||
'deviceSetup_intro_ready_title';
|
||||
static const String deviceSetup_intro_remember_prefix =
|
||||
'deviceSetup_intro_remember_prefix';
|
||||
static const String deviceSetup_intro_plan_name =
|
||||
'deviceSetup_intro_plan_name';
|
||||
static const String deviceSetup_intro_web_prefix =
|
||||
'deviceSetup_intro_web_prefix';
|
||||
static const String deviceSetup_intro_web_link = 'deviceSetup_intro_web_link';
|
||||
static const String deviceSetup_linkInfo_title = 'deviceSetup_linkInfo_title';
|
||||
static const String deviceSetup_linkInfo_item1_prefix =
|
||||
'deviceSetup_linkInfo_item1_prefix';
|
||||
static const String deviceSetup_linkInfo_item1_boldWord =
|
||||
'deviceSetup_linkInfo_item1_boldWord';
|
||||
static const String deviceSetup_linkInfo_item1_subtitle =
|
||||
'deviceSetup_linkInfo_item1_subtitle';
|
||||
static const String deviceSetup_linkInfo_item2_prefix =
|
||||
'deviceSetup_linkInfo_item2_prefix';
|
||||
static const String deviceSetup_linkInfo_item2_boldWord =
|
||||
'deviceSetup_linkInfo_item2_boldWord';
|
||||
static const String deviceSetup_linkInfo_item2_subtitle =
|
||||
'deviceSetup_linkInfo_item2_subtitle';
|
||||
static const String deviceSetup_watchCode_orInsert =
|
||||
'deviceSetup_watchCode_orInsert';
|
||||
static const String deviceSetup_watchCode_continueWithCode =
|
||||
'deviceSetup_watchCode_continueWithCode';
|
||||
static const String deviceSetup_linkTroubleshoot_title =
|
||||
'deviceSetup_linkTroubleshoot_title';
|
||||
static const String deviceSetup_contactUs = 'deviceSetup_contactUs';
|
||||
static const String deviceSetup_accountData_info =
|
||||
'deviceSetup_accountData_info';
|
||||
static const String deviceSetup_startWithOneKid_info =
|
||||
'deviceSetup_startWithOneKid_info';
|
||||
static const String deviceSetup_firstAllowance_title =
|
||||
'deviceSetup_firstAllowance_title';
|
||||
static const String deviceSetup_addAnotherKid = 'deviceSetup_addAnotherKid';
|
||||
static const String deviceSetup_start = 'deviceSetup_start';
|
||||
static const String deviceSetup_giveFirstAllowance =
|
||||
'deviceSetup_giveFirstAllowance';
|
||||
static const String deviceSetup_scanQr = 'deviceSetup_scanQr';
|
||||
static const String deviceSetup_scanQr_hint = 'deviceSetup_scanQr_hint';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user