Skip to content

Conversation

@etama123
Copy link
Contributor

@etama123 etama123 commented Jan 30, 2026

#️⃣ 이슈 번호

#44


🛠️ 작업 내용

  • 탐색화면(대학 검색 화면)을 Compose로 마이그레이션을 했습니다.
  • 메인 화면으로 가는 부분을 sideEffect라는 이름으로 분리했습니다. 한 번만 발생해야하는 이벤트이기 때문에 sideEffect 객체와 SharedFlow(replay = 0, extraBufferCapacity = 1)를 사용해 작동하도록 구현했습니다.

🙇🏻 중점 리뷰 요청

  • 검색 시에 키보드가 화면에 나타나기 때문에, AnimatedContent를 활용해서 자연스럽게 검색창의 위치가 바뀌도록 했습니다. 근데 검색 내용 입력 시에 검색창이 깜박거리는 부분이 있어서 확인 한 번 씩 부탁드립니다

📸 이미지 첨부 (Optional)

Screen_recording_20260131_021301.mp4

Summary by CodeRabbit

릴리스 노트

  • 리팩토링
    • 탐색 화면 UI 아키텍처를 최신 기술로 현대화
    • 검색 결과 표시 및 상호작용 개선
    • UI 상태 관리 최적화로 성능 향상

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

- 검색 결과 목록을 `ExploreSearchResultList` 컴포저블로 분리했습니다.
- 검색어 유무에 따라 검색창과 로고의 위치가 애니메이션과 함께 동적으로 변경되도록 수정했습니다.
- 검색 실행 시 키보드가 자동으로 숨겨지도록 구현했습니다.
- 화면 전체적인 레이아웃과 패딩을 조정하여 UI를 개선했습니다.
@etama123 etama123 self-assigned this Jan 30, 2026
@etama123 etama123 added the Feat label Jan 30, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • ✅ Full review completed - (🔄 Check again to review again)
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/44

Tip

🧪 Unit Test Generation v2 is now available!

We have significantly improved our unit test generation capabilities.

To enable: Add this to your .coderabbit.yaml configuration:

reviews:
  finishing_touches:
    unit_tests:
      enabled: true

Try it out by using the @coderabbitai generate unit tests command on your code files or under ✨ Finishing Touches on the walkthrough!

