349 lines
10 KiB
Dart
349 lines
10 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../config/api_config.dart';
|
|
import '../models/member_model.dart';
|
|
import './api_service.dart';
|
|
import './event_bus.dart';
|
|
|
|
class MemberService with ChangeNotifier {
|
|
List<Member> _members = [];
|
|
Member? _currentMember;
|
|
bool _isLoading = false;
|
|
String? _token;
|
|
String? _clubId;
|
|
final ApiService _apiService;
|
|
StreamSubscription? _eventSubscription;
|
|
|
|
// 기본 생성자
|
|
MemberService() : _apiService = ApiService();
|
|
|
|
// 테스트용 생성자
|
|
MemberService.forTest(this._apiService);
|
|
|
|
List<Member> get members => [..._members];
|
|
bool get isLoading => _isLoading;
|
|
|
|
// 초기화 함수
|
|
void initialize(String token) {
|
|
_token = token;
|
|
_apiService.setToken(token);
|
|
|
|
// 이벤트 구독 - 기존 구독이 있으면 취소 후 다시 구독
|
|
_eventSubscription?.cancel();
|
|
_eventSubscription = EventBus().on<ClubChangedEvent>().listen((event) {
|
|
if (event.clubId.isNotEmpty) {
|
|
setClubId(event.clubId);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 비동기 리소스 상태 로깅
|
|
Future<void> _logResourceState(String context) async {
|
|
if (kDebugMode) {
|
|
debugPrint('===== MemberService 비동기 리소스 상태 ($context) =====');
|
|
debugPrint('시간: ${DateTime.now().toIso8601String()}');
|
|
debugPrint('구독 상태: ${_eventSubscription != null ? '활성' : '비활성'}');
|
|
debugPrint('클럽 ID: $_clubId');
|
|
debugPrint('토큰 상태: ${_token != null ? '있음' : '없음'}');
|
|
debugPrint('로딩 상태: $_isLoading');
|
|
debugPrint('회원 수: ${_members.length}');
|
|
debugPrint('EventBus 활성 구독 수: ${EventBus.activeSubscriptionCount}');
|
|
debugPrint('===========================================');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
// dispose 시작 상태 로깅
|
|
if (kDebugMode) {
|
|
debugPrint('MemberService: dispose 시작');
|
|
}
|
|
await _logResourceState('dispose 시작');
|
|
|
|
// 이벤트 구독 취소 확실히 처리
|
|
if (_eventSubscription != null) {
|
|
try {
|
|
// 구독 취소 전 로깅
|
|
if (kDebugMode) {
|
|
debugPrint('MemberService: 구독 취소 시작');
|
|
}
|
|
|
|
// 구독 취소
|
|
_eventSubscription!.cancel();
|
|
|
|
// 마이크로태스크 실행으로 비동기 작업 완료 보장
|
|
await Future.microtask(() {});
|
|
|
|
// 구독 취소 후 로깅
|
|
if (kDebugMode) {
|
|
debugPrint('MemberService: 구독 취소 완료');
|
|
}
|
|
|
|
// 구독 참조 제거
|
|
_eventSubscription = null;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
debugPrint('MemberService: 구독 취소 중 오류 - $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 추가 마이크로태스크 실행으로 비동기 작업 완료 보장 (타이머 생성 회피)
|
|
await Future.microtask(() {});
|
|
await Future.microtask(() {});
|
|
|
|
// dispose 완료 후 상태 로깅
|
|
await _logResourceState('dispose 완료');
|
|
|
|
// 구독 수 로깅 - 지연 없이 즉시 처리
|
|
if (kDebugMode) {
|
|
debugPrint('MemberService: dispose 완료 후 구독 수 - ${EventBus.activeSubscriptionCount}');
|
|
}
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
// 클럽 ID 설정
|
|
Future<void> setClubId(String clubId) async {
|
|
// 클럽 ID가 변경된 경우에만 처리
|
|
if (_clubId != clubId) {
|
|
_clubId = clubId;
|
|
|
|
// SharedPreferences에 저장
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(ApiConfig.clubIdKey, clubId);
|
|
|
|
notifyListeners();
|
|
|
|
// 새 클럽의 회원 목록 가져오기
|
|
if (_token != null) {
|
|
await fetchClubMembers();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 클럽의 모든 회원 가져오기
|
|
Future<void> fetchClubMembers() async {
|
|
print('fetchClubMembers 호출됨: token=${_token}');
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
// 클럽 ID 가져오기 - 메모리에 저장된 값을 우선 사용
|
|
String? clubId = _clubId;
|
|
|
|
// 메모리에 없으면 SharedPreferences에서 가져오기
|
|
if (clubId == null) {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
clubId = prefs.getString(ApiConfig.clubIdKey);
|
|
}
|
|
|
|
if (clubId == null) {
|
|
throw Exception('선택된 클럽이 없습니다');
|
|
}
|
|
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/members',
|
|
data: {'clubId': clubId},
|
|
);
|
|
|
|
print('회원 응답 데이터: $data');
|
|
|
|
// 응답 데이터가 리스트 형태로 직접 왔으므로 그대로 사용
|
|
if (data is List) {
|
|
final List<dynamic> membersData = data;
|
|
_members = membersData
|
|
.map((memberData) => Member.fromJson(memberData))
|
|
.toList();
|
|
print('변환된 회원 리스트 길이: ${_members.length}');
|
|
} else {
|
|
// 응답 데이터가 객체 형태로 왔을 경우 members 키를 통해 접근
|
|
final List<dynamic> membersData = data['members'] ?? [];
|
|
_members = membersData
|
|
.map((memberData) => Member.fromJson(memberData))
|
|
.toList();
|
|
print('변환된 회원 리스트 길이: ${_members.length}');
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('회원 목록을 불러오는데 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// ID로 회원 정보 가져오기
|
|
Future<Member> fetchMemberById(String memberId) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/members/detail',
|
|
data: {'memberId': memberId},
|
|
);
|
|
|
|
if (data['statusCode'] == 200) {
|
|
final memberData = data['member'];
|
|
return Member.fromJson(memberData);
|
|
} else {
|
|
throw Exception('회원 정보를 불러오는데 실패했습니다');
|
|
}
|
|
} catch (e) {
|
|
throw Exception('회원 정보를 불러오는데 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 회원 추가
|
|
Future<Member> addMember(Map<String, dynamic> memberData) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/members/add',
|
|
data: memberData,
|
|
);
|
|
|
|
// 백엔드 응답 처리: 직접 회원 객체가 반환되거나 {member: {...}} 형태로 반환될 수 있음
|
|
final newMember = data is Map && data.containsKey('member')
|
|
? Member.fromJson(data['member'])
|
|
: Member.fromJson(data);
|
|
|
|
_members.add(newMember);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return newMember;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('회원 추가에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 회원 정보 업데이트
|
|
Future<Member> updateMember(
|
|
String memberId,
|
|
Map<String, dynamic> updates,
|
|
) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/members/update',
|
|
data: {'clubId': _clubId, 'memberId': memberId, ...updates},
|
|
);
|
|
|
|
// 응답 데이터가 { member: {...} } 형태인지 또는 직접 회원 객체인지 확인
|
|
final Map<String, dynamic> memberData = data['member'] != null
|
|
? data['member'] as Map<String, dynamic>
|
|
: data as Map<String, dynamic>;
|
|
|
|
final updatedMember = Member.fromJson(memberData);
|
|
|
|
// 회원 목록 업데이트
|
|
final index = _members.indexWhere((member) => member.id == memberId);
|
|
if (index >= 0) {
|
|
_members[index] = updatedMember;
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return updatedMember;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('회원 정보 업데이트에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 회원 삭제 (또는 비활성화)
|
|
Future<bool> deleteMember(String memberId) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
await _apiService.post(
|
|
'${ApiConfig.clubs}/members/delete',
|
|
data: {'memberId': memberId, 'clubId': _clubId},
|
|
);
|
|
|
|
// 회원 목록에서 제거
|
|
_members.removeWhere((member) => member.id == memberId);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return true;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('회원 삭제에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 여러 회원 ID로 회원 정보 가져오기
|
|
Future<List<Member>> fetchMembersByIds(String clubId, List<String> memberIds) async {
|
|
if (_token == null) {
|
|
throw Exception('인증이 필요합니다');
|
|
}
|
|
|
|
if (memberIds.isEmpty) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
// 이미 로드된 회원 정보 중에서 필터링
|
|
if (_members.isNotEmpty && _clubId == clubId) {
|
|
final cachedMembers = _members.where((member) => memberIds.contains(member.id)).toList();
|
|
|
|
// 모든 회원 정보가 캐시에 있는 경우
|
|
if (cachedMembers.length == memberIds.length) {
|
|
return cachedMembers;
|
|
}
|
|
}
|
|
|
|
// API로 회원 정보 요청
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/members/batch',
|
|
data: {
|
|
'clubId': clubId,
|
|
'memberIds': memberIds,
|
|
},
|
|
);
|
|
|
|
if (data is List) {
|
|
return data.map((memberData) => Member.fromJson(memberData)).toList();
|
|
} else if (data['members'] != null && data['members'] is List) {
|
|
return (data['members'] as List).map((memberData) => Member.fromJson(memberData)).toList();
|
|
} else {
|
|
throw Exception('회원 정보 형식이 올바르지 않습니다');
|
|
}
|
|
} catch (e) {
|
|
throw Exception('회원 정보를 불러오는데 실패했습니다: $e');
|
|
}
|
|
}
|
|
}
|