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'; final _storage = const FlutterSecureStorage(); /// ๐Ÿ”ฝ [์‹ ๊ทœ] iOS ์•ฑ ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ณต์œ ๋ฅผ ์œ„ํ•œ ์˜ต์…˜ IOSOptions _getIOSOptions() => const IOSOptions( // ๐Ÿ”ฝ [์ˆ˜์ •] Xcode ์„ค์ • ์ „๊นŒ์ง€ 'groupId'๋ฅผ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜์—ฌ ํฌ๋ž˜์‹œ ๋ฐฉ์ง€ // groupId: 'group.com.lunaticbum.mygamecenter', ); AndroidOptions _getAndroidOptions() => const AndroidOptions( encryptedSharedPreferences: true, ); // ๐Ÿ”ฝ [์‹ ๊ทœ] 1. ํ˜„์žฌ ์„ธ์…˜ ์ •๋ณด๋ฅผ '๊ฐ์ฒด'๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ Future getUserSession() async { final userId = await getOrCreateUserId(); // ๊ฒŒ์ŠคํŠธ ID๋Š” ํ•ญ์ƒ ๋ณด์žฅ 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, // ์„œ๋ฒ„๊ฐ€ ๋ฐœ๊ธ‰ํ•œ ๋งˆ์Šคํ„ฐ ๊ณ„์ • ID required String newUserName, required String newEmail, required String provider, // "google" ๋˜๋Š” "apple" }) 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 { final key = gameType == 'SPIDER' ? _spiderMaxLevelKey : _sudokuMaxLevelKey; String? levelString = await _storage.read( key: key, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); return int.parse(levelString ?? '1'); // ๊ธฐ๋ณธ๊ฐ’ 1 } // 8. ์ตœ๋Œ€ ๋ ˆ๋ฒจ ์ €์žฅํ•˜๊ธฐ Future saveMaxUnlockedLevel(int level, {String gameType = 'SUDOKU'}) async { final key = gameType == 'SPIDER' ? _spiderMaxLevelKey : _sudokuMaxLevelKey; await _storage.write( key: key, value: level.toString(), iOptions: _getIOSOptions(), aOptions: _getAndroidOptions(), ); } // 9. ๋งˆ์ง€๋ง‰ ๋žญํ‚น ๋งต ๊ฐ€์ ธ์˜ค๊ธฐ Future> getLastSavedRankMap({String gameType = 'SUDOKU'}) async { final key = gameType == 'SPIDER' ? _spiderRankMapKey : _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 { final key = gameType == 'SPIDER' ? _spiderRankMapKey : _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(), ); } }