fix settings
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import '../../../../core/errors/exceptions.dart';
|
||||
import '../models/user_model.dart';
|
||||
|
||||
abstract class AuthLocalDataSource {
|
||||
Future<void> cacheUser(UserModel user);
|
||||
Future<UserModel?> getCachedUser();
|
||||
Future<void> clearCache();
|
||||
Future<void> cacheToken(String token);
|
||||
Future<String?> getCachedToken();
|
||||
}
|
||||
|
||||
class AuthLocalDataSourceImpl implements AuthLocalDataSource {
|
||||
final FlutterSecureStorage secureStorage;
|
||||
|
||||
static const String userKey = 'CACHED_USER';
|
||||
static const String tokenKey = 'AUTH_TOKEN';
|
||||
|
||||
AuthLocalDataSourceImpl({required this.secureStorage});
|
||||
|
||||
@override
|
||||
Future<void> cacheUser(UserModel user) async {
|
||||
try {
|
||||
final userJson = json.encode(user.toJson());
|
||||
await secureStorage.write(key: userKey, value: userJson);
|
||||
} catch (e) {
|
||||
throw CacheException('Failed to cache user');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserModel?> getCachedUser() async {
|
||||
try {
|
||||
final userJson = await secureStorage.read(key: userKey);
|
||||
if (userJson != null) {
|
||||
final userMap = json.decode(userJson) as Map<String, dynamic>;
|
||||
return UserModel.fromJson(userMap);
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
throw CacheException('Failed to get cached user');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clearCache() async {
|
||||
try {
|
||||
await secureStorage.delete(key: userKey);
|
||||
await secureStorage.delete(key: tokenKey);
|
||||
} catch (e) {
|
||||
throw CacheException('Failed to clear cache');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> cacheToken(String token) async {
|
||||
try {
|
||||
await secureStorage.write(key: tokenKey, value: token);
|
||||
} catch (e) {
|
||||
throw CacheException('Failed to cache token');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getCachedToken() async {
|
||||
try {
|
||||
return await secureStorage.read(key: tokenKey);
|
||||
} catch (e) {
|
||||
throw CacheException('Failed to get cached token');
|
||||
}
|
||||
}
|
||||
}
|
||||
232
lib/features/auth/data/datasources/auth_remote_datasource.dart
Normal file
232
lib/features/auth/data/datasources/auth_remote_datasource.dart
Normal file
@@ -0,0 +1,232 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../../../core/errors/exceptions.dart';
|
||||
import '../../../../core/network/dio_client.dart';
|
||||
import '../models/user_model.dart';
|
||||
|
||||
abstract class AuthRemoteDataSource {
|
||||
Future<UserModel> login({
|
||||
required String email,
|
||||
required String password,
|
||||
});
|
||||
|
||||
Future<UserModel> register({
|
||||
required String email,
|
||||
required String password,
|
||||
required String name,
|
||||
});
|
||||
|
||||
Future<void> logout();
|
||||
|
||||
Future<UserModel> refreshToken(String token);
|
||||
|
||||
Future<UserModel> updateProfile({
|
||||
required String name,
|
||||
String? avatarUrl,
|
||||
});
|
||||
|
||||
Future<void> changePassword({
|
||||
required String oldPassword,
|
||||
required String newPassword,
|
||||
});
|
||||
|
||||
Future<void> resetPassword({
|
||||
required String email,
|
||||
});
|
||||
}
|
||||
|
||||
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
final DioClient dioClient;
|
||||
|
||||
AuthRemoteDataSourceImpl({required this.dioClient});
|
||||
|
||||
@override
|
||||
Future<UserModel> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
// Using JSONPlaceholder as a mock API
|
||||
// In real app, this would be your actual auth endpoint
|
||||
final response = await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/posts',
|
||||
data: {
|
||||
'email': email,
|
||||
'password': password,
|
||||
},
|
||||
);
|
||||
|
||||
// Mock validation - accept any email/password for demo
|
||||
// In real app, the server would validate credentials
|
||||
if (email.isEmpty || password.isEmpty) {
|
||||
throw const ServerException('Invalid credentials');
|
||||
}
|
||||
|
||||
// Mock response for demonstration
|
||||
// In real app, parse actual API response
|
||||
final mockUser = {
|
||||
'id': '1',
|
||||
'email': email,
|
||||
'name': email.split('@').first,
|
||||
'token': 'mock_jwt_token_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'tokenExpiry': DateTime.now().add(const Duration(days: 7)).toIso8601String(),
|
||||
};
|
||||
|
||||
return UserModel.fromJson(mockUser);
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 401) {
|
||||
throw const ServerException('Invalid credentials');
|
||||
} else if (e.response?.statusCode == 404) {
|
||||
throw const ServerException('User not found');
|
||||
} else {
|
||||
throw ServerException(e.message ?? 'Login failed');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.toString().contains('Invalid credentials')) {
|
||||
rethrow;
|
||||
}
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserModel> register({
|
||||
required String email,
|
||||
required String password,
|
||||
required String name,
|
||||
}) async {
|
||||
try {
|
||||
// Mock API call
|
||||
final response = await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/users',
|
||||
data: {
|
||||
'email': email,
|
||||
'password': password,
|
||||
'name': name,
|
||||
},
|
||||
);
|
||||
|
||||
// Mock response
|
||||
final mockUser = {
|
||||
'id': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
'email': email,
|
||||
'name': name,
|
||||
'token': 'mock_jwt_token_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'tokenExpiry': DateTime.now().add(const Duration(days: 7)).toIso8601String(),
|
||||
};
|
||||
|
||||
return UserModel.fromJson(mockUser);
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 409) {
|
||||
throw const ServerException('Email already exists');
|
||||
} else {
|
||||
throw ServerException(e.message ?? 'Registration failed');
|
||||
}
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
// Mock API call
|
||||
await dioClient.dio.post('https://jsonplaceholder.typicode.com/posts');
|
||||
// In real app, you might call a logout endpoint to invalidate token
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserModel> refreshToken(String token) async {
|
||||
try {
|
||||
// Mock API call
|
||||
final response = await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/users',
|
||||
options: Options(
|
||||
headers: {'Authorization': 'Bearer $token'},
|
||||
),
|
||||
);
|
||||
|
||||
// Mock response
|
||||
final mockUser = {
|
||||
'id': '1',
|
||||
'email': 'user@example.com',
|
||||
'name': 'User',
|
||||
'token': 'refreshed_token_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'tokenExpiry': DateTime.now().add(const Duration(days: 7)).toIso8601String(),
|
||||
};
|
||||
|
||||
return UserModel.fromJson(mockUser);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserModel> updateProfile({
|
||||
required String name,
|
||||
String? avatarUrl,
|
||||
}) async {
|
||||
try {
|
||||
// Mock API call
|
||||
final response = await dioClient.dio.put(
|
||||
'https://jsonplaceholder.typicode.com/users/1',
|
||||
data: {
|
||||
'name': name,
|
||||
'avatarUrl': avatarUrl,
|
||||
},
|
||||
);
|
||||
|
||||
// Mock response
|
||||
final mockUser = {
|
||||
'id': '1',
|
||||
'email': 'user@example.com',
|
||||
'name': name,
|
||||
'avatarUrl': avatarUrl,
|
||||
'token': 'mock_jwt_token_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'tokenExpiry': DateTime.now().add(const Duration(days: 7)).toIso8601String(),
|
||||
};
|
||||
|
||||
return UserModel.fromJson(mockUser);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> changePassword({
|
||||
required String oldPassword,
|
||||
required String newPassword,
|
||||
}) async {
|
||||
try {
|
||||
// Mock API call
|
||||
await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/posts',
|
||||
data: {
|
||||
'oldPassword': oldPassword,
|
||||
'newPassword': newPassword,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> resetPassword({
|
||||
required String email,
|
||||
}) async {
|
||||
try {
|
||||
// Mock API call
|
||||
await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/posts',
|
||||
data: {
|
||||
'email': email,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
42
lib/features/auth/data/models/user_model.dart
Normal file
42
lib/features/auth/data/models/user_model.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import '../../domain/entities/user.dart';
|
||||
|
||||
part 'user_model.freezed.dart';
|
||||
part 'user_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class UserModel with _$UserModel {
|
||||
const factory UserModel({
|
||||
required String id,
|
||||
required String email,
|
||||
required String name,
|
||||
String? avatarUrl,
|
||||
required String token,
|
||||
DateTime? tokenExpiry,
|
||||
}) = _UserModel;
|
||||
|
||||
const UserModel._();
|
||||
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserModelFromJson(json);
|
||||
|
||||
/// Convert to domain entity
|
||||
User toEntity() => User(
|
||||
id: id,
|
||||
email: email,
|
||||
name: name,
|
||||
avatarUrl: avatarUrl,
|
||||
token: token,
|
||||
tokenExpiry: tokenExpiry,
|
||||
);
|
||||
|
||||
/// Create from domain entity
|
||||
factory UserModel.fromEntity(User user) => UserModel(
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
token: user.token,
|
||||
tokenExpiry: user.tokenExpiry,
|
||||
);
|
||||
}
|
||||
259
lib/features/auth/data/models/user_model.freezed.dart
Normal file
259
lib/features/auth/data/models/user_model.freezed.dart
Normal file
@@ -0,0 +1,259 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// 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 'user_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
UserModel _$UserModelFromJson(Map<String, dynamic> json) {
|
||||
return _UserModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UserModel {
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
String get email => throw _privateConstructorUsedError;
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String? get avatarUrl => throw _privateConstructorUsedError;
|
||||
String get token => throw _privateConstructorUsedError;
|
||||
DateTime? get tokenExpiry => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$UserModelCopyWith<UserModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $UserModelCopyWith<$Res> {
|
||||
factory $UserModelCopyWith(UserModel value, $Res Function(UserModel) then) =
|
||||
_$UserModelCopyWithImpl<$Res, UserModel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String id,
|
||||
String email,
|
||||
String name,
|
||||
String? avatarUrl,
|
||||
String token,
|
||||
DateTime? tokenExpiry});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UserModelCopyWithImpl<$Res, $Val extends UserModel>
|
||||
implements $UserModelCopyWith<$Res> {
|
||||
_$UserModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? email = null,
|
||||
Object? name = null,
|
||||
Object? avatarUrl = freezed,
|
||||
Object? token = null,
|
||||
Object? tokenExpiry = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
avatarUrl: freezed == avatarUrl
|
||||
? _value.avatarUrl
|
||||
: avatarUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
token: null == token
|
||||
? _value.token
|
||||
: token // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
tokenExpiry: freezed == tokenExpiry
|
||||
? _value.tokenExpiry
|
||||
: tokenExpiry // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$UserModelImplCopyWith<$Res>
|
||||
implements $UserModelCopyWith<$Res> {
|
||||
factory _$$UserModelImplCopyWith(
|
||||
_$UserModelImpl value, $Res Function(_$UserModelImpl) then) =
|
||||
__$$UserModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String id,
|
||||
String email,
|
||||
String name,
|
||||
String? avatarUrl,
|
||||
String token,
|
||||
DateTime? tokenExpiry});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$UserModelImplCopyWithImpl<$Res>
|
||||
extends _$UserModelCopyWithImpl<$Res, _$UserModelImpl>
|
||||
implements _$$UserModelImplCopyWith<$Res> {
|
||||
__$$UserModelImplCopyWithImpl(
|
||||
_$UserModelImpl _value, $Res Function(_$UserModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? email = null,
|
||||
Object? name = null,
|
||||
Object? avatarUrl = freezed,
|
||||
Object? token = null,
|
||||
Object? tokenExpiry = freezed,
|
||||
}) {
|
||||
return _then(_$UserModelImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
avatarUrl: freezed == avatarUrl
|
||||
? _value.avatarUrl
|
||||
: avatarUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
token: null == token
|
||||
? _value.token
|
||||
: token // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
tokenExpiry: freezed == tokenExpiry
|
||||
? _value.tokenExpiry
|
||||
: tokenExpiry // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$UserModelImpl extends _UserModel {
|
||||
const _$UserModelImpl(
|
||||
{required this.id,
|
||||
required this.email,
|
||||
required this.name,
|
||||
this.avatarUrl,
|
||||
required this.token,
|
||||
this.tokenExpiry})
|
||||
: super._();
|
||||
|
||||
factory _$UserModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UserModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String email;
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final String? avatarUrl;
|
||||
@override
|
||||
final String token;
|
||||
@override
|
||||
final DateTime? tokenExpiry;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserModel(id: $id, email: $email, name: $name, avatarUrl: $avatarUrl, token: $token, tokenExpiry: $tokenExpiry)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UserModelImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.email, email) || other.email == email) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.avatarUrl, avatarUrl) ||
|
||||
other.avatarUrl == avatarUrl) &&
|
||||
(identical(other.token, token) || other.token == token) &&
|
||||
(identical(other.tokenExpiry, tokenExpiry) ||
|
||||
other.tokenExpiry == tokenExpiry));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, id, email, name, avatarUrl, token, tokenExpiry);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$UserModelImplCopyWith<_$UserModelImpl> get copyWith =>
|
||||
__$$UserModelImplCopyWithImpl<_$UserModelImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$UserModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _UserModel extends UserModel {
|
||||
const factory _UserModel(
|
||||
{required final String id,
|
||||
required final String email,
|
||||
required final String name,
|
||||
final String? avatarUrl,
|
||||
required final String token,
|
||||
final DateTime? tokenExpiry}) = _$UserModelImpl;
|
||||
const _UserModel._() : super._();
|
||||
|
||||
factory _UserModel.fromJson(Map<String, dynamic> json) =
|
||||
_$UserModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get id;
|
||||
@override
|
||||
String get email;
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
String? get avatarUrl;
|
||||
@override
|
||||
String get token;
|
||||
@override
|
||||
DateTime? get tokenExpiry;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$UserModelImplCopyWith<_$UserModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
29
lib/features/auth/data/models/user_model.g.dart
Normal file
29
lib/features/auth/data/models/user_model.g.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$UserModelImpl _$$UserModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$UserModelImpl(
|
||||
id: json['id'] as String,
|
||||
email: json['email'] as String,
|
||||
name: json['name'] as String,
|
||||
avatarUrl: json['avatarUrl'] as String?,
|
||||
token: json['token'] as String,
|
||||
tokenExpiry: json['tokenExpiry'] == null
|
||||
? null
|
||||
: DateTime.parse(json['tokenExpiry'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$UserModelImplToJson(_$UserModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'email': instance.email,
|
||||
'name': instance.name,
|
||||
'avatarUrl': instance.avatarUrl,
|
||||
'token': instance.token,
|
||||
'tokenExpiry': instance.tokenExpiry?.toIso8601String(),
|
||||
};
|
||||
232
lib/features/auth/data/repositories/auth_repository_impl.dart
Normal file
232
lib/features/auth/data/repositories/auth_repository_impl.dart
Normal file
@@ -0,0 +1,232 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import '../../../../core/errors/exceptions.dart';
|
||||
import '../../../../core/errors/failures.dart';
|
||||
import '../../../../core/network/network_info.dart';
|
||||
import '../../domain/entities/user.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
import '../datasources/auth_local_datasource.dart';
|
||||
import '../datasources/auth_remote_datasource.dart';
|
||||
import '../models/user_model.dart';
|
||||
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
final AuthRemoteDataSource remoteDataSource;
|
||||
final AuthLocalDataSource localDataSource;
|
||||
final NetworkInfo networkInfo;
|
||||
|
||||
AuthRepositoryImpl({
|
||||
required this.remoteDataSource,
|
||||
required this.localDataSource,
|
||||
required this.networkInfo,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
if (!await networkInfo.isConnected) {
|
||||
return const Left(NetworkFailure('No internet connection'));
|
||||
}
|
||||
|
||||
try {
|
||||
final userModel = await remoteDataSource.login(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
// Cache user data and token
|
||||
await localDataSource.cacheUser(userModel);
|
||||
await localDataSource.cacheToken(userModel.token);
|
||||
|
||||
return Right(userModel.toEntity());
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> register({
|
||||
required String email,
|
||||
required String password,
|
||||
required String name,
|
||||
}) async {
|
||||
if (!await networkInfo.isConnected) {
|
||||
return const Left(NetworkFailure('No internet connection'));
|
||||
}
|
||||
|
||||
try {
|
||||
final userModel = await remoteDataSource.register(
|
||||
email: email,
|
||||
password: password,
|
||||
name: name,
|
||||
);
|
||||
|
||||
// Cache user data and token
|
||||
await localDataSource.cacheUser(userModel);
|
||||
await localDataSource.cacheToken(userModel.token);
|
||||
|
||||
return Right(userModel.toEntity());
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> logout() async {
|
||||
try {
|
||||
// Clear local cache first
|
||||
await localDataSource.clearCache();
|
||||
|
||||
// If online, notify server
|
||||
if (await networkInfo.isConnected) {
|
||||
await remoteDataSource.logout();
|
||||
}
|
||||
|
||||
return const Right(null);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} on CacheException catch (e) {
|
||||
return Left(CacheFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User?>> getCurrentUser() async {
|
||||
try {
|
||||
final cachedUser = await localDataSource.getCachedUser();
|
||||
if (cachedUser != null) {
|
||||
// Check if token is still valid
|
||||
final user = cachedUser.toEntity();
|
||||
if (user.isTokenValid) {
|
||||
return Right(user);
|
||||
} else {
|
||||
// Token expired, try to refresh
|
||||
if (await networkInfo.isConnected) {
|
||||
return await refreshToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
return const Right(null);
|
||||
} on CacheException catch (e) {
|
||||
return Left(CacheFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(CacheFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isAuthenticated() async {
|
||||
try {
|
||||
final cachedUser = await localDataSource.getCachedUser();
|
||||
if (cachedUser != null) {
|
||||
final user = cachedUser.toEntity();
|
||||
return user.isTokenValid;
|
||||
}
|
||||
return false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> refreshToken() async {
|
||||
if (!await networkInfo.isConnected) {
|
||||
return const Left(NetworkFailure('No internet connection'));
|
||||
}
|
||||
|
||||
try {
|
||||
final token = await localDataSource.getCachedToken();
|
||||
if (token == null) {
|
||||
return const Left(AuthFailure('No token available'));
|
||||
}
|
||||
|
||||
final userModel = await remoteDataSource.refreshToken(token);
|
||||
|
||||
// Update cached user and token
|
||||
await localDataSource.cacheUser(userModel);
|
||||
await localDataSource.cacheToken(userModel.token);
|
||||
|
||||
return Right(userModel.toEntity());
|
||||
} on ServerException catch (e) {
|
||||
// If refresh fails, clear cache
|
||||
await localDataSource.clearCache();
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> updateProfile({
|
||||
required String name,
|
||||
String? avatarUrl,
|
||||
}) async {
|
||||
if (!await networkInfo.isConnected) {
|
||||
return const Left(NetworkFailure('No internet connection'));
|
||||
}
|
||||
|
||||
try {
|
||||
final userModel = await remoteDataSource.updateProfile(
|
||||
name: name,
|
||||
avatarUrl: avatarUrl,
|
||||
);
|
||||
|
||||
// Update cached user
|
||||
await localDataSource.cacheUser(userModel);
|
||||
|
||||
return Right(userModel.toEntity());
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> changePassword({
|
||||
required String oldPassword,
|
||||
required String newPassword,
|
||||
}) async {
|
||||
if (!await networkInfo.isConnected) {
|
||||
return const Left(NetworkFailure('No internet connection'));
|
||||
}
|
||||
|
||||
try {
|
||||
await remoteDataSource.changePassword(
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPassword,
|
||||
);
|
||||
|
||||
return const Right(null);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> resetPassword({
|
||||
required String email,
|
||||
}) async {
|
||||
if (!await networkInfo.isConnected) {
|
||||
return const Left(NetworkFailure('No internet connection'));
|
||||
}
|
||||
|
||||
try {
|
||||
await remoteDataSource.resetPassword(email: email);
|
||||
|
||||
return const Right(null);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user