Have feedback? Share your thoughts on our Discord thread!


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

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreBackHeader.kt`:
- Around line 34-37: The Icon composable in ExploreBackHeader.kt uses a
hardcoded contentDescription "뒤로가기"; replace it with a localized string by
adding <string name="explore_back">뒤로가기</string> to strings.xml and calling
stringResource(R.string.explore_back) for the Icon's contentDescription (locate
the Icon call using ImageVector.vectorResource and R.drawable.ic_arrow_back).

In
`@app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreSearchBar.kt`:
- Around line 100-103: The KeyboardActions onSearch currently only hides the
keyboard (keyboardController?.hide()) and never triggers the component's search
callback; update the onSearch handler in ExploreSearchBar.kt (the
KeyboardActions onSearch block) to first hide the keyboard and then invoke the
component's search submission function (e.g., call the existing
onSearch/onSearchSubmit/submitSearch lambda or method that performs the search,
passing the current query if required) so the IME search action actually runs
the search logic; also ensure the KeyboardOptions imeAction is set to Search if
not already.

In
`@app/src/main/java/com/daedan/festabook/presentation/explore/ExploreViewModel.kt`:
- Around line 44-50: checkFestivalId() performs synchronous SharedPreferences
I/O by calling exploreRepository.getFestivalId() on the main thread; wrap the
call in viewModelScope.launch(iODispatcher) (same pattern as SplashViewModel) so
getFestivalId() runs on the IO dispatcher and then update _uiState (via
_uiState.update { ... }) from that coroutine, avoiding blocking the main thread.
🧹 Nitpick comments (3)
app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreScreen.kt (2)

81-114: modifier 파라미터가 사용되지 않고 있습니다.

modifier 파라미터가 선언되었지만 Scaffold에 적용되지 않아 외부에서 전달한 modifier가 무시됩니다.

♻️ 수정 제안
 `@Composable`
 fun ExploreSearchScreen(
     query: String,
     searchState: SearchUiState,
     onQueryChange: (String) -> Unit,
     onUniversitySelect: (SearchResultUiModel) -> Unit,
     onBackClick: () -> Unit,
     modifier: Modifier = Modifier,
 ) {
     Scaffold(
+        modifier = modifier,
         containerColor = Color.White,
         topBar = {

116-134: modifier 파라미터가 사용되지 않고 있습니다.

ExploreSearchScreen과 동일하게 modifier 파라미터가 Scaffold에 적용되어야 합니다.

♻️ 수정 제안
 `@Composable`
 fun ExploreLandingScreen(
     query: String,
     searchState: SearchUiState,
     onQueryChange: (String) -> Unit,
     onUniversitySelect: (SearchResultUiModel) -> Unit,
     modifier: Modifier = Modifier,
 ) {
     // ...
     Scaffold(
+        modifier = modifier,
         containerColor = Color.White,
     ) { innerPadding ->
app/src/main/java/com/daedan/festabook/presentation/explore/ExploreViewModel.kt (1)

84-89: viewModelScope.launch가 불필요합니다.

tryEmit은 non-suspending 함수이므로 코루틴 내에서 실행할 필요가 없습니다.

♻️ 수정 제안
 fun onUniversitySelected(university: SearchResultUiModel) {
     exploreRepository.saveFestivalId(university.festivalId)
-    viewModelScope.launch {
-        _sideEffect.tryEmit(ExploreSideEffect.NavigateToMain(university))
-    }
+    _sideEffect.tryEmit(ExploreSideEffect.NavigateToMain(university))
 }

Comment on lines +34 to +37
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back),
contentDescription = "뒤로가기",
tint = Color.Unspecified,
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:

# First, find the ExploreBackHeader.kt file
find . -name "ExploreBackHeader.kt" -type f

Repository: festabook/android

Length of output: 153


🏁 Script executed:

# Check if strings.xml exists and look for existing localization entries
find . -name "strings.xml" -type f | head -20

Repository: festabook/android

Length of output: 98


🏁 Script executed:

# Look for any existing "explore_back" or similar entries in strings.xml files
rg "explore_back|뒤로가기" --type xml -B 2 -A 2

Repository: festabook/android

Length of output: 454


🏁 Script executed:

# Read the ExploreBackHeader.kt file to verify the hardcoded string
cat -n ./app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreBackHeader.kt | head -50

Repository: festabook/android

Length of output: 1862


🏁 Script executed:

# Check if stringResource is already imported or available in the file
rg "import.*stringResource|stringResource" ./app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreBackHeader.kt

Repository: festabook/android

Length of output: 43


🏁 Script executed:

# Check the current imports in ExploreBackHeader.kt
head -30 ./app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreBackHeader.kt

Repository: festabook/android

Length of output: 1067


🏁 Script executed:

# Check strings.xml to see the exact structure and what entries exist
cat -n ./app/src/main/res/values/strings.xml | head -100

Repository: festabook/android

Length of output: 5157


하드코딩된 문자열을 stringResource로 현지화하세요.

현재 "뒤로가기"가 하드코딩되어 있어 다국어 대응이 불가능합니다. stringResource를 사용하여 리소스로 관리해야 합니다.

🔧 제안 수정
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.res.stringResource
@@
             Icon(
                 imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back),
-                contentDescription = "뒤로가기",
+                contentDescription = stringResource(R.string.explore_back),
                 tint = Color.Unspecified,
             )

app/src/main/res/values/strings.xml의 "탐색 화면" 섹션에 다음 항목을 추가하세요:

<string name="explore_back">뒤로가기</string>
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreBackHeader.kt`
around lines 34 - 37, The Icon composable in ExploreBackHeader.kt uses a
hardcoded contentDescription "뒤로가기"; replace it with a localized string by
adding <string name="explore_back">뒤로가기</string> to strings.xml and calling
stringResource(R.string.explore_back) for the Icon's contentDescription (locate
the Icon call using ImageVector.vectorResource and R.drawable.ic_arrow_back).

