diff --git a/analysis_options.yaml b/analysis_options.yaml index a17b760..8bf901b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,11 +1,82 @@ include: package:flutter_lints/flutter.yaml +analyzer: + exclude: [lib/**.freezed.dart, lib/**.g.dart] + linter: rules: - always_declare_return_types: true - avoid_init_to_null: true - use_string_buffers: true prefer_single_quotes: true require_trailing_commas: true + always_declare_return_types: true + avoid_init_to_null: true always_put_control_body_on_new_line: true - prefer_relative_imports: true + always_put_required_named_parameters_first: true + # always_specify_types: true + # always_use_package_imports: true + avoid_annotating_with_dynamic: true + avoid_bool_literals_in_conditional_expressions: true + avoid_catches_without_on_clauses: true + avoid_catching_errors: true + avoid_classes_with_only_static_members: true + avoid_final_parameters: true + avoid_function_literals_in_foreach_calls: true + avoid_relative_lib_imports: true + avoid_return_types_on_setters: true + avoid_returning_null_for_void: true + avoid_shadowing_type_parameters: true + avoid_types_as_parameter_names: true + await_only_futures: true + camel_case_extensions: true + camel_case_types: true + constant_identifier_names: true + control_flow_in_finally: true + curly_braces_in_flow_control_structures: true + empty_catches: true + empty_constructor_bodies: true + empty_statements: true + exhaustive_cases: true + file_names: true + void_checks: true + implementation_imports: true + implicit_call_tearoffs: true + library_prefixes: true + no_duplicate_case_values: true + no_leading_underscores_for_library_prefixes: true + no_leading_underscores_for_local_identifiers: true + non_constant_identifier_names: true + null_check_on_nullable_type_parameter: true + null_closures: true + prefer_adjacent_string_concatenation: true + prefer_collection_literals: true + prefer_conditional_assignment: true + prefer_contains: true + prefer_final_fields: true + prefer_function_declarations_over_variables: true + prefer_generic_function_type_aliases: true + prefer_if_null_operators: true + prefer_initializing_formals: true + prefer_inlined_adds: true + prefer_interpolation_to_compose_strings: true + prefer_is_empty: true + prefer_is_not_empty: true + prefer_is_not_operator: true + prefer_iterable_whereType: true + prefer_null_aware_operators: true + prefer_spread_collections: true + recursive_getters: true + type_init_formals: true + unnecessary_constructor_name: true + unnecessary_getters_setters: true + unnecessary_late: true + unnecessary_new: true + unnecessary_null_aware_assignments: true + unnecessary_null_in_if_null_operators: true + unnecessary_nullable_for_final_variable_declarations: true + unnecessary_string_escapes: true + unnecessary_string_interpolations: true + unnecessary_this: true + unnecessary_to_list_in_spreads: true + unrelated_type_equality_checks: true + use_rethrow_when_possible: true + use_string_in_part_of_directives: true + use_function_type_syntax_for_parameters: true \ No newline at end of file diff --git a/lib/common/models/.gitkeep b/lib/common/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/core/config/dependencies.dart b/lib/core/config/dependencies.dart new file mode 100644 index 0000000..97fb698 --- /dev/null +++ b/lib/core/config/dependencies.dart @@ -0,0 +1,39 @@ +import 'package:provider/provider.dart'; +import 'package:provider/single_child_widget.dart'; + +import '../../data/repositories/auth_repository.dart'; +import '../../data/services/auth_service.dart'; +import '../../data/services/network_manager.dart'; +import '../../view_models/auth_view_model.dart'; + +List get providersRemote { + return [...cores, ...apiClients, ...repositories, ...viewModels, ...others]; +} + +List get cores { + return [Provider(create: (context) => NetworkManager())]; +} + +List get apiClients { + return [ + Provider(create: (context) => AuthService(networkManager: context.read())), + ]; +} + +List get repositories { + return [ + Provider(create: (context) => AuthRepository(authService: context.read())), + ]; +} + +List get viewModels { + return [ + Provider( + create: (context) => AuthViewModel(authRepository: context.read()), + ), + ]; +} + +List get others { + return []; +} diff --git a/lib/core/utils/either.dart b/lib/core/utils/either.dart deleted file mode 100644 index 88828b8..0000000 --- a/lib/core/utils/either.dart +++ /dev/null @@ -1,70 +0,0 @@ -typedef OnError = void Function(E error); - -abstract class Either { - const Either(); - - E? getError(); - - S? getSuccess(); - - bool isError(); - - bool isSuccess(); - - void when({ - required OnError error, - required void Function(S success) success, - }); -} - -class Success extends Either { - final S _success; - - const Success(this._success); - - @override - E? getError() => null; - - @override - S? getSuccess() => _success; - - @override - bool isError() => false; - - @override - bool isSuccess() => true; - - @override - void when({ - required OnError error, - required void Function(S success) success, - }) { - success(_success); - } -} - -class Error extends Either { - final E _error; - - const Error(this._error); - - @override - E? getError() => _error; - - @override - S? getSuccess() => null; - - @override - bool isError() => true; - - @override - bool isSuccess() => false; - - @override - void when({ - required OnError error, - required void Function(S success) success, - }) { - error(_error); - } -} diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index b1f3726..7c669d8 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -6,7 +6,8 @@ import '../services/auth_service.dart'; class AuthRepository { final AuthService _authService; - AuthRepository(this._authService); + AuthRepository({required AuthService authService}) + : _authService = authService; Future> login(String email, String password) async { try { diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart index f1225d2..e8c4f39 100644 --- a/lib/data/services/auth_service.dart +++ b/lib/data/services/auth_service.dart @@ -1,4 +1,11 @@ +import 'network_manager.dart'; + class AuthService { + AuthService({required NetworkManager networkManager}) + : _networkManager = networkManager; + + final NetworkManager _networkManager; + Future> login(String email, String password) async { // شبیه‌سازی API Call await Future.delayed(const Duration(seconds: 2)); @@ -10,3 +17,20 @@ class AuthService { } } } + +// Future> getImages(String projectId) async { +// try { +// final result = await networkManager.makeRequest( +// '/api/v1/projects/$projectId/files', +// (json) { +// Logger().i(json); +// return OnlineGalleryResponse.fromJson(json); +// }, +// method: 'GET', +// ); + +// return Result.ok(result); +// } on DioException catch (e) { +// return Result.error(e); +// } +// } diff --git a/lib/data/services/network_manager.dart b/lib/data/services/network_manager.dart new file mode 100644 index 0000000..9594504 --- /dev/null +++ b/lib/data/services/network_manager.dart @@ -0,0 +1,129 @@ +import 'package:dio/dio.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class NetworkManager { + final Dio mainDio; + + NetworkManager() + : mainDio = Dio( + BaseOptions( + // baseUrl: 'https://stage.cura-bau.de', + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), + contentType: 'application/json', + headers: {'Accept': 'application/json'}, + ), + ); + + Future init() async { + await addInterceptors(); + } + + Future addInterceptors() async { + final requestInterceptor = InterceptorsWrapper( + onRequest: (options, handler) { + // TODO: add more + return handler.next(options); + }, + ); + + mainDio.interceptors.add(requestInterceptor); + } + + Future makeRequest( + String endpoint, + T Function(Map) fromJson, { + String method = 'GET', + FormData? data, + }) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + + Dio dioInstance = mainDio; + + await addInterceptors(); + + Future makeApiCall(String token, String url) async { + final headers = + token.isEmpty + ? {} + : {'Authorization': 'Bearer $token'}; + switch (method.toUpperCase()) { + case 'POST': + return await dioInstance.post( + url + endpoint, + data: data, + options: Options(headers: headers), + ); + case 'PUT': + return await dioInstance.put( + url + endpoint, + data: data, + options: Options(headers: headers), + ); + case 'PATCH': + return await dioInstance.patch( + url + endpoint, + data: data, + options: Options(headers: headers), + ); + case 'DELETE': + return await dioInstance.delete( + url + endpoint, + data: data, + options: Options(headers: headers), + ); + case 'GET': + return await dioInstance.get( + url + endpoint, + data: data, + options: Options(headers: headers), + ); + default: + return await dioInstance.get( + url + endpoint, + options: Options(headers: headers), + ); + } + } + + try { + String? bearerToken = prefs.getString('bearer'); + String? url = prefs.getString('url'); + Response res = await makeApiCall(bearerToken ?? '', url ?? ''); + switch (res.statusCode) { + case 200: + case 201: + return fromJson(res.data); + case 401: + break; + case 403: + throw Exception('Forbidden: ${res.data['message']}'); + case 404: + throw Exception('Not found: ${res.data['message']}'); + case 423: + throw Exception('Locked: ${res.data['message']}'); + case 500: + throw Exception('Internal server error: ${res.data['message']}'); + default: + throw Exception('Error: ${res.data['message']}'); + } + } on DioException catch (e) { + if (e.response?.statusCode == 401) { + // await api.refreshToken(); + String? bearerToken = prefs.getString('bearer'); + String? url = prefs.getString('url'); + Response res = await makeApiCall(bearerToken ?? '', url ?? ''); + + if (res.statusCode == 200 || res.statusCode == 201) { + return fromJson(res.data); + } + + throw Exception(e.message ?? 'Error occurred'); + } + } catch (e) { + throw Exception(e.toString()); + } + + throw Exception('Unhandled error occurred'); + } +} diff --git a/lib/view_models/auth_view_model.dart b/lib/view_models/auth_view_model.dart index e2fbfed..40492ac 100644 --- a/lib/view_models/auth_view_model.dart +++ b/lib/view_models/auth_view_model.dart @@ -7,7 +7,8 @@ import '../data/repositories/auth_repository.dart'; class AuthViewModel with ChangeNotifier { final AuthRepository _authRepository; - AuthViewModel(this._authRepository); + AuthViewModel({required AuthRepository authRepository}) + : _authRepository = authRepository; bool _isLoading = false; String? _errorMessage; diff --git a/lib/common/widgets/bottom_navigation.dart b/lib/views/shared/bottom_navigation.dart similarity index 100% rename from lib/common/widgets/bottom_navigation.dart rename to lib/views/shared/bottom_navigation.dart diff --git a/pubspec.lock b/pubspec.lock index cb7cb86..0bf1efb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -177,14 +177,38 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + dio: + dependency: "direct main" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" fake_async: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" file: dependency: transitive description: @@ -340,10 +364,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -448,6 +472,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -456,6 +504,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" pool: dependency: transitive description: @@ -488,6 +552,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + url: "https://pub.dev" + source: hosted + version: "2.4.10" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" shelf: dependency: transitive description: @@ -633,10 +753,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" watcher: dependency: transitive description: @@ -669,6 +789,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1936050..3b69769 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,8 @@ dependencies: flutter_svg: ^2.2.0 go_router: ^16.0.0 logger: ^2.6.0 + dio: ^5.8.0+1 + shared_preferences: ^2.5.3 dev_dependencies: