feat: Implement initial authentication, multi-environment support, and core app infrastructure.

This commit is contained in:
pradeepkumar
2026-02-23 19:23:30 +05:30
parent 0f07a16132
commit 4f8ad7fe49
55 changed files with 3836 additions and 118 deletions

View File

@@ -0,0 +1,92 @@
import 'package:dio/dio.dart';
import 'package:real_estate_mobile/config/app_config.dart';
import 'package:real_estate_mobile/core/network/api_exceptions.dart';
import 'package:real_estate_mobile/core/storage/secure_storage.dart';
class ApiClient {
static ApiClient? _instance;
late final Dio _dio;
ApiClient._() {
_dio = Dio(
BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
_dio.interceptors.addAll([
_authInterceptor(),
_errorInterceptor(),
]);
}
static ApiClient get instance {
_instance ??= ApiClient._();
return _instance!;
}
Dio get dio => _dio;
InterceptorsWrapper _authInterceptor() {
return InterceptorsWrapper(
onRequest: (options, handler) async {
final token = await SecureStorage.getAccessToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
},
);
}
InterceptorsWrapper _errorInterceptor() {
return InterceptorsWrapper(
onError: (error, handler) {
final response = error.response;
if (response != null) {
final data = response.data;
if (data is Map<String, dynamic>) {
final message = data['message'] is List
? (data['message'] as List).join(', ')
: data['message']?.toString() ?? 'An error occurred';
final errors = (data['errors'] as List<dynamic>?)
?.map((e) => FieldError.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
handler.reject(
DioException(
requestOptions: error.requestOptions,
response: error.response,
error: ApiException(
message: message,
statusCode: response.statusCode,
fieldErrors: errors,
),
),
);
return;
}
}
handler.reject(
DioException(
requestOptions: error.requestOptions,
response: error.response,
error: ApiException(
message: error.message ?? 'Network error occurred',
statusCode: response?.statusCode,
),
),
);
},
);
}
}

View File

@@ -0,0 +1,31 @@
class ApiException implements Exception {
final String message;
final int? statusCode;
final List<FieldError> fieldErrors;
const ApiException({
required this.message,
this.statusCode,
this.fieldErrors = const [],
});
@override
String toString() => 'ApiException: $message (status: $statusCode)';
}
class FieldError {
final String field;
final List<String> errors;
const FieldError({required this.field, required this.errors});
factory FieldError.fromJson(Map<String, dynamic> json) {
return FieldError(
field: json['field'] as String? ?? '',
errors: (json['errors'] as List<dynamic>?)
?.map((e) => e.toString())
.toList() ??
[],
);
}
}