Comment on lines +100 to +103
KeyboardActions(
onSearch = {
keyboardController?.hide()
},
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

🧩 Analysis chain

🏁 Script executed:

find . -name "ExploreSearchBar.kt" -type f

Repository: festabook/android

Length of output: 152


🏁 Script executed:

head -150 app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreSearchBar.kt

Repository: festabook/android

Length of output: 4557


🏁 Script executed:

sed -n '95,110p' app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreSearchBar.kt

Repository: festabook/android

Length of output: 439


🏁 Script executed:

# Search for the onSearch parameter definition to understand its signature
rg "onSearch\s*[:=]" app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreSearchBar.kt -A 2

Repository: festabook/android

Length of output: 339


🏁 Script executed:

# Look for the function signature and parameter definitions
rg "fun ExploreSearchBar|@Composable" app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreSearchBar.kt -A 5

Repository: festabook/android

Length of output: 624


IME 검색 시 onSearch 콜백이 호출되지 않습니다.

KeyboardActions의 onSearch 핸들러가 키보드 숨김만 수행하고 실제 검색 함수를 호출하지 않아 검색 기능이 작동하지 않습니다.

제안 수정
         keyboardActions =
             KeyboardActions(
                 onSearch = {
+                    onSearch(query)
                     keyboardController?.hide()
                 },
             ),
📝 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
KeyboardActions(
onSearch = {
keyboardController?.hide()
},
KeyboardActions(
onSearch = {
onSearch(query)
keyboardController?.hide()
},
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/daedan/festabook/presentation/explore/component/ExploreSearchBar.kt`
around lines 100 - 103, The KeyboardActions onSearch currently only hides the
keyboard (keyboardController?.hide()) and never triggers the component's search
callback; update the onSearch handler in ExploreSearchBar.kt (the
KeyboardActions onSearch block) to first hide the keyboard and then invoke the
component's search submission function (e.g., call the existing
onSearch/onSearchSubmit/submitSearch lambda or method that performs the search,
passing the current query if required) so the IME search action actually runs
the search logic; also ensure the KeyboardOptions imeAction is set to Search if
not already.

Comment on lines +44 to +50
private fun checkFestivalId() {
val festivalId = exploreRepository.getFestivalId()
Timber.d("festival ID : $festivalId")
if (festivalId != null) {
_uiState.update { it.copy(hasFestivalId = true) }
}
}
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:

# Find ExploreRepository and getFestivalId implementation
find . -type f -name "*.kt" | xargs grep -l "fun getFestivalId"

Repository: festabook/android

Length of output: 406


🏁 Script executed:

# Look for ExploreRepository file
fd -e "\.kt$" ExploreRepository

Repository: festabook/android

Length of output: 43


🏁 Script executed:

# Check ExploreViewModel.kt to see the current implementation
fd -e "\.kt$" ExploreViewModel

Repository: festabook/android

Length of output: 43


🏁 Script executed:

# Search for SplashViewModel to compare patterns
fd -e "\.kt$" SplashViewModel

Repository: festabook/android

Length of output: 43


🏁 Script executed:

# Find all ViewModel files
find . -type f -name "*ViewModel.kt"

Repository: festabook/android

Length of output: 790


🏁 Script executed:

# Look at ExploreRepositoryImpl to understand getFestivalId
cat ./app/src/main/java/com/daedan/festabook/data/repository/ExploreRepositoryImpl.kt

Repository: festabook/android

Length of output: 1398


🏁 Script executed:

# Check the ExploreRepository interface
cat ./app/src/main/java/com/daedan/festabook/domain/repository/ExploreRepository.kt

Repository: festabook/android

Length of output: 331


🏁 Script executed:

# Search for ExploreViewModel in the codebase
rg "class ExploreViewModel" -A 50

Repository: festabook/android

Length of output: 11420


🏁 Script executed:

# Check FestivalLocalDataSource implementation
cat ./app/src/main/java/com/daedan/festabook/data/datasource/local/FestivalLocalDataSourceImpl.kt

Repository: festabook/android

Length of output: 1360


🏁 Script executed:

# Check the interface
cat ./app/src/main/java/com/daedan/festabook/data/datasource/local/FestivalLocalDataSource.kt

Repository: festabook/android

Length of output: 259


🏁 Script executed:

# Check SplashViewModel to see the claimed pattern
cat ./app/src/main/java/com/daedan/festabook/presentation/splash/SplashViewModel.kt

Repository: festabook/android

Length of output: 1996


checkFestivalId()가 메인 스레드에서 동기적으로 SharedPreferences I/O를 수행합니다.

getFestivalId()SharedPreferences.getLong()을 호출하는 동기 작업입니다. SplashViewModel에서는 동일한 작업을 viewModelScope.launch(iODispatcher)로 감싸서 실행합니다. 메인 스레드 블로킹을 피하려면 이 패턴을 따르세요.

🛡️ 수정 제안
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+
 class ExploreViewModel(
     private val exploreRepository: ExploreRepository,
+    private val iODispatcher: CoroutineDispatcher = Dispatchers.IO,
 ) : ViewModel() {

     private fun checkFestivalId() {
+        viewModelScope.launch(iODispatcher) {
             val festivalId = exploreRepository.getFestivalId()
             Timber.d("festival ID : $festivalId")
             if (festivalId != null) {
                 _uiState.update { it.copy(hasFestivalId = true) }
             }
+        }
     }
 }
📝 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
private fun checkFestivalId() {
val festivalId = exploreRepository.getFestivalId()
Timber.d("festival ID : $festivalId")
if (festivalId != null) {
_uiState.update { it.copy(hasFestivalId = true) }
}
}
private fun checkFestivalId() {
viewModelScope.launch {
val festivalId = withContext(iODispatcher) {
exploreRepository.getFestivalId()
}
Timber.d("festival ID : $festivalId")
if (festivalId != null) {
_uiState.update { it.copy(hasFestivalId = true) }
}
}
}
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/daedan/festabook/presentation/explore/ExploreViewModel.kt`
around lines 44 - 50, checkFestivalId() performs synchronous SharedPreferences
I/O by calling exploreRepository.getFestivalId() on the main thread; wrap the
call in viewModelScope.launch(iODispatcher) (same pattern as SplashViewModel) so
getFestivalId() runs on the IO dispatcher and then update _uiState (via
_uiState.update { ... }) from that coroutine, avoiding blocking the main thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants