added device setup flow, qr reader, createChildProfile models and cookies packages
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user