Skip to content

Commit 7e01764

Browse files
authored
Merge pull request #68 from nimblehq/release/0.4.0
2 parents d5e3bdf + 5aa9335 commit 7e01764

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1136
-154
lines changed

assets/images/dummy_background.png

-234 KB
Binary file not shown.

assets/images/placeholder.png

19.4 KB
Loading

ios/Podfile.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ PODS:
88
- Flutter
99
- package_info_plus (0.4.5):
1010
- Flutter
11+
- path_provider_foundation (0.0.1):
12+
- Flutter
13+
- FlutterMacOS
1114
- permission_handler_apple (9.1.1):
1215
- Flutter
1316

@@ -17,6 +20,7 @@ DEPENDENCIES:
1720
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
1821
- integration_test (from `.symlinks/plugins/integration_test/ios`)
1922
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
23+
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
2024
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
2125

2226
EXTERNAL SOURCES:
@@ -30,6 +34,8 @@ EXTERNAL SOURCES:
3034
:path: ".symlinks/plugins/integration_test/ios"
3135
package_info_plus:
3236
:path: ".symlinks/plugins/package_info_plus/ios"
37+
path_provider_foundation:
38+
:path: ".symlinks/plugins/path_provider_foundation/darwin"
3339
permission_handler_apple:
3440
:path: ".symlinks/plugins/permission_handler_apple/ios"
3541

@@ -39,6 +45,7 @@ SPEC CHECKSUMS:
3945
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
4046
integration_test: 13825b8a9334a850581300559b8839134b124670
4147
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
48+
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
4249
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
4350

4451
PODFILE CHECKSUM: 632d6ac0b577d6e268ff7a13a105bbc4f7941989
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import 'package:dio/dio.dart';
2-
import 'package:survey_flutter/model/request/login_request.dart';
3-
import 'package:survey_flutter/model/response/login_response.dart';
42
import 'package:retrofit/retrofit.dart';
3+
import 'package:survey_flutter/model/request/login_request.dart';
4+
import 'package:survey_flutter/model/request/refresh_token_request.dart';
5+
import 'package:survey_flutter/model/response/token_response.dart';
56

67
part 'authentication_api_service.g.dart';
78

