feat : new injection logic
test : some file :) chore : upgrade android gradle
This commit is contained in:
33
packages/core/.gitignore
vendored
Normal file
33
packages/core/.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Ignore build output and other generated files
|
||||
build/
|
||||
|
||||
# Dart/Pub related
|
||||
.dart_tool/
|
||||
.packages
|
||||
.pub/
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Other
|
||||
*.log
|
||||
*.swp
|
||||
*.pyc
|
||||
|
||||
# Flutter specific
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
pubspec.lock
|
||||
|
||||
# Test outputs
|
||||
test_cache/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/flutter_map/lib/assets/flutter_map_logo.png":["packages/flutter_map/lib/assets/flutter_map_logo.png"],"packages/font_awesome_flutter/lib/fonts/fa-brands-400.ttf":["packages/font_awesome_flutter/lib/fonts/fa-brands-400.ttf"],"packages/font_awesome_flutter/lib/fonts/fa-regular-400.ttf":["packages/font_awesome_flutter/lib/fonts/fa-regular-400.ttf"],"packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf":["packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf"]}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -2,6 +2,7 @@ library;
|
||||
|
||||
export 'package:android_intent_plus/android_intent.dart';
|
||||
export 'package:android_intent_plus/flag.dart';
|
||||
export 'package:connectivity_plus/connectivity_plus.dart';
|
||||
export 'package:device_info_plus/device_info_plus.dart';
|
||||
export 'package:dio/dio.dart';
|
||||
//other packages
|
||||
|
||||
24
packages/core/lib/data/services/network_status.dart
Normal file
24
packages/core/lib/data/services/network_status.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class NetworkStatus {
|
||||
NetworkStatus._();
|
||||
|
||||
static final NetworkStatus _instance = NetworkStatus._();
|
||||
|
||||
factory NetworkStatus() => _instance;
|
||||
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
|
||||
RxBool isConnected = false.obs;
|
||||
|
||||
void startListening() {
|
||||
_connectivity.onConnectivityChanged.listen((result) {
|
||||
isConnected.value = !result.contains(ConnectivityResult.none);
|
||||
});
|
||||
|
||||
_connectivity.checkConnectivity().then((result) {
|
||||
isConnected.value = !result.contains(ConnectivityResult.none);
|
||||
});
|
||||
}
|
||||
}
|
||||
168
packages/core/lib/infrastructure/remote/app_interceptor_n.dart
Normal file
168
packages/core/lib/infrastructure/remote/app_interceptor_n.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'dart:async';
|
||||
|
||||
import '../../core.dart';
|
||||
|
||||
/// Callback to refresh the authentication token.
|
||||
/// Typically used to request a new token from the server.
|
||||
typedef RefreshTokenCallback = Future<String?> Function();
|
||||
|
||||
/// Callback to save a new authentication token.
|
||||
typedef SaveTokenCallback = Future<void> Function(String token);
|
||||
|
||||
/// Callback to clear the authentication token, e.g., on logout or failure.
|
||||
typedef ClearTokenCallback = Future<void> Function();
|
||||
|
||||
/// Callback invoked when token refresh fails.
|
||||
/// Typically used to redirect the user to login or show a logout message.
|
||||
typedef OnRefreshFailedCallback = Future<void> Function();
|
||||
|
||||
/// Represents a queued request waiting for token refresh.
|
||||
class QueuedRequest {
|
||||
/// The original request options.
|
||||
final RequestOptions options;
|
||||
|
||||
/// Completer used to complete the response once the request is retried.
|
||||
final Completer<Response> completer;
|
||||
|
||||
/// Constructs a queued request.
|
||||
QueuedRequest(this.options, this.completer);
|
||||
}
|
||||
|
||||
/// An interceptor for automatic token management and refresh handling.
|
||||
///
|
||||
/// Features:
|
||||
/// - Queues requests while a token refresh is in progress.
|
||||
/// - Saves and clears tokens via provided callbacks.
|
||||
/// - Calls [OnRefreshFailedCallback] if token refresh fails.
|
||||
class AppInterceptorN extends Interceptor {
|
||||
/// Callback to refresh the authentication token.
|
||||
final RefreshTokenCallback? refreshTokenCallback;
|
||||
|
||||
/// Callback to save the new token.
|
||||
final SaveTokenCallback saveTokenCallback;
|
||||
|
||||
/// Callback to clear the token.
|
||||
final ClearTokenCallback clearTokenCallback;
|
||||
|
||||
/// Callback executed when token refresh fails.
|
||||
final OnRefreshFailedCallback onRefreshFailed;
|
||||
|
||||
/// Optional additional arguments for authentication.
|
||||
final dynamic authArguments;
|
||||
|
||||
/// The Dio instance used to send requests.
|
||||
final Dio dio;
|
||||
|
||||
/// Maximum number of retry attempts for failed requests.
|
||||
final int maxRetries;
|
||||
|
||||
/// Whether a token refresh is currently in progress.
|
||||
bool _isRefreshing = false;
|
||||
|
||||
/// Queue of requests waiting for a new token.
|
||||
final List<QueuedRequest> _queue = [];
|
||||
|
||||
/// Current token in use.
|
||||
String? _currentToken;
|
||||
|
||||
/// Constructs the interceptor.
|
||||
AppInterceptorN({
|
||||
required this.dio,
|
||||
required this.saveTokenCallback,
|
||||
required this.clearTokenCallback,
|
||||
required this.onRefreshFailed,
|
||||
this.refreshTokenCallback,
|
||||
this.authArguments,
|
||||
this.maxRetries = 3,
|
||||
});
|
||||
|
||||
/// Called before sending a request.
|
||||
/// If a token refresh is in progress, the request is added to the queue.
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
|
||||
if (_isRefreshing) {
|
||||
final completer = Completer<Response>();
|
||||
_queue.add(QueuedRequest(options, completer));
|
||||
return handler.resolve(await completer.future);
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
/// Called when an error occurs during a request.
|
||||
///
|
||||
/// - If the error is a 401 (unauthorized) and retry count is below `maxRetries`,
|
||||
/// the token is refreshed and queued requests are retried.
|
||||
/// - If the token refresh fails, all queued requests are cancelled and
|
||||
/// [onRefreshFailed] is executed.
|
||||
@override
|
||||
Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
int currentRetry = err.requestOptions.extra['retryCount'] ?? 0;
|
||||
|
||||
if (err.response?.statusCode == 401 &&
|
||||
err.type != DioExceptionType.cancel &&
|
||||
currentRetry < maxRetries) {
|
||||
final completer = Completer<Response>();
|
||||
final updatedOptions = err.requestOptions.copyWith(
|
||||
extra: {...err.requestOptions.extra, 'retryCount': currentRetry + 1},
|
||||
);
|
||||
_queue.add(QueuedRequest(updatedOptions, completer));
|
||||
|
||||
if (!_isRefreshing) {
|
||||
_isRefreshing = true;
|
||||
try {
|
||||
final newToken = await refreshTokenCallback?.call();
|
||||
if (newToken != null && newToken.isNotEmpty) {
|
||||
_currentToken = newToken;
|
||||
await saveTokenCallback(newToken);
|
||||
|
||||
for (var req in _queue) {
|
||||
final newOptions = req.options.copyWith(
|
||||
headers: {...req.options.headers, 'Authorization': 'Bearer $newToken'},
|
||||
);
|
||||
dio
|
||||
.fetch(newOptions)
|
||||
.then(req.completer.complete)
|
||||
.catchError(req.completer.completeError);
|
||||
}
|
||||
} else {
|
||||
await clearTokenCallback();
|
||||
await _handleRefreshFailure();
|
||||
for (var req in _queue) {
|
||||
req.completer.completeError(
|
||||
DioException(requestOptions: req.options, type: DioExceptionType.cancel),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
await clearTokenCallback();
|
||||
await _handleRefreshFailure();
|
||||
for (var req in _queue) {
|
||||
req.completer.completeError(e);
|
||||
}
|
||||
} finally {
|
||||
_queue.clear();
|
||||
_isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
return handler.resolve(await completer.future);
|
||||
}
|
||||
|
||||
handler.next(err);
|
||||
}
|
||||
|
||||
/// Handles token refresh failure:
|
||||
/// - Cancels all ongoing requests via [ApiHandler].
|
||||
/// - Executes external [onRefreshFailed] callback.
|
||||
Future<void> _handleRefreshFailure() async {
|
||||
ApiHandler.cancelAllRequests("Token refresh failed");
|
||||
|
||||
await onRefreshFailed.call();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
set isRefreshingForTest(bool value) => _isRefreshing = value;
|
||||
|
||||
@visibleForTesting
|
||||
List<QueuedRequest> get queue => _queue;
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:rasadyar_core/data/services/auth_middelware.dart';
|
||||
import 'package:rasadyar_core/data/services/network_status.dart';
|
||||
import 'package:rasadyar_core/infrastructure/local/hive_local_storage.dart';
|
||||
|
||||
final diCore = GetIt.instance;
|
||||
|
||||
Future<void> setupAllCoreProvider() async {
|
||||
|
||||
await _setUpLogger();
|
||||
await _setupLocalStorage();
|
||||
await _setupRemote();
|
||||
diCore.registerSingleton(NetworkStatus()..startListening());
|
||||
await diCore.allReady();
|
||||
}
|
||||
|
||||
@@ -23,4 +23,4 @@ Future<void> _setupLocalStorage() async {
|
||||
|
||||
Future<void> _setupRemote() async {
|
||||
// diCore.registerSingleton<HiveLocalStorage>(HiveLocalStorage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core.dart';
|
||||
|
||||
/// Handles global API requests management with CancelToken.
|
||||
class ApiHandler {
|
||||
// Global CancelToken for all requests.
|
||||
static CancelToken _globalCancelToken = CancelToken();
|
||||
|
||||
/// Returns the current global CancelToken.
|
||||
static CancelToken get globalCancelToken => _globalCancelToken;
|
||||
|
||||
/// Resets the global CancelToken to a new one.
|
||||
static Future<void> reset() async {
|
||||
_globalCancelToken = CancelToken();
|
||||
}
|
||||
|
||||
/// Cancels all ongoing requests and resets the CancelToken.
|
||||
/// [reason] is optional text explaining why requests are canceled.
|
||||
static void cancelAllRequests(String reason) {
|
||||
if (!_globalCancelToken.isCancelled) {
|
||||
_globalCancelToken.cancel(reason);
|
||||
|
||||
@@ -77,18 +77,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "7d95cbbb1526ab5ae977df9b4cc660963b9b27f6d1075c0b34653868911385e4"
|
||||
sha256: "6439a9c71a4e6eca8d9490c1b380a25b02675aa688137dfbe66d2062884a23ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "3.0.2"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.2.0"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -101,26 +101,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "38c9c339333a09b090a638849a4c56e70a404c6bdd3b511493addfbc113b60c2"
|
||||
sha256: "2b21a125d66a86b9511cc3fb6c668c42e9a1185083922bf60e46d483a81a9712"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "3.0.2"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: b971d4a1c789eba7be3e6fe6ce5e5b50fd3719e3cb485b3fad6d04358304351d
|
||||
sha256: fd3c09f4bbff7fa6e8d8ef688a0b2e8a6384e6483a25af0dac75fef362bcfe6f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
version: "2.7.0"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: c04e612ca801cd0928ccdb891c263a2b1391cb27940a5ea5afcf9ba894de5d62
|
||||
sha256: ab27e46c8aa233e610cf6084ee6d8a22c6f873a0a9929241d8855b7a72978ae7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.0"
|
||||
version: "9.3.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -153,6 +153,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -209,6 +217,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -761,66 +777,66 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||
sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.2.0"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d"
|
||||
sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+24"
|
||||
version: "0.8.13"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
|
||||
sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.1.0"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
|
||||
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+2"
|
||||
version: "0.8.13"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
|
||||
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
version: "0.2.2"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
|
||||
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
version: "0.2.2"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||
sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.1"
|
||||
version: "2.11.0"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
version: "0.2.2"
|
||||
image_size_getter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -889,26 +905,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
version: "11.0.1"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1021,6 +1037,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1161,10 +1185,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: persian_datetime_picker
|
||||
sha256: "7ccbfd3a68dc89d405550f624e9fa590c914fed2aa2d48973c4f4400baab2e06"
|
||||
sha256: "0ec2879d2bee8390dda088b412739e6316e3a54d77640ec54dc1eeca8c5baa59"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1285,6 +1309,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1322,6 +1362,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.6"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1378,14 +1434,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.26.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.6"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.11"
|
||||
time:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1454,10 +1526,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1498,6 +1570,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1555,5 +1635,5 @@ packages:
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
sdks:
|
||||
dart: ">=3.8.1 <4.0.0"
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
|
||||
@@ -4,7 +4,7 @@ publish_to: none
|
||||
version: 1.2.0+2
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
sdk: ^3.9.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@@ -17,7 +17,7 @@ dependencies:
|
||||
package_info_plus: ^8.3.1
|
||||
|
||||
##image_picker
|
||||
image_picker: ^1.1.2
|
||||
image_picker: ^1.2.0
|
||||
image_cropper: ^9.1.0
|
||||
|
||||
#UI
|
||||
@@ -58,7 +58,7 @@ dependencies:
|
||||
|
||||
#other
|
||||
permission_handler: ^12.0.1
|
||||
persian_datetime_picker: ^3.1.0
|
||||
persian_datetime_picker: ^3.1.1
|
||||
encrypt: ^5.0.3
|
||||
|
||||
#L10N tools
|
||||
@@ -89,10 +89,11 @@ dev_dependencies:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
##code generation
|
||||
build_runner: ^2.6.0
|
||||
build_runner: ^2.7.0
|
||||
hive_ce_generator: ^1.9.3
|
||||
freezed: ^3.2.0
|
||||
json_serializable: ^6.10.0
|
||||
test: ^1.24.0
|
||||
|
||||
|
||||
##test
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:rasadyar_core/infrastructure/remote/app_interceptor_n.dart';
|
||||
|
||||
class MockDio extends Mock implements Dio {}
|
||||
|
||||
class MockResponse extends Mock implements Response {}
|
||||
|
||||
class MockRequestOptions extends Mock implements RequestOptions {}
|
||||
|
||||
|
||||
class FakeRequestOptions extends Fake implements RequestOptions {}
|
||||
|
||||
|
||||
void main() {
|
||||
late MockDio dio;
|
||||
late AppInterceptorN interceptor;
|
||||
late MockResponse response;
|
||||
late MockRequestOptions requestOptions;
|
||||
|
||||
late bool saveCalled;
|
||||
late bool clearCalled;
|
||||
late bool refreshFailedCalled;
|
||||
String? savedToken;
|
||||
|
||||
setUp(() {
|
||||
dio = MockDio();
|
||||
response = MockResponse();
|
||||
requestOptions = MockRequestOptions();
|
||||
|
||||
saveCalled = false;
|
||||
clearCalled = false;
|
||||
refreshFailedCalled = false;
|
||||
savedToken = null;
|
||||
interceptor = AppInterceptorN(
|
||||
dio: dio,
|
||||
saveTokenCallback: (token) async {
|
||||
savedToken = token;
|
||||
saveCalled = true;
|
||||
},
|
||||
clearTokenCallback: () async {
|
||||
clearCalled = true;
|
||||
},
|
||||
refreshTokenCallback: () async => 'newToken',
|
||||
onRefreshFailed: () async {
|
||||
refreshFailedCalled = true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
setUpAll(() {
|
||||
registerFallbackValue(FakeRequestOptions());
|
||||
});
|
||||
|
||||
|
||||
|
||||
test('should refresh token and retry queued requests on 401', () async {
|
||||
final options = RequestOptions(path: "/test");
|
||||
final response = Response(requestOptions: options, statusCode: 200, data: 'ok');
|
||||
when(() => dio.fetch(any())).thenAnswer((_) async => response);
|
||||
final handler = ErrorInterceptorHandler();
|
||||
final dioException = DioException(
|
||||
requestOptions: options,
|
||||
response: Response(requestOptions: options, statusCode: 401),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
await interceptor.onError(dioException, handler);
|
||||
expect(saveCalled, isTrue);
|
||||
expect(savedToken, 'newToken');
|
||||
expect(interceptor.queue.isEmpty, isTrue);
|
||||
expect(clearCalled, isFalse);
|
||||
expect(refreshFailedCalled, isFalse);
|
||||
});
|
||||
|
||||
|
||||
test('should queue request if refreshing', () async {
|
||||
interceptor.isRefreshingForTest = true;
|
||||
final options = RequestOptions(path: "/test");
|
||||
final handler = RequestInterceptorHandler();
|
||||
final completer = Completer<Response>();
|
||||
interceptor.queue.add(QueuedRequest(options, completer));
|
||||
interceptor.onRequest(options, handler);
|
||||
expect(interceptor.queue.length, 2); // One added in setUp, one here
|
||||
});
|
||||
|
||||
test('should refresh token and retry queued requests on 401', () async {
|
||||
final options = RequestOptions(path: "/test");
|
||||
final response = Response(requestOptions: options, statusCode: 200, data: 'ok');
|
||||
when(() => dio.fetch(any())).thenAnswer((_) async => response);
|
||||
final handler = ErrorInterceptorHandler();
|
||||
final dioException = DioException(
|
||||
requestOptions: options,
|
||||
response: Response(requestOptions: options, statusCode: 401),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
await interceptor.onError(dioException, handler);
|
||||
expect(saveCalled, isTrue);
|
||||
expect(savedToken, 'newToken');
|
||||
expect(interceptor.queue.isEmpty, isTrue);
|
||||
expect(clearCalled, isFalse);
|
||||
expect(refreshFailedCalled, isFalse);
|
||||
});
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'interfaces/i_form_data.dart';
|
||||
|
||||
class DioFormData implements IFormData {
|
||||
final FormData _formData = FormData();
|
||||
|
||||
@override
|
||||
void addFile(String field, Uint8List bytes, String filename) {
|
||||
_formData.files.add(MapEntry(
|
||||
field,
|
||||
MultipartFile.fromBytes(bytes, filename: filename),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void addField(String key, String value) {
|
||||
_formData.fields.add(MapEntry(key, value));
|
||||
}
|
||||
|
||||
FormData get raw => _formData;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
void main() {
|
||||
group('DioFormData', () {
|
||||
late DioFormData formData;
|
||||
|
||||
setUp(() {
|
||||
formData = DioFormData();
|
||||
});
|
||||
|
||||
test('addField should add a field to FormData', () {
|
||||
formData.addField('userName', 'mojtaba');
|
||||
expect(formData.raw.fields.length, 1);
|
||||
expect(formData.raw.fields.first.key, 'userName');
|
||||
expect(formData.raw.fields.first.value, 'mojtaba');
|
||||
});
|
||||
|
||||
test('addFile should add a file to FormData', () async {
|
||||
final bytes = Uint8List.fromList([1, 2, 3, 4]);
|
||||
formData.addFile('fileField', bytes, 'test.txt');
|
||||
|
||||
expect(formData.raw.files.length, 1);
|
||||
|
||||
final fileEntry = formData.raw.files.first;
|
||||
expect(fileEntry.key, 'fileField');
|
||||
|
||||
final multipart = fileEntry.value;
|
||||
expect(multipart.filename, 'test.txt');
|
||||
|
||||
final uploadedBytes = await multipart.finalize().toBytes();
|
||||
expect(uploadedBytes, bytes);
|
||||
|
||||
});
|
||||
|
||||
test('raw getter should return the internal FormData instance', () {
|
||||
final tmp = formData.raw;
|
||||
expect(tmp, isA<FormData>());
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
extension on Stream<List<int>> {
|
||||
/// Helper to collect stream into a single Uint8List for comparison
|
||||
Future<Uint8List> toBytes() async {
|
||||
final chunks = <int>[];
|
||||
await for (final chunk in this) {
|
||||
chunks.addAll(chunk);
|
||||
}
|
||||
return Uint8List.fromList(chunks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
class MockDio extends Mock implements Dio {}
|
||||
|
||||
class MockInterceptor extends Mock implements AppInterceptor {}
|
||||
|
||||
class FakeRequestOptions extends Fake implements RequestOptions {}
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
registerFallbackValue(FakeRequestOptions());
|
||||
registerFallbackValue(RequestOptions(path: ''));
|
||||
registerFallbackValue(Options());
|
||||
registerFallbackValue(CancelToken());
|
||||
});
|
||||
|
||||
group('Dio Remote', () {
|
||||
late DioRemote dioRemote;
|
||||
late MockDio mockDio;
|
||||
|
||||
setUp(() {
|
||||
mockDio = MockDio();
|
||||
dioRemote = DioRemote();
|
||||
dioRemote.dio = mockDio;
|
||||
});
|
||||
|
||||
test('init sets dio and adds interceptor if provided', () async {
|
||||
final interceptor = MockInterceptor();
|
||||
|
||||
final client = DioRemote(interceptors: interceptor);
|
||||
await client.init();
|
||||
|
||||
expect(client.dio, isA<Dio>());
|
||||
|
||||
});
|
||||
|
||||
test('get returns DioResponse with raw data', () async {
|
||||
final response = Response(
|
||||
requestOptions: RequestOptions(path: '/test'),
|
||||
statusCode: 200,
|
||||
data: {'message': 'ok'},
|
||||
);
|
||||
when(
|
||||
() => mockDio.get(
|
||||
any(),
|
||||
queryParameters: any(named: 'queryParameters'),
|
||||
options: any(named: 'options'),
|
||||
onReceiveProgress: any(named: 'onReceiveProgress'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
|
||||
final result = await dioRemote.get<Map<String, dynamic>>('/test');
|
||||
|
||||
expect(result, isA<DioResponse<Map<String, dynamic>>>());
|
||||
expect(result.data, {'message': 'ok'});
|
||||
expect(result.statusCode, 200);
|
||||
});
|
||||
|
||||
test('get applies fromJson mapper', () async {
|
||||
final response = Response(
|
||||
requestOptions: RequestOptions(path: '/user'),
|
||||
statusCode: 200,
|
||||
data: {'id': 1, 'name': 'Ali'},
|
||||
);
|
||||
when(
|
||||
() => mockDio.get(
|
||||
any(),
|
||||
queryParameters: any(named: 'queryParameters'),
|
||||
options: any(named: 'options'),
|
||||
onReceiveProgress: any(named: 'onReceiveProgress'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
|
||||
final result = await dioRemote.get<String>(
|
||||
'/user',
|
||||
fromJson: (json) => "User: ${json['name']}",
|
||||
);
|
||||
|
||||
expect(result.data, 'User: Ali');
|
||||
});
|
||||
|
||||
test('post applies fromJson correctly', () async {
|
||||
final response = Response(
|
||||
requestOptions: RequestOptions(path: '/post'),
|
||||
statusCode: 200,
|
||||
data: {'id': 99},
|
||||
);
|
||||
when(
|
||||
() => mockDio.post(
|
||||
any(),
|
||||
data: any(named: 'data'),
|
||||
queryParameters: any(named: 'queryParameters'),
|
||||
options: any(named: 'options'),
|
||||
onSendProgress: any(named: 'onSendProgress'),
|
||||
onReceiveProgress: any(named: 'onReceiveProgress'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
|
||||
final result = await dioRemote.post<int>(
|
||||
'/post',
|
||||
fromJson: (json) => json['id'],
|
||||
);
|
||||
|
||||
expect(result.data, 99);
|
||||
});
|
||||
|
||||
test('put returns parsed data', () async {
|
||||
final response = Response(
|
||||
requestOptions: RequestOptions(path: '/put'),
|
||||
statusCode: 200,
|
||||
data: {'value': 'updated'},
|
||||
);
|
||||
when(
|
||||
() => mockDio.put(
|
||||
any(),
|
||||
data: any(named: 'data'),
|
||||
queryParameters: any(named: 'queryParameters'),
|
||||
options: any(named: 'options'),
|
||||
onSendProgress: any(named: 'onSendProgress'),
|
||||
onReceiveProgress: any(named: 'onReceiveProgress'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
|
||||
final result = await dioRemote.put<String>('/put', fromJson: (json) => json['value']);
|
||||
|
||||
expect(result.data, 'updated');
|
||||
});
|
||||
|
||||
test('delete works with fromJson', () async {
|
||||
final response = Response(
|
||||
requestOptions: RequestOptions(path: '/delete'),
|
||||
statusCode: 200,
|
||||
data: {'removed': true},
|
||||
);
|
||||
when(
|
||||
() => mockDio.delete(
|
||||
any(),
|
||||
data: any(named: 'data'),
|
||||
queryParameters: any(named: 'queryParameters'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
|
||||
final result = await dioRemote.delete<bool>('/delete', fromJson: (json) => json['removed']);
|
||||
|
||||
expect(result.data, true);
|
||||
});
|
||||
|
||||
test('download returns DioResponse with bytes', () async {
|
||||
final response = Response<Uint8List>(
|
||||
requestOptions: RequestOptions(path: '/download'),
|
||||
statusCode: 200,
|
||||
data: Uint8List.fromList([1, 2, 3]),
|
||||
);
|
||||
when(
|
||||
() => mockDio.get<Uint8List>(
|
||||
any(),
|
||||
options: any(named: 'options'),
|
||||
onReceiveProgress: any(named: 'onReceiveProgress'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
|
||||
final result = await dioRemote.download('/download');
|
||||
|
||||
expect(result.data, isA<Uint8List>());
|
||||
expect(result.data!.length, 3);
|
||||
});
|
||||
|
||||
test('upload sends DioFormData and returns DioResponse', () async {
|
||||
final formData = DioFormData();
|
||||
formData.addField('field', 'value');
|
||||
final response = Response(
|
||||
requestOptions: RequestOptions(path: '/upload'),
|
||||
statusCode: 200,
|
||||
data: 'uploaded',
|
||||
);
|
||||
when(
|
||||
() => mockDio.post(
|
||||
any(),
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
onSendProgress: any(named: 'onSendProgress'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
|
||||
final result = await dioRemote.upload<String>('/upload', formData: formData);
|
||||
|
||||
expect(result.data, 'uploaded');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import 'interfaces/i_http_response.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class DioResponse<T> implements IHttpResponse<T> {
|
||||
final Response<dynamic> _response;
|
||||
|
||||
DioResponse(this._response);
|
||||
|
||||
@override
|
||||
T? get data => _response.data;
|
||||
|
||||
@override
|
||||
int get statusCode => _response.statusCode ?? 0;
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? get headers => _response.headers.map;
|
||||
|
||||
@override
|
||||
bool get isSuccessful => statusCode >= 200 && statusCode < 300;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
void main() {
|
||||
group('DioResponse', () {
|
||||
test('data should return response data', () {
|
||||
final response = Response(
|
||||
requestOptions: RequestOptions(path: "/"),
|
||||
data: 'hello',
|
||||
);
|
||||
|
||||
final dioResponse = DioResponse<String>(response);
|
||||
|
||||
expect(dioResponse.data, 'hello');
|
||||
});
|
||||
|
||||
test('status Code should return 0 if null', () {
|
||||
final response = Response(requestOptions: RequestOptions(path: "/"), statusCode: null);
|
||||
final dioResponse = DioResponse(response);
|
||||
|
||||
expect(dioResponse.statusCode, 0);
|
||||
});
|
||||
|
||||
test('headers should return response headers map', () {
|
||||
final headers = Headers.fromMap({
|
||||
'content-type': ['application/json'],
|
||||
});
|
||||
final response = Response(
|
||||
requestOptions: RequestOptions(path: "/"),
|
||||
headers: headers,
|
||||
);
|
||||
final dioResponse = DioResponse(response);
|
||||
|
||||
expect(dioResponse.headers, isA<Map>());
|
||||
expect(dioResponse.headers, {
|
||||
'content-type': ['application/json'],
|
||||
});
|
||||
});
|
||||
|
||||
test('isSuccessful should return true for 2xx codes', () {
|
||||
final response = Response(requestOptions: RequestOptions(path: "/"), statusCode: 200);
|
||||
final dioResponse = DioResponse(response);
|
||||
|
||||
expect(dioResponse.statusCode, 200);
|
||||
|
||||
expect(dioResponse.isSuccessful, true);
|
||||
});
|
||||
|
||||
test('isSuccessful should return false for non-2xx codes', () {
|
||||
final response = Response(requestOptions: RequestOptions(path: "/"),statusCode: 404);
|
||||
final dioResponse = DioResponse(response);
|
||||
|
||||
expect(dioResponse.statusCode, 404);
|
||||
|
||||
expect(dioResponse.isSuccessful, false);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class IFormData{
|
||||
void addFile(String field, Uint8List bytes, String filename);
|
||||
void addField(String key, String value);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'i_form_data.dart';
|
||||
import 'i_http_response.dart';
|
||||
|
||||
abstract class IHttpClient {
|
||||
Future<void> init();
|
||||
|
||||
Future<IHttpResponse<T>> get<T>(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
});
|
||||
|
||||
|
||||
Future<IHttpResponse<T>> post<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
});
|
||||
|
||||
Future<IHttpResponse<T>> put<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
});
|
||||
|
||||
Future<IHttpResponse<T>> delete<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
});
|
||||
|
||||
Future<IHttpResponse<T>> download<T>(
|
||||
String url, {
|
||||
ProgressCallback? onReceiveProgress,
|
||||
});
|
||||
|
||||
Future<IHttpResponse<T>> upload<T>(
|
||||
String path, {
|
||||
required IFormData formData,
|
||||
Map<String, String>? headers,
|
||||
ProgressCallback? onSendProgress,
|
||||
});
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
abstract class IHttpResponse<T> {
|
||||
T? get data;
|
||||
int get statusCode;
|
||||
Map<String, dynamic>? get headers;
|
||||
bool get isSuccessful;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
abstract class IRemote<T>{
|
||||
Future<T> init();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user