games/packages/service_api/lib/services/identity_service.dart
2025-11-17 18:21:49 +09:00

235 lines
7.3 KiB
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';
final _storage = const FlutterSecureStorage();
IOSOptions _getIOSOptions() => const IOSOptions(
// 🔽 [수정] Xcode 설정 전까지 'groupId'를 주석 처리하여 크래시 방지
// groupId: 'group.com.lunaticbum.mygamecenter',
);
AndroidOptions _getAndroidOptions() => const AndroidOptions(
encryptedSharedPreferences: true,
);
// 1. 현재 세션 정보를 '객체'로 가져오기
Future<UserSession> 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<String> 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<String?> getSavedUserName() async {
return await _storage.read(
key: _userNameKey,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
}
// 4. 랭킹 등록 성공 시, 사용자 이름 저장하기
Future<void> saveUserName(String name) async {
await _storage.write(
key: _userNameKey,
value: name,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
}
// 5. 소셜 로그인 성공 시 호출 (계정 연결)
Future<UserSession> 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<UserSession> 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. 최대 레벨 가져오기 (gameType 분기)
Future<int> getMaxUnlockedLevel({String gameType = 'SUDOKU'}) async {
String key;
switch (gameType) {
case 'SPIDER':
key = _spiderMaxLevelKey;
break;
case 'MATH_QUIZ': // 👈 [추가]
key = _mathQuizMaxLevelKey;
break;
default: // 'SUDOKU'
key = _sudokuMaxLevelKey;
}
String? levelString = await _storage.read(
key: key,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
return int.parse(levelString ?? '1');
}
// 🔽 [수정] 8. 최대 레벨 저장하기 (gameType 분기)
Future<void> saveMaxUnlockedLevel(int level, {String gameType = 'SUDOKU'}) async {
String key;
switch (gameType) {
case 'SPIDER':
key = _spiderMaxLevelKey;
break;
case 'MATH_QUIZ': // 👈 [추가]
key = _mathQuizMaxLevelKey;
break;
default: // 'SUDOKU'
key = _sudokuMaxLevelKey;
}
await _storage.write(
key: key,
value: level.toString(),
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
}
// 🔽 [수정] 9. 마지막 랭킹 맵 가져오기 (gameType 분기)
Future<Map<int, int>> getLastSavedRankMap({String gameType = 'SUDOKU'}) async {
String key;
switch (gameType) {
case 'SPIDER':
key = _spiderRankMapKey;
break;
case 'MATH_QUIZ': // 👈 [추가]
key = _mathQuizRankMapKey;
break;
default: // 'SUDOKU'
key = _sudokuRankMapKey;
}
String? jsonString = await _storage.read(
key: key,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
if (jsonString == null) return {};
try {
final Map<String, dynamic> decodedMap = jsonDecode(jsonString);
return decodedMap.map((key, value) => MapEntry(int.parse(key), value as int));
} catch (e) {
return {};
}
}
// 🔽 [수정] 10. 마지막 랭킹 맵 저장하기 (gameType 분기)
Future<void> saveLastRankMap(Map<int, int> rankMap, {String gameType = 'SUDOKU'}) async {
String key;
switch (gameType) {
case 'SPIDER':
key = _spiderRankMapKey;
break;
case 'MATH_QUIZ': // 👈 [추가]
key = _mathQuizRankMapKey;
break;
default: // 'SUDOKU'
key = _sudokuRankMapKey;
}
final Map<String, int> stringKeyMap =
rankMap.map((key, value) => MapEntry(key.toString(), value));
String jsonString = jsonEncode(stringKeyMap);
await _storage.write(
key: key,
value: jsonString,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
}
}