@@ -11,7 +12,12 @@ abstract class AuthenticationApiService {
1112
_AuthenticationApiService;
1213

1314
@POST('/oauth/token')
14-
Future<LoginResponse> login(
15-
@Body() LoginRequest body,
15+
Future<TokenResponse> login(
16+
@Body() LoginRequest loginRequest,
17+
);
18+
19+
@POST('/oauth/token')
20+
Future<TokenResponse> refreshToken(
21+
@Body() RefreshTokenRequest refreshTokenRequest,
1622
);
1723
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import 'package:flutter_riverpod/flutter_riverpod.dart';
2+
import 'package:survey_flutter/api/authentication_api_service.dart';
3+
import 'package:survey_flutter/di/provider/dio_provider.dart';
4+
import 'package:survey_flutter/env.dart';
5+
import 'package:survey_flutter/model/api_token.dart';
6+
import 'package:survey_flutter/model/request/refresh_token_request.dart';
7+
import 'package:survey_flutter/storage/secure_storage.dart';
8+
import 'package:survey_flutter/storage/secure_storage_impl.dart';
9+
import 'package:survey_flutter/utils/serializer/api_token_serializer.dart';
10+
11+
final tokenDataSourceProvider = Provider<TokenDataSource>((ref) {
12+
return TokenDataSourceImpl(ref.watch(secureStorageProvider),
13+
AuthenticationApiService(DioProvider().getDio()));
14+
});
15+
16+
abstract class TokenDataSource {
17+
Future<ApiToken> getToken({bool forceRefresh});
18+
Future<void> setToken(ApiToken token);
19+
}
20+
21+
class TokenDataSourceImpl extends TokenDataSource {
22+
final SecureStorage _secureStorage;
23+
final AuthenticationApiService _authenticationApiService;
24+
final String _grantType = 'refresh_token';
25+
26+
TokenDataSourceImpl(
27+
this._secureStorage,
28+
this._authenticationApiService,
29+
);
30+
31+
@override
32+
Future<ApiToken> getToken({bool forceRefresh = false}) async {
33+
final currentToken = await _secureStorage.getValue(
34+
key: SecureStorageKey.apiToken,
35+
serializer: ApiTokenSerializer(),
36+
);
37+
38+
if (!forceRefresh) {
39+
return currentToken;
40+
}
41+
42+
final tokenResponse = await _authenticationApiService.refreshToken(
43+
RefreshTokenRequest(
44+
grantType: _grantType,
45+
refreshToken: currentToken.refreshToken,
46+
clientId: Env.clientId,
47+
clientSecret: Env.clientSecret,
48+
),
49+
);
50+
final newToken = tokenResponse.toApiToken();
51+
await _secureStorage.save(
52+
value: newToken,
53+
key: SecureStorageKey.apiToken,
54+
);
55+
return newToken;
56+
}
57+
58+
@override
59+
Future<void> setToken(ApiToken token) async {
60+
await _secureStorage.save(value: token, key: SecureStorageKey.apiToken);
61+
}
62+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import 'dart:io';
2+
3+
import 'package:dio/dio.dart';
4+
import 'package:survey_flutter/api/data_sources/token_data_source.dart';
5+
6+
const String _headerAuthorization = 'Authorization';
7+
const String _retryCountOption = 'Retry-Count';
8+
9+
class AuthInterceptor extends Interceptor {
10+
final Dio _dio;
11+
final TokenDataSource _tokenDataSource;
12+
13+
AuthInterceptor(
14+
this._dio,
15+
this._tokenDataSource,
16+
);
17+
18+
@override
19+
void onRequest(
20+
RequestOptions options,
21+
RequestInterceptorHandler handler,
22+
) async {
23+
final token = await _tokenDataSource.getToken();
24+
options.headers.putIfAbsent(
25+
_headerAuthorization, () => "${token.tokenType} ${token.accessToken}");
26+
super.onRequest(options, handler);
27+
}
28+
29+
@override
30+
void onError(
31+
DioError err,
32+
ErrorInterceptorHandler handler,
33+
) {
34+
final statusCode = err.response?.statusCode;
35+
final requestOptions = err.requestOptions;
36+
37+
if (statusCode != HttpStatus.forbidden &&
38+
statusCode != HttpStatus.unauthorized &&
39+
requestOptions.extra[_retryCountOption] != 1) {
40+
handler.next(err);
41+
return;
42+
}
43+
44+
requestOptions.extra[_retryCountOption] = 1;
45+
_refreshTokenAndRetry(requestOptions, handler);
46+
}
47+
48+
Future<void> _refreshTokenAndRetry(
49+
RequestOptions options,
50+
ErrorInterceptorHandler handler,
51+
) async {
52+
final token = await _tokenDataSource.getToken(forceRefresh: true);
53+
final headers = options.headers;
54+
headers[_headerAuthorization] = "${token.tokenType} ${token.accessToken}";
55+
final newOptions = options.copyWith(headers: headers);
56+
await _dio.fetch(newOptions).then((response) => handler.resolve(response));
57+
}
58+
}

lib/api/response_decoder.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import 'package:japx/japx.dart';
22

33
class ResponseDecoder {
44
static Map<String, dynamic> decodeData(Map<String, dynamic> json) {
5-
return Japx.decode(json)['data'];
5+
return json.containsKey('data') ? Japx.decode(json)['data'] : json;
66
}
77

8-
static Map<String, dynamic> decode(Map<String, dynamic> json) =>
9-
Japx.decode(json);
8+
static Map<String, dynamic> decode(Map<String, dynamic> json) {
9+
return Japx.decode(json);
10+
}
1011
}

lib/di/provider/dio_provider.dart

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
11
import 'package:dio/dio.dart';
22
import 'package:flutter/foundation.dart';
3-
import 'package:survey_flutter/di/interceptor/app_interceptor.dart';
3+
import 'package:survey_flutter/api/data_sources/token_data_source.dart';
4+
import 'package:survey_flutter/api/interceptor/auth_interceptor.dart';
45
import 'package:survey_flutter/env.dart';
56

67
const String _headerContentType = 'Content-Type';
78
const String _defaultContentType = 'application/json; charset=utf-8';
89

910
class DioProvider {
1011
Dio? _dio;
12+
Dio? _authorizedDio;
13+
TokenDataSource? _tokenDataSource;
1114

1215
Dio getDio() {
1316
_dio ??= _createDio();
1417
return _dio!;
1518
}
1619

17-
Dio _createDio({bool requireAuthenticate = false}) {
20+
Dio getAuthorizedDio({
21+
required TokenDataSource tokenDataSource,
22+
}) {
23+
_tokenDataSource = tokenDataSource;
24+
_authorizedDio ??= _createDio(requireAuthentication: true);
25+
return _authorizedDio!;
26+
}
27+
28+
Dio _createDio({bool requireAuthentication = false}) {
1829
final dio = Dio();
19-
final appInterceptor = AppInterceptor(
20-
requireAuthenticate,
21-
dio,
22-
);
30+
2331
final interceptors = <Interceptor>[];
24-
interceptors.add(appInterceptor);
32+
if (requireAuthentication) {
33+
final authInterceptor = AuthInterceptor(
34+
dio,
35+
_tokenDataSource!,
36+
);
37+
interceptors.add(authInterceptor);
38+
}
2539
if (!kReleaseMode) {
2640
interceptors.add(LogInterceptor(
2741
request: true,

lib/l10n/app_en.arb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
"noInternetConnectionError": "You seem to be offline.\nPlease try again!",
1010
"genericError": "Something went wrong.\nPlease try again!",
1111
"loginFailAlertTitle": "Unable to log in",
12-
"today": "Today"
12+
"today": "Today",
13+
"errorText": "Error"
1314
}

lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import 'package:go_router/go_router.dart';
66
import 'package:survey_flutter/screens/home/home_screen.dart';
77
import 'package:survey_flutter/screens/login/login_screen.dart';
88
import 'package:survey_flutter/screens/splash/splash_screen.dart';
9+
import 'package:survey_flutter/storage/survey_storage.dart';
910
import 'package:survey_flutter/theme/app_theme.dart';
1011

1112
void main() async {
1213
WidgetsFlutterBinding.ensureInitialized();
1314
await FlutterConfig.loadEnvVariables();
15+
await setupHive();
1416
runApp(
1517
ProviderScope(
1618
child: App(),

0 commit comments

Comments
 (0)