Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

M3-181 로그아웃 및 토큰 리프레시 구현 #26

Merged
merged 13 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/controllers/login_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class LoginController extends GetxController {

loginWithKakao() async {
try {
AuthResponse authResponse = await authService.loginWithKakao();
LoginResponse authResponse = await authService.loginWithKakao();
if (authResponse.isSignUp!) {
Get.toNamed('/signup');
} else {
Expand Down
4 changes: 2 additions & 2 deletions lib/controllers/my_page_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ class MyPageController extends GetxController {
return currentUserInfo.value.communityName ?? "-";
}

int getCurrentUserPixel(){
int getCurrentUserPixel() {
return userPixelCount.value.currentPixelCount ?? 0;
}

int getAccumulateUserPixel(){
int getAccumulateUserPixel() {
return userPixelCount.value.accumulatePixelCount ?? 0;
}
}
36 changes: 36 additions & 0 deletions lib/controllers/setting_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../service/auth_service.dart';

class SettingController extends GetxController {
final AuthService authService = AuthService();

logout() async {
_showLogoutDialog();
}

void _showLogoutDialog() {
Get.dialog(
AlertDialog(
title: Text('로그아웃 하시겠습니까?'),
actions: [
TextButton(
child: Text('아니오'),
onPressed: () async {
await authService.logout();
Get.back();
},
),
TextButton(
child: Text('예'),
onPressed: () async {
await authService.logout();
Get.offAllNamed('/login');
},
),
],
),
);
}
}
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:kakao_flutter_sdk/kakao_flutter_sdk_common.dart';

import 'screens/login_screen.dart';
import 'screens/main_screen.dart';
import 'screens/setting_screen.dart';
import 'service/auth_service.dart';

Future<void> main() async {
Expand Down Expand Up @@ -41,6 +42,7 @@ class MyApp extends StatelessWidget {
getPages: [
GetPage(name: '/main', page: () => const MainScreen()),
GetPage(name: '/login', page: () => const LoginScreen()),
GetPage(name: '/setting', page: () => const SettingScreen()),
],
);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/models/auth_response.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
class AuthResponse {
class LoginResponse {
String? accessToken;
String? refreshToken;
bool? isSignUp;

AuthResponse({
LoginResponse({
this.accessToken,
this.refreshToken,
this.isSignUp,
});

AuthResponse.fromJson(Map<String, dynamic> json) {
LoginResponse.fromJson(Map<String, dynamic> json) {
accessToken = json['accessToken'];
refreshToken = json['refreshToken'];
isSignUp = json['isSignUp'];
Expand Down
14 changes: 14 additions & 0 deletions lib/models/reissue_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class ReissueResponse {
String? accessToken;
String? refreshToken;

ReissueResponse({
this.accessToken,
this.refreshToken,
});

ReissueResponse.fromJson(Map<String, dynamic> json) {
accessToken = json['accessToken'];
refreshToken = json['refreshToken'];
}
}
2 changes: 0 additions & 2 deletions lib/screens/search_group.dart

This file was deleted.

57 changes: 57 additions & 0 deletions lib/screens/setting_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../controllers/setting_controller.dart';
import '../widgets/setting/setting_item.dart';
import '../widgets/setting/setting_section.dart';

class SettingScreen extends StatelessWidget {
const SettingScreen({super.key});

@override
Widget build(BuildContext context) {
SettingController settingController = Get.put(SettingController());

return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('설정'),
),
body: ListView(
children: [
SettingsSection(
title: '설정',
items: [
SettingsItem(title: '수령 정보 설정'),
SettingsItem(title: '알림 설정'),
SettingsItem(title: '맵 설정'),
SettingsItem(title: '실험실'),
SettingsItem(title: '앱 최적화'),
SettingsItem(title: 'App Store 리뷰 남기기'),
],
),
SettingsSection(
title: '가이드',
items: [
SettingsItem(title: '공지사항'),
SettingsItem(title: '플레이 가이드'),
SettingsItem(title: '고객 문의 및 개선 요청'),
],
),
SettingsSection(
title: '기타',
items: [
SettingsItem(title: '서비스이용약관'),
SettingsItem(
title: '로그아웃',
onTap: () {
settingController.logout();
},
),
],
),
],
),
);
}
}
26 changes: 18 additions & 8 deletions lib/service/auth_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,20 @@ class AuthService {
return _instance;
}

logout() {
secureStorage.deleteAccessToken();
secureStorage.deleteRefreshToken();
UserManager().init();
logout() async {
try {
await postLogout();
} catch (e) {
debugPrint('Error during logout: $e');
} finally {
await secureStorage.deleteAccessToken();
await secureStorage.deleteRefreshToken();
UserManager().init();
}
}

postLogout() async {
await dio.post('/auth/logout');
}

Future<bool> isLogin() async {
Expand All @@ -41,22 +51,22 @@ class AuthService {

loginWithKakao() async {
String accessToken = await _getKakaoAccessToken();
AuthResponse authResponse = await postKakaoLogin(accessToken);
LoginResponse authResponse = await postKakaoLogin(accessToken);
await _saveTokens(authResponse);
return authResponse;
}

Future<void> _saveTokens(AuthResponse authResponse) async {
Future<void> _saveTokens(LoginResponse authResponse) async {
await secureStorage.writeAccessToken(authResponse.accessToken);
await secureStorage.writeRefreshToken(authResponse.refreshToken);
UserManager().setUserId(_extractUserIdFromToken(authResponse.accessToken!));
}

Future<AuthResponse> postKakaoLogin(String accessToken) async {
Future<LoginResponse> postKakaoLogin(String accessToken) async {
try {
var response = await dio
.post('/auth/kakao/login', data: {"accessToken": accessToken});
return AuthResponse.fromJson(response.data["data"]);
return LoginResponse.fromJson(response.data["data"]);
} catch (error) {
throw Exception("로그인 실패");
}
Expand Down
47 changes: 42 additions & 5 deletions lib/utils/dio_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get/get.dart' hide Response;

import '../models/reissue_response.dart';
import '../service/auth_service.dart';
import 'secure_storage.dart';

Expand Down Expand Up @@ -42,18 +43,54 @@ class DioService {
onError: (
DioException dioException,
ErrorInterceptorHandler errorInterceptorHandler,
) {
) async {
if (dioException.response?.statusCode == HttpStatus.unauthorized) {
AuthService().logout();
Get.offAllNamed('/login');
try {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분 하나의 콜백이 너무 길어지는 느낌이 드는데, 한번 더 크게 묶어서 handleAuthorization() 같은 이름의 메소드로 묶는 건 어떨까요?

ReissueResponse reissueResponse = await reissueToken();
await secureStorage.writeAccessToken(reissueResponse.accessToken);
await secureStorage
.writeRefreshToken(reissueResponse.refreshToken);

Response<dynamic> resendResponse =
await resendRequest(dioException, reissueResponse);
errorInterceptorHandler.resolve(resendResponse);
debugPrint('-------------refresh---------------');
} catch (err) {
AuthService().logout();
Get.offAllNamed('/login');
}
} else {
logError(dioException);
return errorInterceptorHandler.next(dioException);
}
logError(dioException);
return errorInterceptorHandler.next(dioException);
},
),
);
}

Future<Response<dynamic>> resendRequest(
DioException dioException,
ReissueResponse reissueResponse,
) async {
final options = dioException.requestOptions;
final dio = Dio();
options.headers.addAll({
'Authorization': 'Bearer ${reissueResponse.accessToken}',
});
var response = await dio.fetch(options);
return response;
}

Future<ReissueResponse> reissueToken() async {
final refreshToken = await secureStorage.readRefreshToken();
final dio = Dio();
var response = await dio
.post("$baseUrl/auth/reissue", data: {"refreshToken": refreshToken});
ReissueResponse reissueResponse =
ReissueResponse.fromJson(response.data["data"]);
return reissueResponse;
}

Dio getDio() {
return _dio;
}
Expand Down
9 changes: 9 additions & 0 deletions lib/widgets/common/app_bar.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class MapAppBar extends StatelessWidget {
const MapAppBar({super.key});
Expand Down Expand Up @@ -44,6 +45,14 @@ class MyPageAppBar extends StatelessWidget {
return AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('마이페이지'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
Get.toNamed('/setting');
},
),
],
);
}
}
2 changes: 1 addition & 1 deletion lib/widgets/map/pixel_owner_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class PixelOwnerInfo extends StatelessWidget {
fit: BoxFit.cover,
)
: Image.asset(
'assets/default_profile_image.png',
'assets/images/default_profile_image.png',
width: 50,
height: 50,
),
Expand Down
9 changes: 5 additions & 4 deletions lib/widgets/map/visited_user_list_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class VisitedUserListView extends StatelessWidget {
fit: BoxFit.cover,
)
: Image.asset(
'assets/default_profile_image.png',
'assets/images/default_profile_image.png',
width: 50,
height: 50,
),
Expand All @@ -47,9 +47,10 @@ class VisitedUserListView extends StatelessWidget {
Text(
visitList[index].nickname!,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,),
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
Expand Down
9 changes: 5 additions & 4 deletions lib/widgets/pixel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import '../models/individual_history_pixel.dart';
import '../models/individual_history_pixel_info.dart';
import '../models/individual_mode_pixel.dart';
import '../models/individual_mode_pixel_info.dart';
import '../utils/user_manager.dart';
import 'map/individual_history_pixel_info_bottom_sheet.dart';
import 'map/individual_mode_pixel_info_bottom_sheet.dart';

class Pixel extends Polygon {
static const double latPerPixel = 0.000724;
static const double lonPerPixel = 0.000909;

static const int defaultUserId = 2;

final int x;
final int y;
final int pixelId;
Expand Down Expand Up @@ -89,8 +88,10 @@ class Pixel extends Polygon {
strokeWidth: 1,
onTap: (int pixelId) async {
IndividualHistoryPixelInfo pixelInfo =
await Get.find<PixelInfoController>()
.getIndividualHistoryPixelInfo(pixelId, defaultUserId);
await Get.find<PixelInfoController>().getIndividualHistoryPixelInfo(
pixelId,
UserManager().getUserId()!,
);

Get.bottomSheet(
IndividualHistoryPixelInfoBottomSheet(pixelInfo: pixelInfo),
Expand Down
20 changes: 20 additions & 0 deletions lib/widgets/setting/setting_item.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';

class SettingsItem extends StatelessWidget {
final String title;
final VoidCallback? onTap;

const SettingsItem({required this.title, this.onTap, super.key});

@override
Widget build(BuildContext context) {
return ListTile(
title: Text(
title,
style: const TextStyle(color: Colors.black),
),
trailing: const Icon(Icons.arrow_forward_ios, color: Colors.black),
onTap: onTap,
);
}
}
Loading