Skip to content

맵시 PRD에 맞는 기능 구현 확인 점검 필요#16

Merged
Cassiiopeia merged 9 commits intomainfrom
20260128_#14_맵시_PRD에_맞는_기능_구현_확인_점검_필요
Feb 4, 2026

Hidden character warning

The head ref may contain hidden characters: "20260128_#14_\ub9f5\uc2dc_PRD\uc5d0_\ub9de\ub294_\uae30\ub2a5_\uad6c\ud604_\ud655\uc778_\uc810\uac80_\ud544\uc694"
Merged

맵시 PRD에 맞는 기능 구현 확인 점검 필요#16
Cassiiopeia merged 9 commits intomainfrom
20260128_#14_맵시_PRD에_맞는_기능_구현_확인_점검_필요

Conversation

@Cassiiopeia
Copy link
Contributor

@Cassiiopeia Cassiiopeia commented Jan 28, 2026

…com//issues/14

✨ 변경 사항


✅ 테스트


  • 수동 테스트 완료
  • 테스트 코드 완료

Summary by CodeRabbit

  • 새로운 기능

    • 백엔드 연동 로그인(토큰 자동 갱신 포함) 및 자동 로그인 지원
    • 단계별 온보딩 흐름 도입(약관 → 생년월일 → 성별 → 닉네임) 및 관련 UI/네비게이션
    • 닉네임 실시간 중복 확인, 기기·푸시 정보 연동, 회원 탈퇴 기능 추가
  • 버그 수정

    • 로그인 후 온보딩/홈으로의 자동 이동 로직 안정화

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

백엔드 API 통합, Dio 기반 네트워크 클라이언트와 인터셉터(인증/토큰 갱신/에러), 보안 토큰 저장소, 인증·온보딩 리포지토리 및 Riverpod 제공자들, 온보딩 4단계 UI와 라우터 경로를 추가합니다.

Changes

Cohort / File(s) Summary
Git / Xcode / macOS
​.gitignore, macos/Runner.xcworkspace/contents.xcworkspacedata, macos/Runner.xcodeproj/project.pbxproj, ios/Runner.xcodeproj/project.pbxproj, ios/com.elipair.mapsy.share/Info.plist
CocoaPods 산출물 .gitignore 추가, macOS Pods 프로젝트/빌드 스크립트 및 파일 참조 추가, iOS 빌드 설정에서 Info.plist 자동생성 비활성화 및 iOS 공유 확장 Info.plist 버전 고정.
API·색상 상수
lib/core/constants/api_endpoints.dart, lib/core/constants/app_colors.dart
기본 API/WS URL을 .env로 로드하도록 변경(기본값 wss/https), 다수의 REST 엔드포인트 상수 추가, AppColors에 gray50..gray900 그레이 스케일 상수 추가.
네트워크 클라이언트 및 제공자
lib/core/network/api_client.dart, lib/core/network/api_client.g.dart
Dio 전역 클라이언트 및 별도 refreshDio 제공자 추가(타임아웃·헤더 설정 포함), 인터셉터 체인(로그·Auth·TokenRefresh·Error) 구성.
인터셉터/오류 변환
lib/core/network/auth_interceptor.dart, lib/core/network/token_refresh_interceptor.dart, lib/core/network/error_interceptor.dart
AuthInterceptor: 공용 경로를 제외한 요청에 Bearer 토큰 주입. TokenRefreshInterceptor: 401 시 refresh 흐름(큐잉·재시도·로그아웃 처리). ErrorInterceptor: DioException → AppException 매핑.
토큰 저장소 및 제공자
lib/core/network/token_storage.dart, lib/core/network/token_storage.g.dart
FlutterSecureStorage 기반 TokenStorage 및 onboarding 상태 관리, save/delete/hasTokens 등 API와 Riverpod 제공자 추가.
서비스 제공자(기기·FCM)
lib/core/services/device/device_info_service.dart, lib/core/services/device/device_info_service.g.dart, lib/core/services/fcm/firebase_messaging_service.dart, lib/core/services/fcm/firebase_messaging_service.g.dart
DeviceInfoService와 FirebaseMessagingService에 Riverpod 제공자 추가 및 DeviceInfo DTO와 getDeviceInfo 구현(기기 id/타입/OS/실기기 여부 포함).
인증 데이터·모델·리포지토리
lib/features/auth/data/datasources/auth_remote_datasource.dart, lib/features/auth/data/*.{reissue,sign_in}*.{dart,freezed.dart,g.dart}, lib/features/auth/data/repositories/auth_repository_impl.dart, lib/features/auth/data/* .g.dart
원격 인증 API 호출(signIn, reissue, logout, withdraw) 구현, SignIn/Reissue 요청·응답 Freezed 모델 추가, AuthRepositoryImpl 구현 및 Riverpod 제공자.
인증 도메인·프레젠테이션
lib/features/auth/domain/*, lib/features/auth/presentation/providers/auth_provider.dart, lib/features/auth/presentation/pages/login_page.dart, lib/features/auth/presentation/pages/splash_page.dart
OnboardingStep 열거형 및 AuthRepository 인터페이스 추가. AuthNotifier에 Firebase 로그인 후 백엔드 로그인 통합, 자동로그인·온보딩 검사·탈퇴·로그아웃 로직 추가. Login/Splash 페이지에서 백엔드 응답에 따른 온보딩 리다이렉트 구현.
온보딩 데이터·모델·리포지토리
lib/features/onboarding/data/datasources/onboarding_remote_datasource.dart, lib/features/onboarding/data/models/*_request*.{dart,freezed.dart,g.dart}, lib/features/onboarding/data/repositories/onboarding_repository_impl.dart, lib/features/onboarding/data/* .g.dart
온보딩 원격 데이터소스(약관/생년월일/성별/프로필·닉네임 체크), 관련 Freezed 모델들, OnboardingRepositoryImpl 및 제공자 구현(단계 전환시 로컬 onboarding 상태 저장).
온보딩 도메인·프레젠테이션
lib/features/onboarding/domain/repositories/onboarding_repository.dart, lib/features/onboarding/presentation/providers/onboarding_provider.dart, lib/features/onboarding/presentation/providers/*.{freezed.dart,g.dart}
OnboardingRepository 인터페이스, OnboardingState 및 OnboardingNotifier 추가(단계별 제출, 닉네임 검증, 상태/에러 관리, 입력 검증 규칙 포함).
온보딩 UI 및 위젯
lib/features/onboarding/presentation/pages/{terms,birth_date,gender,nickname}_step_page.dart, lib/features/onboarding/presentation/widgets/onboarding_button.dart, lib/features/onboarding/presentation/widgets/step_indicator.dart
4단계 온보딩 페이지(약관→생년월일→성별→닉네임)와 재사용 버튼/스텝 인디케이터 위젯 추가(닉네임 실시간 검증·debounce 포함).
라우팅
lib/router/route_paths.dart, lib/router/app_router.dart
온보딩 경로 4개(RoutePaths) 및 라우터에 온보딩 라우트·리다이렉트 로직 연결.
워크플로우 권한
.github/workflows/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml, .github/workflows/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml
워크플로우에 permissions 블록 추가(contents: read, issues/write, pull-requests/write).

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Client as Flutter 클라이언트
    participant Firebase as Firebase Auth
    participant Server as 백엔드 API
    participant Storage as TokenStorage

    User->>Client: Google/Apple 로그인 시도
    Client->>Firebase: ID 토큰 요청
    Firebase-->>Client: ID 토큰 반환
    Client->>Client: FCM 토큰·기기정보 수집
    Client->>Server: POST /api/auth/sign-in (idToken, fcmToken, deviceInfo)
    Server-->>Client: accessToken, refreshToken, requiresOnboarding
    Client->>Storage: accessToken/refreshToken 및 온보딩 상태 저장
    alt requiresOnboarding == true
        Client->>Client: 온보딩 단계로 리다이렉트
    else
        Client->>Client: 홈으로 리다이렉트
    end
Loading
sequenceDiagram
    participant Client as 클라이언트
    participant Dio as Dio
    participant AuthInt as AuthInterceptor
    participant RefreshInt as TokenRefreshInterceptor
    participant ErrorInt as ErrorInterceptor
    participant Server as 백엔드
    participant Storage as TokenStorage

    Client->>Dio: API 요청
    Dio->>AuthInt: onRequest
    AuthInt->>Storage: getAccessToken()
    Storage-->>AuthInt: token
    AuthInt->>Dio: Authorization 헤더 추가
    Dio->>Server: 요청 전송
    Server-->>Dio: 401 응답
    Dio->>RefreshInt: onError(401)
    RefreshInt->>Storage: getRefreshToken()
    RefreshInt->>Dio: refreshDio POST /api/auth/reissue
    Dio-->>RefreshInt: 새로운 토큰
    RefreshInt->>Storage: saveTokens()
    RefreshInt->>Dio: 원래 요청 재시도
    Dio->>Server: 재시도 요청
    Server-->>Dio: 200 응답
    Dio->>ErrorInt: onResponse/onError 처리
    ErrorInt-->>Client: 최종 응답/예외 변환 반환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive Pull request title is vague and non-descriptive. Uses a generic phrase about 'checking if features match PRD' without clearly indicating the main changes. The title does not convey the actual implementation scope (authentication, onboarding, API integration, CocoaPods setup). Replace with a concise, specific title that highlights the primary change, such as 'feat: Implement authentication and onboarding flows with API integration' or similar that accurately summarizes the main deliverable.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 20260128_#14_맵시_PRD에_맞는_기능_구현_확인_점검_필요

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In `@lib/core/services/fcm/firebase_messaging_service.dart`:
- Around line 13-17: The provider for FirebaseMessagingService is currently
auto-disposable and may drop listeners; update the provider to be kept alive for
the app lifetime by marking the generated provider as non-disposable—e.g.,
change the annotation for firebaseMessagingService to keep the provider alive
(add keepAlive: true to the `@riverpod` annotation or call ref.keepAlive() inside
firebaseMessagingService) so FirebaseMessagingService.instance(), its persistent
listeners (onMessage, onMessageOpenedApp, onTokenRefresh) and the
contentCompletedStream broadcast are not prematurely disposed.

In `@lib/features/auth/data/repositories/auth_repository_impl.dart`:
- Around line 124-128: The method getCurrentOnboardingStep references the
OnboardingStep type and OnboardingStep.fromString but the class isn't imported;
add an import for the OnboardingStep entity (from
../../domain/entities/onboarding_step.dart) at the top of the file so
getCurrentOnboardingStep and OnboardingStep.fromString resolve and the file
compiles.
- Line 13: Add the missing import for the OnboardingStep entity so references to
OnboardingStep and OnboardingStep.fromString resolve; update the top of
auth_repository_impl.dart (and auth_repository.dart where OnboardingStep? is
used in the return type) to import the onboarding_step.dart module (the file
that defines OnboardingStep) so the compiler recognizes OnboardingStep and its
fromString method while keeping the existing implements AuthRepository usage
intact.

In `@lib/features/auth/presentation/providers/auth_provider.dart`:
- Line 13: Remove the unused import
'../../domain/repositories/auth_repository.dart' and add the missing import for
OnboardingStep by adding import '../../domain/entities/onboarding_step.dart'; so
that the OnboardingStep type used around the AuthProvider (where OnboardingStep
is referenced at line ~253) resolves correctly.

In `@lib/features/onboarding/presentation/pages/terms_step_page.dart`:
- Around line 206-214: The code uses AppColors.withValues(alpha:) in
terms_step_page.dart which requires Flutter 3.27+, so update the package
environment to explicitly require that Flutter minimum: open pubspec.yaml and
add a flutter entry under environment (e.g. flutter: ">=3.27.0") alongside the
existing sdk line (^3.9.2); this makes the dependency on the newer Flutter API
explicit and avoids build/runtime mismatches for the withValues(alpha:) call.

In `@lib/features/onboarding/presentation/providers/onboarding_provider.dart`:
- Line 10: Remove the unused import of profile_request.dart from
onboarding_provider.dart; locate the import statement "import
'../../data/models/profile_request.dart';" at the top of the file and delete it
(or replace it with a used symbol if that model is needed elsewhere), ensuring
no references in the file to ProfileRequest remain before committing the change.

In `@lib/features/onboarding/presentation/widgets/step_indicator.dart`:
- Around line 56-92: Clamp the computed progress before passing it to the
LinearProgressIndicator: in the build method where progress is calculated from
currentStep.index and totalSteps (when currentStep can be
OnboardingStep.completed), ensure progress is constrained to the 0.0–1.0 range
(e.g., use a clamp or min/max on the progress value) and then use that clamped
value for the LinearProgressIndicator's value to avoid values >1.0.

In `@lib/router/app_router.dart`:
- Around line 112-118: The onboarding redirect block currently only allows
onboardingPaths and never redirects users who need onboarding; update the logic
in the router (near onboardingPaths and currentPath) to read the user's
onboarding-needed state (e.g., an isOnboardingRequired / needsOnboarding flag
from your auth/session state or a getOnboardingStatus() helper) and when
currentPath is not in onboardingPaths and the user needs onboarding, return a
redirect to the appropriate onboarding route (e.g., the first entry in
onboardingPaths or a named onboarding route) instead of null; ensure you
reference onboardingPaths, currentPath and the onboarding flag/getter you use so
the router sends users to onboarding when required.
- Around line 24-25: 현재 파일에 선언된 import '../core/network/token_storage.dart'가
사용되지 않아 CI 경고가 발생하므로 해당 미사용 import 문(import
'../core/network/token_storage.dart';)을 제거해 주세요; 관련 코드에서 TokenStorage 참조가 없음을
확인하고, 필요 없다면 import 라인을 삭제하여 불필요한 의존성을 제거하면 됩니다.

In `@macos/Runner.xcodeproj/project.pbxproj`:
- Around line 422-465: 빌드 단계 "[CP] Check Pods Manifest.lock"
(PBXShellScriptBuildPhase) 실패는 macOS 타겟의 Podfile.lock이 없기 때문입니다; 해결하려면 CI에 macOS
프로젝트 디렉터리로 이동해 pod install을 실행하도록 워크플로우를 추가(예: cd <macOS project dir> && pod
install)하거나 macOS용 Podfile.lock을 저장소에 커밋해 체크아웃 시 항상 존재하도록 만드세요.
🧹 Nitpick comments (20)
ios/com.elipair.mapsy.share/Info.plist (1)

19-22: 확장 버전 고정값은 릴리스 동기화 부담을 만듭니다.
앱 버전이 갱신될 때 Share Extension도 수동으로 맞춰야 해서 누락 위험이 있습니다. 확장 타깃에 값이 정의돼 있다면 MARKETING_VERSION/CURRENT_PROJECT_VERSION과 연결해 자동 동기화를 고려해 주세요.

♻️ 제안 변경
-	<string>1.0</string>
+	<string>$(MARKETING_VERSION)</string>
...
-	<string>1</string>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
lib/core/network/error_interceptor.dart (1)

27-34: DioException 생성 시 원본 stackTrace를 명시적으로 전달해야 합니다.

DioException을 생성자로 만들 때 stackTrace 파라미터를 지정하지 않으면 원본 err.stackTrace가 유실되어 디버깅과 크래시 분석에 영향을 줍니다.

♻️ 제안 변경
    handler.next(DioException(
      requestOptions: err.requestOptions,
      response: err.response,
      type: err.type,
      error: exception,
      message: exception.message,
+     stackTrace: err.stackTrace,
    ));

또는 copyWith()를 사용하면 stackTrace가 자동으로 보존됩니다:

handler.next(err.copyWith(
  error: exception,
  message: exception.message,
));
lib/core/services/device/device_info_service.dart (1)

213-227: 성능 최적화: 병렬 실행을 고려해 보세요.

현재 디바이스 정보를 순차적으로 가져오고 있습니다. Future.wait를 사용하면 병렬로 실행하여 응답 시간을 단축할 수 있습니다.

♻️ 병렬 실행 제안
  Future<DeviceInfo> getDeviceInfo() async {
-   final deviceName = await getDeviceName();
-   final deviceType = getDeviceType();
-   final deviceId = await DeviceIdManager.getOrCreateDeviceId();
-   final osVersion = await getOSVersion();
-   final physical = await isPhysicalDevice();
+   final results = await Future.wait([
+     getDeviceName(),
+     DeviceIdManager.getOrCreateDeviceId(),
+     getOSVersion(),
+     isPhysicalDevice(),
+   ]);
+   
+   final deviceName = results[0] as String;
+   final deviceType = getDeviceType();
+   final deviceId = results[1] as String;
+   final osVersion = results[2] as String;
+   final physical = results[3] as bool;

    return DeviceInfo(
      deviceName: deviceName,
      deviceType: deviceType,
      deviceId: deviceId,
      osVersion: osVersion,
      isPhysicalDevice: physical,
    );
  }
lib/features/auth/domain/entities/onboarding_step.dart (2)

21-58: 코드 중복 감소를 고려해 보세요.

fromStringfromStringNullable 메서드에 동일한 switch 로직이 중복되어 있습니다. 내부 헬퍼 메서드로 추출하면 유지보수성이 향상됩니다.

♻️ 중복 제거 제안
+ static OnboardingStep? _parseString(String value) {
+   switch (value.toUpperCase()) {
+     case 'TERMS':
+       return OnboardingStep.terms;
+     case 'BIRTH_DATE':
+       return OnboardingStep.birthDate;
+     case 'GENDER':
+       return OnboardingStep.gender;
+     case 'NICKNAME':
+       return OnboardingStep.nickname;
+     case 'COMPLETED':
+       return OnboardingStep.completed;
+     default:
+       return null;
+   }
+ }
+
  static OnboardingStep fromString(String? value) {
-   if (value == null) return OnboardingStep.terms;
-   switch (value.toUpperCase()) {
-     case 'TERMS':
-       return OnboardingStep.terms;
-     // ... 생략
-     default:
-       return OnboardingStep.terms;
-   }
+   if (value == null) return OnboardingStep.terms;
+   return _parseString(value) ?? OnboardingStep.terms;
  }

  static OnboardingStep? fromStringNullable(String? value) {
-   if (value == null) return null;
-   switch (value.toUpperCase()) {
-     // ... 생략
-   }
+   if (value == null) return null;
+   return _parseString(value);
  }

92-106: enum.index 활용을 고려해 보세요.

stepIndex getter는 enum의 기본 index 속성을 활용하면 더 간단해집니다.

♻️ 간소화 제안
  /// 단계 인덱스 (0-4)
- int get stepIndex {
-   switch (this) {
-     case OnboardingStep.terms:
-       return 0;
-     case OnboardingStep.birthDate:
-       return 1;
-     case OnboardingStep.gender:
-       return 2;
-     case OnboardingStep.nickname:
-       return 3;
-     case OnboardingStep.completed:
-       return 4;
-   }
- }
+ int get stepIndex => index;
lib/features/onboarding/presentation/widgets/onboarding_button.dart (1)

39-46: 로딩 인디케이터 크기 일관성 확인

width: 24.wheight: 24.h가 화면 비율에 따라 다르게 계산될 수 있어 정사각형이 아닌 타원형 인디케이터가 될 수 있습니다. 일관된 크기를 원하시면 .r 사용을 고려해 보세요.

♻️ 정사각형 크기 보장
            child: SizedBox(
-               width: 24.w,
-               height: 24.h,
+               width: 24.r,
+               height: 24.r,
                child: const CircularProgressIndicator(
lib/core/network/token_storage.dart (1)

111-122: 순차적 쓰기 작업을 병렬로 개선할 수 있습니다.

saveTokensclearTokens에서는 Future.wait을 사용하여 병렬로 처리하지만, 여기서는 onboardingStep이 있을 때 순차적으로 작성됩니다. 일관성을 위해 병렬 처리를 고려해보세요.

♻️ 제안된 수정
 Future<void> saveOnboardingState({
   required bool requiresOnboarding,
   String? onboardingStep,
 }) async {
-  await _storage.write(
-    key: _requiresOnboardingKey,
-    value: requiresOnboarding.toString(),
-  );
-  if (onboardingStep != null) {
-    await _storage.write(key: _onboardingStepKey, value: onboardingStep);
-  }
+  final futures = <Future<void>>[
+    _storage.write(
+      key: _requiresOnboardingKey,
+      value: requiresOnboarding.toString(),
+    ),
+  ];
+  if (onboardingStep != null) {
+    futures.add(_storage.write(key: _onboardingStepKey, value: onboardingStep));
+  }
+  await Future.wait(futures);
 }
lib/core/network/auth_interceptor.dart (2)

53-56: contains() 사용 시 잘못된 매칭 가능성이 있습니다.

현재 path.contains(publicPath) 방식은 /api/auth/sign-in-admin 같은 경로도 /api/auth/sign-in과 매칭됩니다. 정확한 경로 매칭이 필요하다면 수정을 고려해보세요.

♻️ 제안된 수정
 bool _isPublicPath(String path) {
-  return _publicPaths.any((publicPath) => path.contains(publicPath));
+  return _publicPaths.any((publicPath) => 
+    path == publicPath || path.startsWith('$publicPath?') || path.startsWith('$publicPath/'));
 }

또는 더 간단하게 정확한 매칭만 원하면:

return _publicPaths.contains(path);

32-33: 프로덕션 환경에서 debugPrint 제거를 고려해보세요.

토큰 관련 로그는 보안상 민감할 수 있습니다. 현재 토큰 값 자체는 로그에 출력되지 않아 괜찮지만, 프로덕션에서는 이러한 디버그 로그를 비활성화하거나 로그 레벨을 조정하는 것이 좋습니다.

Also applies to: 42-44

lib/core/constants/api_endpoints.dart (1)

78-88: 동적 경로 빌더에서 입력값 검증을 고려해보세요.

placeId와 같은 사용자 입력이 경로에 직접 삽입됩니다. 현재는 문제가 없어 보이지만, 방어적으로 빈 문자열이나 특수 문자 검증을 추가하면 더 안전합니다.

♻️ 예시
static String placeDetail(String placeId) {
  assert(placeId.isNotEmpty, 'placeId must not be empty');
  return '/api/place/$placeId';
}
lib/features/onboarding/presentation/pages/birth_date_step_page.dart (2)

27-29: 윤년을 고려하지 않은 날짜 계산

Duration(days: 365 * 20)은 윤년을 고려하지 않아 약 5일 정도 오차가 발생할 수 있습니다. 기본값 표시 목적으로는 큰 문제가 아니지만, 더 정확한 계산을 원하시면 아래와 같이 수정할 수 있습니다.

♻️ 제안하는 수정
  `@override`
  void initState() {
    super.initState();
    // 기본값: 20년 전
-   _selectedDate = DateTime.now().subtract(const Duration(days: 365 * 20));
+   final now = DateTime.now();
+   _selectedDate = DateTime(now.year - 20, now.month, now.day);
  }

133-136: 최대 날짜 범위 엣지 케이스

maxDateDateTime(now.year - 10)으로 설정되어 해당 연도의 1월 1일이 됩니다. 예를 들어 현재가 2026년 7월이면, 2016년 7월에 태어난 만 10세 사용자의 생일을 선택할 수 없습니다.

♻️ 제안하는 수정
  void _showDatePicker(BuildContext context, OnboardingNotifier notifier) {
    final now = DateTime.now();
    final minDate = DateTime(now.year - 100);
-   final maxDate = DateTime(now.year - 10); // 최소 10세 이상
+   final maxDate = DateTime(now.year - 10, now.month, now.day); // 최소 10세 이상
lib/core/network/api_client.dart (1)

38-51: 디버그 로그에 민감정보 노출 가능성 최소화 권장.

LogInterceptor가 요청/응답 바디를 출력하도록 설정되어 있어 인증/토큰 응답이 로그에 남을 수 있습니다. 디버그 환경이라도 최소화 또는 마스킹을 권장합니다.

🔐 최소 로그 예시
       LogInterceptor(
         requestHeader: true,
-        requestBody: true,
+        requestBody: false,
         responseHeader: false,
-        responseBody: true,
+        responseBody: false,
         error: true,
         logPrint: (obj) => debugPrint('📡 $obj'),
       ),
lib/features/onboarding/presentation/pages/terms_step_page.dart (1)

170-188: TODO 주석 확인 필요

_getTermsContent에 플레이스홀더 약관 내용이 있습니다. 배포 전 실제 약관 내용으로 교체가 필요합니다.

실제 약관 내용을 별도 파일이나 원격 설정에서 로드하도록 구현하는 것을 도와드릴까요? 새 이슈를 열어 이 작업을 추적할 수도 있습니다.

lib/core/network/token_refresh_interceptor.dart (2)

143-152: 대기 요청 재시도 시 에러 처리 개선 권장

_retryPendingRequests에서 개별 요청 실패가 다른 대기 요청에 영향을 주지 않도록 try-catch를 추가하는 것이 좋습니다.

♻️ 개선 제안
  void _retryPendingRequests() {
+   final requests = List.of(_pendingRequests);
+   _pendingRequests.clear();
-   for (final pending in _pendingRequests) {
+   for (final pending in requests) {
      _retryRequest(pending.options).then(
        (response) => pending.handler.resolve(response),
-       onError: (error) => pending.handler.reject(error as DioException),
+       onError: (error) {
+         if (error is DioException) {
+           pending.handler.reject(error);
+         } else {
+           pending.handler.reject(DioException(
+             requestOptions: pending.options,
+             error: error,
+           ));
+         }
+       },
      );
    }
-   _pendingRequests.clear();
  }

162-174: TODO: 로그인 화면 리다이렉트 미구현

토큰 갱신 실패 시 로그인 화면으로 리다이렉트하는 로직이 TODO로 남아있습니다. 사용자 경험상 중요한 부분이므로 구현이 필요합니다.

GoRouter를 사용한 리다이렉트 구현을 도와드릴까요? 아니면 이 작업을 추적할 새 이슈를 열어드릴까요?

lib/features/onboarding/domain/repositories/onboarding_repository.dart (1)

1-2: Clean Architecture 의존성 방향 위반

Domain 레이어에서 Data 레이어의 모델(gender_request.dart, profile_request.dart)을 import하고 있습니다. Clean Architecture에서 Domain은 가장 내부 레이어로, 외부 레이어(Data)에 의존하면 안 됩니다.

Gender enum과 CheckNameResponse를 Domain 레이어에 정의하고, Data 레이어에서 Domain을 참조하도록 변경하는 것을 권장합니다.

♻️ 구조 개선 제안
  1. lib/features/onboarding/domain/entities/에 엔티티 정의:
// lib/features/onboarding/domain/entities/gender.dart
enum Gender { male, female, other }

// lib/features/onboarding/domain/entities/check_name_result.dart
class CheckNameResult {
  final bool available;
  final String? message;
  
  const CheckNameResult({required this.available, this.message});
}
  1. Repository에서 domain 엔티티 사용:
-import '../../data/models/gender_request.dart';
-import '../../data/models/profile_request.dart';
+import '../entities/gender.dart';
+import '../entities/check_name_result.dart';

abstract class OnboardingRepository {
  // ...
  Future<void> submitGender(Gender gender);
-  Future<CheckNameResponse> checkName(String name);
+  Future<CheckNameResult> checkName(String name);
  // ...
}
lib/features/auth/domain/repositories/auth_repository.dart (1)

1-23: 도메인 레이어가 데이터 레이어 DTO에 의존하고 있습니다.

AuthRepository가 data 레이어의 SignInResponse에 직접 의존해 경계가 흐려집니다. 도메인 엔티티로 승격하거나 매핑 계층을 두는 방향을 검토해주세요.

lib/features/onboarding/data/repositories/onboarding_repository_impl.dart (1)

46-50: 온보딩 단계 문자열에 상수 사용을 권장합니다.

'BIRTH_DATE', 'GENDER', 'NICKNAME', 'COMPLETED' 등의 하드코딩된 문자열이 여러 곳에서 사용됩니다. 오타 방지와 유지보수성을 위해 상수나 enum으로 정의하는 것이 좋습니다.

♻️ 상수 정의 예시
// 예: lib/core/constants/onboarding_steps.dart
class OnboardingSteps {
  OnboardingSteps._();
  
  static const String terms = 'TERMS';
  static const String birthDate = 'BIRTH_DATE';
  static const String gender = 'GENDER';
  static const String nickname = 'NICKNAME';
  static const String completed = 'COMPLETED';
}
lib/features/auth/data/models/sign_in_response.freezed.dart (1)

215-218: 보안 고려사항: toString()에서 민감한 토큰 노출 가능성

자동 생성된 toString() 메서드가 accessTokenrefreshToken을 포함하고 있습니다. 이 객체가 로그에 출력될 경우 민감한 인증 토큰이 노출될 수 있습니다.

소스 파일(sign_in_response.dart)에서 커스텀 toString() 메서드를 오버라이드하여 토큰 값을 마스킹하는 것을 고려해 보세요:

`@override`
String toString() => 'SignInResponse(accessToken: [REDACTED], refreshToken: [REDACTED], ...)';

Comment on lines +13 to +17
/// FirebaseMessagingService Provider
@riverpod
FirebaseMessagingService firebaseMessagingService(Ref ref) {
return FirebaseMessagingService.instance();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🌐 Web query:

riverpod @riverpod keepAlive default behavior autoDispose

💡 Result:

With Riverpod code-generation (@riverpod / @Riverpod):

  • Default behavior: providers are auto-disposed when they have no listeners for a full frame (i.e., state is destroyed after it stops being listened to). (riverpod.dev)
  • keepAlive default: @riverpod is effectively the same as @Riverpod(keepAlive: false)autoDispose is ON by default. (riverpod.dev)
  • Disable autoDispose: use @Riverpod(keepAlive: true) to keep the provider alive even when it has no listeners. (riverpod.dev)

Also related (different thing): inside an auto-disposed provider you can call ref.keepAlive() to fine-tune/extend how long state is kept after listeners disappear. (riverpod.dev)

Citations:


🏁 Script executed:

# Find usages of firebaseMessagingService provider to understand listener patterns
rg "firebaseMessagingService" --type dart -B 2 -A 5

Repository: MapSee-Lab/MapSy-FE

Length of output: 3408


🏁 Script executed:

# Check FirebaseMessagingService implementation to understand if it maintains persistent listeners
fd "firebase_messaging_service\.dart$" --type f

Repository: MapSee-Lab/MapSy-FE

Length of output: 116


🏁 Script executed:

# Search for watch patterns or persistent listener usage of FCM service
rg "watch.*firebaseMessagingService|firebaseMessagingService.*watch|\.listen|onMessage" --type dart -B 2 -A 3

Repository: MapSee-Lab/MapSy-FE

Length of output: 4166


🏁 Script executed:

# Check the FirebaseMessagingService implementation to see if it maintains persistent state/listeners
head -80 lib/core/services/fcm/firebase_messaging_service.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 2477


앱 수명 동안 FCM 리스너 유지를 위해 keepAlive 옵션 필수 고려.

이 서비스는 FirebaseMessaging.onMessage, onMessageOpenedApp, onTokenRefresh 등 지속적인 리스너와 contentCompletedStream broadcast를 관리합니다. 기본 자동 dispose 동작으로 인해 활성 listener가 없을 때 이들 리스너가 의도치 않게 해제될 수 있습니다. FCM 메시지 수신을 보장하려면 keepAlive: true를 명시하는 것이 필수적입니다.

♻️ 권장 수정
-@riverpod
-FirebaseMessagingService firebaseMessagingService(Ref ref) {
+@riverpod(keepAlive: true)
+FirebaseMessagingService firebaseMessagingService(Ref ref) {
   return FirebaseMessagingService.instance();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// FirebaseMessagingService Provider
@riverpod
FirebaseMessagingService firebaseMessagingService(Ref ref) {
return FirebaseMessagingService.instance();
}
/// FirebaseMessagingService Provider
`@riverpod`(keepAlive: true)
FirebaseMessagingService firebaseMessagingService(Ref ref) {
return FirebaseMessagingService.instance();
}
🤖 Prompt for AI Agents
In `@lib/core/services/fcm/firebase_messaging_service.dart` around lines 13 - 17,
The provider for FirebaseMessagingService is currently auto-disposable and may
drop listeners; update the provider to be kept alive for the app lifetime by
marking the generated provider as non-disposable—e.g., change the annotation for
firebaseMessagingService to keep the provider alive (add keepAlive: true to the
`@riverpod` annotation or call ref.keepAlive() inside firebaseMessagingService) so
FirebaseMessagingService.instance(), its persistent listeners (onMessage,
onMessageOpenedApp, onTokenRefresh) and the contentCompletedStream broadcast are
not prematurely disposed.

import '../models/reissue_request.dart';

part 'auth_repository_impl.g.dart';

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the auth_repository_impl.dart file
fd -e dart "auth_repository_impl.dart" -x cat -n {}

Repository: MapSee-Lab/MapSy-FE

Length of output: 4653


🏁 Script executed:

# Check where AuthRepository is exported from
rg -n "export.*auth_repository" --type dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# Check if AuthRepository is re-exported from a barrel file
fd -e dart "\.dart$" lib/features/auth -x grep -l "export.*auth_repository"

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# Check the auth_repository.dart file
fd -e dart "auth_repository.dart" -x cat -n {}

Repository: MapSee-Lab/MapSy-FE

Length of output: 1504


🏁 Script executed:

# Find where OnboardingStep is defined
rg -n "class OnboardingStep\|enum OnboardingStep" --type dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# Check where OnboardingStep should be imported from
rg -n "export.*OnboardingStep" --type dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# Check if OnboardingStep is imported in any auth files
rg -n "import.*onboarding" --type dart lib/features/auth/

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# Search for OnboardingStep more broadly without type restriction
rg -n "OnboardingStep" --max-count 20

Repository: MapSee-Lab/MapSy-FE

Length of output: 6814


🏁 Script executed:

# Check the structure of lib/features to understand organization
fd -type f -name "*.dart" lib/features/ | head -30

Repository: MapSee-Lab/MapSy-FE

Length of output: 233


🏁 Script executed:

# Check if auth_repository.dart imports OnboardingStep
cat -n lib/features/auth/domain/repositories/auth_repository.dart | head -10

Repository: MapSee-Lab/MapSy-FE

Length of output: 382


🏁 Script executed:

# Check the onboarding_step.dart file to see full definition
cat -n lib/features/auth/domain/entities/onboarding_step.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 3911


🏁 Script executed:

# Verify the complete auth_repository_impl.dart to see all imports
head -15 lib/features/auth/data/repositories/auth_repository_impl.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 589


auth_repository.dart import는 실제로 사용 중입니다

파이프라인 경고는 잘못되었습니다. line 6의 auth_repository.dart import는 line 27의 implements AuthRepository에서 사용됩니다.

실제 누락된 import: OnboardingStep

auth_repository_impl.dart의 line 125-128에서 OnboardingStep.fromString()을 사용하지만 import가 없습니다. 또한 auth_repository.dart의 line 49도 OnboardingStep? 반환 타입을 사용하지만 import가 없습니다. 다음을 추가하세요:

import '../../domain/entities/onboarding_step.dart';
🤖 Prompt for AI Agents
In `@lib/features/auth/data/repositories/auth_repository_impl.dart` at line 13,
Add the missing import for the OnboardingStep entity so references to
OnboardingStep and OnboardingStep.fromString resolve; update the top of
auth_repository_impl.dart (and auth_repository.dart where OnboardingStep? is
used in the return type) to import the onboarding_step.dart module (the file
that defines OnboardingStep) so the compiler recognizes OnboardingStep and its
fromString method while keeping the existing implements AuthRepository usage
intact.

Comment on lines +124 to +128
@override
Future<OnboardingStep?> getCurrentOnboardingStep() async {
final stepString = await _tokenStorage.getOnboardingStep();
return OnboardingStep.fromString(stepString);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# auth_repository_impl.dart 파일의 import 구문 확인
head -n 30 lib/features/auth/data/repositories/auth_repository_impl.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 1075


🏁 Script executed:

# OnboardingStep 정의 위치 찾기
rg -l "class OnboardingStep|enum OnboardingStep" --type dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 117


🏁 Script executed:

# auth_repository.dart의 export 확인
cat lib/features/auth/data/repositories/auth_repository.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 151


🏁 Script executed:

# 추상 auth_repository.dart 확인
cat lib/features/auth/domain/repositories/auth_repository.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 1154


🏁 Script executed:

# OnboardingStep 정의 확인
cat lib/features/auth/domain/entities/onboarding_step.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 3050


🏁 Script executed:

# auth_repository_impl.dart의 전체 import 섹션과 해당 메소드 확인
sed -n '1,150p' lib/features/auth/data/repositories/auth_repository_impl.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 3750


OnboardingStep 임포트 필수

이 파일에서 OnboardingStep이 사용되지만 임포트되지 않았습니다. 125-128줄의 반환 타입과 메서드 호출(OnboardingStep.fromString())에서 OnboardingStep이 직접 참조되므로 다음 임포트를 추가해야 합니다:

import '../../domain/entities/onboarding_step.dart';

임포트가 없으면 컴파일 오류가 발생합니다.

🤖 Prompt for AI Agents
In `@lib/features/auth/data/repositories/auth_repository_impl.dart` around lines
124 - 128, The method getCurrentOnboardingStep references the OnboardingStep
type and OnboardingStep.fromString but the class isn't imported; add an import
for the OnboardingStep entity (from ../../domain/entities/onboarding_step.dart)
at the top of the file so getCurrentOnboardingStep and OnboardingStep.fromString
resolve and the file compiles.

import '../../data/datasources/firebase_auth_datasource.dart';
import '../../data/models/sign_in_response.dart';
import '../../data/repositories/auth_repository_impl.dart';
import '../../domain/repositories/auth_repository.dart';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# auth_provider.dart 파일의 import 부분과 OnboardingStep 사용 부분 확인
cat -n lib/features/auth/presentation/providers/auth_provider.dart | head -30

Repository: MapSee-Lab/MapSy-FE

Length of output: 1432


🏁 Script executed:

# OnboardingStep 정의 위치 찾기
rg -n "enum OnboardingStep|class OnboardingStep" --type dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 142


🏁 Script executed:

# auth_repository.dart 파일 확인
cat lib/features/auth/domain/repositories/auth_repository.dart | head -100

Repository: MapSee-Lab/MapSy-FE

Length of output: 1154


🏁 Script executed:

# auth_provider.dart에서 OnboardingStep 사용 부분 확인
rg -n "OnboardingStep" lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 193


🏁 Script executed:

# auth_provider.dart에서 OnboardingStep 및 AuthRepository 사용 확인
rg -n "import.*onboarding|import.*auth_repository" lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 185


🏁 Script executed:

# auth_provider.dart 전체 import 부분 확인
head -50 lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 1683


🏁 Script executed:

# AuthRepository 타입 직접 사용 확인
rg -n "AuthRepository[^I]|: AuthRepository|<AuthRepository" lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# auth_repository.dart에서 OnboardingStep import 확인
rg -n "import.*onboarding" lib/features/auth/domain/repositories/auth_repository.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# auth_provider.dart에서 OnboardingStep import 찾기
rg "import.*onboarding_step" lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# auth_provider.dart 전체에서 OnboardingStep 사용 context 확인
rg -B2 -A2 "OnboardingStep" lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 350


🏁 Script executed:

# auth_repository.dart 전체 내용 확인
cat lib/features/auth/domain/repositories/auth_repository.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 1154


🏁 Script executed:

# auth_repository.dart 전체 import 확인
head -100 lib/features/auth/domain/repositories/auth_repository.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 1154


🏁 Script executed:

# auth_provider.dart 전체 파일에서 import 부분 전체 확인
sed -n '1,20p' lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 949


🏁 Script executed:

# OnboardingStep이 어디서 import되어야 하는지 확인
find lib/features/auth -name "*.dart" -type f | xargs grep -l "export.*OnboardingStep\|import.*onboarding_step" | head -10

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# OnboardingStep 관련 모든 import/export 찾기
rg -r "onboarding_step|OnboardingStep" lib/features/auth/domain/ --type dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# auth_provider.g.dart 생성 파일에서 OnboardingStep 확인
grep -n "OnboardingStep" lib/features/auth/presentation/providers/auth_provider.g.dart 2>/dev/null || echo "파일 없음 또는 OnboardingStep 없음"

Repository: MapSee-Lab/MapSy-FE

Length of output: 89


🏁 Script executed:

# lib/features/auth 전체 구조 확인
find lib/features/auth -type f -name "*.dart" | grep -E "(barrel|export)" | head -20

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# onboarding_step.dart 파일 위치 및 export 확인
cat lib/features/auth/domain/entities/onboarding_step.dart | head -30

Repository: MapSee-Lab/MapSy-FE

Length of output: 731


🏁 Script executed:

# auth_repository_impl.dart에서 OnboardingStep import/export 확인
cat lib/features/auth/data/repositories/auth_repository_impl.dart | head -30

Repository: MapSee-Lab/MapSy-FE

Length of output: 1075


🏁 Script executed:

# 전체 도메인 레이어에서 OnboardingStep 위치 확인
find lib/features/auth/domain -type f -name "*.dart" -exec grep -l "OnboardingStep" {} \;

Repository: MapSee-Lab/MapSy-FE

Length of output: 176


🏁 Script executed:

# auth_provider.dart에서 실제 onboarding_step 관련 모든 사용 라인 확인
rg -n "onboarding|Onboarding" lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 577


🏁 Script executed:

# auth_repository.dart가 OnboardingStep을 export하는지 확인
cat lib/features/auth/domain/repositories/auth_repository.dart | grep -E "^export|^import.*onboarding"

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


🏁 Script executed:

# auth 모듈의 barrel 파일들 확인
find lib/features/auth -name "*.dart" -type f | xargs grep -l "^export" | head -10

Repository: MapSee-Lab/MapSy-FE

Length of output: 114


🏁 Script executed:

# auth_provider.dart에서 사실상 AuthRepository 타입 직접 사용 여부 재확인
rg ":\s*AuthRepository|as AuthRepository|\bAuthRepository\b" lib/features/auth/presentation/providers/auth_provider.dart

Repository: MapSee-Lab/MapSy-FE

Length of output: 45


Line 13의 import를 제거하세요. 또한 OnboardingStep을 import 추가해야 합니다.

auth_repository.dart는 이 파일에서 타입으로 사용되지 않으므로 제거할 수 있습니다. 하지만 더 중요한 문제가 있습니다: OnboardingStep이 라인 253에서 사용되지만 import되지 않았습니다. 다음을 추가하세요:

import '../../domain/entities/onboarding_step.dart';

그리고 line 13을 제거하세요.

🧰 Tools
🪛 GitHub Actions: Project Flutter CI

[warning] 13-13: Unused import: '../../domain/repositories/auth_repository.dart'.

🤖 Prompt for AI Agents
In `@lib/features/auth/presentation/providers/auth_provider.dart` at line 13,
Remove the unused import '../../domain/repositories/auth_repository.dart' and
add the missing import for OnboardingStep by adding import
'../../domain/entities/onboarding_step.dart'; so that the OnboardingStep type
used around the AuthProvider (where OnboardingStep is referenced at line ~253)
resolves correctly.

Comment on lines +206 to +214
child: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isChecked ? AppColors.primary.withValues(alpha: 0.1) : AppColors.gray100,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isChecked ? AppColors.primary : AppColors.gray200,
),
),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -t f "pubspec.yaml" --exec cat {} \;

Repository: MapSee-Lab/MapSy-FE

Length of output: 3231


🌐 Web query:

Flutter withValues alpha method version requirement

💡 Result:

Color.withValues(alpha: ...) (the replacement for withOpacity) requires Flutter 3.27+. The wide-gamut color migration guide explicitly notes that before Flutter 3.27 colors used opacity/withOpacity, and since Flutter 3.27 you should use .a and .withValues() instead. [1]

So:

  • Flutter ≥ 3.27: color.withValues(alpha: 0.3) (alpha is a double in 0.0–1.0) [2]
  • Flutter < 3.27: withValues doesn’t exist—use color.withOpacity(0.3) (or withAlpha(…) if you want 0–255). [3]

Sources:
[1] Flutter migration guide (wide gamut): https://docs.flutter.cn/release/breaking-changes/wide-gamut-cupertino-dynamic-color/
[2] Color.withValues API: https://api.flutter.dev/flutter/dart-ui/Color/withValues.html
[3] Report of missing withValues on Flutter 3.24.x (points to 3.27 introduction): https://stackoverflow.com/questions/79481509/the-method-withvalues-isnt-defined-for-the-type-color


명시적 Flutter 최소 버전 지정 필요

withValues(alpha:) 메서드는 Flutter 3.27 이상에서 사용 가능합니다. 현재 pubspec.yaml은 Dart SDK 버전(^3.9.2)만 지정하고 있는데, Dart 3.9.2는 Flutter 3.27+과 함께 배포됩니다. 코드에서 사용하는 Flutter 3.27+ 기능을 명확히 하기 위해 pubspec.yamlenvironment 섹션에 Flutter 최소 버전을 명시적으로 추가해주세요:

environment:
  sdk: ^3.9.2
  flutter: ">=3.27.0"
🤖 Prompt for AI Agents
In `@lib/features/onboarding/presentation/pages/terms_step_page.dart` around lines
206 - 214, The code uses AppColors.withValues(alpha:) in terms_step_page.dart
which requires Flutter 3.27+, so update the package environment to explicitly
require that Flutter minimum: open pubspec.yaml and add a flutter entry under
environment (e.g. flutter: ">=3.27.0") alongside the existing sdk line (^3.9.2);
this makes the dependency on the newer Flutter API explicit and avoids
build/runtime mismatches for the withValues(alpha:) call.

Comment on lines +56 to +92
@override
Widget build(BuildContext context) {
final progress = (currentStep.index + 1) / totalSteps;

return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_getStepText(),
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: AppColors.gray600,
),
),
Text(
'${currentStep.index + 1}/$totalSteps',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: AppColors.primary,
),
),
],
),
SizedBox(height: 8.h),
LinearProgressIndicator(
value: progress,
backgroundColor: AppColors.gray200,
valueColor: const AlwaysStoppedAnimation<Color>(AppColors.primary),
minHeight: 4.h,
borderRadius: BorderRadius.circular(2.r),
),
],
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

OnboardingStep.completed 상태에서 진행률이 1.0을 초과할 수 있습니다.

currentStepOnboardingStep.completed(index = 4)인 경우, progress(4 + 1) / 4 = 1.25가 됩니다. LinearProgressIndicator는 1.0 초과 값을 처리하지만, 의도치 않은 시각적 결과가 발생할 수 있습니다.

🔧 progress 값을 clamp하는 수정 제안
  `@override`
  Widget build(BuildContext context) {
-   final progress = (currentStep.index + 1) / totalSteps;
+   final progress = ((currentStep.index + 1) / totalSteps).clamp(0.0, 1.0);

    return Column(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@override
Widget build(BuildContext context) {
final progress = (currentStep.index + 1) / totalSteps;
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_getStepText(),
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: AppColors.gray600,
),
),
Text(
'${currentStep.index + 1}/$totalSteps',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: AppColors.primary,
),
),
],
),
SizedBox(height: 8.h),
LinearProgressIndicator(
value: progress,
backgroundColor: AppColors.gray200,
valueColor: const AlwaysStoppedAnimation<Color>(AppColors.primary),
minHeight: 4.h,
borderRadius: BorderRadius.circular(2.r),
),
],
);
`@override`
Widget build(BuildContext context) {
final progress = ((currentStep.index + 1) / totalSteps).clamp(0.0, 1.0);
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_getStepText(),
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: AppColors.gray600,
),
),
Text(
'${currentStep.index + 1}/$totalSteps',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: AppColors.primary,
),
),
],
),
SizedBox(height: 8.h),
LinearProgressIndicator(
value: progress,
backgroundColor: AppColors.gray200,
valueColor: const AlwaysStoppedAnimation<Color>(AppColors.primary),
minHeight: 4.h,
borderRadius: BorderRadius.circular(2.r),
),
],
);
🤖 Prompt for AI Agents
In `@lib/features/onboarding/presentation/widgets/step_indicator.dart` around
lines 56 - 92, Clamp the computed progress before passing it to the
LinearProgressIndicator: in the build method where progress is calculated from
currentStep.index and totalSteps (when currentStep can be
OnboardingStep.completed), ensure progress is constrained to the 0.0–1.0 range
(e.g., use a clamp or min/max on the progress value) and then use that clamped
value for the LinearProgressIndicator's value to avoid values >1.0.

Comment on lines +112 to 118
// 3. 온보딩 중인 사용자가 홈에 접근 시 → 온보딩으로 리다이렉트
// ====================================================================
// TODO: 온보딩 완료 여부 확인
// SharedPreferences나 Firestore에서 온보딩 완료 상태 확인
// if (!onboardingCompleted && currentPath != RoutePaths.onboarding) {
// return RoutePaths.onboarding;
// }
// 온보딩 경로에 있으면 허용
if (onboardingPaths.contains(currentPath)) {
return null;
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

온보딩 리다이렉트 로직이 실제로 동작하지 않습니다.

현재 블록은 온보딩 경로 허용만 하고, 온보딩 필요 여부를 확인해 리다이렉트하는 로직이 없습니다. 온보딩 필요 상태를 읽어 홈 접근 시 적절한 온보딩 경로로 보내는 처리가 필요합니다(또는 주석을 정정).

🤖 Prompt for AI Agents
In `@lib/router/app_router.dart` around lines 112 - 118, The onboarding redirect
block currently only allows onboardingPaths and never redirects users who need
onboarding; update the logic in the router (near onboardingPaths and
currentPath) to read the user's onboarding-needed state (e.g., an
isOnboardingRequired / needsOnboarding flag from your auth/session state or a
getOnboardingStatus() helper) and when currentPath is not in onboardingPaths and
the user needs onboarding, return a redirect to the appropriate onboarding route
(e.g., the first entry in onboardingPaths or a named onboarding route) instead
of null; ensure you reference onboardingPaths, currentPath and the onboarding
flag/getter you use so the router sends users to onboarding when required.

Comment on lines +422 to +465
704003F8267A84DA97BF5147 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9A4B57CF7FC060BA107CC516 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Podfile.lock existence and git tracking
echo "=== Checking for Podfile.lock files ==="
fd -a -g 'Podfile.lock'

echo -e "\n=== Checking git tracking ==="
git ls-files | grep -i 'podfile\.lock'

echo -e "\n=== Git status for Podfile.lock ==="
git status | grep -i 'podfile'

echo -e "\n=== Checking macOS directory structure ==="
ls -la macos/ | head -20

echo -e "\n=== Checking for Pods directory ==="
find macos -type d -name "Pods" 2>/dev/null

Repository: MapSee-Lab/MapSy-FE

Length of output: 849


🏁 Script executed:

#!/bin/bash
echo "=== Checking macos/.gitignore ==="
cat macos/.gitignore

echo -e "\n=== Git log for macos/Podfile.lock ==="
git log --oneline -20 -- macos/Podfile.lock 2>&1 | head -20

echo -e "\n=== Checking if Pods is gitignored ==="
grep -r "Pods/" macos/.gitignore 2>/dev/null

echo -e "\n=== Checking CI configuration ==="
find . -type f \( -name "*.yml" -o -name "*.yaml" \) | grep -E "(github|gitlab|circleci|action)" | head -10

echo -e "\n=== Checking for workflow files ==="
ls -la .github/workflows/ 2>/dev/null || echo "No .github/workflows found"

Repository: MapSee-Lab/MapSy-FE

Length of output: 2451


🏁 Script executed:

#!/bin/bash
echo "=== Git log for project.pbxproj (last 10 commits) ==="
git log --oneline -10 -- macos/Runner.xcodeproj/project.pbxproj

echo -e "\n=== Checking if macos Podfile exists and is tracked ==="
git ls-files | grep macos/Podfile

echo -e "\n=== Recent changes to macos Podfile ==="
git log --oneline -5 -- macos/Podfile

echo -e "\n=== Checking iOS Podfile.lock size and content preview ==="
head -20 ios/Podfile.lock

echo -e "\n=== Checking pod-related scripts or setup in repo root ==="
grep -r "pod install" . --include="*.md" --include="*.sh" --include="*.yaml" 2>/dev/null | head -10

Repository: MapSee-Lab/MapSy-FE

Length of output: 1556


🏁 Script executed:

#!/bin/bash
echo "=== Searching for macOS pod install in workflows ==="
grep -r "pod install" .github/workflows/ | grep -i "macos\|mac"

echo -e "\n=== Checking iOS workflow for pod install ==="
grep -A5 -B5 "pod install" .github/workflows/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml | head -20

echo -e "\n=== Checking if macOS build is part of any workflow ==="
grep -r "macos" .github/workflows/ | grep -i "build\|pod" | head -10

echo -e "\n=== Listing current git status ==="
git status --short | grep -E "(macos|ios).*Podfile"

echo -e "\n=== Checking PR/recent commits for context ==="
git log --all --oneline -5

Repository: MapSee-Lab/MapSy-FE

Length of output: 1113


macOS 빌드 단계에서 Podfile.lock 누락으로 인한 빌드 실패

macos/Podfile.lock이 존재하지 않아 프로젝트의 Check Pods Manifest.lock 빌드 단계가 실패합니다. iOS는 CI에서 pod install을 실행하여 Podfile.lock을 생성하지만, macOS는 CI 구성에 해당 단계가 없습니다. 다음 중 하나를 해결해야 합니다:

  • CI 워크플로우에 cd macos && pod install 단계 추가
  • 또는 macos/Podfile.lock을 git에 커밋 (iOS와 동일하게)

현재 상태에서는 로컬 개발자가 pod install을 수동으로 실행해야만 빌드할 수 있습니다.

🤖 Prompt for AI Agents
In `@macos/Runner.xcodeproj/project.pbxproj` around lines 422 - 465, 빌드 단계 "[CP]
Check Pods Manifest.lock" (PBXShellScriptBuildPhase) 실패는 macOS 타겟의 Podfile.lock이
없기 때문입니다; 해결하려면 CI에 macOS 프로젝트 디렉터리로 이동해 pod install을 실행하도록 워크플로우를 추가(예: cd
<macOS project dir> && pod install)하거나 macOS용 Podfile.lock을 저장소에 커밋해 체크아웃 시 항상
존재하도록 만드세요.

@Cassiiopeia Cassiiopeia merged commit df992c1 into main Feb 4, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant