// packages/service_api/lib/services/identity_service.dart import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:uuid/uuid.dart'; // 현재 로그인 세션을 담을 모델 class UserSession { final String userId; final String? userName; final String loginProvider; // "guest", "google", "apple" final String? email; UserSession({ required this.userId, this.userName, this.loginProvider = "guest", this.email, }); bool get isGuest => loginProvider == "guest"; } // 앱-고유 ID와 사용자 이름, 레벨 진행 상황을 관리하는 서비스 class IdentityService { static const String _userIdKey = 'app_user_id'; static const String _userNameKey = 'app_user_name'; static const String _loginProviderKey = 'app_login_provider'; static const String _userEmailKey = 'app_user_email'; // 게임별 저장 키 정의 static const String _sudokuMaxLevelKey = 'max_unlocked_level'; static const String _sudokuRankMapKey = 'last_checked_rank_map'; static const String _spiderMaxLevelKey = 'max_unlocked_spider_level'; static const String _spiderRankMapKey = 'last_checked_spider_rank_map'; static const String _mathQuizMaxLevelKey = 'max_unlocked_mathquiz_level'; static const String _mathQuizRankMapKey = 'last_checked_mathquiz_rank_map'; // [🔥 신규] 색상 일치 게임 키 static const String _colorMatchMaxLevelKey = 'max_unlocked_colormatch_level'; static const String _colorMatchRankMapKey = 'last_checked_colormatch_rank_map'; // [🔥 신규] 순서 기억 게임 키 static const String _sequenceMaxLevelKey = 'max_unlocked_sequence_level'; static const String _sequenceRankMapKey = 'last_checked_sequence_rank_map'; // [🔥 신규] 카드 뒤집기 게임 키 static const String _cardFlipMaxLevelKey = 'max_unlocked_cardflip_level'; static const String _cardFlipRankMapKey = 'last_checked_cardflip_rank_map'; final _storage = const FlutterSecureStorage(); IOSOptions _getIOSOptions() => const IOSOptions(); AndroidOptions _getAndroidOptions() => const AndroidOptions( encryptedSharedPreferences: true, ); // 1. 현재 세션 정보를 '객체'로 가져오기 Future getUserSession() async { final userId = await getOrCreateUserId(); final userName = await getSavedUserName(); final loginProvider = await _storage.read( key: _loginProviderKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions() ) ?? "guest"; final email = await _storage.read( key: _userEmailKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions() ); return UserSession( userId: userId, userName: userName, loginProvider: loginProvider, email: email, ); } // 2. 앱-고유 ID 가져오기 (없으면 생성) Future getOrCreateUserId() async { String? userId = await _storage.read( key: _userIdKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); if (userId == null) { userId = const Uuid().v4(); await _storage.write( key: _userIdKey, value: userId, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); } return userId; } // 3. 랭킹에 등록한 사용자 이름 가져오기 Future getSavedUserName() async { return await _storage.read( key: _userNameKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); } // 4. 랭킹 등록 성공 시, 사용자 이름 저장하기 Future saveUserName(String name) async { await _storage.write( key: _userNameKey, value: name, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); } // 5. 소셜 로그인 성공 시 호출 (계정 연결) Future saveSocialLogin({ required String newUserId, required String newUserName, required String newEmail, required String provider, }) async { await _storage.write(key: _userIdKey, value: newUserId, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions()); await _storage.write(key: _userNameKey, value: newUserName, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions()); await _storage.write(key: _userEmailKey, value: newEmail, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions()); await _storage.write(key: _loginProviderKey, value: provider, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions()); return UserSession( userId: newUserId, userName: newUserName, loginProvider: provider, email: newEmail, ); } // 6. 로그아웃 (게스트 계정으로 되돌리기) Future logout() async { await _storage.delete(key: _userNameKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions()); await _storage.delete(key: _userEmailKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions()); await _storage.delete(key: _loginProviderKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions()); return await getUserSession(); } // 7. [수정] 최대 레벨 가져오기 (모든 게임 타입 지원) Future getMaxUnlockedLevel({String gameType = 'SUDOKU'}) async { String key; switch (gameType) { case 'SPIDER': key = _spiderMaxLevelKey; break; case 'MATH_QUIZ': key = _mathQuizMaxLevelKey; break; case 'COLOR_MATCH': // 👈 추가 key = _colorMatchMaxLevelKey; break; case 'SEQUENCE': // 👈 추가 key = _sequenceMaxLevelKey; break; case 'CARD_FLIP': // 👈 추가 key = _cardFlipMaxLevelKey; break; default: // 'SUDOKU' key = _sudokuMaxLevelKey; } String? levelString = await _storage.read( key: key, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); return int.parse(levelString ?? '1'); } // 8. [수정] 최대 레벨 저장하기 (모든 게임 타입 지원) Future saveMaxUnlockedLevel(int level, {String gameType = 'SUDOKU'}) async { String key; switch (gameType) { case 'SPIDER': key = _spiderMaxLevelKey; break; case 'MATH_QUIZ': key = _mathQuizMaxLevelKey; break; case 'COLOR_MATCH': // 👈 추가 key = _colorMatchMaxLevelKey; break; case 'SEQUENCE': // 👈 추가 key = _sequenceMaxLevelKey; break; case 'CARD_FLIP': // 👈 추가 key = _cardFlipMaxLevelKey; break; default: // 'SUDOKU' key = _sudokuMaxLevelKey; } await _storage.write( key: key, value: level.toString(), iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); } // 9. [수정] 마지막 랭킹 맵 가져오기 (모든 게임 타입 지원) Future> getLastSavedRankMap({String gameType = 'SUDOKU'}) async { String key; switch (gameType) { case 'SPIDER': key = _spiderRankMapKey; break; case 'MATH_QUIZ': key = _mathQuizRankMapKey; break; case 'COLOR_MATCH': // 👈 추가 key = _colorMatchRankMapKey; break; case 'SEQUENCE': // 👈 추가 key = _sequenceRankMapKey; break; case 'CARD_FLIP': // 👈 추가 key = _cardFlipRankMapKey; break; default: // 'SUDOKU' key = _sudokuRankMapKey; } String? jsonString = await _storage.read( key: key, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); if (jsonString == null) return {}; try { final Map decodedMap = jsonDecode(jsonString); return decodedMap.map((key, value) => MapEntry(int.parse(key), value as int)); } catch (e) { return {}; } } // 10. [수정] 마지막 랭킹 맵 저장하기 (모든 게임 타입 지원) Future saveLastRankMap(Map rankMap, {String gameType = 'SUDOKU'}) async { String key; switch (gameType) { case 'SPIDER': key = _spiderRankMapKey; break; case 'MATH_QUIZ': key = _mathQuizRankMapKey; break; case 'COLOR_MATCH': // 👈 추가 key = _colorMatchRankMapKey; break; case 'SEQUENCE': // 👈 추가 key = _sequenceRankMapKey; break; case 'CARD_FLIP': // 👈 추가 key = _cardFlipRankMapKey; break; default: // 'SUDOKU' key = _sudokuRankMapKey; } final Map stringKeyMap = rankMap.map((key, value) => MapEntry(key.toString(), value)); String jsonString = jsonEncode(stringKeyMap); await _storage.write( key: key, value: jsonString, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); } }