refactor: improve specialization card layout and unify chat screen header and navigation with home screen components.
This commit is contained in:
66
lib/features/profile/data/profile_repository.dart
Normal file
66
lib/features/profile/data/profile_repository.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:real_estate_mobile/core/network/api_client.dart';
|
||||
import 'package:real_estate_mobile/core/network/api_exceptions.dart';
|
||||
|
||||
class ProfileRepository {
|
||||
final Dio _dio = ApiClient.instance.dio;
|
||||
|
||||
/// Fetch profile based on role.
|
||||
/// Agent: GET /agents/profile/me
|
||||
/// User: GET /users/profile/me
|
||||
Future<Map<String, dynamic>> getMyProfile(String role) async {
|
||||
try {
|
||||
final endpoint = role == 'AGENT'
|
||||
? '/agents/profile/me'
|
||||
: '/users/profile/me';
|
||||
final response = await _dio.get(endpoint);
|
||||
return response.data['data'] as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
if (e.error is ApiException) throw e.error as ApiException;
|
||||
throw ApiException(
|
||||
message: e.message ?? 'Failed to fetch profile',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update profile based on role.
|
||||
/// Agent: PUT /agents/profile
|
||||
/// User: PATCH /users/profile/me
|
||||
Future<Map<String, dynamic>> updateProfile(
|
||||
String role,
|
||||
Map<String, dynamic> data,
|
||||
) async {
|
||||
try {
|
||||
final Response response;
|
||||
if (role == 'AGENT') {
|
||||
response = await _dio.put('/agents/profile', data: data);
|
||||
} else {
|
||||
response = await _dio.patch('/users/profile/me', data: data);
|
||||
}
|
||||
return response.data['data'] as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
if (e.error is ApiException) throw e.error as ApiException;
|
||||
throw ApiException(
|
||||
message: e.message ?? 'Failed to update profile',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getAvatarUploadUrl(String role, String fileName) async {
|
||||
try {
|
||||
final endpoint = role == 'AGENT'
|
||||
? '/upload/avatar-presigned-url'
|
||||
: '/upload/user-avatar-presigned-url';
|
||||
final response = await _dio.post(endpoint, data: {'fileName': fileName});
|
||||
return response.data['data']['url'] as String;
|
||||
} on DioException catch (e) {
|
||||
if (e.error is ApiException) throw e.error as ApiException;
|
||||
throw ApiException(
|
||||
message: e.message ?? 'Failed to get upload URL',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart';
|
||||
import 'package:real_estate_mobile/features/profile/data/profile_repository.dart';
|
||||
|
||||
// Profile state
|
||||
class ProfileState {
|
||||
final String role;
|
||||
final Map<String, dynamic> profile;
|
||||
final bool isLoading;
|
||||
final bool isSaving;
|
||||
final String? errorMessage;
|
||||
final String? successMessage;
|
||||
|
||||
const ProfileState({
|
||||
this.role = 'USER',
|
||||
this.profile = const {},
|
||||
this.isLoading = true,
|
||||
this.isSaving = false,
|
||||
this.errorMessage,
|
||||
this.successMessage,
|
||||
});
|
||||
|
||||
bool get isAgent => role == 'AGENT';
|
||||
|
||||
ProfileState copyWith({
|
||||
String? role,
|
||||
Map<String, dynamic>? profile,
|
||||
bool? isLoading,
|
||||
bool? isSaving,
|
||||
String? errorMessage,
|
||||
String? successMessage,
|
||||
bool clearError = false,
|
||||
bool clearSuccess = false,
|
||||
}) {
|
||||
return ProfileState(
|
||||
role: role ?? this.role,
|
||||
profile: profile ?? this.profile,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
isSaving: isSaving ?? this.isSaving,
|
||||
errorMessage: clearError ? null : (errorMessage ?? this.errorMessage),
|
||||
successMessage:
|
||||
clearSuccess ? null : (successMessage ?? this.successMessage),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileNotifier extends StateNotifier<ProfileState> {
|
||||
final ProfileRepository _repository;
|
||||
final String _role;
|
||||
|
||||
ProfileNotifier(this._repository, this._role)
|
||||
: super(ProfileState(role: _role)) {
|
||||
loadProfile();
|
||||
}
|
||||
|
||||
Future<void> loadProfile() async {
|
||||
state = state.copyWith(isLoading: true, clearError: true);
|
||||
try {
|
||||
final profile = await _repository.getMyProfile(_role);
|
||||
state = state.copyWith(profile: profile, isLoading: false);
|
||||
} catch (e) {
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
errorMessage: e.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateProfile(Map<String, dynamic> data) async {
|
||||
state =
|
||||
state.copyWith(isSaving: true, clearError: true, clearSuccess: true);
|
||||
try {
|
||||
final updated = await _repository.updateProfile(_role, data);
|
||||
state = state.copyWith(
|
||||
profile: updated,
|
||||
isSaving: false,
|
||||
successMessage: 'Profile updated successfully',
|
||||
);
|
||||
} catch (e) {
|
||||
state = state.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: 'Failed to update profile',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Providers
|
||||
final profileRepositoryProvider = Provider<ProfileRepository>((ref) {
|
||||
return ProfileRepository();
|
||||
});
|
||||
|
||||
final profileProvider =
|
||||
StateNotifierProvider.autoDispose<ProfileNotifier, ProfileState>((ref) {
|
||||
final authState = ref.watch(authProvider);
|
||||
final role = authState.user?.role ?? 'USER';
|
||||
return ProfileNotifier(ref.watch(profileRepositoryProvider), role);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user