refactor: Clean Architecture 적용 및 코드베이스 전면 리팩토링
## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
This commit is contained in:
193
lib/screens/login/controllers/login_controller_with_usecase.dart
Normal file
193
lib/screens/login/controllers/login_controller_with_usecase.dart
Normal file
@@ -0,0 +1,193 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user