Skip to content

[feat/#13] 스터디 등록하기 화면 구현#15

Merged
fredleeJH merged 15 commits intodevelopfrom
feat/#13-study-register
Nov 28, 2025
Merged

[feat/#13] 스터디 등록하기 화면 구현#15
fredleeJH merged 15 commits intodevelopfrom
feat/#13-study-register

Conversation

@fredleeJH
Copy link
Contributor

@fredleeJH fredleeJH commented Nov 23, 2025

Related issue 🛠

Work Description 📝

  • 스터디 등록하기 구현 및 일부 컴포넌트 디자인 수정

Screenshot 📸

Screen_recording_20251123_183704.mp4

Uncompleted Tasks 😅

  • api 연동은 추후 진행

PR Point 📌

스터디 등록하기 단순 화면 구현 및 기존 디자인 시스템 일부 수정
기존에 있던 버튼 컴포넌트 수정

트러블 슈팅 💥

갑작스럽게 바뀐 것이 많아 추후 다시 정리해보겠습니다.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 스터디 등록 화면을 재구성하여 직관적인 단계별 입력 프로세스 제공
    • 위치 선택 기능 강화 및 새로운 지역 선택 UI 추가
    • 스터디 정보 입력 시 회원 수, 참가비, 성향 선택 기능 추가
    • 스터디 설명 및 이미지 업로드 기능 개선
  • 리팩토링

    • 디자인 시스템 컴포넌트 재정렬 및 최적화
    • 스터디 필터 화면 UI 컴포넌트 통합

✏️ Tip: You can customize this high-level summary in your review settings.

@fredleeJH fredleeJH requested a review from starshape7 November 23, 2025 09:46
@fredleeJH fredleeJH self-assigned this Nov 23, 2025
@fredleeJH fredleeJH added 🐶 종훈 🍒 [FEAT] 새로운 기능 구현 labels Nov 23, 2025
@fredleeJH fredleeJH linked an issue Nov 23, 2025 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Nov 23, 2025

Walkthrough

스터디 등록 화면 구현 및 상태 관리 기능을 추가합니다. 위치 선택 바텀시트, 다양한 선택 컴포넌트, 입력 필드를 포함한 UI 계층을 확장하고 ViewModel을 통한 폼 데이터 및 검증 로직을 구현합니다.

Changes

Cohort / File(s) 변경 요약
디자인 시스템 의존성 및 색상
core/designsystem/build.gradle.kts projects.core.common 의존성 추가
core/designsystem/src/main/java/com/umcspot/spot/designsystem/theme/Color.kt SpotColorsdefault: Color 필드 추가, G300 값으로 초기화
디자인 시스템 컴포넌트 - 하단시트
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/bottomsheet/LocationBottomSheet.kt 위치 선택 바텀시트 컴포넌트 신규 추가 (검색, 선택 칩, 결과 목록)
디자인 시스템 컴포넌트 - 버튼
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/ImageTextButton.kt MultiButtonM 제거, MultiButton 리팩토링 (Box 기반, 고정 크기)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/SocialLoginButton.kt 패키지 이동 (componentcomponent.button)
디자인 시스템 컴포넌트 - 스터디 섹션
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/section/ActivityThemeSection.kt 테마 선택 섹션 오버로드 2개 추가 (단일/복수 선택)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/section/ActivityTypeSection.kt 활동 유형 선택 섹션 신규 추가
디자인 시스템 리소스
core/designsystem/src/main/res/drawable/dismiss.xml 아이콘 크기 재설정 (60dp → 20dp), 경로 및 색상 변경
core/designsystem/src/main/res/drawable/ic_location.xml 위치 아이콘 신규 추가
피처 - 메인 화면
feature/main/src/main/java/com/umcspot/spot/landing/LandingScreen.kt 불필요 import 제거, 버튼 path 업데이트
feature/main/src/main/java/com/umcspot/spot/main/MainScreen.kt RegisterStudy 라우트 topBar 처리 추가
피처 - 스터디 (선호 위치)
feature/study/src/main/java/com/umcspot/spot/study/preferLocation/PreferLocationBottomSheet.kt 전체 기능 제거
feature/study/src/main/java/com/umcspot/spot/study/preferLocation/PreferLocationStudyScreen.kt PreferLocationBottomSheetLocationBottomSheet로 교체
피처 - 스터디 (필터링)
feature/study/src/main/java/com/umcspot/spot/study/recruiting/RecruitingStudyFilterScreen.kt 내부 섹션 구현 제거, 외부 ActivityTypeSection, ActivityThemeSection import 사용
피처 - 스터디 등록 (상태 및 ViewModel)
feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyScreen.kt 전체 리팩토링: ViewModel 의존성, 상태 관리, BackHandler, TopBar, 다중 콜백 추가
feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyViewModel.kt 전체 구현 추가 (위치 로드, 검색, 지역 관리, 폼 이벤트, 검증, 제출)
feature/study/src/main/java/com/umcspot/spot/study/register/model/RegisterStudyState.kt RegisterStudyState 데이터 클래스 확장, RegisterStudySideEffect sealed interface 신규 추가
피처 - 스터디 등록 (컴포넌트)
feature/study/src/main/java/com/umcspot/spot/study/register/component/BinaryChoiceRow.kt 이진 선택 행 컴포넌트 신규 추가
feature/study/src/main/java/com/umcspot/spot/study/register/component/FeeInputSection.kt 요금 입력 섹션 컴포넌트 신규 추가
feature/study/src/main/java/com/umcspot/spot/study/register/component/MemberCountSelector.kt 인원 수 선택 컴포넌트 신규 추가
feature/study/src/main/java/com/umcspot/spot/study/register/component/PriceTextField.kt 가격 입력 필드 컴포넌트 신규 추가
feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectedRegionsSection.kt 선택된 지역 섹션 컴포넌트 신규 추가
feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectionChip.kt 선택 칩 컴포넌트 신규 추가
피처 - 스터디 등록 (화면)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyCategoryScreen.kt 상태 기반 리팩토링: 외부 상태, 콜백 추가, UI 확장
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyPlaceScreen.kt 전체 구현: 활동 유형, 위치, 지역 선택 UI 추가
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyInfoScreen.kt 전체 구현: 인원, 요금, 성향 선택 UI 추가
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyIntroduceScreen.kt 전체 구현: 설명 입력, 이미지 선택 UI 추가

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant RegisterStudyRoute as RegisterStudyRoute
    participant RegisterStudyScreen as RegisterStudyScreen
    participant ViewModel as RegisterStudyViewModel
    participant State as UI State<br/>(uiState)
    participant SideEffect as SideEffect<br/>(SharedFlow)

    User->>RegisterStudyRoute: 스터디 등록 화면 진입
    RegisterStudyRoute->>ViewModel: hiltViewModel() 초기화
    ViewModel->>ViewModel: loadLocationData()
    ViewModel->>State: 초기 상태 설정
    RegisterStudyRoute->>RegisterStudyScreen: uiState, 콜백 전달

    User->>RegisterStudyScreen: 스터디명 입력
    RegisterStudyScreen->>ViewModel: onStudyNameChange(name)
    ViewModel->>State: studyName 업데이트

    User->>RegisterStudyScreen: 테마 선택
    RegisterStudyScreen->>ViewModel: onCategorySelect(themes)
    ViewModel->>State: studyThemes 업데이트

    User->>RegisterStudyScreen: 활동 유형 선택
    RegisterStudyScreen->>ViewModel: onActivityTypeSelect(type)
    ViewModel->>State: activityType 업데이트

    User->>RegisterStudyScreen: 지역 검색
    RegisterStudyScreen->>ViewModel: onLocationQueryChange(query)
    ViewModel->>ViewModel: searchLocation(query) [debounced]
    ViewModel->>State: locationResults 업데이트

    User->>RegisterStudyScreen: 지역 추가
    RegisterStudyScreen->>ViewModel: addSelectedRegion(region)
    ViewModel->>State: selectedRegions 업데이트 (max 3)

    User->>RegisterStudyScreen: 인원 수 선택
    RegisterStudyScreen->>ViewModel: onMemberCountChange(count)
    ViewModel->>State: memberCount 업데이트

    User->>RegisterStudyScreen: 요금 정보 입력
    RegisterStudyScreen->>ViewModel: onFeeInfoChange(hasFee, amount)
    ViewModel->>State: hasFee, feeAmount 업데이트

    User->>RegisterStudyScreen: 성향 선택
    RegisterStudyScreen->>ViewModel: onPersonalityChange(categoryIdx, value)
    ViewModel->>State: preferences[categoryIdx] 업데이트

    User->>RegisterStudyScreen: 설명 입력
    RegisterStudyScreen->>ViewModel: onDescriptionChange(desc)
    ViewModel->>State: description 업데이트

    User->>RegisterStudyScreen: 제출 클릭
    RegisterStudyScreen->>ViewModel: isStepValid(currentPage) 확인
    alt 유효한 경우
        RegisterStudyScreen->>ViewModel: submit()
        ViewModel->>SideEffect: NavigateToHome 발행
        SideEffect->>RegisterStudyRoute: side effect 수신
        RegisterStudyRoute->>User: 홈 화면으로 이동
    else 무효한 경우
        RegisterStudyScreen->>User: 다음 페이지로 진행 또는 유지
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

주의 깊게 검토해야 할 영역:

  • RegisterStudyViewModel.kt: 로케이션 데이터 로드, debounced 검색, 상태 업데이트, 단계별 검증 로직이 복잡하게 얽혀 있음
  • RegisterStudyScreen.kt: 대규모 리팩토링으로 ViewModel 의존성, 상태 관리, 여러 콜백 핸들러, BackHandler 추가
  • 스터디 등록 화면들 (StudyCategoryScreen.kt, StudyPlaceScreen.kt, StudyInfoScreen.kt, StudyIntroduceScreen.kt): 각각 상태 기반으로 리팩토링되었으며, 폼 데이터 흐름과 유효성 검사 로직 검증 필요
  • LocationBottomSheet vs PreferLocationBottomSheet 교체: 기존 컴포넌트 제거 및 새 컴포넌트 도입 시 동작 호환성 확인
  • 다중 컴포넌트 신규 추가 (BinaryChoiceRow, FeeInputSection, MemberCountSelector 등): 각 컴포넌트의 상태 관리 및 콜백 전달이 올바르게 구현되었는지 확인

Possibly related PRs

  • #1 [init] : 초기 세팅 #3: core/designsystem/build.gradle.kts 파일의 동일한 수정사항(projects.core.common 의존성 추가)을 포함하고 있음

Suggested reviewers

  • starshape7

Poem

🐰 깡총깡총, 등록 화면을 짓네요
위치부터 테마, 요금에 성향까지
일곱 개 화면이 모여 춤을 춘다네
ViewModel이 상태를 쓸어담고
콜백들이 춤을 추며
스터디 등록의 꿈을 펼쳐봅니다! 🎉
검증이 철저하기를 바라며... ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive 대부분의 변경사항이 스터디 등록 기능과 디자인 시스템 컴포넌트 개선에 관련되어 있으나, 일부 불필요한 변경사항(SocialLoginButton 패키지 이동, dismiss.xml 리소스 수정 등)이 포함되었습니다. 불필요한 변경사항들(SocialLoginButton 패키지 변경, dismiss.xml 수정)이 issue #13의 스터디 등록 기능과의 관련성을 명확히 하거나 별도의 이슈로 분리하기를 권장합니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 요약하고 있습니다. 스터디 등록하기 화면 구현이라는 핵심 기능을 간결하게 표현했습니다.
Description check ✅ Passed PR 설명이 제공된 템플릿의 모든 주요 섹션을 포함하고 있으며, 작업 내용, 스크린샷, 미완료 작업, PR 포인트를 명확히 기술했습니다.
Linked Issues check ✅ Passed 스터디 등록하기 화면 구현 기능이 완전히 구현되었으며, RegisterStudyScreen, RegisterStudyViewModel, 그리고 관련 UI 컴포넌트들이 추가되었습니다. API 연동은 PR 설명에서 추후 진행한다고 명시했습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#13-study-register

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/study/src/main/java/com/umcspot/spot/study/preferLocation/PreferLocationStudyScreen.kt (1)

80-100: 탭 인덱싱 오프셋 버그 및 필터 미구현으로 인한 동작 오류 확인

코드베이스 검증 결과, 리뷰 코멘트의 지적이 모두 확인되었습니다.

1. 탭 인덱싱 버그 (line 90 확인됨)

현재 코드:

val tabs: List<String> = remember(selected) { listOf("전체") + selected }  // line 58
var selectedTab by remember { mutableStateOf(0) }  // line 57
val currentLocation = selected.getOrNull(selectedTab)  // line 90
  • selectedTab == 0일 때는 "전체" 탭이지만, selected.getOrNull(0)으로 첫 번째 지역을 반환하게 됩니다.
  • 결과적으로 "전체" 탭 선택 시에도 첫 번째 선택 지역으로 필터가 적용되는 버그가 발생합니다.

2. 필터 로직 미구현 (lines 93-100 확인됨)

val studiesForUi = if (currentLocation.isNullOrBlank()) {
    studiesAll
} else {
    studiesAll.filter { item ->
        false  // ← 항상 false 반환
    }
}

지역을 선택하면 무조건 빈 리스트가 반환됩니다.

3. 추가 정보: StudyResult에 지역 필드 부재

StudyResult 데이터 클래스에는 location, address 등의 지역 관련 필드가 없습니다. 필터 구현 전에 StudyResult에 지역 필드를 먼저 추가해야 합니다.


수정 필요 사항:

-    val currentLocation = selected.getOrNull(selectedTab)
-    // 🔧 여기서 실제 StudyResult의 지역 필드명으로 필터하세요 (예: item.location / item.address 등)
-    val studiesForUi = if (currentLocation.isNullOrBlank()) {
-        studiesAll
-    } else {
-        studiesAll.filter { item ->
-            // 예시) item.location?.contains(currentLocation) == true
-            // 필드명이 다르면 위 라인만 고치면 됨
-            false
-        }
-    }
+    val currentLocation = if (selectedTab == 0) null else selected.getOrNull(selectedTab - 1)
+
+    val studiesForUi = if (currentLocation.isNullOrBlank()) {
+        studiesAll
+    } else {
+        studiesForUi.filter { item ->
+            // TODO: StudyResult에 지역 필드 추가 후 실제 필터 조건 구현
+            // item.location?.contains(currentLocation) == true
+        }
+    }
🧹 Nitpick comments (15)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/SocialLoginButton.kt (1)

1-190: SocialLoginButton 패키지 이동 구조는 적절합니다 (색 사용 일관성은 한 번 정리 제안)

버튼 전용 패키지로 이동한 건 컴포넌트 구조화 측면에서 좋아 보입니다. 다만 NaverStartButton에서는 contentColor = White, NaverLoginButton에서는 SpotTheme.colors.White를 쓰고 있어 색 접근 방식이 섞여 있습니다.

디자인 시스템 관점에서 둘 중 하나로만 통일해 두면(예: 전부 SpotTheme.colors.White) 이후 유지보수가 조금 더 수월할 것 같습니다.

feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectionChip.kt (1)

22-54: SelectionChip에 접근성 semantics 추가를 권장합니다

noRippleClickable만 사용하고 있어 시각적/터치 동작은 좋지만, 스크린 리더 입장에선 버튼/선택 상태라는 정보가 노출되지 않습니다.

아래처럼 Role.Buttonselected semantics를 추가해 두면 토글형 컴포넌트로 더 잘 인식될 수 있습니다.

@@
 fun SelectionChip(
     text: String,
     isSelected: Boolean,
     onClick: () -> Unit,
     modifier: Modifier = Modifier
 ) {
-    Box(
-        modifier = modifier
+    Box(
+        modifier = modifier
+            .semantics {
+                role = Role.Button
+                selected = isSelected
+            }
             .height(screenHeightDp(35.dp))
@@
             .clip(RoundedCornerShape(6.dp))
             .noRippleClickable(onClick = onClick),

위 변경 시 import androidx.compose.ui.semantics.Role / semantics, selected 임포트만 추가해 주시면 됩니다.

feature/study/src/main/java/com/umcspot/spot/study/register/component/PriceTextField.kt (1)

28-79: PriceTextField에서 숫자 이외 문자 필터링을 권장합니다

KeyboardType.Number만으로는 사용자가 -, . 또는 공백/문자를 입력·붙여넣기 할 수 있어서, 금액 파싱 쪽에서 별도 검증이 필요합니다.

컴포넌트 단에서 숫자만 허용하도록 한 번 걸러 주면 이후 로직이 단순해집니다.

-    BasicTextField(
-        value = value,
-        onValueChange = onValueChange,
+    BasicTextField(
+        value = value,
+        onValueChange = { newValue ->
+            // 숫자만 허용 (예: "1,000" 붙여넣기 시 "1000"으로 정규화)
+            onValueChange(newValue.filter { it.isDigit() })
+        },

추가로, 나중에 필요하다면 최소/최대 길이(자리 수) 제한도 함께 고려해 볼 수 있을 것 같습니다.

core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/ImageTextButton.kt (1)

46-201: MultiButton 구현은 안정적으로 보이며, 긴 텍스트용 ellipsis 정도만 추가를 제안드립니다

semantics { role = Role.Button; selected = checked }MutableInteractionSource + pressed 색상 처리까지 잘 연결되어 있어서 토글 버튼으로 동작은 안정적으로 보입니다.

다만 TextmaxLines = 1만 설정되어 있고 overflow가 없어, 기획 변경 등으로 라벨이 길어질 경우 잘려 보일 수 있습니다. 방어적으로 아래처럼 ellipsis만 추가해 두면 향후 변경에도 안전합니다.

-                    Text(
-                        text = text,
-                        style = SpotTheme.typography.h4,
-                        color = colors.text,
-                        maxLines = 1
-                    )
+                    Text(
+                        text = text,
+                        style = SpotTheme.typography.h4,
+                        color = colors.text,
+                        maxLines = 1,
+                        overflow = TextOverflow.Ellipsis,
+                    )

(위 변경 시 import androidx.compose.ui.text.style.TextOverflow 임포트가 필요합니다.)

core/designsystem/src/main/res/drawable/ic_location.xml (1)

1-8: 아이콘 색상 하드코딩 (선택 사항)

벡터 자체는 문제 없어 보입니다. 다만 android:fillColor="#337BFF"처럼 색을 직접 하드코딩하면 다크 모드/브랜드 색 변경 시 재사용성이 조금 떨어질 수 있습니다.
나중에 테마 확장을 고려한다면 @color/… 리소스로 분리해 두는 것도 한 번 생각해볼 만합니다.

feature/study/src/main/java/com/umcspot/spot/study/register/component/FeeInputSection.kt (1)

12-48: 수강료 선택/입력 분리가 명확하게 잘 되어 있습니다 (소소한 제안 포함)

  • hasFee: Boolean?로 “미선택 / 없음 / 있음” 3가지 상태를 표현하고, 선택이 발생한 이후에는 onFeeTypeChange(Boolean)으로만 true/false를 전달하는 구조가 직관적입니다.
  • hasFee == true일 때만 PriceTextField를 보여주는 것도 UX 측면에서 자연스럽습니다.

사소한 수준이지만, 나중에 다국어 지원이나 텍스트 재사용을 고려하신다면 "없음", "있음" 문자열은 string.xml로 빼 두는 것도 좋을 것 같습니다.

feature/study/src/main/java/com/umcspot/spot/study/register/component/BinaryChoiceRow.kt (1)

17-54: BinaryChoiceRow 추상화가 재사용성 측면에서 좋습니다 (타입 표현은 선택 사항)

  • 좌/우 옵션을 SelectionChip 두 개와 중앙 VerticalDivider로 구성해서 디자인 요구사항을 잘 캡쳐한 것 같습니다.
  • selectedIndex: Int?로 “미선택(null) / 왼쪽(0) / 오른쪽(1)” 상태를 표현한 것도 간단하고 이해하기 쉽습니다.

추가로, 나중에 선택 상태가 더 복잡해질 여지가 있다면
enum classsealed class로 선택 상태를 표현하는 것도 타입 세이프티 측면에서 고려해볼 만합니다만, 지금 단계에서는 Int?도 충분해 보입니다.

core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/bottomsheet/LocationBottomSheet.kt (1)

331-375: SelectedChips에서 칩 전체를 제거 액션으로 쓸지 고민 여지가 있습니다 (선택 사항)

지금 구현은:

  • AssistChip(onClick = {})로 칩 자체 클릭은 아무 동작이 없고,
  • trailingIconModifier.clickable { onRemove(name) }로만 삭제가 동작합니다.

디자인 요구사항에 따라 그대로 두셔도 문제는 없지만,
사용자 입장에서는 “칩 전체를 탭해도 제거가 될 것”이라고 기대할 수 있어서, 나중에 여유가 되면 아래 정도는 한 번 고민해볼 만합니다.

  • 칩 전체 클릭(onClick)에서도 onRemove(name)을 호출하게 조정, 혹은
  • 최소한 a11y 관점에서 삭제 액션이 명확히 읽히도록 contentDescription이나 TalkBack 문구를 튜닝.

지금 단계에서는 선택적으로 보완해도 될 수준이라고 생각합니다.

feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectedRegionsSection.kt (1)

31-95: 지역 선택 섹션 전반 구조는 좋고, 간격 헬퍼만 한 번 확인해 보면 좋겠습니다

  • SelectedRegionsSectionImmutableList + 콜백만 받는 순수 컴포저블로 잘 설계되어 있어서 상태 관리 관점에서 깔끔합니다.
  • 다만 RegionItem 의 패딩에서 horizontal = screenHeightDp(10.dp) 를 사용하고 있는데, 의도한 것이 아니라면 가로 방향에는 screenWidthDp 를 쓰는 게 더 자연스러워 보입니다(세로 방향에는 이미 screenHeightDp 를 사용 중이라 일관성이 맞습니다).
  • 나중에 다국어 대응을 고려하면 "지역", "삭제" 같은 contentDescription"지역 추가" 텍스트도 string 리소스로 분리해 두면 유지보수에 도움이 될 것 같습니다.
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyIntroduceScreen.kt (1)

44-153: 소개 화면의 이미지 선택 상태를 상위 상태(RegisterStudyState)와 맞추면 이후 확장에 더 유리합니다

  • StudyIntroduceScreendescription 은 파라미터로 주입받아 외부 상태와 잘 연결되어 있지만, 이미지 선택 여부는 isImageSelected 로 로컬에서만 관리하고 있습니다.
  • 같은 PR 에서 RegisterStudyStatestudyImageUri: String? 가 추가된 것을 보면, 실제 이미지 피커 연동 시에는 이 화면도 URI(또는 선택 여부 Boolean)를 인자로 받아 렌더링만 담당하는 구조가 더 자연해 보입니다.
  • 지금은 UI 목업 단계로 충분해 보이지만, 추후 연동 시에는 isImageSelected 를 제거하고 studyImageUri 기반으로:
    • URI 존재 여부에 따라 placeholder vs 이미지 렌더링,
    • 필요 시 상위에서 선택/삭제 이벤트를 처리
      하는 방향으로 리팩터링을 고려해 보시면 좋겠습니다.
feature/study/src/main/java/com/umcspot/spot/study/register/model/RegisterStudyState.kt (1)

7-35: 상태 구조는 명확하고, preference 계열은 도메인 타입으로 감싸면 더 읽기 좋아질 것 같습니다

  • RegisterStudyState 에 필요한 필드들이 한 곳에 잘 모여 있고, 기본값도 지정되어 있어 ViewModel 에서 사용하기 편해 보입니다.
  • 다만 networkingPreference, goalDurationPreference, discussionPreference 등 여러 preference 가 모두 Int? 로 선언돼 있어, 나중에 1/2/3 값이 각각 무엇을 의미하는지 추적하기가 어려울 수 있습니다.
  • 여유가 생기면 각 preference 를 enum class@JvmInline value class 같은 도메인 전용 타입으로 감싸 두면, 호출부 가독성과 타입 안정성이 모두 좋아질 것 같습니다.
feature/study/src/main/java/com/umcspot/spot/study/register/component/MemberCountSelector.kt (1)

38-156: 인원 선택 드롭다운은 기본 동작은 좋고, 닫힘 동작·옵션 정의를 나중에 다듬어도 좋겠습니다

  • 현재는 expanded 플래그만으로 열림/닫힘을 제어해서, 셀렉터 바깥을 탭해도 드롭다운이 닫히지 않는 상태입니다. UX 를 생각하면 DropdownMenu / ExposedDropdownMenuBox 등을 활용해 바깥 클릭이나 뒤로가기 시 닫히도록 개선해 볼 여지가 있습니다.
  • memberOptionspersistentListOf(2, 3, 4, 5) 로 고정돼 있는데, 기획 변경 가능성이 있다면 상수로 분리하거나 파라미터로 받아서 재사용성을 높이는 것도 고려해 볼 수 있습니다.
  • 그 외 레이아웃, 타이포그래피, 색상 사용은 기존 디자인 시스템과 잘 맞아 떨어지는 구현으로 보입니다.
feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyScreen.kt (3)

65-72: sideEffect 수집용 LaunchedEffect 키를 단순화하면 더 명확해집니다.

현재 LaunchedEffect(viewModel.sideEffect) 안에서 다시 viewModel.sideEffect.collectLatest { ... }를 호출하고 있어, 키와 실제 수집 대상이 동일한 참조를 가리키는 구조입니다. 기능상 문제는 없지만, LaunchedEffect(Unit) 또는 LaunchedEffect(viewModel) 정도로 키를 단순화하면 “뷰모델 수명 동안 한 번 collect 한다”는 의도가 더 분명해질 것 같습니다.


74-78: Route 레벨 modifier와 배경 지정 책임을 정리하면 레이아웃 구조가 더 깔끔해집니다.

지금은 Route의 Column이 항상 Modifier.fillMaxSize().background(white)를 사용하고, 전달받은 modifier는 내부 RegisterStudyScreen에만 넘기고 있습니다. 상위에서 레이아웃을 제어해야 할 일이 있다면, Line 74–78에서 modifier를 연결해 modifier.fillMaxSize().background(...)처럼 사용하고, 내부 RegisterStudyScreen 쪽에서는 배경 중복을 제거하거나 패딩만 담당하도록 역할을 나누면 구조 이해와 재사용성이 조금 더 좋아질 것 같습니다.

Also applies to: 87-94, 118-143


159-197: 마지막 단계의 onIntroduceValid 콜백은 현재 사용되지 않아 시그니처를 단순화할 수 있습니다.

페이지 3에서 StudyIntroduceScreenonIntroduceValid = { }를 넘기고 있는데, 해당 화면 내부에서는 LaunchedEffect(description)로 항상 콜백을 호출하는 구조라 현재는 아무 효과가 없는 상태입니다. 실제로 유효성 상태를 ViewModel 쪽에서 별도로 쓰지 않는다면 onIntroduceValid 파라미터를 제거해 양쪽 시그니처를 단순화하거나, 반대로 유효성 플래그를 ViewModel에 전달하는 용도로 다시 연결하는 쪽 중 하나로 정리하면 이후 유지보수 시 혼동을 줄일 수 있을 것 같습니다. isStepValid가 이미 description 기반 검증을 하고 있으니, 지금 로직만으로도 기능적으로는 충분해 보입니다.

Also applies to: 203-218

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57af34d and 2defa38.

📒 Files selected for processing (27)
  • core/designsystem/build.gradle.kts (1 hunks)
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/bottomsheet/LocationBottomSheet.kt (1 hunks)
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/ImageTextButton.kt (4 hunks)
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/SocialLoginButton.kt (1 hunks)
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/section/ActivityThemeSection.kt (1 hunks)
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/section/ActivityTypeSection.kt (1 hunks)
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/theme/Color.kt (6 hunks)
  • core/designsystem/src/main/res/drawable/dismiss.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_location.xml (1 hunks)
  • feature/main/src/main/java/com/umcspot/spot/landing/LandingScreen.kt (1 hunks)
  • feature/main/src/main/java/com/umcspot/spot/main/MainScreen.kt (2 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/preferLocation/PreferLocationBottomSheet.kt (0 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/preferLocation/PreferLocationStudyScreen.kt (2 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/recruiting/RecruitingStudyFilterScreen.kt (5 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyScreen.kt (3 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyViewModel.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/component/BinaryChoiceRow.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/component/FeeInputSection.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/component/MemberCountSelector.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/component/PriceTextField.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectedRegionsSection.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectionChip.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/model/RegisterStudyState.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyCategoryScreen.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyInfoScreen.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyIntroduceScreen.kt (1 hunks)
  • feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyPlaceScreen.kt (1 hunks)
💤 Files with no reviewable changes (1)
  • feature/study/src/main/java/com/umcspot/spot/study/preferLocation/PreferLocationBottomSheet.kt
🧰 Additional context used
🧬 Code graph analysis (18)
feature/study/src/main/java/com/umcspot/spot/study/register/component/FeeInputSection.kt (3)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenWidthDp (18-23)
feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectionChip.kt (1)
  • SelectionChip (22-54)
feature/study/src/main/java/com/umcspot/spot/study/register/component/PriceTextField.kt (1)
  • PriceTextField (28-79)
feature/study/src/main/java/com/umcspot/spot/study/register/component/PriceTextField.kt (1)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenHeightDp (11-16)
  • screenWidthDp (18-23)
feature/study/src/main/java/com/umcspot/spot/study/preferLocation/PreferLocationStudyScreen.kt (1)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/bottomsheet/LocationBottomSheet.kt (1)
  • LocationBottomSheet (79-328)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyCategoryScreen.kt (2)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenHeightDp (11-16)
feature/study/src/main/java/com/umcspot/spot/study/register/component/StudyNameTextField.kt (1)
  • StudyNameTextField (36-108)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/section/ActivityThemeSection.kt (2)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenWidthDp (18-23)
  • screenHeightDp (11-16)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/ImageTextButton.kt (1)
  • MultiButton (93-171)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyIntroduceScreen.kt (1)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenHeightDp (11-16)
  • screenWidthDp (18-23)
feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectionChip.kt (1)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenHeightDp (11-16)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/bottomsheet/LocationBottomSheet.kt (1)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenHeightDp (11-16)
  • screenWidthDp (18-23)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyInfoScreen.kt (5)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenHeightDp (11-16)
feature/study/src/main/java/com/umcspot/spot/study/register/component/MemberCountSelector.kt (1)
  • MemberCountSelector (38-157)
feature/study/src/main/java/com/umcspot/spot/study/register/component/FeeInputSection.kt (1)
  • FeeInputSection (12-49)
feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyViewModel.kt (2)
  • onFeeInfoChange (129-131)
  • onPersonalityChange (133-144)
feature/study/src/main/java/com/umcspot/spot/study/register/component/BinaryChoiceRow.kt (1)
  • BinaryChoiceRow (17-55)
feature/study/src/main/java/com/umcspot/spot/study/register/component/BinaryChoiceRow.kt (2)
feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectionChip.kt (1)
  • SelectionChip (22-54)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenWidthDp (18-23)
  • screenHeightDp (11-16)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/section/ActivityTypeSection.kt (2)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenWidthDp (18-23)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/ImageTextButton.kt (1)
  • MultiButton (93-171)
feature/study/src/main/java/com/umcspot/spot/study/register/component/MemberCountSelector.kt (2)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenHeightDp (11-16)
  • screenWidthDp (18-23)
feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyViewModel.kt (1)
  • onMemberCountChange (125-127)
feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyViewModel.kt (1)
core/common/src/main/java/com/umcspot/spot/common/location/Location.kt (1)
  • searchLocations (54-63)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/ImageTextButton.kt (2)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/shapes/Shapes.kt (1)
  • ShapeBox (148-170)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenWidthDp (18-23)
  • screenHeightDp (11-16)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyPlaceScreen.kt (4)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/bottomsheet/LocationBottomSheet.kt (1)
  • LocationBottomSheet (79-328)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenHeightDp (11-16)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/section/ActivityTypeSection.kt (1)
  • ActivityTypeSection (14-40)
feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectedRegionsSection.kt (1)
  • SelectedRegionsSection (31-53)
feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyScreen.kt (8)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/appBar/AppBar.kt (1)
  • BackTopBar (113-143)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyCategoryScreen.kt (1)
  • StudyCategoryScreen (20-68)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyPlaceScreen.kt (1)
  • StudyPlaceScreen (22-76)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyInfoScreen.kt (1)
  • StudyInfoScreen (22-104)
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyIntroduceScreen.kt (1)
  • StudyIntroduceScreen (43-154)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenHeightDp (11-16)
feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyViewModel.kt (1)
  • isStepValid (150-171)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/SpotActivationButton.kt (1)
  • SpotActivationButton (24-57)
feature/study/src/main/java/com/umcspot/spot/study/register/component/SelectedRegionsSection.kt (1)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenHeightDp (11-16)
  • screenWidthDp (18-23)
feature/study/src/main/java/com/umcspot/spot/study/recruiting/RecruitingStudyFilterScreen.kt (1)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenHeightDp (11-16)
🔇 Additional comments (12)
feature/main/src/main/java/com/umcspot/spot/landing/LandingScreen.kt (1)

26-27: 디자인 시스템 리팩토링에 따른 깔끔한 import 경로 업데이트입니다.

버튼 컴포넌트들이 새로운 디자인 시스템 구조로 이동하면서 import 경로가 적절히 업데이트되었습니다. 두 버튼 모두 코드에서 정상적으로 사용되고 있으며(83, 86번 라인), 변경사항이 명확합니다.

feature/main/src/main/java/com/umcspot/spot/main/MainScreen.kt (1)

81-81: LGTM!

trailing comma를 유지한 것은 Kotlin의 권장 스타일을 따르고 있어 좋습니다.

core/designsystem/build.gradle.kts (1)

13-17: core.common 의존성 추가 방향 괜찮습니다

디자인 시스템에서 공통 모듈을 사용하는 구조로 자연스럽고, 기존 도메인 의존성과도 충돌될 부분은 없어 보입니다. 이 변경만으로는 추가 조치 필요 없어 보입니다.

core/designsystem/src/main/res/drawable/dismiss.xml (1)

2-8: dismiss 아이콘 리소스 사이즈/색상 변경 확인

20dp/20 viewport로 맞추고 색상도 G500 계열로 통일된 것 같아 디자인 시스템 관점에서 일관성이 좋아 보입니다. 경로나 속성 상으로는 문제 없어 보입니다.

core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/section/ActivityTypeSection.kt (1)

14-40: ActivityTypeSection 구성/상태 관리 구조 괜찮습니다

ActivityType.entries를 돌면서 MultiButton으로 선택 상태를 표현하고, onSelect(type)만 넘겨서 상태는 상위에서 관리하도록 분리한 구조가 깔끔합니다.

spacing도 screenWidthDp(14.dp)로 Figma 비율을 따라가고 있어 디자인 시스템과 잘 맞는 구현으로 보입니다.

core/designsystem/src/main/java/com/umcspot/spot/designsystem/theme/Color.kt (1)

67-213: SpotColors.default 필드 추가 및 연결 일관성 좋습니다

SpotColorsdefault 필드를 추가하고, copy(), update(), SpotDayColors()까지 모두 업데이트되어 컬러 상태 객체 일관성이 잘 유지되고 있습니다.

val SpotColors.Default: Color get() = default 형태로 기존 Bxxx/Gxxx와 동일한 접근 패턴을 맞춘 것도 좋아 보이고, 현재 코드만 보면 추가 수정 필요해 보이진 않습니다.

feature/study/src/main/java/com/umcspot/spot/study/preferLocation/PreferLocationStudyScreen.kt (1)

154-164: LocationBottomSheet 연동 구조는 자연스럽습니다

showSheet, query, results, selected를 모두 viewmodelStateFlow와 묶어두고, onAddSelected/onRemoveSelectedviewmodel::add/remove를 그대로 넘기는 구조가 깔끔합니다.
시트 열기/닫기(showSheet 토글) 책임도 화면 쪽에서만 가지고 있어서, 나중에 다른 화면에서 재사용하기에도 좋아 보입니다.

core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/bottomsheet/LocationBottomSheet.kt (1)

79-327: 바텀시트 애니메이션·키보드·선택 제한 로직이 잘 정리되어 있습니다

  • visible 가드 → DialogAnimatable로 슬라이드 인/아웃을 처리하는 흐름이 깔끔합니다.
  • animateAndDismiss()에서 포커스를 블러하고 키보드를 먼저 내린 뒤 애니메이션 후 onDismiss()를 호출해 주는 것도 실제 사용감을 잘 고려한 구조 같습니다.
  • 최대 3개까지 선택, 초과 시 리스트 항목을 enabled = false + 회색 텍스트로 표현하는 부분도 UX 상 명확합니다.
  • 검색 필드 trailing 아이콘으로 포커스를 토글하는 동작과 onFocusChanged에서 키보드를 제어하는 것도 잘 맞물립니다.

현재 구조 기준으로 큰 기능 이슈는 없어 보입니다.

feature/study/src/main/java/com/umcspot/spot/study/register/RegisterStudyScreen.kt (4)

16-24: lifecycle‑aware 상태 수집과 DI 구성이 잘 되어 있습니다.

hiltViewModel()으로 RegisterStudyViewModel을 주입하고, collectAsStateWithLifecycle()uiState를 수집하는 구조가 생명주기 안전하고, 화면 재구성에도 안정적으로 동작할 것으로 보입니다. RegisterStudyState를 단일 진실 소스로 내려보내는 방향도 이후 API 연동·검증 로직 확장에 적합합니다.

Also applies to: 28-29, 36-38, 50-51


53-64: 단계별 뒤로가기 처리 UX가 자연스럽습니다.

handleBackPress에서 먼저 pagerState.currentPage를 확인해 스텝 > 0일 때는 이전 페이지로 애니메이션 이동, 0일 때만 onBackClick()을 호출하는 구조가 잘 잡혀 있습니다. BackHandlerBackTopBar가 같은 핸들러를 공유해 하드웨어·툴바 뒤로가기가 일관된 동작을 하는 점도 좋습니다.

Also applies to: 81-85


95-111: 카테고리/장소/정보 단계의 상태 전달 및 선택 로직이 의도에 맞게 잘 구성되어 있습니다.

onThemeSelect에서 현재 선택 목록을 복사해 토글하고, 최대 3개까지만 추가되도록 제약을 두는 로직이 명확합니다. 각 Step 화면으로는 uiState의 필드를 명시적으로 풀어서 내려주고 있어(예: 선택된 지역, 활동비 정보, 성향 preference 리스트 등) 각 화면이 필요한 데이터만 알게 되는 점도 좋습니다.

Also applies to: 159-192


203-209: 단계별 유효성 검사와 활성화 버튼 연동이 일관성 있게 잘 구현되어 있습니다.

isStepValid(pagerState.currentPage)로 현재 스텝의 검증 결과를 계산하고, 이를 SpotActivationButtonisEnabled에 그대로 연결해 시각적 상태와 클릭 가능 여부가 일치하도록 한 점이 좋습니다. 뷰모델의 isStepValid 구현(필수 필드·성향 선택·설명 입력 등)도 UX 요구사항에 맞게 세밀하게 나뉘어 있어 추후 변경에도 대응하기 수월해 보입니다.

Comment on lines +47 to +55
if (dest?.hasRoute(RegisterStudy::class) == true) {
}
else if (navigator.showBackTopBar()) {
val title = when {
dest?.hasRoute(Alert::class) == true -> "알림"
dest?.hasRoute(AppliedAlert::class) == true -> "신청한 알림"
dest?.hasRoute(RecruitingFilter::class) == true -> "모집중인 스터디"
else -> ""
}
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

빈 if 블록을 제거하고 조건을 반전시켜 가독성을 개선하세요.

RegisterStudy 라우트에서 상단바를 표시하지 않는 로직은 올바르지만, 빈 if 블록은 코드 스멜입니다. 조건을 반전시켜 더 명확한 의도를 표현할 수 있습니다.

다음 diff를 적용하여 리팩터링하세요:

-                if (dest?.hasRoute(RegisterStudy::class) == true) {
-                }
-                else if (navigator.showBackTopBar()) {
+                if (dest?.hasRoute(RegisterStudy::class) != true && navigator.showBackTopBar()) {
                     val title = when {
                         dest?.hasRoute(Alert::class) == true -> "알림"
                         dest?.hasRoute(AppliedAlert::class) == true -> "신청한 알림"
                         dest?.hasRoute(RecruitingFilter::class) == true -> "모집중인 스터디"
                         else -> ""
                     }
                     BackTopBar(
                         title = title,
                         onBackClick = { navController.popBackStack() },
                         modifier = Modifier.statusBarsPadding()
                     )
                 } else {
📝 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
if (dest?.hasRoute(RegisterStudy::class) == true) {
}
else if (navigator.showBackTopBar()) {
val title = when {
dest?.hasRoute(Alert::class) == true -> "알림"
dest?.hasRoute(AppliedAlert::class) == true -> "신청한 알림"
dest?.hasRoute(RecruitingFilter::class) == true -> "모집중인 스터디"
else -> ""
}
if (dest?.hasRoute(RegisterStudy::class) != true && navigator.showBackTopBar()) {
val title = when {
dest?.hasRoute(Alert::class) == true -> "알림"
dest?.hasRoute(AppliedAlert::class) == true -> "신청한 알림"
dest?.hasRoute(RecruitingFilter::class) == true -> "모집중인 스터디"
else -> ""
}
🤖 Prompt for AI Agents
In feature/main/src/main/java/com/umcspot/spot/main/MainScreen.kt around lines
47 to 55, there's an empty if (dest?.hasRoute(RegisterStudy::class) == true)
block which is a code smell; invert the condition so you check if
dest?.hasRoute(RegisterStudy::class) != true (or == false) and only then
evaluate navigator.showBackTopBar() and the title when showing the top bar,
removing the empty branch; ensure the logic and title when
Alert/AppliedAlert/RecruitingFilter routes are handled inside the
non-RegisterStudy path.

Comment on lines +82 to +100
val choiceLabels = persistentListOf(
"네트워킹 중시" to "목표/규율 중시",
"단기 목표" to "장기 목표",
"개인 학습 + 함께 토론형" to "공동 학습 + 동시 진행형",
"학습형" to "토론형",
"가볍게 + 유연하게" to "규칙적인 + 계획적인"
)

choiceLabels.forEachIndexed { index, (left, right) ->
BinaryChoiceRow(
leftText = left,
rightText = right,
selectedIndex = preferences[index],
onSelect = { value -> onPersonalityChange(index, value) }
)
if (index < choiceLabels.lastIndex) {
Spacer(modifier = Modifier.height(screenHeightDp(12.dp)))
}
}
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

preferences 인덱싱 안정성 보강 필요

preferences[index] 접근은 넘겨받은 리스트 길이를 전제하고 있어서, 호출 측에서 요소 수가 모자라면 곧바로 IndexOutOfBoundsException이 터집니다. 공개 컴포저블이라면 방어적으로 getOrNull 등을 써서 NPE 없이 처리하거나, 최소한 require(preferences.size == choiceLabels.size)로 계약을 명시해 주세요. 예시는 다음과 같습니다.

-        choiceLabels.forEachIndexed { index, (left, right) ->
-            BinaryChoiceRow(
-                leftText = left,
-                rightText = right,
-                selectedIndex = preferences[index],
-                onSelect = { value -> onPersonalityChange(index, value) }
-            )
+        choiceLabels.forEachIndexed { index, (left, right) ->
+            val selection = preferences.getOrNull(index)
+            BinaryChoiceRow(
+                leftText = left,
+                rightText = right,
+                selectedIndex = selection,
+                onSelect = { value -> onPersonalityChange(index, value) }
+            )
         }

이렇게 하면 리스트 길이가 달라도 안전하게 동작합니다.

📝 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
val choiceLabels = persistentListOf(
"네트워킹 중시" to "목표/규율 중시",
"단기 목표" to "장기 목표",
"개인 학습 + 함께 토론형" to "공동 학습 + 동시 진행형",
"학습형" to "토론형",
"가볍게 + 유연하게" to "규칙적인 + 계획적인"
)
choiceLabels.forEachIndexed { index, (left, right) ->
BinaryChoiceRow(
leftText = left,
rightText = right,
selectedIndex = preferences[index],
onSelect = { value -> onPersonalityChange(index, value) }
)
if (index < choiceLabels.lastIndex) {
Spacer(modifier = Modifier.height(screenHeightDp(12.dp)))
}
}
val choiceLabels = persistentListOf(
"네트워킹 중시" to "목표/규율 중시",
"단기 목표" to "장기 목표",
"개인 학습 + 함께 토론형" to "공동 학습 + 동시 진행형",
"학습형" to "토론형",
"가볍게 + 유연하게" to "규칙적인 + 계획적인"
)
choiceLabels.forEachIndexed { index, (left, right) ->
val selection = preferences.getOrNull(index)
BinaryChoiceRow(
leftText = left,
rightText = right,
selectedIndex = selection,
onSelect = { value -> onPersonalityChange(index, value) }
)
if (index < choiceLabels.lastIndex) {
Spacer(modifier = Modifier.height(screenHeightDp(12.dp)))
}
}
🤖 Prompt for AI Agents
In
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyInfoScreen.kt
around lines 82 to 100, the code directly indexes preferences[index] which can
throw IndexOutOfBoundsException if the passed list is shorter than choiceLabels;
either enforce the contract at the start with require(preferences.size ==
choiceLabels.size) to fail fast, or make the access defensive by using
preferences.getOrNull(index) and mapping null to a safe default (e.g., 0 or an
explicit unselected state) before passing to BinaryChoiceRow; apply one of these
fixes and ensure onSelect still updates the backing state consistently.

Comment on lines +65 to +74
AnimatedVisibility(visible = activityType == ActivityType.OFFLINE && selectedRegions.isNotEmpty()) {
Column {
Spacer(modifier = Modifier.height(screenHeightDp(20.dp)))
SelectedRegionsSection(
selectedRegions = selectedRegions,
onRemoveClick = onRemoveSelected,
onAddClick = onSheetOpen
)
}
}
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

오프라인 선택 후 처음에는 지역 추가 버튼이 아예 보이지 않는 상태입니다

  • AnimatedVisibilityvisible 조건이 activityType == ActivityType.OFFLINE && selectedRegions.isNotEmpty() 로 되어 있어, 오프라인을 선택해도 아직 지역이 하나도 없으면 SelectedRegionsSection 이 전혀 렌더링되지 않습니다.
  • 이 경우 사용자는 SelectedRegionsSection 내부의 AddRegionButton 을 누를 수 없고, 결과적으로 onSheetOpen 을 호출할 경로가 없어서 지역을 추가할 수 없습니다.
  • 오프라인 여부만으로 섹션을 노출하고, 섹션 내부에서 이미 selectedRegions.size < 3 조건으로 버튼 노출을 제어하고 있으므로, 아래처럼 조건을 단순화하는 편이 자연해 보입니다.
-        AnimatedVisibility(visible = activityType == ActivityType.OFFLINE && selectedRegions.isNotEmpty()) {
+        AnimatedVisibility(visible = activityType == ActivityType.OFFLINE) {
             Column {
                 Spacer(modifier = Modifier.height(screenHeightDp(20.dp)))
                 SelectedRegionsSection(
                     selectedRegions = selectedRegions,
                     onRemoveClick = onRemoveSelected,
                     onAddClick = onSheetOpen
                 )
             }
         }
📝 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
AnimatedVisibility(visible = activityType == ActivityType.OFFLINE && selectedRegions.isNotEmpty()) {
Column {
Spacer(modifier = Modifier.height(screenHeightDp(20.dp)))
SelectedRegionsSection(
selectedRegions = selectedRegions,
onRemoveClick = onRemoveSelected,
onAddClick = onSheetOpen
)
}
}
AnimatedVisibility(visible = activityType == ActivityType.OFFLINE) {
Column {
Spacer(modifier = Modifier.height(screenHeightDp(20.dp)))
SelectedRegionsSection(
selectedRegions = selectedRegions,
onRemoveClick = onRemoveSelected,
onAddClick = onSheetOpen
)
}
}
🤖 Prompt for AI Agents
In
feature/study/src/main/java/com/umcspot/spot/study/register/screen/StudyPlaceScreen.kt
around lines 65 to 74, the AnimatedVisibility currently only shows
SelectedRegionsSection when activityType == ActivityType.OFFLINE &&
selectedRegions.isNotEmpty(), preventing the AddRegionButton from ever appearing
for a newly selected offline activity; change the visible condition to only
check activityType == ActivityType.OFFLINE so the section renders immediately
when Offline is selected, rely on SelectedRegionsSection's internal
selectedRegions.size < 3 logic to show/hide the AddRegionButton, and keep the
AnimatedVisibility wrapper as-is to preserve the animation behavior.

@fredleeJH fredleeJH merged commit a89f3cb into develop Nov 28, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍒 [FEAT] 새로운 기능 구현 🐶 종훈

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT/#13] 스터디 등록하기 화면 구현 및 api 연동

1 participant

Comments