From 69980925adde118f56d59cef2f0fb9a36170a7e6 Mon Sep 17 00:00:00 2001 From: llsajjad Date: Tue, 16 Sep 2025 15:51:56 +0330 Subject: [PATCH] added api for forgot password --- lib/core/config/app_config.dart | 4 +- lib/data/repositories/auth_repository.dart | 7 + lib/data/services/api/auth_api.dart | 1 + lib/data/services/auth_service.dart | 16 + .../forgot_password_response_model.dart | 32 + ...orgot_password_response_model.freezed.dart | 611 ++++++++++++++++++ .../forgot_password_response_model.g.dart | 42 ++ lib/gen/assets.gen.dart | 20 + lib/view_models/auth_view_model.dart | 72 ++- .../pages/d_forgot_apssword_page.dart | 67 +- .../pages/m_forgot_password_page.dart | 50 +- .../pages/t_forgot_password_page.dart | 66 +- lib/views/login/pages/login_mobile_page.dart | 28 +- 13 files changed, 886 insertions(+), 130 deletions(-) create mode 100644 lib/data/services/model/forgot_password_response/forgot_password_response_model.dart create mode 100644 lib/data/services/model/forgot_password_response/forgot_password_response_model.freezed.dart create mode 100644 lib/data/services/model/forgot_password_response/forgot_password_response_model.g.dart diff --git a/lib/core/config/app_config.dart b/lib/core/config/app_config.dart index d23a8e0..b23e7de 100644 --- a/lib/core/config/app_config.dart +++ b/lib/core/config/app_config.dart @@ -8,14 +8,14 @@ class AppConfig { // این متد بر اساس محیط فعال، URL مناسب را برمی‌گرداند. static String get apiBaseUrl { - // return 'http://10.0.2.2:8081'; + return 'http://10.0.2.2:8081'; // return '192.168.1.103:8081'; // if (isDevelopment) { // // Handle different platforms for local development // if (kIsWeb) { // For web, use localhost // return 'https://app.opplens.com'; - return 'http://localhost:8081'; + // return 'http://localhost:8081'; // } else { // For Android emulator, use 10.0.2.2 (special IP for host machine) // return 'http://10.0.2.2:8081'; diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index a931d05..2a36e11 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -1,3 +1,4 @@ +import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import '../../core/utils/result.dart'; @@ -24,4 +25,10 @@ class AuthRepository { Future localLogout() async { return _authService.localLogout(); } + + Future> forgotPassword({ + required String email, + }) async { + return _authService.forgotPassword(email: email); + } } diff --git a/lib/data/services/api/auth_api.dart b/lib/data/services/api/auth_api.dart index bc2a7ba..9fd6b59 100644 --- a/lib/data/services/api/auth_api.dart +++ b/lib/data/services/api/auth_api.dart @@ -1,4 +1,5 @@ class AuthApi { static const String login = '/api/v1/profile/login'; static const String logout = '/api/v1/profile/logout'; + static const String forgotPassword = '/api/v1/profile/forgot-password'; } diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart index 66ef13c..76e705d 100644 --- a/lib/data/services/auth_service.dart +++ b/lib/data/services/auth_service.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tm_app/core/utils/logger.dart'; import 'package:tm_app/data/services/api/auth_api.dart'; +import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; @@ -56,4 +57,19 @@ class AuthService { await prefs.remove('refresh_token'); appLogger.info('tokens cleared!'); } + + Future> forgotPassword({ + required String email, + }) async { + final data = jsonEncode({"email": email}); + + final result = await _networkManager.makeRequest( + AuthApi.forgotPassword, + method: 'POST', + (json) => ForgotPasswordResponseModel.fromJson(json), + data: data, + ); + + return result; + } } diff --git a/lib/data/services/model/forgot_password_response/forgot_password_response_model.dart b/lib/data/services/model/forgot_password_response/forgot_password_response_model.dart new file mode 100644 index 0000000..5eda868 --- /dev/null +++ b/lib/data/services/model/forgot_password_response/forgot_password_response_model.dart @@ -0,0 +1,32 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; +import '../error/error_model.dart'; + +part 'forgot_password_response_model.freezed.dart'; +part 'forgot_password_response_model.g.dart'; + +@freezed +abstract class ForgotPasswordResponseModel with _$ForgotPasswordResponseModel { + const factory ForgotPasswordResponseModel({ + required bool success, + required String? message, + required ForgotPasswordData? data, + required ErrorModel? error, + required Map? meta, + }) = _ForgotPasswordResponseModel; + + factory ForgotPasswordResponseModel.fromJson(Map json) => + _$ForgotPasswordResponseModelFromJson(json); +} + +@freezed +abstract class ForgotPasswordData with _$ForgotPasswordData { + const factory ForgotPasswordData({ + required String? message, + required bool? success, + }) = _ForgotPasswordData; + + factory ForgotPasswordData.fromJson(Map json) => + _$ForgotPasswordDataFromJson(json); +} diff --git a/lib/data/services/model/forgot_password_response/forgot_password_response_model.freezed.dart b/lib/data/services/model/forgot_password_response/forgot_password_response_model.freezed.dart new file mode 100644 index 0000000..eeb73e7 --- /dev/null +++ b/lib/data/services/model/forgot_password_response/forgot_password_response_model.freezed.dart @@ -0,0 +1,611 @@ +// 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 'forgot_password_response_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$ForgotPasswordResponseModel { + + bool get success; String? get message; ForgotPasswordData? get data; ErrorModel? get error; Map? get meta; +/// Create a copy of ForgotPasswordResponseModel +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ForgotPasswordResponseModelCopyWith get copyWith => _$ForgotPasswordResponseModelCopyWithImpl(this as ForgotPasswordResponseModel, _$identity); + + /// Serializes this ForgotPasswordResponseModel to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ForgotPasswordResponseModel&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.data, data) || other.data == data)&&(identical(other.error, error) || other.error == error)&&const DeepCollectionEquality().equals(other.meta, meta)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,success,message,data,error,const DeepCollectionEquality().hash(meta)); + +@override +String toString() { + return 'ForgotPasswordResponseModel(success: $success, message: $message, data: $data, error: $error, meta: $meta)'; +} + + +} + +/// @nodoc +abstract mixin class $ForgotPasswordResponseModelCopyWith<$Res> { + factory $ForgotPasswordResponseModelCopyWith(ForgotPasswordResponseModel value, $Res Function(ForgotPasswordResponseModel) _then) = _$ForgotPasswordResponseModelCopyWithImpl; +@useResult +$Res call({ + bool success, String? message, ForgotPasswordData? data, ErrorModel? error, Map? meta +}); + + +$ForgotPasswordDataCopyWith<$Res>? get data;$ErrorModelCopyWith<$Res>? get error; + +} +/// @nodoc +class _$ForgotPasswordResponseModelCopyWithImpl<$Res> + implements $ForgotPasswordResponseModelCopyWith<$Res> { + _$ForgotPasswordResponseModelCopyWithImpl(this._self, this._then); + + final ForgotPasswordResponseModel _self; + final $Res Function(ForgotPasswordResponseModel) _then; + +/// Create a copy of ForgotPasswordResponseModel +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? success = null,Object? message = freezed,Object? data = freezed,Object? error = freezed,Object? meta = freezed,}) { + return _then(_self.copyWith( +success: null == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable +as ForgotPasswordData?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as ErrorModel?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable +as Map?, + )); +} +/// Create a copy of ForgotPasswordResponseModel +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ForgotPasswordDataCopyWith<$Res>? get data { + if (_self.data == null) { + return null; + } + + return $ForgotPasswordDataCopyWith<$Res>(_self.data!, (value) { + return _then(_self.copyWith(data: value)); + }); +}/// Create a copy of ForgotPasswordResponseModel +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ErrorModelCopyWith<$Res>? get error { + if (_self.error == null) { + return null; + } + + return $ErrorModelCopyWith<$Res>(_self.error!, (value) { + return _then(_self.copyWith(error: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [ForgotPasswordResponseModel]. +extension ForgotPasswordResponseModelPatterns on ForgotPasswordResponseModel { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ForgotPasswordResponseModel value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ForgotPasswordResponseModel() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ForgotPasswordResponseModel value) $default,){ +final _that = this; +switch (_that) { +case _ForgotPasswordResponseModel(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ForgotPasswordResponseModel value)? $default,){ +final _that = this; +switch (_that) { +case _ForgotPasswordResponseModel() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( bool success, String? message, ForgotPasswordData? data, ErrorModel? error, Map? meta)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ForgotPasswordResponseModel() when $default != null: +return $default(_that.success,_that.message,_that.data,_that.error,_that.meta);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( bool success, String? message, ForgotPasswordData? data, ErrorModel? error, Map? meta) $default,) {final _that = this; +switch (_that) { +case _ForgotPasswordResponseModel(): +return $default(_that.success,_that.message,_that.data,_that.error,_that.meta);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool success, String? message, ForgotPasswordData? data, ErrorModel? error, Map? meta)? $default,) {final _that = this; +switch (_that) { +case _ForgotPasswordResponseModel() when $default != null: +return $default(_that.success,_that.message,_that.data,_that.error,_that.meta);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ForgotPasswordResponseModel implements ForgotPasswordResponseModel { + const _ForgotPasswordResponseModel({required this.success, required this.message, required this.data, required this.error, required final Map? meta}): _meta = meta; + factory _ForgotPasswordResponseModel.fromJson(Map json) => _$ForgotPasswordResponseModelFromJson(json); + +@override final bool success; +@override final String? message; +@override final ForgotPasswordData? data; +@override final ErrorModel? error; + final Map? _meta; +@override Map? get meta { + final value = _meta; + if (value == null) return null; + if (_meta is EqualUnmodifiableMapView) return _meta; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); +} + + +/// Create a copy of ForgotPasswordResponseModel +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ForgotPasswordResponseModelCopyWith<_ForgotPasswordResponseModel> get copyWith => __$ForgotPasswordResponseModelCopyWithImpl<_ForgotPasswordResponseModel>(this, _$identity); + +@override +Map toJson() { + return _$ForgotPasswordResponseModelToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ForgotPasswordResponseModel&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.data, data) || other.data == data)&&(identical(other.error, error) || other.error == error)&&const DeepCollectionEquality().equals(other._meta, _meta)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,success,message,data,error,const DeepCollectionEquality().hash(_meta)); + +@override +String toString() { + return 'ForgotPasswordResponseModel(success: $success, message: $message, data: $data, error: $error, meta: $meta)'; +} + + +} + +/// @nodoc +abstract mixin class _$ForgotPasswordResponseModelCopyWith<$Res> implements $ForgotPasswordResponseModelCopyWith<$Res> { + factory _$ForgotPasswordResponseModelCopyWith(_ForgotPasswordResponseModel value, $Res Function(_ForgotPasswordResponseModel) _then) = __$ForgotPasswordResponseModelCopyWithImpl; +@override @useResult +$Res call({ + bool success, String? message, ForgotPasswordData? data, ErrorModel? error, Map? meta +}); + + +@override $ForgotPasswordDataCopyWith<$Res>? get data;@override $ErrorModelCopyWith<$Res>? get error; + +} +/// @nodoc +class __$ForgotPasswordResponseModelCopyWithImpl<$Res> + implements _$ForgotPasswordResponseModelCopyWith<$Res> { + __$ForgotPasswordResponseModelCopyWithImpl(this._self, this._then); + + final _ForgotPasswordResponseModel _self; + final $Res Function(_ForgotPasswordResponseModel) _then; + +/// Create a copy of ForgotPasswordResponseModel +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? success = null,Object? message = freezed,Object? data = freezed,Object? error = freezed,Object? meta = freezed,}) { + return _then(_ForgotPasswordResponseModel( +success: null == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable +as ForgotPasswordData?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as ErrorModel?,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable +as Map?, + )); +} + +/// Create a copy of ForgotPasswordResponseModel +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ForgotPasswordDataCopyWith<$Res>? get data { + if (_self.data == null) { + return null; + } + + return $ForgotPasswordDataCopyWith<$Res>(_self.data!, (value) { + return _then(_self.copyWith(data: value)); + }); +}/// Create a copy of ForgotPasswordResponseModel +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ErrorModelCopyWith<$Res>? get error { + if (_self.error == null) { + return null; + } + + return $ErrorModelCopyWith<$Res>(_self.error!, (value) { + return _then(_self.copyWith(error: value)); + }); +} +} + + +/// @nodoc +mixin _$ForgotPasswordData { + + String? get message; bool? get success; +/// Create a copy of ForgotPasswordData +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ForgotPasswordDataCopyWith get copyWith => _$ForgotPasswordDataCopyWithImpl(this as ForgotPasswordData, _$identity); + + /// Serializes this ForgotPasswordData to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ForgotPasswordData&&(identical(other.message, message) || other.message == message)&&(identical(other.success, success) || other.success == success)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,message,success); + +@override +String toString() { + return 'ForgotPasswordData(message: $message, success: $success)'; +} + + +} + +/// @nodoc +abstract mixin class $ForgotPasswordDataCopyWith<$Res> { + factory $ForgotPasswordDataCopyWith(ForgotPasswordData value, $Res Function(ForgotPasswordData) _then) = _$ForgotPasswordDataCopyWithImpl; +@useResult +$Res call({ + String? message, bool? success +}); + + + + +} +/// @nodoc +class _$ForgotPasswordDataCopyWithImpl<$Res> + implements $ForgotPasswordDataCopyWith<$Res> { + _$ForgotPasswordDataCopyWithImpl(this._self, this._then); + + final ForgotPasswordData _self; + final $Res Function(ForgotPasswordData) _then; + +/// Create a copy of ForgotPasswordData +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? message = freezed,Object? success = freezed,}) { + return _then(_self.copyWith( +message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ForgotPasswordData]. +extension ForgotPasswordDataPatterns on ForgotPasswordData { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ForgotPasswordData value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ForgotPasswordData() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ForgotPasswordData value) $default,){ +final _that = this; +switch (_that) { +case _ForgotPasswordData(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ForgotPasswordData value)? $default,){ +final _that = this; +switch (_that) { +case _ForgotPasswordData() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? message, bool? success)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ForgotPasswordData() when $default != null: +return $default(_that.message,_that.success);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? message, bool? success) $default,) {final _that = this; +switch (_that) { +case _ForgotPasswordData(): +return $default(_that.message,_that.success);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? message, bool? success)? $default,) {final _that = this; +switch (_that) { +case _ForgotPasswordData() when $default != null: +return $default(_that.message,_that.success);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ForgotPasswordData implements ForgotPasswordData { + const _ForgotPasswordData({required this.message, required this.success}); + factory _ForgotPasswordData.fromJson(Map json) => _$ForgotPasswordDataFromJson(json); + +@override final String? message; +@override final bool? success; + +/// Create a copy of ForgotPasswordData +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ForgotPasswordDataCopyWith<_ForgotPasswordData> get copyWith => __$ForgotPasswordDataCopyWithImpl<_ForgotPasswordData>(this, _$identity); + +@override +Map toJson() { + return _$ForgotPasswordDataToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ForgotPasswordData&&(identical(other.message, message) || other.message == message)&&(identical(other.success, success) || other.success == success)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,message,success); + +@override +String toString() { + return 'ForgotPasswordData(message: $message, success: $success)'; +} + + +} + +/// @nodoc +abstract mixin class _$ForgotPasswordDataCopyWith<$Res> implements $ForgotPasswordDataCopyWith<$Res> { + factory _$ForgotPasswordDataCopyWith(_ForgotPasswordData value, $Res Function(_ForgotPasswordData) _then) = __$ForgotPasswordDataCopyWithImpl; +@override @useResult +$Res call({ + String? message, bool? success +}); + + + + +} +/// @nodoc +class __$ForgotPasswordDataCopyWithImpl<$Res> + implements _$ForgotPasswordDataCopyWith<$Res> { + __$ForgotPasswordDataCopyWithImpl(this._self, this._then); + + final _ForgotPasswordData _self; + final $Res Function(_ForgotPasswordData) _then; + +/// Create a copy of ForgotPasswordData +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? message = freezed,Object? success = freezed,}) { + return _then(_ForgotPasswordData( +message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool?, + )); +} + + +} + +// dart format on diff --git a/lib/data/services/model/forgot_password_response/forgot_password_response_model.g.dart b/lib/data/services/model/forgot_password_response/forgot_password_response_model.g.dart new file mode 100644 index 0000000..9b27e53 --- /dev/null +++ b/lib/data/services/model/forgot_password_response/forgot_password_response_model.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'forgot_password_response_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_ForgotPasswordResponseModel _$ForgotPasswordResponseModelFromJson( + Map json, +) => _ForgotPasswordResponseModel( + success: json['success'] as bool, + message: json['message'] as String?, + data: + json['data'] == null + ? null + : ForgotPasswordData.fromJson(json['data'] as Map), + error: + json['error'] == null + ? null + : ErrorModel.fromJson(json['error'] as Map), + meta: json['meta'] as Map?, +); + +Map _$ForgotPasswordResponseModelToJson( + _ForgotPasswordResponseModel instance, +) => { + 'success': instance.success, + 'message': instance.message, + 'data': instance.data, + 'error': instance.error, + 'meta': instance.meta, +}; + +_ForgotPasswordData _$ForgotPasswordDataFromJson(Map json) => + _ForgotPasswordData( + message: json['message'] as String?, + success: json['success'] as bool?, + ); + +Map _$ForgotPasswordDataToJson(_ForgotPasswordData instance) => + {'message': instance.message, 'success': instance.success}; diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index a59db8c..935ff91 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -26,6 +26,9 @@ class $AssetsIconsGen { /// File path: assets/icons/arrow-circle-right.svg String get arrowCircleRight => 'assets/icons/arrow-circle-right.svg'; + /// File path: assets/icons/arrow-down.svg + String get arrowDown => 'assets/icons/arrow-down.svg'; + /// File path: assets/icons/arrow-left-small.svg String get arrowLeftSmall => 'assets/icons/arrow-left-small.svg'; @@ -35,6 +38,9 @@ class $AssetsIconsGen { /// File path: assets/icons/arrow-right.svg String get arrowRight => 'assets/icons/arrow-right.svg'; + /// File path: assets/icons/arrow-up.svg + String get arrowUp => 'assets/icons/arrow-up.svg'; + /// File path: assets/icons/arrow_down_small.svg String get arrowDownSmall => 'assets/icons/arrow_down_small.svg'; @@ -44,9 +50,15 @@ class $AssetsIconsGen { /// File path: assets/icons/calendar.svg String get calendar => 'assets/icons/calendar.svg'; + /// File path: assets/icons/clipboard-text.svg + String get clipboardText => 'assets/icons/clipboard-text.svg'; + /// File path: assets/icons/close-circle.svg String get closeCircle => 'assets/icons/close-circle.svg'; + /// File path: assets/icons/danger-fill.svg + String get dangerFill => 'assets/icons/danger-fill.svg'; + /// File path: assets/icons/danger.svg String get danger => 'assets/icons/danger.svg'; @@ -141,19 +153,26 @@ class $AssetsIconsGen { /// File path: assets/icons/trash.svg String get trash => 'assets/icons/trash.svg'; + /// File path: assets/icons/upload_file.svg + String get uploadFile => 'assets/icons/upload_file.svg'; + /// List of all assets List get values => [ se, approve, arrowCircleLeft, arrowCircleRight, + arrowDown, arrowLeftSmall, arrowRightSmall, arrowRight, + arrowUp, arrowDownSmall, arrows, calendar, + clipboardText, closeCircle, + dangerFill, danger, dislike, edit, @@ -185,6 +204,7 @@ class $AssetsIconsGen { tick, trailingIcon, trash, + uploadFile, ]; } diff --git a/lib/view_models/auth_view_model.dart b/lib/view_models/auth_view_model.dart index d481ef2..ecc6e4f 100644 --- a/lib/view_models/auth_view_model.dart +++ b/lib/view_models/auth_view_model.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:tm_app/data/services/model/customer/customer.dart'; +import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; import '../core/utils/result.dart'; import '../data/repositories/auth_repository.dart'; @@ -10,14 +11,12 @@ class AuthViewModel with ChangeNotifier { final AuthRepository _authRepository; AuthViewModel({required AuthRepository authRepository}) - : _authRepository = authRepository { + : _authRepository = authRepository { usernameController.addListener(_onTextChanged); passwordController.addListener(_onTextChanged); emailController.addListener(_onTextChanged); otpController.addListener(_onTextChanged); newPasswordController.addListener(_onTextChanged); - // usernameFocus.addListener(_onTextChanged); - // passwordFocus.addListener(_onTextChanged); } final TextEditingController usernameController = TextEditingController(); @@ -25,6 +24,7 @@ class AuthViewModel with ChangeNotifier { final TextEditingController emailController = TextEditingController(); final TextEditingController newPasswordController = TextEditingController(); final TextEditingController otpController = TextEditingController(); + final FocusNode usernameFocus = FocusNode(); final FocusNode passwordFocus = FocusNode(); final FocusNode emailFocus = FocusNode(); @@ -33,25 +33,36 @@ class AuthViewModel with ChangeNotifier { bool _obscurePassword = true; bool get obscurePassword => _obscurePassword; + bool _obscureForgotPassword = true; bool get obscureForgotPassword => _obscureForgotPassword; + bool get isLoginEnabled => - usernameController.text.isNotEmpty && passwordController.text.isNotEmpty; + usernameController.text.isNotEmpty && + passwordController.text.isNotEmpty; // Auth state bool _isLoading = false; String? _errorMessage; Customer? _loggedInUser; + String? successMessage; + String? _forgotPasswordMessage; bool get isLoading => _isLoading; String? get errorMessage => _errorMessage; Customer? get loggedInUser => _loggedInUser; + String? get forgotPasswordMessage => _forgotPasswordMessage; void togglePasswordVisibility() { _obscurePassword = !_obscurePassword; notifyListeners(); } + void toggleForgotPasswordVisibility() { + _obscureForgotPassword = !_obscureForgotPassword; + notifyListeners(); + } + bool get isEmailValid { final email = emailController.text.trim(); final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); @@ -78,11 +89,6 @@ class AuthViewModel with ChangeNotifier { return hasMinLength && hasUppercase && hasLowercase && hasSpecialChar; } - void toggleForgotPasswordVisibility() { - _obscureForgotPassword = !_obscureForgotPassword; - notifyListeners(); - } - void _onTextChanged() { notifyListeners(); } @@ -102,8 +108,6 @@ class AuthViewModel with ChangeNotifier { switch (result) { case Ok(): _loggedInUser = result.value.data.customer; - usernameController.text.trim(); - passwordController.text.trim(); usernameController.clear(); passwordController.clear(); break; @@ -114,6 +118,8 @@ class AuthViewModel with ChangeNotifier { _isLoading = false; notifyListeners(); + + // reset error after notify _errorMessage = null; notifyListeners(); } @@ -140,16 +146,60 @@ class AuthViewModel with ChangeNotifier { } notifyListeners(); + + // reset error after notify _errorMessage = null; notifyListeners(); } + Future forgotPassword(BuildContext context) async { + _isLoading = true; + _errorMessage = null; + successMessage = null; + notifyListeners(); + + final result = await _authRepository.forgotPassword( + email: emailController.text.trim(), + ); + + switch (result) { + case Ok(): + _isLoading = false; + successMessage = result.value.message; + notifyListeners(); + + // پاک شدن بعد از یک بار مصرف + Future.microtask(() { + successMessage = null; + }); + break; + + case Error(): + _isLoading = false; + _errorMessage = result.error.toString(); + notifyListeners(); + + Future.microtask(() { + _errorMessage = null; + }); + break; + } + } + @override void dispose() { usernameController.dispose(); passwordController.dispose(); + emailController.dispose(); + newPasswordController.dispose(); + otpController.dispose(); + usernameFocus.dispose(); passwordFocus.dispose(); + emailFocus.dispose(); + newPasswordFocus.dispose(); + otpFocus.dispose(); + super.dispose(); } } diff --git a/lib/views/forgot_password/pages/d_forgot_apssword_page.dart b/lib/views/forgot_password/pages/d_forgot_apssword_page.dart index cfd7e4e..0b6f2cd 100644 --- a/lib/views/forgot_password/pages/d_forgot_apssword_page.dart +++ b/lib/views/forgot_password/pages/d_forgot_apssword_page.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/views/forget_password_otp.dart/pages/forgot_password_otp_screen.dart'; - import '../../../core/constants/assets.dart'; import '../../../core/theme/colors.dart'; +import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; import '../../login/widgets/login_textfield.dart'; import '../../shared/base_button.dart'; @@ -22,6 +21,22 @@ class DesktopForgotPasswordPage extends StatelessWidget { body: Center( child: Consumer( builder: (context, viewModel, _) { + if (viewModel.successMessage != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + AppToast.success(context, viewModel.successMessage!); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ForgotPasswordOtpScreen(), + ), + ); + }); + } else if (viewModel.errorMessage != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + AppToast.error(context, viewModel.errorMessage!); + }); + } + return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ @@ -60,55 +75,32 @@ class DesktopForgotPasswordPage extends StatelessWidget { ), ), SizedBox(height: 40.0.h()), - // LoginTextField( - // controller: viewModel.emailController, - // focusNode: viewModel.emailFocus, - // label: ForgotPasswordStrings.usernameHint, - // iconPath: AssetsManager.userIcon, - // isPassword: false, - // ), - // SizedBox(height: 24.0.h()), LoginTextField( controller: viewModel.emailController, focusNode: viewModel.emailFocus, label: ForgotPasswordStrings.emailHint, isPassword: false, ), - // SizedBox(height: 24.0.h()), - // LoginTextField( - // controller: viewModel.emailController, - // focusNode: viewModel.emailFocus, - // label: ForgotPasswordStrings.companyIdHint, - // isPassword: false, - // ), SizedBox(height: 70.0.h()), viewModel.isLoading ? const BaseButton( - isEnabled: false, - text: ForgotPasswordStrings.resetLink, - onPressed: null, - ) + isEnabled: false, + text: ForgotPasswordStrings.resetLink, + onPressed: null, + ) : BaseButton( - isEnabled: viewModel.isEmailValid, - text: ForgotPasswordStrings.resetLink, - onPressed: - viewModel.isEmailValid - ? () async { - Navigator.push( - context, - MaterialPageRoute( - builder: - (context) => - const ForgotPasswordOtpScreen(), - ), - ); + isEnabled: viewModel.isEmailValid, + text: ForgotPasswordStrings.resetLink, + onPressed: viewModel.isEmailValid + ? () async { + await viewModel.forgotPassword(context); } - : null, - ), + : null, + ), const SizedBox(height: 12), TextButton( onPressed: () { - context.pop(); + Navigator.pop(context); }, child: Text( ForgotPasswordStrings.backToSignInButton, @@ -123,7 +115,6 @@ class DesktopForgotPasswordPage extends StatelessWidget { ), ), ), - Padding( padding: EdgeInsets.symmetric(vertical: 20.0.h()), child: ClipRRect( diff --git a/lib/views/forgot_password/pages/m_forgot_password_page.dart b/lib/views/forgot_password/pages/m_forgot_password_page.dart index 5694646..c1f4fdb 100644 --- a/lib/views/forgot_password/pages/m_forgot_password_page.dart +++ b/lib/views/forgot_password/pages/m_forgot_password_page.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/app_toast.dart'; import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; import 'package:tm_app/views/forget_password_otp.dart/pages/forgot_password_otp_screen.dart'; -import '../../../core/constants/assets.dart'; -import '../../../core/theme/colors.dart'; -import '../../../view_models/auth_view_model.dart'; import '../../login/widgets/login_textfield.dart'; import '../../shared/base_button.dart'; import '../strings/forgot_password_strings.dart'; @@ -24,6 +24,22 @@ class MobileForgotPasswordPage extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 24.0.w()), child: Consumer( builder: (context, viewModel, _) { + if (viewModel.successMessage == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + AppToast.success(context, viewModel.successMessage!); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ForgotPasswordOtpScreen(), + ), + ); + }); + } else if (viewModel.errorMessage != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + AppToast.error(context, viewModel.errorMessage!); + }); + } + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -55,14 +71,6 @@ class MobileForgotPasswordPage extends StatelessWidget { ), ), SizedBox(height: 40.0.h()), - // LoginTextField( - // controller: viewModel.emailController, - // focusNode: viewModel.emailFocus, - // label: ForgotPasswordStrings.usernameHint, - // iconPath: AssetsManager.userIcon, - // isPassword: false, - // ), - // SizedBox(height: 24.0.h()), LoginTextField( controller: viewModel.emailController, focusNode: viewModel.emailFocus, @@ -70,13 +78,6 @@ class MobileForgotPasswordPage extends StatelessWidget { isPassword: false, ), SizedBox(height: 24.0.h()), - // LoginTextField( - // controller: viewModel.emailController, - // focusNode: viewModel.emailFocus, - // label: ForgotPasswordStrings.companyIdHint, - // isPassword: false, - // ), - // SizedBox(height: 70.0.h()), viewModel.isLoading ? const BaseButton( isEnabled: false, @@ -89,20 +90,13 @@ class MobileForgotPasswordPage extends StatelessWidget { onPressed: viewModel.isEmailValid ? () async { - Navigator.push( - context, - MaterialPageRoute( - builder: - (context) => - const ForgotPasswordOtpScreen(), - ), - ); + await viewModel.forgotPassword(context); } : null, ), TextButton( onPressed: () { - context.pop(); + Navigator.pop(context); }, child: Text( ForgotPasswordStrings.backToSignInButton, diff --git a/lib/views/forgot_password/pages/t_forgot_password_page.dart b/lib/views/forgot_password/pages/t_forgot_password_page.dart index 0c1cafb..94f2ee4 100644 --- a/lib/views/forgot_password/pages/t_forgot_password_page.dart +++ b/lib/views/forgot_password/pages/t_forgot_password_page.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/views/forget_password_otp.dart/pages/forgot_password_otp_screen.dart'; import '../../../core/constants/assets.dart'; import '../../../core/theme/colors.dart'; +import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; import '../../login/widgets/login_textfield.dart'; import '../../shared/base_button.dart'; @@ -20,6 +20,22 @@ class TabletForgotPasswordPage extends StatelessWidget { return Scaffold( body: Consumer( builder: (context, viewModel, _) { + if (viewModel.successMessage != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + AppToast.success(context, viewModel.successMessage!); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ForgotPasswordOtpScreen(), + ), + ); + }); + } else if (viewModel.errorMessage != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + AppToast.error(context, viewModel.errorMessage!); + }); + } + return Center( child: SizedBox( width: 364, @@ -54,14 +70,6 @@ class TabletForgotPasswordPage extends StatelessWidget { ), ), SizedBox(height: 40.0.h()), - // LoginTextField( - // controller: viewModel.emailController, - // focusNode: viewModel.emailFocus, - // label: ForgotPasswordStrings.usernameHint, - // iconPath: AssetsManager.userIcon, - // isPassword: false, - // ), - // SizedBox(height: 24.0.h()), LoginTextField( controller: viewModel.emailController, focusNode: viewModel.emailFocus, @@ -69,41 +77,25 @@ class TabletForgotPasswordPage extends StatelessWidget { isPassword: false, ), SizedBox(height: 24.0.h()), - // LoginTextField( - // controller: viewModel.emailController, - // focusNode: viewModel.emailFocus, - // label: ForgotPasswordStrings.companyIdHint, - // isPassword: false, - // ), - // SizedBox(height: 70.0.h()), viewModel.isLoading ? const BaseButton( - isEnabled: false, - text: ForgotPasswordStrings.resetLink, - onPressed: null, - ) + isEnabled: false, + text: ForgotPasswordStrings.resetLink, + onPressed: null, + ) : BaseButton( - isEnabled: viewModel.isEmailValid, - text: ForgotPasswordStrings.resetLink, - onPressed: - viewModel.isEmailValid - ? () async { - Navigator.push( - context, - MaterialPageRoute( - builder: - (context) => - const ForgotPasswordOtpScreen(), - ), - ); + isEnabled: viewModel.isEmailValid, + text: ForgotPasswordStrings.resetLink, + onPressed: viewModel.isEmailValid + ? () async { + await viewModel.forgotPassword(context); } - : null, - ), - + : null, + ), const SizedBox(height: 12), TextButton( onPressed: () { - context.pop(); + Navigator.pop(context); }, child: Text( ForgotPasswordStrings.backToSignInButton, diff --git a/lib/views/login/pages/login_mobile_page.dart b/lib/views/login/pages/login_mobile_page.dart index e6549c5..b826cf7 100644 --- a/lib/views/login/pages/login_mobile_page.dart +++ b/lib/views/login/pages/login_mobile_page.dart @@ -102,20 +102,20 @@ class _LoginMobilePageState extends State { } }, ), - // SizedBox(height: 32.0.h()), - // TextButton( - // onPressed: () { - // ForgotPasswordRouteData().push(context); - // }, - // child: Text( - // LoginStrings.forgotPassword, - // style: TextStyle( - // fontSize: 14.0.sp(), - // fontWeight: FontWeight.w500, - // color: AppColors.mainBlue, - // ), - // ), - // ), + SizedBox(height: 32.0.h()), + TextButton( + onPressed: () { + const ForgotPasswordRouteData().push(context); + }, + child: Text( + LoginStrings.forgotPassword, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w500, + color: AppColors.mainBlue, + ), + ), + ), ], ); },