diff --git a/.gitignore b/.gitignore
index 3820a95..20c5693 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,5 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
+
+.env
\ No newline at end of file
diff --git a/.metadata b/.metadata
index 3803806..2c6187b 100644
--- a/.metadata
+++ b/.metadata
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: "a402d9a4376add5bc2d6b1e33e53edaae58c07f8"
+ revision: "b45fa18946ecc2d9b4009952c636ba7e2ffbb787"
channel: "stable"
project_type: app
@@ -13,11 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
+ create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
- platform: android
- create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
+ create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ - platform: ios
+ create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ - platform: linux
+ create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ - platform: macos
+ create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ - platform: web
+ create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ - platform: windows
+ create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
+ base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
# User provided section
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 752019b..87182bb 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -10,10 +10,14 @@
+
+
+
-
+
+
@@ -51,18 +56,18 @@
-
-
-
-
+
+
+
-
-
+
-
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/assets/images/icons/gray_line_graph_icon.svg b/assets/images/icons/gray_line_graph_icon.svg
new file mode 100644
index 0000000..77bd908
--- /dev/null
+++ b/assets/images/icons/gray_line_graph_icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/icons/green_line_graph_icon.svg b/assets/images/icons/green_line_graph_icon.svg
new file mode 100644
index 0000000..e1005d5
--- /dev/null
+++ b/assets/images/icons/green_line_graph_icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/profiles/user1.png b/assets/images/profiles/user1.png
deleted file mode 100644
index d3e9c87..0000000
Binary files a/assets/images/profiles/user1.png and /dev/null differ
diff --git a/assets/images/profiles/user2.png b/assets/images/profiles/user2.png
deleted file mode 100644
index d3e9c87..0000000
Binary files a/assets/images/profiles/user2.png and /dev/null differ
diff --git a/assets/images/profiles/user3.png b/assets/images/profiles/user3.png
deleted file mode 100644
index d3e9c87..0000000
Binary files a/assets/images/profiles/user3.png and /dev/null differ
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index cb2ac8d..e9b2236 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -30,6 +30,11 @@
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
NSCameraUsageDescription
프로필 사진 촬영을 위해 카메라 권한이 필요합니다.
NSPhotoLibraryUsageDescription
diff --git a/lib/app.dart b/lib/app.dart
index 232d0a6..4bceab8 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -1,9 +1,9 @@
// 최초 작성자: 김채영
import 'package:flutter/material.dart';
-import 'package:haenaem/features/social/screens/social_screen.dart';
+import 'package:haenaem/features/social/screens/social_main_screen.dart';
import 'features/challenge/create/screens/challenge_create_screen.dart';
-import 'features/user/screens/my_page_screen.dart';
+import 'features/user/screens/my_page_main_screen.dart';
import 'features/main/screens/main_screen.dart';
class App extends StatelessWidget {
@@ -13,7 +13,7 @@ class App extends StatelessWidget {
Widget build(BuildContext context) {
return const MaterialApp(
// theme:,
- home: MyPageScreen(),
+ home: MyPageMainScreen(),
//home: MainScreen(),
//home: SocialScreen(),
);
diff --git a/lib/core/network/dio_provider.dart b/lib/core/network/dio_provider.dart
index b66c4ad..0fc2b42 100644
--- a/lib/core/network/dio_provider.dart
+++ b/lib/core/network/dio_provider.dart
@@ -13,9 +13,8 @@ part 'dio_provider.g.dart';
Dio dio(DioRef ref) {
final dio = Dio(
BaseOptions(
- baseUrl: 'https://hanaem.onrender.com/',
- connectTimeout: const Duration(seconds: 45),
- receiveTimeout: const Duration(seconds: 45),
+ baseUrl: 'http://158.247.216.11:8080',
+ connectTimeout: const Duration(seconds: 5),
),
);
@@ -24,6 +23,8 @@ Dio dio(DioRef ref) {
onRequest: (options, handler) async {
const storage = FlutterSecureStorage();
final String? token = await storage.read(key: 'accessToken');
+ // 💡 [디버깅 로그] 저장소에서 꺼낸 생생한 토큰 상태를 확인합니다.
+ debugPrint('🕵️♂️ [Interceptor] Storage Read (accessToken): $token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
@@ -31,11 +32,46 @@ Dio dio(DioRef ref) {
},
onError: (DioException e, handler) async {
if (e.response?.statusCode == 401) {
- final newToken = await AuthService.refreshTokens();
- if (newToken != null) {
- e.requestOptions.headers['Authorization'] = 'Bearer $newToken';
- final response = await dio.fetch(e.requestOptions);
- return handler.resolve(response);
+ const storage = FlutterSecureStorage();
+ final refreshToken = await storage.read(key: 'refreshToken');
+ if (refreshToken != null) {
+ try {
+ // 🎯 토큰 갱신 전용 가벼운 Dio 생성 (인터셉터 없음)
+ final refreshDio = Dio(
+ BaseOptions(baseUrl: e.requestOptions.baseUrl),
+ );
+
+ final response = await refreshDio.post(
+ '/api/token',
+ data: {"refreshToken": refreshToken},
+ );
+
+ // 1. 서버 응답에서 새 토큰 추출 (백엔드 응답 키값에 맞게 수정하세요)
+ final newAccessToken = response.data['accessToken'];
+ final newRefreshToken = response.data['refreshToken'];
+
+ // 2. 새 토큰 스토리지에 저장
+ await storage.write(key: 'accessToken', value: newAccessToken);
+ if (newRefreshToken != null) {
+ await storage.write(
+ key: 'refreshToken',
+ value: newRefreshToken,
+ );
+ }
+
+ // 3. 실패했던 원래 요청의 헤더를 새 토큰으로 변경
+ e.requestOptions.headers['Authorization'] =
+ 'Bearer $newAccessToken';
+
+ // 4. 원래 요청 재시도 및 결과 반환
+ final retryResponse = await dio.fetch(e.requestOptions);
+ return handler.resolve(retryResponse);
+ } catch (err) {
+ // 재발급 실패 시: 토큰 찌꺼기 삭제
+ debugPrint("재발급 실패! 저장된 토큰 삭제");
+ await storage.deleteAll();
+ return handler.next(e);
+ }
}
}
return handler.next(e);
diff --git a/lib/core/network/dio_provider.g.dart b/lib/core/network/dio_provider.g.dart
index 92d4137..7668117 100644
--- a/lib/core/network/dio_provider.g.dart
+++ b/lib/core/network/dio_provider.g.dart
@@ -6,7 +6,7 @@ part of 'dio_provider.dart';
// RiverpodGenerator
// **************************************************************************
-String _$dioHash() => r'db900e733514ad57f7f6f488298446b8ed14a932';
+String _$dioHash() => r'f946130859aa45b04593c36dacd077dbeacea6b6';
/// See also [dio].
@ProviderFor(dio)
diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart
index 341f0f3..de842b2 100644
--- a/lib/core/theme/app_colors.dart
+++ b/lib/core/theme/app_colors.dart
@@ -9,7 +9,8 @@ class AppColors {
static const Color gray2 = Color(0xFF616161);
static const Color gray3 = Color(0xFF8c8c8c);
static const Color gray4 = Color(0xFFD9D9D9);
- static final Color gray5 = const Color(0xFFd9d9d9).withValues(alpha: 0.5);
+ static const Color gray5 = Color(0x7Fd9d9d9);
+ //static final Color gray5 = const Color(0xFFd9d9d9).withValues(alpha: 0.5);
// Green - primary
static const Color primaryAble = Color(0xff009951);
@@ -17,8 +18,8 @@ class AppColors {
static const Color disable = Color(0xffd9e0d7);
// Mainlist
- static Color success = const Color(0xffbbf4bd).withValues(alpha: 0.5);
- static Color warning = const Color(0xffffd6c8).withValues(alpha: 0.5);
+ static const success = Color(0x7fbbf4bd);
+ static const warning = Color(0x7fffd6c8);
static const Color fire = Color(0xFFFB7039);
static const Color notification = Color(0xffD11E1B);
diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart
index bd20b5b..b162c05 100644
--- a/lib/core/theme/app_theme.dart
+++ b/lib/core/theme/app_theme.dart
@@ -1,6 +1,7 @@
// 최초 작성자: 김채영
import 'package:flutter/material.dart';
+import 'app_colors.dart';
class AppTheme {
static ThemeData lightTheme = ThemeData(
@@ -8,6 +9,16 @@ class AppTheme {
fontFamily: 'Pretendard',
scaffoldBackgroundColor: Colors.white,
+ // 스피너랑 커서 우리 앱 초록색으로 변경
+ progressIndicatorTheme: const ProgressIndicatorThemeData(
+ color: AppColors.primaryAble,
+ ),
+ textSelectionTheme: TextSelectionThemeData(
+ cursorColor: AppColors.primaryAble,
+ selectionHandleColor: AppColors.primaryAble,
+ selectionColor: AppColors.primaryAble.withValues(alpha: 0.3),
+ ),
+
// 상단 AppBar 테마 설정
appBarTheme: const AppBarTheme(
backgroundColor: Colors.white,
diff --git a/lib/core/utils/image_utils.dart b/lib/core/utils/image_utils.dart
new file mode 100644
index 0000000..9afda6f
--- /dev/null
+++ b/lib/core/utils/image_utils.dart
@@ -0,0 +1,36 @@
+// 최초 작성자: 김채영
+import 'dart:io';
+import 'dart:typed_data';
+import 'package:flutter/material.dart';
+import 'package:flutter_image_compress/flutter_image_compress.dart';
+import 'package:path_provider/path_provider.dart';
+
+// 클라우더리 용량 이슈 때문에 필요한 인증글의 이미지 압축 유틸 함수
+Future compressImageFile(File file) async {
+ final tempDir = await getTemporaryDirectory();
+ final targetPath =
+ '${tempDir.path}/compressed_${DateTime.now().millisecondsSinceEpoch}.jpg';
+
+ final XFile? result = await FlutterImageCompress.compressAndGetFile(
+ file.absolute.path,
+ targetPath,
+ quality: 80, // 80 정도면 육안상 차이 거의 없음
+ minWidth: 1080, // 긴 쪽 기준 최대 해상도
+ minHeight: 1080,
+ format: CompressFormat.jpeg,
+ );
+
+ // ✅ 압축 전후 크기 비교 로그
+ final int originalSize = await file.length();
+ final int compressedSize = result != null
+ ? await File(result.path).length()
+ : 0;
+
+ debugPrint('🖼️ 압축 전: ${(originalSize / 1024).toStringAsFixed(1)} KB');
+ debugPrint('🖼️ 압축 후: ${(compressedSize / 1024).toStringAsFixed(1)} KB');
+ debugPrint(
+ '🖼️ 압축률: ${((1 - compressedSize / originalSize) * 100).toStringAsFixed(1)}%',
+ );
+
+ return result != null ? File(result.path) : file; // 실패 시 원본 반환
+}
diff --git a/lib/features/auth/login/login_screen.dart b/lib/features/auth/login/login_screen.dart
index ed46901..69d74f2 100644
--- a/lib/features/auth/login/login_screen.dart
+++ b/lib/features/auth/login/login_screen.dart
@@ -1,5 +1,10 @@
// 최초 작성자 : 김채영
+import 'dart:convert';
+import 'dart:math';
import 'package:flutter/material.dart';
+import 'package:url_launcher/url_launcher.dart';
+import 'package:crypto/crypto.dart'; // PKCE 해싱용
+import 'package:webview_flutter/webview_flutter.dart'; // 웹뷰용
import 'package:flutter_svg/flutter_svg.dart';
import 'package:haenaem/core/theme/app_colors.dart';
import 'package:haenaem/core/theme/app_typography.dart';
@@ -60,14 +65,38 @@ class LoginScreen extends StatelessWidget {
textColor: Colors.black,
iconPath: 'assets/images/icons/kakao_logo.svg',
onTap: () async {
- // 1. 인가 코드와 Verifier 가져오기
- final authResult = await AuthService.signInWithKakao();
+ // 1. PKCE 데이터 및 URL 준비 (AuthService 이용)
+ final pkce = AuthService.generatePkcePair();
+ final authUrl = AuthService.getKakaoAuthUrl(
+ pkce['challenge']!,
+ );
+ String? kakaoAuthCode;
- if (authResult != null && context.mounted) {
- // 2. 백엔드가 정의한 @RequestBody 형식으로 쏘기
+ if (!context.mounted) return;
+
+ // 2. 웹뷰 실행 (UI 부분이라 Screen에 두는 게 적절합니다)
+ await showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ useSafeArea: true,
+ backgroundColor: Colors.transparent,
+ builder: (ctx) => Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(ctx).viewInsets.bottom,
+ ),
+ child: _buildKakaoWebView(
+ context: ctx,
+ authUrl: authUrl,
+ onCodeCaptured: (code) => kakaoAuthCode = code,
+ ),
+ ),
+ );
+
+ // 3. 획득한 코드가 있다면 백엔드로 전송
+ if (kakaoAuthCode != null && context.mounted) {
await AuthService.sendKakaoAuthToBackend(
- code: authResult['code']!,
- codeVerifier: authResult['codeVerifier']!,
+ code: kakaoAuthCode!,
+ codeVerifier: pkce['codeVerifier']!, // 원본 열쇠 전송
context: context,
);
}
@@ -107,8 +136,42 @@ class LoginScreen extends StatelessWidget {
backgroundColor: const Color(0xFF03C75A),
textColor: Colors.white,
iconPath: 'assets/images/icons/naver_logo.svg',
- onTap: navigateToSignup,
- //onTap = () {},
+ onTap: () async {
+ // 1. 상태(state) 문자열 생성 (카카오의 PKCE 함수를 재사용하여 임의의 문자열 15자리 생성)
+ final state = AuthService.generatePkcePair()['challenge']!
+ .substring(0, 15);
+ final authUrl = AuthService.getNaverAuthUrl(state);
+ String? naverAuthCode;
+
+ if (!context.mounted) return;
+
+ // 2. 카카오처럼 바텀시트로 네이버 웹뷰 실행
+ await showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ useSafeArea: true,
+ backgroundColor: Colors.transparent,
+ builder: (ctx) => Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(ctx).viewInsets.bottom,
+ ),
+ child: _buildNaverWebView(
+ context: ctx,
+ authUrl: authUrl,
+ onCodeCaptured: (code) => naverAuthCode = code,
+ ),
+ ),
+ );
+
+ // 3. 획득한 코드가 있다면 백엔드로 전송
+ if (naverAuthCode != null && context.mounted) {
+ await AuthService.sendNaverAuthToBackend(
+ code: naverAuthCode!,
+ state: state,
+ context: context,
+ );
+ }
+ },
),
const SizedBox(height: 30),
@@ -165,4 +228,160 @@ class LoginScreen extends StatelessWidget {
),
);
}
+
+ Widget _buildKakaoWebView({
+ required BuildContext context,
+ required String authUrl,
+ required Function(String) onCodeCaptured,
+ }) {
+ late final WebViewController controller;
+
+ controller = WebViewController()
+ ..setJavaScriptMode(JavaScriptMode.unrestricted)
+ ..setUserAgent(
+ "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
+ )
+ ..setNavigationDelegate(
+ NavigationDelegate(
+ onWebResourceError: (WebResourceError error) {
+ debugPrint('''
+ ⚠️ 웹뷰 로딩 에러 발생!
+ - 코드: ${error.errorCode}
+ - 설명: ${error.description}
+ - URL: ${error.url}
+ ''');
+ },
+ onNavigationRequest: (NavigationRequest request) async {
+ final url = request.url;
+
+ // 📍 [중요] 주소 감지 로그 추가
+ if (url.contains('/oauth/kakao/callback')) {
+ debugPrint('🎣 [감지 성공] 카카오 콜백 주소가 포착되었습니다!');
+ final uri = Uri.parse(url);
+ final code = uri.queryParameters['code'];
+
+ if (code != null) {
+ debugPrint('✅ 획득한 인가 코드: $code');
+ onCodeCaptured(code);
+ Navigator.pop(context); // 웹뷰 닫기
+ return NavigationDecision.prevent; // 페이지 이동 중단
+ }
+ }
+
+ // 2️⃣ 카카오톡 앱 호출 주소 처리
+ if (url.startsWith('kakaotalk://') || url.startsWith('intent://')) {
+ try {
+ debugPrint('📱 카카오톡 앱 실행 시도: $url');
+
+ // intent:// 스킴인 경우 안드로이드용 특수 처리가 필요할 수 있지만,
+ // url_launcher가 대부분 해결해줍니다.
+ final canLaunch = await canLaunchUrl(Uri.parse(url));
+ if (canLaunch) {
+ await launchUrl(
+ Uri.parse(url),
+ mode: LaunchMode.externalApplication,
+ );
+ return NavigationDecision.prevent;
+ }
+ } catch (e) {
+ debugPrint('🚨 앱 실행 실패: $e');
+ // 앱 실행 실패 시 웹에서 로그인하도록 유지 (prevent 하지 않음)
+ }
+ }
+
+ // 리다이렉트 및 기타 주소 처리
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
+ return NavigationDecision.prevent;
+ }
+ return NavigationDecision.navigate;
+ },
+ onPageStarted: (url) {
+ debugPrint("🚀 웹뷰 로딩 시작됨: $url");
+ // ✅ AuthService.kakaoRedirectUri로 시작하는지 감시
+ if (url.startsWith(AuthService.kakaoRedirectUri)) {
+ debugPrint('🎣 리다이렉트 감지!');
+ final uri = Uri.parse(url);
+ final code = uri.queryParameters['code'];
+ if (code != null) {
+ onCodeCaptured(code);
+ Navigator.pop(context);
+ }
+ }
+ },
+ onPageFinished: (url) {
+ debugPrint("✅ 웹뷰 로딩 완료됨: $url");
+ },
+ ),
+ )
+ ..loadRequest(Uri.parse(authUrl));
+
+ // 📍 로드하기 직전에 실제 어떤 주소를 부르는지 확인!
+ debugPrint("🌍 웹뷰 로딩 시도 URL: $authUrl");
+
+ return Container(
+ height: MediaQuery.of(context).size.height * 0.9,
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
+ ),
+ child: WebViewWidget(controller: controller),
+ );
+ }
+}
+
+Widget _buildNaverWebView({
+ required BuildContext context,
+ required String authUrl,
+ required Function(String) onCodeCaptured,
+}) {
+ late final WebViewController controller;
+
+ controller = WebViewController()
+ ..setJavaScriptMode(JavaScriptMode.unrestricted)
+ ..setUserAgent(
+ "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
+ )
+ ..setNavigationDelegate(
+ NavigationDelegate(
+ onNavigationRequest: (NavigationRequest request) {
+ final url = request.url;
+
+ // 📍 [핵심] 백엔드의 네이버 콜백 주소 감지
+ if (url.contains('/oauth/naver/callback')) {
+ debugPrint('🎣 [감지 성공] 네이버 콜백 주소가 포착되었습니다!');
+ final uri = Uri.parse(url);
+ final code = uri.queryParameters['code'];
+
+ if (code != null) {
+ debugPrint('✅ 획득한 네이버 인가 코드: $code');
+ onCodeCaptured(code);
+ Navigator.pop(context); // 웹뷰 닫기
+ return NavigationDecision.prevent; // 리다이렉트 방지
+ }
+ }
+ return NavigationDecision.navigate;
+ },
+ onPageStarted: (url) {
+ // 이중 체크 (만약 onNavigationRequest에서 못 잡았을 경우)
+ if (url.startsWith(AuthService.naverRedirectUri)) {
+ final uri = Uri.parse(url);
+ final code = uri.queryParameters['code'];
+ if (code != null) {
+ onCodeCaptured(code);
+ Navigator.pop(context);
+ }
+ }
+ },
+ ),
+ )
+ ..loadRequest(Uri.parse(authUrl));
+
+ return Container(
+ height: MediaQuery.of(context).size.height * 0.9,
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
+ ),
+ child: WebViewWidget(controller: controller),
+ );
}
diff --git a/lib/features/auth/services/auth_service.dart b/lib/features/auth/services/auth_service.dart
index 40bafbd..a007432 100644
--- a/lib/features/auth/services/auth_service.dart
+++ b/lib/features/auth/services/auth_service.dart
@@ -1,11 +1,17 @@
// 최초 작성자: 김채영
import 'package:flutter/material.dart';
+import 'dart:convert';
+import 'dart:math';
+import 'package:crypto/crypto.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:dio/dio.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+import '../../../core/network/dio_provider.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; // 토큰 저장을 위해 필요
import 'package:haenaem/features/auth/signup/screens/signup_main_screen.dart';
import 'package:haenaem/features/main/screens/main_screen.dart';
import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
// 구글 OAuth 2.0 기반의 사용자 인증과 JWT 토큰의 생명주기(발급, 재발급, 파기)를 전담하는 클래스
// 서버로부터 받은 userStatus(NEW/ACTIVE)를 분석하여 사용자별 맞춤형 초기 화면 진입 경로를 제어
@@ -14,54 +20,135 @@ class AuthService {
static const _storage = FlutterSecureStorage(); // 보안 저장소
// 구글 설정 정보
- static const String androidClientId =
- '433865217738-m3uqqdv9lumpf1ne8e3bkpsbtsa6919i.apps.googleusercontent.com';
+ static String androidClientId = dotenv.env['GOOGLE_ANDROID_CLIENT_ID'] ?? '';
static const String customScheme =
'com.googleusercontent.apps.433865217738-m3uqqdv9lumpf1ne8e3bkpsbtsa6919i';
static const String redirectUri = '$customScheme:/oauth2redirect';
// 카카오 설정 정보
- static const String kakaoRestApiKey = '9fdd13c0777c415d8fa4055b5b26a6c5';
- static const String kakaoNativeAppKey = '05a36f172ea2945260862834654385ea';
+ static String kakaoRestApiKey = dotenv.env['KAKAO_REST_API_KEY'] ?? '';
+ static String kakaoNativeAppKey = dotenv.env['KAKAO_NATIVE_APP_KEY'] ?? '';
// static const String kakaoRedirectUri =
// 'https://hanaem.onrender.com/api/oauth/kakao/token';
+
static const String kakaoRedirectUri =
- 'kakao9fdd13c0777c415d8fa4055b5b26a6c5://oauth';
+ 'http://158.247.216.11:8080/oauth/kakao/callback';
+
+ //static const String kakaoRedirectUri =
+ //'kakao9fdd13c0777c415d8fa4055b5b26a6c5://oauth';
+
+ // 네이버 설정 정보
+ static String naverClientId = dotenv.env['NAVER_CLIENT_ID'] ?? '';
+ static const String naverRedirectUri =
+ 'http://158.247.216.11:8080/oauth/naver/callback';
+ // ♥️ 기존 서버
static final Dio _dio = Dio(
- BaseOptions(baseUrl: 'https://hanaem.onrender.com'),
+ BaseOptions(baseUrl: 'http://158.247.216.11:8080'),
);
- // 카카오 로그인
- static Future