Skip to content
This repository was archived by the owner on Mar 22, 2025. It is now read-only.

Commit c241589

Browse files
authored
Merge pull request #118 from chornerman/release/0.3.0
Release - 0.3.0
2 parents 5748e63 + 8c13da7 commit c241589

Some content is hidden

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

56 files changed

+1461
-203
lines changed

assets/color/colors.xml

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<color name="chinese_black">#15151A</color>
4+
<color name="eerie_black_90">#E61E1E1E</color>
5+
<color name="raisin_black">#252525</color>
46
</resources>
File renamed without changes.

assets/images/ic_arrow_back.svg

+5
Loading
File renamed without changes.

assets/images/ic_notification.svg

+5
Loading

lib/api/repository/auth_repository.dart

+66-4
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,30 @@ import 'package:injectable/injectable.dart';
22
import 'package:survey/api/exception/network_exceptions.dart';
33
import 'package:survey/api/grant_type.dart';
44
import 'package:survey/api/request/login_request.dart';
5+
import 'package:survey/api/request/logout_request.dart';
6+
import 'package:survey/api/request/refresh_token_request.dart';
7+
import 'package:survey/api/request/reset_password_request.dart';
58
import 'package:survey/api/service/auth_service.dart';
69
import 'package:survey/env_variables.dart';
7-
import 'package:survey/model/login_model.dart';
10+
import 'package:survey/model/token_model.dart';
811

