From f0d275701f81da4c0bd74c0009fdc73338cb5a7c Mon Sep 17 00:00:00 2001 From: dzmitry-struk-surf Date: Thu, 4 Apr 2024 16:43:06 +0300 Subject: [PATCH] feat: add fresh package for authentication --- docs/authentication.md | 39 +++++ docs/authorization.md | 135 ------------------ lib/common/interceptors/auth_interceptor.dart | 117 --------------- lib/common/mixin/theme_wm_mixin.dart | 1 + .../i_token_operations_service.dart | 17 --- lib/common/widgets/di_scope.dart | 6 +- lib/features/app/app_flow.dart | 2 +- .../debug/presentation/debug/debug_flow.dart | 2 +- .../presentation/feature_example_flow.dart | 2 +- .../shared/domain/repositories/.gitkeep | 0 .../domain/repositories/auth_repository.dart | 10 -- .../refresh_tokens_repository.dart | 19 --- .../presentation/theme_mode_provider.dart | 2 +- .../tokens_storage/auth_token_pair.dart | 25 ++++ .../tokens_storage/auth_token_pair.g.dart | 17 +++ .../data/auth_tokens_storage_dto.dart | 17 --- .../tokens_storage/i_tokens_storage.dart | 16 --- .../tokens_storage/token_storage_impl.dart | 50 +++++++ .../tokens_storage/tokens_storage.dart | 46 ------ pubspec.lock | 16 +++ pubspec.yaml | 1 + test/flutter_test_config.dart | 2 +- .../{{name.snakeCase()}}_wm.dart | 2 +- .../{{name.snakeCase()}}_flow.dart | 2 +- .../{{screen_name.snakeCase()}}_wm.dart | 2 +- 25 files changed, 160 insertions(+), 388 deletions(-) create mode 100644 docs/authentication.md delete mode 100644 docs/authorization.md delete mode 100644 lib/common/interceptors/auth_interceptor.dart delete mode 100644 lib/common/service/token_operations/i_token_operations_service.dart create mode 100644 lib/features/shared/domain/repositories/.gitkeep delete mode 100644 lib/features/shared/domain/repositories/auth_repository.dart delete mode 100644 lib/features/shared/domain/repositories/refresh_tokens_repository.dart create mode 100644 lib/persistence/storage/tokens_storage/auth_token_pair.dart create mode 100644 lib/persistence/storage/tokens_storage/auth_token_pair.g.dart delete mode 100644 lib/persistence/storage/tokens_storage/data/auth_tokens_storage_dto.dart delete mode 100644 lib/persistence/storage/tokens_storage/i_tokens_storage.dart create mode 100644 lib/persistence/storage/tokens_storage/token_storage_impl.dart delete mode 100644 lib/persistence/storage/tokens_storage/tokens_storage.dart diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 00000000..5832c752 --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,39 @@ +# Authentication + +The guide describes token-based authentication which relies on **access** and **refresh** tokens. + +## Fresh + +The recommended approach is to use the `fresh` package. + +[**Fresh**](https://pub.dev/packages/fresh) is a token refresh library for dart. This library consists of a collection of packages which specialize in a particular aspect of token refresh. + +Project contains preconfigured classes for `fresh_dio` package to make its usage even more easier. + +Example: +```dart +final fresh = Fresh( + tokenHeader: (token) => {'Authorization': 'Bearer ${token.accessToken}'}, + tokenStorage: const TokenStorageImpl(FlutterSecureStorage()), + refreshToken: (options, handler) async { ... }, +); + +// Add interceptor to Dio instance. +dio.interceptors.add(fresh); + +// Listen to authentication status changes. +fresh.authenticationStatus.listen((status) { ... }); + +// Set token. +fresh.setToken(token); + +// Clear token. +fresh.clearToken(); +``` +## Token Storage + +`TokenStorageImpl` is an implementation of `TokenStorage` from `fresh` package which relies on `flutter_secure_storage`. + +## Auth Tokens + +`AuthTokenPair` is a class that represents a pair of access and refresh tokens. It is used by `TokenStorageImpl`. diff --git a/docs/authorization.md b/docs/authorization.md deleted file mode 100644 index fae35b00..00000000 --- a/docs/authorization.md +++ /dev/null @@ -1,135 +0,0 @@ -# Authorization - -To implement authorization, follow these steps: - -### 1. Generate the API level. - -The `lib/api` folder is intended for API files. - -### 2. Prepare entities and converters. - -A converter - `Converter` is used for data mapping. - -Example: -```dart -/// auth_tokens_storage_dto_converter.dart -/// Converter for [TokensEntity] -typedef IAuthTokensStorageDtoConverter = Converter; - -/// {@template auth_tokens_storage_dto_converter.class} -/// Implementation [ITokensEntityConverter] -/// {@endtemplate} -final class AuthTokensStorageDtoConverter extends IAuthTokensStorageDtoConverter { - /// {@macro auth_tokens_storage_dto_converter.class} - const AuthTokensStorageDtoConverter(); - - @override - AuthTokensStorageDto convert(TokensApiDto from) { - return AuthTokensStorageDto( - accessToken: from.accessToken, - refreshToken: from.refreshToken, - ); - } -} -``` - -### 3. Implement network error handling. - -Create network error classes and implement their mapping in repositories. - -### 4. Implement AuthRepository - -The project uses AuthRepository for authorization. -Conceptually, this class is the business logic of authentication. - -```dart -abstract interface class IAuthRepository { - /// logout - RequestOperation logout(); -} -``` - -Depending on the project requirements, add the necessary methods: -* authentication state -* methods for user authorization -* repository initialization -* ... - -```dart - -abstract interface class IAuthRepository { - /// Stream with authentication state - Stream get authState; - - /// Current authentication state - AuthState get currentAuthState; - - /// Initialize repository - RequestOperation init(); - - /// Get otp-token by phone number - RequestOperation otpToken(String phone); - - /// Verify otp-code. - /// Successful completion of the request - the user is authorized. - RequestOperation verifyOtp(String otpToken, String otpCode); - - /// logout - RequestOperation logout(); - - /// Dispose resources - void dispose(); - RequestOperation logout(); -} - -``` -In the **initialization method**, you can perform a check for the first launch of the application, -and it is also recommended to perform a cleanup `_tokensStorage.clear()` to avoid problems when reinstalling -the application on **ios**. - -```dart -/// Example -Future _checkFirstRun() async { - final isFirstRun = _firstRunStorage.getIsFirstRun(); - - if (isFirstRun) { - await _firstRunStorage.setIsFirstRun(value: false); - - final result = await logout(); - _handleResultFailure(result); - } - } -``` -### 5. AuthInterceptor - -The `AuthInterceptor` class is designed to handle requests and errors in the Dio library when working with requests -for authorized users. - -It contains the following methods: - -* **onRequest:** This method is called before each request. It gets the access token and, if the access token - is missing, rejects the request. If the access token is present, it adds it to the request header. - -* **onError:** This method is called when an error occurs. If the error is not related to an invalid status, - it simply passes it on. If the error is related to an invalid status, the method tries to refresh the tokens - and retry the request. If the token refresh fails, the `onLogout` callback is called. - -* **_retryRequest:** This private method is used to retry the request with new tokens after they are refreshed. - If the request is successful, it returns the response. If the request fails, it logs the error and returns a failure. - - -`AuthInterceptor` is fully implemented. It needs to implement `ITokenOperationsService` and `IRefreshTokensRepository`. - - -### 6. RefreshTokensRepository - -Implement the `IRefreshTokensRepository` for interact with the api for working with authorization tokens. - - -### Classes for authorization - -* `AuthInterceptor` - fully implemented -* `TokenOperationsService` - needs implement -* `RefreshTokensRepository` - needs implement for a specific project -* `TokensStorage` - fully implemented -* `AuthRepository` - needs implement for a specific project diff --git a/lib/common/interceptors/auth_interceptor.dart b/lib/common/interceptors/auth_interceptor.dart deleted file mode 100644 index 16e74cab..00000000 --- a/lib/common/interceptors/auth_interceptor.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'dart:async'; - -import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_template/common/service/token_operations/i_token_operations_service.dart'; -import 'package:flutter_template/core/architecture/domain/entity/failure.dart'; -import 'package:flutter_template/core/architecture/domain/entity/result.dart'; - -/// Authorization error status code. -const unauthorisedStatusCode = 401; - -/// {@template auth_interceptor.class} -/// Interceptor for requests requiring authorization. -/// {@endtemplate} -final class AuthInterceptor extends QueuedInterceptorsWrapper { - final Dio _dio; - final ITokenOperationsService _tokenOperationsService; - final AsyncCallback _onLogout; - - /// {@macro auth_interceptor.class} - AuthInterceptor({ - required Dio dio, - required ITokenOperationsService tokenOperationsService, - required AsyncCallback onLogout, - }) : _dio = dio, - _tokenOperationsService = tokenOperationsService, - _onLogout = onLogout; - - @override - Future onRequest(RequestOptions options, RequestInterceptorHandler handler) async { - final accessToken = await _tokenOperationsService.getAccessToken(); - - if (accessToken == null) { - return handler.reject(DioException(requestOptions: options)); - } - - return super.onRequest( - options..addAuthHeader(accessToken), - handler, - ); - } - - @override - Future onError(DioException err, ErrorInterceptorHandler handler) async { - if (err.response?.statusCode != unauthorisedStatusCode) { - return super.onError(err, handler); - } - - final refreshToken = await _tokenOperationsService.getRefreshToken(); - if (refreshToken == null) { - return super.onError(err, handler); - } - - final tokensResult = await _tokenOperationsService.refreshTokens(refreshToken); - - switch (tokensResult) { - case ResultOk(data: final tokens): - await _tokenOperationsService.saveTokens(tokens); - - final retryResult = await _retryRequest(err.requestOptions..addAuthHeader(tokens.accessToken)); - - switch (retryResult) { - case ResultOk(data: final response): - return handler.resolve(response); - case ResultFailed(:final failure): - return super.onError(failure.original, handler); - } - case ResultFailed(:final failure): - await _onLogout(); - final originalException = failure.original; - final dioException = originalException is DioException ? originalException : null; - return dioException != null ? handler.reject(dioException) : null; - } - } - - // ignore: avoid-dynamic - Future, Failure>> _retryRequest(RequestOptions requestOptions) async { - try { - final response = await _dio.request( - requestOptions.path, - data: requestOptions.data, - queryParameters: requestOptions.queryParameters, - cancelToken: requestOptions.cancelToken, - options: requestOptions.toOptions(), - onReceiveProgress: requestOptions.onReceiveProgress, - onSendProgress: requestOptions.onSendProgress, - ); - - return Result.ok(response); - } on DioException catch (e, s) { - return Result.failed(Failure(original: e, trace: s)); - } - } -} - -extension on RequestOptions { - void addAuthHeader(String accessToken) { - headers.addAll({'Authorization': 'Bearer $accessToken'}); - } - - Options toOptions() => Options( - method: method, - sendTimeout: sendTimeout, - receiveTimeout: receiveTimeout, - extra: extra, - headers: headers, - responseType: responseType, - contentType: contentType, - validateStatus: validateStatus, - receiveDataWhenStatusError: receiveDataWhenStatusError, - followRedirects: followRedirects, - maxRedirects: maxRedirects, - requestEncoder: requestEncoder, - responseDecoder: responseDecoder, - listFormat: listFormat, - ); -} diff --git a/lib/common/mixin/theme_wm_mixin.dart b/lib/common/mixin/theme_wm_mixin.dart index f4e8974d..69dd2b39 100644 --- a/lib/common/mixin/theme_wm_mixin.dart +++ b/lib/common/mixin/theme_wm_mixin.dart @@ -17,6 +17,7 @@ mixin ThemeWMMixin on Wid @override void didChangeDependencies() { + super.didChangeDependencies(); _colorScheme = AppColorScheme.of(context); _textScheme = AppTextScheme.of(context); } diff --git a/lib/common/service/token_operations/i_token_operations_service.dart b/lib/common/service/token_operations/i_token_operations_service.dart deleted file mode 100644 index d83a9255..00000000 --- a/lib/common/service/token_operations/i_token_operations_service.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter_template/core/architecture/domain/entity/request_operation.dart'; -import 'package:flutter_template/persistence/storage/tokens_storage/data/auth_tokens_storage_dto.dart'; - -/// Interface for the token management service -abstract interface class ITokenOperationsService { - /// refresh Tokens - RequestOperation refreshTokens(String refreshToken); - - /// Refresh token - Future saveTokens(AuthTokensStorageDto tokens); - - /// Get Access Token from storage - Future getAccessToken(); - - /// Get Refresh Token from storage - Future getRefreshToken(); -} diff --git a/lib/common/widgets/di_scope.dart b/lib/common/widgets/di_scope.dart index a93fcd7c..1819fc69 100644 --- a/lib/common/widgets/di_scope.dart +++ b/lib/common/widgets/di_scope.dart @@ -10,14 +10,14 @@ typedef ScopeFactory = T Function(BuildContext context); class DiScope extends SingleChildStatefulWidget { /// Create an instance [DiScope]. const DiScope({ - required this.onFactory, + required this.factory, this.onDispose, super.key, super.child, }); /// Factory that returns the dependency scope. - final ScopeFactory onFactory; + final ScopeFactory factory; /// The method called when disposing the widget. final ValueChanged? onDispose; @@ -32,7 +32,7 @@ class _DiScopeState extends SingleChildState> { @override void initState() { super.initState(); - _scope = widget.onFactory(context); + _scope = widget.factory(context); } @override diff --git a/lib/features/app/app_flow.dart b/lib/features/app/app_flow.dart index 179b1eff..62cdefbd 100644 --- a/lib/features/app/app_flow.dart +++ b/lib/features/app/app_flow.dart @@ -24,7 +24,7 @@ class AppFlow extends StatelessWidget { Widget build(BuildContext context) { return Nested( children: [ - DiScope(onFactory: (_) => appScope), + DiScope(factory: (_) => appScope), ChangeNotifierProvider(create: (_) => AppRouter()), const ThemeModeProvider(), ], diff --git a/lib/features/debug/presentation/debug/debug_flow.dart b/lib/features/debug/presentation/debug/debug_flow.dart index 17ecaf99..73911e8d 100644 --- a/lib/features/debug/presentation/debug/debug_flow.dart +++ b/lib/features/debug/presentation/debug/debug_flow.dart @@ -15,7 +15,7 @@ class DebugFlow extends StatelessWidget implements AutoRouteWrapper { @override Widget wrappedRoute(BuildContext context) { return DiScope( - onFactory: DebugScope.create, + factory: DebugScope.create, onDispose: (scope) => scope.dispose(), child: this, ); diff --git a/lib/features/feature_example/presentation/feature_example_flow.dart b/lib/features/feature_example/presentation/feature_example_flow.dart index 11e4db76..9750d2c6 100644 --- a/lib/features/feature_example/presentation/feature_example_flow.dart +++ b/lib/features/feature_example/presentation/feature_example_flow.dart @@ -15,7 +15,7 @@ class FeatureExampleFlow extends StatelessWidget implements AutoRouteWrapper { @override Widget wrappedRoute(BuildContext context) { return DiScope( - onFactory: FeatureExampleScope.create, + factory: FeatureExampleScope.create, onDispose: (scope) => scope.dispose(), child: this, ); diff --git a/lib/features/shared/domain/repositories/.gitkeep b/lib/features/shared/domain/repositories/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/lib/features/shared/domain/repositories/auth_repository.dart b/lib/features/shared/domain/repositories/auth_repository.dart deleted file mode 100644 index 6a53a469..00000000 --- a/lib/features/shared/domain/repositories/auth_repository.dart +++ /dev/null @@ -1,10 +0,0 @@ -// ignore_for_file: one_member_abstracts -import 'package:flutter_template/core/architecture/domain/entity/request_operation.dart'; - -/// {@template auth_repository.class} -/// Interface AuthRepository. -/// {@endtemplate} -abstract interface class IAuthRepository { - /// Logout. - RequestOperation logout(); -} diff --git a/lib/features/shared/domain/repositories/refresh_tokens_repository.dart b/lib/features/shared/domain/repositories/refresh_tokens_repository.dart deleted file mode 100644 index 370b474e..00000000 --- a/lib/features/shared/domain/repositories/refresh_tokens_repository.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter_template/core/architecture/domain/entity/request_operation.dart'; -import 'package:flutter_template/persistence/storage/tokens_storage/data/auth_tokens_storage_dto.dart'; - -/// {@template refresh_tokens_repository.class} -/// Interface IRefreshTokensRepository. -/// {@endtemplate} -abstract interface class IRefreshTokensRepository { - /// Refresh Tokens - RequestOperation refreshTokens(String refreshToken); - - /// Initialize repository - RequestOperation saveTokens(AuthTokensStorageDto tokens); - - /// Get Access Token from storage - RequestOperation getAccessToken(); - - /// Get Refresh Token from storage - RequestOperation getRefreshToken(); -} diff --git a/lib/features/theme_mode/presentation/theme_mode_provider.dart b/lib/features/theme_mode/presentation/theme_mode_provider.dart index 9b8bc4d5..31c05aaf 100644 --- a/lib/features/theme_mode/presentation/theme_mode_provider.dart +++ b/lib/features/theme_mode/presentation/theme_mode_provider.dart @@ -20,7 +20,7 @@ class ThemeModeProvider extends SingleChildStatelessWidget { @override Widget buildWithChild(BuildContext context, Widget? child) { return DiScope( - onFactory: ThemeModeScope.create, + factory: ThemeModeScope.create, onDispose: (scope) => scope.dispose(), child: ThemeModeWidget(child: child ?? const SizedBox.shrink()), ); diff --git a/lib/persistence/storage/tokens_storage/auth_token_pair.dart b/lib/persistence/storage/tokens_storage/auth_token_pair.dart new file mode 100644 index 00000000..d508c369 --- /dev/null +++ b/lib/persistence/storage/tokens_storage/auth_token_pair.dart @@ -0,0 +1,25 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'auth_token_pair.g.dart'; + +/// Auth token pair. +@JsonSerializable() +class AuthTokenPair { + /// Access-token for accessing server resources for an authorized user. + final String accessToken; + + /// Refresh-token for updating the access-token. + final String refreshToken; + + /// Create an instance [AuthTokenPair]. + const AuthTokenPair({ + required this.accessToken, + required this.refreshToken, + }); + + /// Create an instance [AuthTokenPair] from json. + factory AuthTokenPair.fromJson(Map json) => _$AuthTokenPairFromJson(json); + + /// Convert an instance [AuthTokenPair] to json. + Map toJson() => _$AuthTokenPairToJson(this); +} diff --git a/lib/persistence/storage/tokens_storage/auth_token_pair.g.dart b/lib/persistence/storage/tokens_storage/auth_token_pair.g.dart new file mode 100644 index 00000000..f78ad1db --- /dev/null +++ b/lib/persistence/storage/tokens_storage/auth_token_pair.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_token_pair.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AuthTokenPair _$AuthTokenPairFromJson(Map json) => AuthTokenPair( + accessToken: json['accessToken'] as String, + refreshToken: json['refreshToken'] as String, + ); + +Map _$AuthTokenPairToJson(AuthTokenPair instance) => { + 'accessToken': instance.accessToken, + 'refreshToken': instance.refreshToken, + }; diff --git a/lib/persistence/storage/tokens_storage/data/auth_tokens_storage_dto.dart b/lib/persistence/storage/tokens_storage/data/auth_tokens_storage_dto.dart deleted file mode 100644 index 447e7e8d..00000000 --- a/lib/persistence/storage/tokens_storage/data/auth_tokens_storage_dto.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; - -/// Model for storing tokens in the storage. -@immutable -class AuthTokensStorageDto { - /// Access-token for accessing server resources for an authorized user. - final String accessToken; - - /// Refresh-token for updating the access-token. - final String refreshToken; - - /// Create an instance [AuthTokensStorageDto]. - const AuthTokensStorageDto({ - required this.accessToken, - required this.refreshToken, - }); -} diff --git a/lib/persistence/storage/tokens_storage/i_tokens_storage.dart b/lib/persistence/storage/tokens_storage/i_tokens_storage.dart deleted file mode 100644 index db4efcd6..00000000 --- a/lib/persistence/storage/tokens_storage/i_tokens_storage.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_template/persistence/storage/tokens_storage/data/auth_tokens_storage_dto.dart'; - -/// Storage for tokens -abstract interface class ITokensStorage { - /// Get accessToken - Future get accessToken; - - /// Get refreshToken - Future get refreshToken; - - /// Save tokens - Future saveTokens(AuthTokensStorageDto tokens); - - /// Clear storage - Future clear(); -} diff --git a/lib/persistence/storage/tokens_storage/token_storage_impl.dart b/lib/persistence/storage/tokens_storage/token_storage_impl.dart new file mode 100644 index 00000000..a60325fb --- /dev/null +++ b/lib/persistence/storage/tokens_storage/token_storage_impl.dart @@ -0,0 +1,50 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_template/persistence/storage/tokens_storage/auth_token_pair.dart'; +import 'package:fresh_dio/fresh_dio.dart'; + +/// {@template tokens_storage.class} +/// Implementation [TokenStorage] +/// {@endtemplate} +final class TokenStorageImpl implements TokenStorage { + final FlutterSecureStorage _secureStorage; + + /// {@macro tokens_storage.class} + const TokenStorageImpl(this._secureStorage); + + @override + Future read() async { + final tokenJson = await _secureStorage.read(key: TokensStorageKeys.authToken.keyName); + + if (tokenJson == null) return null; + + return AuthTokenPair.fromJson(jsonDecode(tokenJson) as Map); + } + + @override + Future write(AuthTokenPair token) { + return _secureStorage.write( + key: TokensStorageKeys.authToken.keyName, + value: jsonEncode(token.toJson()), + ); + } + + @override + Future delete() async { + for (final key in TokensStorageKeys.values) { + await _secureStorage.delete(key: key.keyName); + } + } +} + +/// Keys for [TokenStorageImpl] +enum TokensStorageKeys { + /// @nodoc + authToken('app_auth_token'); + + /// Key name + final String keyName; + + const TokensStorageKeys(this.keyName); +} diff --git a/lib/persistence/storage/tokens_storage/tokens_storage.dart b/lib/persistence/storage/tokens_storage/tokens_storage.dart deleted file mode 100644 index fcf83694..00000000 --- a/lib/persistence/storage/tokens_storage/tokens_storage.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:flutter_template/persistence/storage/tokens_storage/data/auth_tokens_storage_dto.dart'; -import 'package:flutter_template/persistence/storage/tokens_storage/i_tokens_storage.dart'; - -/// {@template tokens_storage.class} -/// Implementation [ITokensStorage] -/// {@endtemplate} -final class TokensStorage implements ITokensStorage { - final FlutterSecureStorage _secureStorage; - - /// {@macro tokens_storage.class} - const TokensStorage(this._secureStorage); - - @override - Future get accessToken => _secureStorage.read(key: TokensStorageKeys.accessToken.keyName); - - @override - Future get refreshToken => _secureStorage.read(key: TokensStorageKeys.refreshToken.keyName); - - @override - Future saveTokens(AuthTokensStorageDto tokens) async { - await _secureStorage.write(key: TokensStorageKeys.accessToken.keyName, value: tokens.accessToken); - await _secureStorage.write(key: TokensStorageKeys.refreshToken.keyName, value: tokens.refreshToken); - } - - @override - Future clear() async { - for (final key in TokensStorageKeys.values) { - await _secureStorage.delete(key: key.keyName); - } - } -} - -/// Keys for [TokensStorage] -enum TokensStorageKeys { - /// @nodoc - accessToken('app_access_token'), - - /// @nodoc - refreshToken('app_refresh_token'); - - /// Key name - final String keyName; - - const TokensStorageKeys(this.keyName); -} diff --git a/pubspec.lock b/pubspec.lock index 131ee476..32d1fa46 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -477,6 +477,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + fresh: + dependency: transitive + description: + name: fresh + sha256: d0adae5799932b7aed7f17bed05862f994e4f592e9de9f8a9a513377b773fe50 + url: "https://pub.dev" + source: hosted + version: "0.4.2" + fresh_dio: + dependency: "direct main" + description: + name: fresh_dio + sha256: "0edbaff18a3b4b159d09ad39831def89e88b91406356c14498c540c50df5de6d" + url: "https://pub.dev" + source: hosted + version: "0.4.1" frontend_server_client: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f79a71dd..1db5ba25 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: flutter_svg: 2.0.9 freezed: 2.4.7 freezed_annotation: 2.4.1 + fresh_dio: 0.4.1 intl: 0.18.1 json_annotation: 4.8.1 logger: 1.4.0 diff --git a/test/flutter_test_config.dart b/test/flutter_test_config.dart index f15f262d..bc4652bc 100644 --- a/test/flutter_test_config.dart +++ b/test/flutter_test_config.dart @@ -68,7 +68,7 @@ Future testExecutable(OnTestMain testMain) { when(() => themeController.themeMode).thenReturn(ValueNotifier(mode.toThemeMode)); return DiScope( - onFactory: (_) => mockAppScope, + factory: (_) => mockAppScope, child: Provider.value( value: themeController, child: widget, diff --git a/tools/bricks/feature/__brick__/lib/features/{{name.snakeCase()}}/presentation/screens/{{name.snakeCase()}}/{{name.snakeCase()}}_wm.dart b/tools/bricks/feature/__brick__/lib/features/{{name.snakeCase()}}/presentation/screens/{{name.snakeCase()}}/{{name.snakeCase()}}_wm.dart index 09f431ea..dc397eb4 100644 --- a/tools/bricks/feature/__brick__/lib/features/{{name.snakeCase()}}/presentation/screens/{{name.snakeCase()}}/{{name.snakeCase()}}_wm.dart +++ b/tools/bricks/feature/__brick__/lib/features/{{name.snakeCase()}}/presentation/screens/{{name.snakeCase()}}/{{name.snakeCase()}}_wm.dart @@ -1,6 +1,6 @@ import 'package:elementary/elementary.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_template/features/common/utils/mixin/theme_wm_mixin.dart'; +import 'package:flutter_template/common/mixin/theme_wm_mixin.dart'; import 'package:flutter_template/features/{{name.snakeCase()}}/di/{{name.snakeCase()}}_scope.dart'; import 'package:flutter_template/features/{{name.snakeCase()}}/presentation/screens/{{name.snakeCase()}}/{{name.snakeCase()}}_model.dart'; import 'package:flutter_template/features/{{name.snakeCase()}}/presentation/screens/{{name.snakeCase()}}/{{name.snakeCase()}}_screen.dart'; diff --git a/tools/bricks/feature/__brick__/lib/features/{{name.snakeCase()}}/presentation/{{name.snakeCase()}}_flow.dart b/tools/bricks/feature/__brick__/lib/features/{{name.snakeCase()}}/presentation/{{name.snakeCase()}}_flow.dart index 93983f80..3de014f8 100644 --- a/tools/bricks/feature/__brick__/lib/features/{{name.snakeCase()}}/presentation/{{name.snakeCase()}}_flow.dart +++ b/tools/bricks/feature/__brick__/lib/features/{{name.snakeCase()}}/presentation/{{name.snakeCase()}}_flow.dart @@ -18,7 +18,7 @@ class {{name.pascalCase()}}Flow extends StatelessWidget implements AutoRouteWrap const repository = {{name.pascalCase()}}Repository(); return DiScope( - onFactory: () => {{name.pascalCase()}}Scope(repository), + factory: (context) => {{name.pascalCase()}}Scope(repository), onDispose: (scope) => scope.dispose(), child: this, ); diff --git a/tools/bricks/screen/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/screens/{{screen_name.snakeCase()}}/{{screen_name.snakeCase()}}_wm.dart b/tools/bricks/screen/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/screens/{{screen_name.snakeCase()}}/{{screen_name.snakeCase()}}_wm.dart index c257ade9..308a2209 100644 --- a/tools/bricks/screen/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/screens/{{screen_name.snakeCase()}}/{{screen_name.snakeCase()}}_wm.dart +++ b/tools/bricks/screen/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/screens/{{screen_name.snakeCase()}}/{{screen_name.snakeCase()}}_wm.dart @@ -1,6 +1,6 @@ import 'package:elementary/elementary.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_template/features/common/utils/mixin/theme_wm_mixin.dart'; +import 'package:flutter_template/common/mixin/theme_wm_mixin.dart'; import 'package:flutter_template/features/{{feature_name.snakeCase()}}/di/{{feature_name.snakeCase()}}_scope.dart'; import 'package:flutter_template/features/{{feature_name.snakeCase()}}/presentation/screens/{{screen_name.snakeCase()}}/{{screen_name.snakeCase()}}_model.dart'; import 'package:flutter_template/features/{{feature_name.snakeCase()}}/presentation/screens/{{screen_name.snakeCase()}}/{{screen_name.snakeCase()}}_screen.dart';