246 lines
8.6 KiB
Dart
246 lines
8.6 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import '../models/cognitive_type.dart';
|
|
import '../models/assessment_data.dart';
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// [Fix] UserSession 정의 및 isGuest 추가
|
|
// -----------------------------------------------------------------------------
|
|
class UserSession {
|
|
final String userId;
|
|
final String? userName;
|
|
final String? email;
|
|
final String? photoUrl;
|
|
final String? provider; // 'google', 'apple', 'guest'
|
|
|
|
UserSession({
|
|
required this.userId,
|
|
this.userName,
|
|
this.email,
|
|
this.photoUrl,
|
|
this.provider,
|
|
});
|
|
|
|
// [Fix] 에러 해결: isGuest 게터 추가
|
|
bool get isGuest => provider == 'guest' || provider == null;
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'userId': userId,
|
|
'userName': userName,
|
|
'email': email,
|
|
'photoUrl': photoUrl,
|
|
'provider': provider,
|
|
};
|
|
|
|
factory UserSession.fromJson(Map<String, dynamic> json) => UserSession(
|
|
userId: json['userId'],
|
|
userName: json['userName'],
|
|
email: json['email'],
|
|
photoUrl: json['photoUrl'],
|
|
provider: json['provider'],
|
|
);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// IdentityService 구현
|
|
// -----------------------------------------------------------------------------
|
|
class IdentityService {
|
|
static const String _userIdKey = 'app_user_id';
|
|
static const String _userSessionKey = 'app_user_session';
|
|
static const String _userNameKey = 'app_user_name'; // 추가
|
|
static const String _assessmentHistoryKey = 'cognitive_assessment_history';
|
|
|
|
final _storage = const FlutterSecureStorage();
|
|
final _uuid = const Uuid();
|
|
|
|
IOSOptions _getIOSOptions() => const IOSOptions(accessibility: KeychainAccessibility.first_unlock);
|
|
AndroidOptions _getAndroidOptions() => const AndroidOptions(encryptedSharedPreferences: true);
|
|
|
|
// ===========================================================================
|
|
// 1. 유저 세션 관리 (호환성 복구)
|
|
// ===========================================================================
|
|
|
|
Future<String> getOrCreateUser() async {
|
|
String? userId = await _storage.read(key: _userIdKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
if (userId == null) {
|
|
userId = _uuid.v4();
|
|
await _storage.write(key: _userIdKey, value: userId, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
}
|
|
return userId;
|
|
}
|
|
|
|
/// [Fix] SessionNotifier 에러 해결
|
|
Future<UserSession?> getUserSession() async {
|
|
String? jsonStr = await _storage.read(key: _userSessionKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
if (jsonStr == null) return null;
|
|
try {
|
|
return UserSession.fromJson(jsonDecode(jsonStr));
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// [Fix] SessionNotifier 에러 해결
|
|
Future<UserSession> saveSocialLogin({
|
|
required String userId,
|
|
String? email,
|
|
String? name,
|
|
String? photoUrl,
|
|
required String provider,
|
|
}) async {
|
|
final session = UserSession(
|
|
userId: userId,
|
|
email: email,
|
|
userName: name,
|
|
photoUrl: photoUrl,
|
|
provider: provider,
|
|
);
|
|
await _storage.write(
|
|
key: _userSessionKey,
|
|
value: jsonEncode(session.toJson()),
|
|
iOptions: _getIOSOptions(),
|
|
aOptions: _getAndroidOptions()
|
|
);
|
|
await _storage.write(key: _userIdKey, value: userId, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
|
|
return session;
|
|
}
|
|
|
|
/// [Fix] SessionNotifier 에러 해결
|
|
Future<void> logout() async {
|
|
await _storage.delete(key: _userSessionKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
}
|
|
|
|
/// [Fix] GameCompletionScreen 에러 해결
|
|
Future<void> saveUserName(String name) async {
|
|
await _storage.write(key: _userNameKey, value: name, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
|
|
// 세션이 있다면 세션 이름도 업데이트
|
|
final currentSession = await getUserSession();
|
|
if (currentSession != null) {
|
|
final newSession = UserSession(
|
|
userId: currentSession.userId,
|
|
userName: name,
|
|
email: currentSession.email,
|
|
photoUrl: currentSession.photoUrl,
|
|
provider: currentSession.provider,
|
|
);
|
|
await _storage.write(
|
|
key: _userSessionKey,
|
|
value: jsonEncode(newSession.toJson()),
|
|
iOptions: _getIOSOptions(),
|
|
aOptions: _getAndroidOptions()
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<String?> getUserName() async {
|
|
return await _storage.read(key: _userNameKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
}
|
|
|
|
// ===========================================================================
|
|
// 2. 진단 기록 (Assessment)
|
|
// ===========================================================================
|
|
|
|
Future<void> saveAssessmentResult(Map<CognitiveArea, int> scores) async {
|
|
final record = AssessmentRecord(
|
|
id: _uuid.v4(),
|
|
date: DateTime.now(),
|
|
scores: scores,
|
|
);
|
|
final history = await getAssessmentHistory();
|
|
history.add(record);
|
|
|
|
final jsonString = jsonEncode(history.map((e) => e.toJson()).toList());
|
|
await _storage.write(key: _assessmentHistoryKey, value: jsonString, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
}
|
|
|
|
Future<List<AssessmentRecord>> getAssessmentHistory() async {
|
|
final jsonString = await _storage.read(key: _assessmentHistoryKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
if (jsonString == null) return [];
|
|
try {
|
|
final List<dynamic> jsonList = jsonDecode(jsonString);
|
|
return jsonList.map((e) => AssessmentRecord.fromJson(e)).toList();
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// [Fix] SettingsScreen 에러 해결
|
|
Future<void> clearAssessmentHistory() async {
|
|
await _storage.delete(key: _assessmentHistoryKey, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
}
|
|
|
|
Future<Map<CognitiveArea, int>?> getCognitiveScores() async {
|
|
final history = await getAssessmentHistory();
|
|
if (history.isEmpty) return null;
|
|
history.sort((a, b) => b.date.compareTo(a.date));
|
|
return history.first.scores;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// 3. 게임 데이터 관리 (통합 + 레거시 호환)
|
|
// ===========================================================================
|
|
|
|
String _getMaxLevelKey(String gameType) => 'max_level_${gameType.toLowerCase()}';
|
|
String _getRankMapKey(String gameType) => 'rank_map_${gameType.toLowerCase()}';
|
|
|
|
Future<int> getMaxUnlockedLevel({String gameType = 'SUDOKU'}) async {
|
|
final key = _getMaxLevelKey(gameType);
|
|
String? levelString = await _storage.read(key: key, iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
|
|
return int.parse(levelString ?? '1');
|
|
}
|
|
|
|
/// [Fix] 기존 게임들이 호출하는 메서드 복구
|
|
Future<void> saveMaxUnlockedLevel(int level, {String gameType = 'SUDOKU'}) async {
|
|
await _storage.write(
|
|
key: _getMaxLevelKey(gameType),
|
|
value: level.toString(),
|
|
iOptions: _getIOSOptions(),
|
|
aOptions: _getAndroidOptions()
|
|
);
|
|
}
|
|
|
|
Future<Map<int, int>> getLastSavedRankMap({required String gameType}) async {
|
|
final key = _getRankMapKey(gameType);
|
|
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((k, v) => MapEntry(int.parse(k), v as int));
|
|
} catch (e) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/// [Fix] LobbyHelper 에러 해결
|
|
Future<void> saveLastRankMap(Map<int, int> rankMap, {required String gameType}) async {
|
|
final String jsonString = jsonEncode(rankMap.map((k, v) => MapEntry(k.toString(), v)));
|
|
await _storage.write(
|
|
key: _getRankMapKey(gameType),
|
|
value: jsonString,
|
|
iOptions: _getIOSOptions(),
|
|
aOptions: _getAndroidOptions()
|
|
);
|
|
}
|
|
|
|
/// [신규] 게임 결과 통합 처리
|
|
Future<void> submitGameResult({
|
|
required String gameType,
|
|
required int level,
|
|
required int stars,
|
|
}) async {
|
|
final rankMap = await getLastSavedRankMap(gameType: gameType);
|
|
final int oldStars = rankMap[level] ?? 0;
|
|
if (stars > oldStars) {
|
|
rankMap[level] = stars;
|
|
await saveLastRankMap(rankMap, gameType: gameType);
|
|
}
|
|
|
|
final int currentMax = await getMaxUnlockedLevel(gameType: gameType);
|
|
if (level >= currentMax) {
|
|
await saveMaxUnlockedLevel(level + 1, gameType: gameType);
|
|
}
|
|
}
|
|
} |