## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
193 lines
6.0 KiB
Dart
193 lines
6.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:dartz/dartz.dart';
|
|
import '../../../core/errors/failures.dart';
|
|
import '../../../domain/usecases/base_usecase.dart';
|
|
import '../../../domain/usecases/auth/login_usecase.dart';
|
|
import '../../../domain/usecases/auth/check_auth_status_usecase.dart';
|
|
import '../../../services/auth_service.dart';
|
|
import '../../../services/health_check_service.dart';
|
|
import '../../../di/injection_container.dart';
|
|
|
|
/// UseCase를 활용한 로그인 화면 컨트롤러
|
|
/// 비즈니스 로직을 UseCase로 분리하여 테스트 용이성과 재사용성 향상
|
|
class LoginControllerWithUseCase extends ChangeNotifier {
|
|
// UseCases
|
|
late final LoginUseCase _loginUseCase;
|
|
late final CheckAuthStatusUseCase _checkAuthStatusUseCase;
|
|
|
|
// Services
|
|
final HealthCheckService _healthCheckService = HealthCheckService();
|
|
|
|
// UI Controllers
|
|
final TextEditingController idController = TextEditingController();
|
|
final TextEditingController pwController = TextEditingController();
|
|
|
|
// Focus Nodes
|
|
final FocusNode idFocus = FocusNode();
|
|
final FocusNode pwFocus = FocusNode();
|
|
|
|
// State
|
|
bool saveId = false;
|
|
bool _isLoading = false;
|
|
String? _errorMessage;
|
|
|
|
// Getters
|
|
bool get isLoading => _isLoading;
|
|
String? get errorMessage => _errorMessage;
|
|
|
|
LoginControllerWithUseCase() {
|
|
// UseCase 초기화
|
|
final authService = inject<AuthService>();
|
|
_loginUseCase = LoginUseCase(authService);
|
|
_checkAuthStatusUseCase = CheckAuthStatusUseCase(authService);
|
|
|
|
// 초기 인증 상태 확인
|
|
_checkInitialAuthStatus();
|
|
}
|
|
|
|
/// 초기 인증 상태 확인
|
|
Future<void> _checkInitialAuthStatus() async {
|
|
final result = await _checkAuthStatusUseCase(const NoParams());
|
|
result.fold(
|
|
(failure) => debugPrint('인증 상태 확인 실패: ${failure.message}'),
|
|
(isAuthenticated) {
|
|
if (isAuthenticated) {
|
|
debugPrint('이미 로그인된 상태입니다.');
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 아이디 저장 체크박스 상태 변경
|
|
void setSaveId(bool value) {
|
|
saveId = value;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 에러 메시지 초기화
|
|
void clearError() {
|
|
_errorMessage = null;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 로그인 처리
|
|
Future<bool> login() async {
|
|
// 입력값 검증 (UI 레벨)
|
|
if (idController.text.trim().isEmpty) {
|
|
_errorMessage = '아이디 또는 이메일을 입력해주세요.';
|
|
notifyListeners();
|
|
return false;
|
|
}
|
|
|
|
if (pwController.text.isEmpty) {
|
|
_errorMessage = '비밀번호를 입력해주세요.';
|
|
notifyListeners();
|
|
return false;
|
|
}
|
|
|
|
// 로딩 시작
|
|
_isLoading = true;
|
|
_errorMessage = null;
|
|
notifyListeners();
|
|
|
|
// 입력값이 이메일인지 username인지 판단
|
|
final inputValue = idController.text.trim();
|
|
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
|
final isEmail = emailRegex.hasMatch(inputValue);
|
|
|
|
try {
|
|
// UseCase 실행
|
|
final params = LoginParams(
|
|
email: isEmail ? inputValue : '$inputValue@superport.kr', // username인 경우 임시 도메인 추가
|
|
password: pwController.text,
|
|
);
|
|
|
|
debugPrint('[LoginController] 로그인 시도: ${params.email}');
|
|
|
|
final result = await _loginUseCase(params).timeout(
|
|
const Duration(seconds: 10),
|
|
onTimeout: () async {
|
|
debugPrint('[LoginController] 로그인 요청 타임아웃');
|
|
return Left(NetworkFailure(
|
|
message: '요청 시간이 초과되었습니다. 네트워크 연결을 확인해주세요.',
|
|
));
|
|
},
|
|
);
|
|
|
|
return result.fold(
|
|
(failure) {
|
|
debugPrint('[LoginController] 로그인 실패: ${failure.message}');
|
|
|
|
// 실패 타입에 따른 메시지 처리
|
|
if (failure is ValidationFailure) {
|
|
_errorMessage = failure.message;
|
|
} else if (failure is AuthenticationFailure) {
|
|
_errorMessage = '이메일 또는 비밀번호가 올바르지 않습니다.';
|
|
} else if (failure is NetworkFailure) {
|
|
_errorMessage = '네트워크 연결을 확인해주세요.';
|
|
} else if (failure is ServerFailure) {
|
|
_errorMessage = '서버 오류가 발생했습니다.\n잠시 후 다시 시도해주세요.';
|
|
} else {
|
|
_errorMessage = failure.message;
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
return false;
|
|
},
|
|
(loginResponse) {
|
|
debugPrint('[LoginController] 로그인 성공');
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
return true;
|
|
},
|
|
);
|
|
} catch (e) {
|
|
debugPrint('[LoginController] 예상치 못한 에러: $e');
|
|
_errorMessage = '로그인 중 오류가 발생했습니다.';
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 헬스체크 실행
|
|
Future<bool> performHealthCheck() async {
|
|
debugPrint('[LoginController] 헬스체크 시작');
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final healthResult = await _healthCheckService.checkHealth();
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
// HealthCheckService가 Map을 반환하는 경우 적절히 변환
|
|
final isHealthy = healthResult is bool ? healthResult :
|
|
(healthResult is Map && healthResult['status'] == 'healthy');
|
|
|
|
if (isHealthy == false) {
|
|
_errorMessage = '서버와 연결할 수 없습니다.\n잠시 후 다시 시도해주세요.';
|
|
notifyListeners();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} catch (e) {
|
|
debugPrint('[LoginController] 헬스체크 실패: $e');
|
|
_errorMessage = '서버 상태 확인 중 오류가 발생했습니다.';
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
idController.dispose();
|
|
pwController.dispose();
|
|
idFocus.dispose();
|
|
pwFocus.dispose();
|
|
super.dispose();
|
|
}
|
|
} |