912
abstract class AuthRepository {
10-
Future<LoginModel> login({
13+
Future<TokenModel> login({
1114
required String email,
1215
required String password,
1316
});
17+
18+
Future<void> logout({
19+
required String token,
20+
});
21+
22+
Future<TokenModel> refreshToken({
23+
required String refreshToken,
24+
});
25+
26+
Future<void> resetPassword({
27+
required String email,
28+
});
1429
}
1530

1631
@Singleton(as: AuthRepository)
@@ -20,7 +35,7 @@ class AuthRepositoryImpl extends AuthRepository {
2035
AuthRepositoryImpl(this._authService);
2136

2237
@override
23-
Future<LoginModel> login({
38+
Future<TokenModel> login({
2439
required String email,
2540
required String password,
2641
}) async {
@@ -34,7 +49,54 @@ class AuthRepositoryImpl extends AuthRepository {
3449
clientSecret: EnvVariables.clientSecret,
3550
),
3651
);
37-
return LoginModel.fromResponse(response);
52+
return TokenModel.fromResponse(response);
53+
} catch (exception) {
54+
throw NetworkExceptions.fromDioException(exception);
55+
}
56+
}
57+
58+
@override
59+
Future<void> logout({required String token}) async {
60+
try {
61+
await _authService.logout(
62+
LogoutRequest(
63+
token: token,
64+
clientId: EnvVariables.clientId,
65+
clientSecret: EnvVariables.clientSecret,
66+
),
67+
);
68+
} catch (exception) {
69+
throw NetworkExceptions.fromDioException(exception);
70+
}
71+
}
72+
73+
@override
74+
Future<TokenModel> refreshToken({required String refreshToken}) async {
75+
try {
76+
final response = await _authService.refreshToken(
77+
RefreshTokenRequest(
78+
grantType: GrantType.refreshToken.value,
79+
clientId: EnvVariables.clientId,
80+
clientSecret: EnvVariables.clientSecret,
81+
refreshToken: refreshToken,
82+
),
83+
);
84+
return TokenModel.fromResponse(response);
85+
} catch (exception) {
86+
throw NetworkExceptions.fromDioException(exception);
87+
}
88+
}
89+
90+
@override
91+
Future<void> resetPassword({required String email}) async {
92+
try {
93+
await _authService.resetPassword(
94+
ResetPasswordRequest(
95+
user: ResetPasswordUserRequest(email: email),
96+
clientId: EnvVariables.clientId,
97+
clientSecret: EnvVariables.clientSecret,
98+
),
99+
);
38100
} catch (exception) {
39101
throw NetworkExceptions.fromDioException(exception);
40102
}

lib/api/request/logout_request.dart

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:json_annotation/json_annotation.dart';
2+
3+
part 'logout_request.g.dart';
4+
5+
@JsonSerializable()
6+
class LogoutRequest {
7+
final String token;
8+
final String clientId;
9+
final String clientSecret;
10+
11+
LogoutRequest({
12+
required this.token,
13+
required this.clientId,
14+
required this.clientSecret,
15+
});
16+
17+
factory LogoutRequest.fromJson(Map<String, dynamic> json) =>
18+
_$LogoutRequestFromJson(json);
19+
20+
Map<String, dynamic> toJson() => _$LogoutRequestToJson(this);
21+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import 'package:json_annotation/json_annotation.dart';
2+
3+
part 'refresh_token_request.g.dart';
4+
5+
@JsonSerializable()
6+
class RefreshTokenRequest {
7+
final String grantType;
8+
final String clientId;
9+
final String clientSecret;
10+
final String refreshToken;
11+
12+
RefreshTokenRequest({
13+
required this.grantType,
14+
required this.clientId,
15+
required this.clientSecret,
16+
required this.refreshToken,
17+
});
18+
19+
factory RefreshTokenRequest.fromJson(Map<String, dynamic> json) =>
20+
_$RefreshTokenRequestFromJson(json);
21+
22+
Map<String, dynamic> toJson() => _$RefreshTokenRequestToJson(this);
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'package:json_annotation/json_annotation.dart';
2+
3+
part 'reset_password_request.g.dart';
4+
5+
@JsonSerializable()
6+
class ResetPasswordRequest {
7+
final ResetPasswordUserRequest user;
8+
final String clientId;
9+
final String clientSecret;
10+
11+
ResetPasswordRequest({
12+
required this.user,
13+
required this.clientId,
14+
required this.clientSecret,
15+
});
16+
17+
factory ResetPasswordRequest.fromJson(Map<String, dynamic> json) =>
18+
_$ResetPasswordRequestFromJson(json);
19+
20+
Map<String, dynamic> toJson() => _$ResetPasswordRequestToJson(this);
21+
}
22+
23+
@JsonSerializable()
24+
class ResetPasswordUserRequest {
25+
final String email;
26+
27+
ResetPasswordUserRequest({
28+
required this.email,
29+
});
30+
31+
factory ResetPasswordUserRequest.fromJson(Map<String, dynamic> json) =>
32+
_$ResetPasswordUserRequestFromJson(json);
33+
34+
Map<String, dynamic> toJson() => _$ResetPasswordUserRequestToJson(this);
35+
}
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import 'package:json_annotation/json_annotation.dart';
22
import 'package:survey/api/response/converter/response_converter.dart';
33

4-
part 'login_response.g.dart';
4+
part 'token_response.g.dart';
55

66
@JsonSerializable()
7-
class LoginResponse {
7+
class TokenResponse {
88
final String accessToken;
99
final String tokenType;
1010
final int expiresIn;
1111
final String refreshToken;
1212
final int createdAt;
1313

14-
LoginResponse({
14+
TokenResponse({
1515
required this.accessToken,
1616
required this.tokenType,
1717
required this.expiresIn,
1818
required this.refreshToken,
1919
required this.createdAt,
2020
});
2121

22-
factory LoginResponse.fromJson(Map<String, dynamic> json) =>
23-
_$LoginResponseFromJson(fromDataJsonApi(json));
22+
factory TokenResponse.fromJson(Map<String, dynamic> json) =>
23+
_$TokenResponseFromJson(fromDataJsonApi(json));
2424
}

lib/api/service/auth_service.dart

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import 'package:dio/dio.dart';
22
import 'package:retrofit/retrofit.dart';
33
import 'package:survey/api/request/login_request.dart';
4-
import 'package:survey/api/response/login_response.dart';
4+
import 'package:survey/api/request/logout_request.dart';
5+
import 'package:survey/api/request/refresh_token_request.dart';
6+
import 'package:survey/api/request/reset_password_request.dart';
7+
import 'package:survey/api/response/token_response.dart';
58

69
part 'auth_service.g.dart';
710

@@ -10,7 +13,22 @@ abstract class AuthService {
1013
factory AuthService(Dio dio, {String baseUrl}) = _AuthService;
1114

1215
@POST('/api/v1/oauth/token')
13-
Future<LoginResponse> login(
16+
Future<TokenResponse> login(
1417
@Body() LoginRequest body,
1518
);
19+
20+
@POST('/api/v1/oauth/revoke')
21+
Future<void> logout(
22+
@Body() LogoutRequest body,
23+
);
24+
25+
@POST('/api/v1/oauth/token')
26+
Future<TokenResponse> refreshToken(
27+
@Body() RefreshTokenRequest body,
28+
);
29+
30+
@POST('/api/v1/passwords')
31+
Future<void> resetPassword(
32+
@Body() ResetPasswordRequest body,
33+
);
1634
}

lib/database/shared_preferences_utils.dart

+14
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@ abstract class SharedPreferencesUtils {
1414

1515
String get authToken;
1616

17+
bool get isLoggedIn;
18+
1719
void saveAccessToken(String accessToken);
1820

1921
void saveTokenType(String tokenType);
2022

2123
void saveRefreshToken(String refreshToken);
24+
25+
void clear();
2226
}
2327

2428
@Singleton(as: SharedPreferencesUtils)
@@ -40,6 +44,11 @@ class SharedPreferencesUtilsImpl extends SharedPreferencesUtils {
4044
@override
4145
String get authToken => '$tokenType $accessToken';
4246

47+
@override
48+
bool get isLoggedIn =>
49+
_sharedPreferences.containsKey(_accessTokenKey) &&
50+
_sharedPreferences.containsKey(_tokenTypeKey);
51+
4352
@override
4453
void saveAccessToken(String accessToken) async {
4554
await _sharedPreferences.setString(_accessTokenKey, accessToken);
@@ -54,4 +63,9 @@ class SharedPreferencesUtilsImpl extends SharedPreferencesUtils {
5463
void saveRefreshToken(String refreshToken) async {
5564
await _sharedPreferences.setString(_refreshTokenKey, refreshToken);
5665
}
66+
67+
@override
68+
void clear() async {
69+
await _sharedPreferences.clear();
70+
}
5771
}

lib/di/interceptor/app_interceptor.dart

+57-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1+
import 'dart:io';
2+
13
import 'package:dio/dio.dart';
24
import 'package:survey/database/shared_preferences_utils.dart';
5+
import 'package:survey/di/di.dart';
6+
import 'package:survey/model/token_model.dart';
7+
import 'package:survey/usecase/base/base_use_case.dart';
8+
import 'package:survey/usecase/refresh_token_use_case.dart';
39

410
const String _authorizationHeader = 'Authorization';
511

6-
class AppInterceptor extends Interceptor {
12+
class AppInterceptor extends QueuedInterceptor {
713
final bool requireAuthentication;
814
final SharedPreferencesUtils sharedPreferencesUtils;
15+
final Dio dio;
916

1017
AppInterceptor({
1118
required this.requireAuthentication,
1219
required this.sharedPreferencesUtils,
20+
required this.dio,
1321
});
1422

1523
@override
@@ -21,4 +29,52 @@ class AppInterceptor extends Interceptor {
2129
}
2230
return super.onRequest(options, handler);
2331
}
32+
33+
@override
34+
void onError(DioError error, ErrorInterceptorHandler handler) {
35+
final statusCode = error.response?.statusCode;
36+
if ((statusCode == HttpStatus.forbidden ||
37+
statusCode == HttpStatus.unauthorized) &&
38+
requireAuthentication) {
39+
_refreshToken(error, handler);
40+
} else {
41+
handler.next(error);
42+
}
43+
}
44+
45+
void _refreshToken(
46+
DioError error,
47+
ErrorInterceptorHandler handler,
48+
) async {
49+
try {
50+
final refreshTokenUseCase = getIt<RefreshTokenUseCase>();
51+
final result = await refreshTokenUseCase.call();
52+
if (result is Success<TokenModel>) {
53+
// Update new token header
54+
final newAuthToken = sharedPreferencesUtils.authToken;
55+
error.requestOptions.headers[_authorizationHeader] = newAuthToken;
56+
57+
// Create request with new access token
58+
final options = Options(
59+
method: error.requestOptions.method,
60+
headers: error.requestOptions.headers,
61+
);
62+
final newRequest = await dio.request(
63+
"${error.requestOptions.baseUrl}${error.requestOptions.path}",
64+
options: options,
65+
data: error.requestOptions.data,
66+
queryParameters: error.requestOptions.queryParameters,
67+
);
68+
handler.resolve(newRequest);
69+
} else {
70+
handler.next(error);
71+
}
72+
} catch (exception) {
73+
if (exception is DioError) {
74+
handler.next(exception);
75+
} else {
76+
handler.next(error);
77+
}
78+
}
79+
}
2480
}

lib/di/provider/dio_provider.dart

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class DioProvider {
3232
final appInterceptor = AppInterceptor(
3333
requireAuthentication: requireAuthentication,
3434
sharedPreferencesUtils: _sharedPreferencesUtils,
35+
dio: dio,
3536
);
3637
final interceptors = <Interceptor>[];
3738
interceptors.add(appInterceptor);

0 commit comments

Comments
 (0)