Files
superport/lib/screens/login/controllers/login_controller_with_usecase.dart
JiWoong Sul 162fe08618
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled
refactor: Clean Architecture 적용 및 코드베이스 전면 리팩토링
## 주요 변경사항

### 아키텍처 개선
- Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리)
- Use Case 패턴 도입으로 비즈니스 로직 캡슐화
- Repository 패턴으로 데이터 접근 추상화
- 의존성 주입 구조 개선

### 상태 관리 최적화
- 모든 Controller에서 불필요한 상태 관리 로직 제거
- 페이지네이션 로직 통일 및 간소화
- 에러 처리 로직 개선 (에러 메시지 한글화)
- 로딩 상태 관리 최적화

### Mock 서비스 제거
- MockDataService 완전 제거
- 모든 화면을 실제 API 전용으로 전환
- 불필요한 Mock 관련 코드 정리

### UI/UX 개선
- Overview 화면 대시보드 기능 강화
- 라이선스 만료 알림 위젯 추가
- 사이드바 네비게이션 개선
- 일관된 UI 컴포넌트 사용

### 코드 품질
- 중복 코드 제거 및 함수 추출
- 파일별 책임 분리 명확화
- 테스트 코드 업데이트

## 영향 범위
- 모든 화면의 Controller 리팩토링
- API 통신 레이어 구조 개선
- 에러 처리 및 로깅 시스템 개선

## 향후 계획
- 단위 테스트 커버리지 확대
- 통합 테스트 시나리오 추가
- 성능 모니터링 도구 통합
2025-08-11 00:04:28 +09:00

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();
}
}