Skip to content

Conversation

@sxunea
Copy link
Collaborator

@sxunea sxunea commented Aug 22, 2025

작업 내용

  • 채팅 인트로
    • 온보딩끝나자마자 createRoom해서 온 첫 메시지 요정 위로 뜨도록
    • 화면이 답장하면 해당 두 메시지 가지고 채팅방으로 이동
    • 첫 두메시지는 이미 노출되었으므로 타자기 효과 제거
    • 그 외의 디자인 디테일 적용
  • base url 변경
    • https로 변경되어 secure, plist 관련 코드 삭제
    • github secret도 맞추어 변경
    • 그에 따라 ci/cd 불필요 코드 제거
  • 스플래시스크린
    • splashScreen 의존성 추가해 installSplashScreen()
  • 앱 아이콘
    • 디자이너가 넘겨준 아이콘 해상도별 추가

이건 꼭 봐주세요

  • 스플래시랑 앱 아이콘도 그냥같이해뒀어요 !! 근데 아이콘이 rounded는없다보니까 지금 스플래시에 네모로 뜨는데 이건 좀 수정해볼게요. 재시도하기 로직이 더 급하니까 일단 피알 먼저 올려둡니다 ~

Summary by CodeRabbit

  • New Features

    • Android 스플래시 화면 도입 및 전용 테마 추가
    • 대화 시작 시 ‘첫 메시지’(방 생성) 화면 추가
  • Style

    • 초기 두 메시지에 타이핑 애니메이션 건너뛰기 옵션 추가
    • 채팅 입력창 외곽 보더 추가 및 플레이스홀더 가독성 개선
    • 페어리 선택 영역 동작/배치 개선 및 타이핑 인디케이터 위치 조정
    • 앱 아이콘/스플래시 리소스 정리 및 비트맵 전환
  • Chores

    • CI의 네트워크 설정 자동 생성 제거 및 기본 프로토콜 HTTPS 전환
    • iOS/Android의 평문 트래픽 예외 제거
    • Android SplashScreen 라이브러리 의존성 추가

@coderabbitai
Copy link

coderabbitai bot commented Aug 22, 2025

Walkthrough

안드로이드 스플래시 화면·아이콘 리소스와 채팅방 생성 인트로 UI를 추가하고, 기본 프로토콜을 HTTP→HTTPS로 변경했으며 CI 및 플랫폼별 평문(HTTP) 예외(network_security_config.xml, Info.plist) 생성을 제거했습니다. 일부 네트워크 API에서 토큰 파라미터가 제거되었습니다.

Changes

Cohort / File(s) Summary
CI 네트워크 설정 제거
.github/workflows/ace_build.yml, ​.github/workflows/ace_firebase_distribution.yml
CI에서 network_security_config.xml 동적 생성 단계 삭제; 빌드/배포 나머지 단계 유지.
안드로이드 매니페스트·테마·리소스 업데이트
composeApp/src/androidMain/AndroidManifest.xml, composeApp/src/androidMain/res/values/themes.xml, composeApp/src/androidMain/res/drawable/ic_splash_screen.xml, composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml, composeApp/src/androidMain/res/drawable/ic_launcher_background.xml, composeApp/src/androidMain/res/mipmap-anydpi-v26/...
SplashScreen 테마/리소스 추가 및 적용, networkSecurityConfig 속성 제거, 벡터에서 비트맵으로 런처 리소스 변경, 일부 adaptive 아이콘 삭제.
액티비티 초기화 변경
composeApp/src/androidMain/kotlin/.../MainActivity.kt
onCreate 초기에 installSplashScreen() 호출 추가(Edge-to-edge 등 기존 호출 위치 유지).
Gradle 의존성·버전 추가
composeApp/build.gradle.kts, gradle/libs.versions.toml
androidx.core:core-ktxandroidx.core:core-splashscreen 의존성 추가 및 splashscreen 버전 선언.
네트워크 프로토콜·API 시그니처 변경
core/network/src/.../EmotiaNetwork.kt, core/network/src/.../service/ChatApiService.kt, core/data/chatting/src/.../ChattingRemoteDataSourceImpl.kt
기본Request URL을 HTTPS로 변경. getFairies에서 token 파라미터 제거 및 호출부에서 토큰 미전달.
채팅 UI 및 상태 변경
feature/chatting/src/commonMain/kotlin/.../ChattingScreen.kt, .../ChattingViewModel.kt, .../contract/ChattingState.kt
메시지 수 기반 2모드(생성 인트로/채팅) 도입, CreateRoom 컴포저블 추가, firstMessage 상태 필드 추가, 페어리 페이저/선택 로직 및 초기 메시지에 대한 타입라이터 스킵 적용.
디자인시스템 변경
core/designsystem/src/commonMain/kotlin/.../ChatBubble.kt, .../ChatTextField.kt
ChatBubble에 skipTypewriterEffect 파라미터 추가(전달), ChatTextField 외곽 border 추가 및 placeholder 스타일 수정.
플랫폼 평문 허용 예외 제거
composeApp/src/main/res/xml/network_security_config.xml, iosApp/iosApp/Info.plist
Android 네트워크 보안 설정 파일과 iOS ATS 예외 도메인 블록 삭제(평문 허용 제거).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as 사용자
    participant Android as Android OS
    participant App as MainActivity
    participant UI as Compose UI

    Android->>App: 앱 시작
    App->>App: installSplashScreen()
    App->>Android: 스플래시 테마 표시
    App->>UI: setContent()
    UI-->>Android: UI 렌더링 (CreateRoom 또는 ChattingScreen)
    Android-->>User: 스플래시 → 메인 화면 전환
Loading
sequenceDiagram
    autonumber
    participant VM as ChattingViewModel
    participant UI as ChattingScreen
    participant DS as DesignSystem

    VM->>UI: state(messages, firstMessage, fairies, showFairyPager)
    alt 메시지 수 ≤ 1
        UI->>UI: CreateRoom 렌더(입력, 첫메시지 표시)
        UI->>DS: ChatBubble(skipTypewriterEffect=true)
    else 메시지 수 > 1
        UI->>UI: 채팅 리스트 렌더, 페어리 페이저 동작
        UI->>DS: ChatBubble(초기 항목에 skipTypewriterEffect=true)
        UI->>VM: SelectFairy(currentPage)
    end
    UI->>VM: onInputTextChange / onSendClick
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
채팅방 생성 초기화면 구현 [#37]
https로의 base url 변경 및 관련 secure/plist/CI 수정 [#41]
스플래시 스크린 구현 및 앱 아이콘 적용 [#42]

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
토큰 파라미터 제거 및 호출부 수정 (core/network/.../service/ChatApiService.kt, core/data/chatting/.../ChattingRemoteDataSourceImpl.kt) #41은 HTTPS 전환·플랫폼 보안 예외 제거·CI 수정에 집중됨. 토큰 제거는 이 이슈들의 명시적 요구사항에 포함되지 않아 범위를 벗어날 가능성이 있음.

Possibly related PRs

Suggested reviewers

  • haeti-dev
  • jife-archive

Poem

토끼가 깡충, 스플래시 문을 밀고 들어가요.
HTTPS 길을 따라 귀가 반짝, 안전히 달려요.
편지지 배경에 요정이 미소 지으며 기다리고,
첫 인사 부드럽게—타자 소리 없이 포근히.
새 아이콘 달고 출발! 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/chatting-intro

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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 (2)
feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (2)

3-3: 컴파일 오류 — 잘못된 import 구문

import EmotiaChatTextField는 패키지 없이 심볼만 가져오는 형태로 Kotlin에서 허용되지 않습니다. 정규 패키지를 명시하세요.

-import EmotiaChatTextField
+import com.nexters.emotia.core.designsystem.component.EmotiaChatTextField

135-141: 잠재적 NumberFormatException — roomId toInt 변환

uiState.roomId?.toInt() ?: 0roomId가 숫자 문자열이 아닐 때 NFE로 크래시가 납니다. 안전 변환으로 교체하세요.

-                    uiState.roomId?.toInt() ?: 0
+                    uiState.roomId?.toIntOrNull() ?: 0
🧹 Nitpick comments (18)
feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingViewModel.kt (3)

61-69: 빈 감정 칩 표시 가능성 — showEmotionChips=true인데 emotionOptions가 비어 있음

채팅방 생성 직후 showEmotionChips = true로 켜지만, 동시에 emotionOptions = persistentListOf()로 비워 두고 있어, UI에 “비어 있는 칩 영역”만 노출될 수 있습니다. 옵션 로딩 전까지는 칩을 숨기거나, 로딩이 끝난 뒤에만 켜는 편이 자연스럽습니다.

다음처럼 기본값을 false로 두고, 실제 옵션을 주입할 때 토글하는 것을 권장합니다.

-                        showEmotionChips = true,
-                        emotionOptions = persistentListOf(),
+                        showEmotionChips = false,
+                        emotionOptions = persistentListOf(),

또는 reduce 이전에 임시 리스트를 만들어 showEmotionChips = options.isNotEmpty()로 일관되게 설정하세요.


55-60: 중복 상태 보관(firstMessage) — messages[0]와 firstMessage 동시 유지

최초 메시지를 messages의 첫 요소로 넣는 동시에 state.firstMessage로도 별도 보관합니다. 동일 데이터의 이중 소스는 추후 동기화 비용/버그 포인트가 됩니다. UI에서 “첫 메시지 존재 여부/내용”이 필요하다면, 장기적으로는 messages.firstOrNull()?.text로 유도하고 firstMessage는 제거하는 편이 단순합니다. 지금 PR 목적 상 유지가 필요하다면 TODO로 정리해두면 좋겠습니다.

Also applies to: 61-69


30-31: 하드코딩 문자열을 리소스로 이전하세요

다음 문자열은 다국어/일관성/테스트 용이성을 위해 Compose Resources(Res.string.*)로 이전하는 것이 좋습니다.

  • MAX_CHAT_REACHED_MESSAGE(Line 30)
  • "채팅룸 생성에 실패했습니다: ..."(Line 73)
  • "메시지 전송에 실패했습니다: ..."(Line 139)

리소스 키(예: chat_max_reached_message, error_create_room_failed, error_send_message_failed) 정의 후 교체해 주세요.

Also applies to: 73-74, 139-140

feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/contract/ChattingState.kt (1)

20-21: selectedFairyIndex 기본값 1은 놀랄 수 있음 — 0이 더 안전

기본 리스트가 빈 상태이므로, 기본 인덱스는 일반적으로 0이 안전합니다. ViewModel/Screen에서 coerceIn으로 방어하고 있지만, 기본값이 1인 것은 오해 유발 여지가 있습니다. 초기 페이지를 1로 열고 싶다면 화면 레벨에서만 처리하고, 상태 기본값은 0을 권장합니다.

-    val selectedFairyIndex: Int = 1,
+    val selectedFairyIndex: Int = 0,
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatTextField.kt (1)

123-127: 이중 보더(OutlinedTextField + Modifier.border)로 과도한 강조/오버드로우 가능

외곽선(Modifier.border)과 내부 OutlinedTextField 보더가 동시에 그려져 시각적으로 두껍거나, 테마 변경 시 관리 포인트가 2곳으로 늘어납니다. 내부 보더를 투명 처리해 외곽선만 유지하는 편이 단순합니다.

적용 예:

-        colors = OutlinedTextFieldDefaults.colors(
-            focusedBorderColor = state.getBorderColor(),
-            unfocusedBorderColor = state.getBorderColor(),
-            disabledBorderColor = state.getBorderColor(),
+        colors = OutlinedTextFieldDefaults.colors(
+            focusedBorderColor = Color.Transparent,
+            unfocusedBorderColor = Color.Transparent,
+            disabledBorderColor = Color.Transparent,

또는 반대로 Modifier.border를 제거하고 내부 보더만 사용하도록 일원화하세요.

feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (3)

156-166: 스크롤 인덱스 계산 재검토

showFairyPager가 true일 때 lastIndex = uiState.messages.size로 스크롤합니다. 현재 구현은 messages 블록 뒤에 항상 item { ... }를 추가하므로 유효 인덱스가 맞지만, 향후 아이템 구조 변경 시 범위를 벗어날 수 있습니다. 가장 안전한 패턴은 “실제 아이템 수 - 1”로 스크롤하거나, Lazy DSL의 고정 푸터 아이템 개수를 명시적으로 더해 계산하는 것입니다.

예)

  • val extra = if (uiState.isLoading && uiState.messages.isNotEmpty()) 1 else 0 // 타이핑 인디케이터
  • val footer = 1 // 요정 카드 컨테이너 item
  • animateScrollToItem(uiState.messages.size + extra + footer - 1)

238-248: N^2 성능 — 메시지마다 indexOf 호출

uiState.messages.indexOf(message)를 각 아이템에서 호출하면 O(n^2) 입니다. itemsIndexed로 대체하여 인덱스를 바로 받으세요.

-                        items(
-                            items = uiState.messages,
-                            key = { message -> message.timestamp }
-                        ) { message ->
-                            val messageIndex = uiState.messages.indexOf(message)
+                        itemsIndexed(
+                            items = uiState.messages,
+                            key = { _, message -> message.timestamp }
+                        ) { messageIndex, message ->
                             ChatBubble(
                                 text = message.text,
                                 type = message.type,
                                 messageId = message.timestamp.toString(),
                                 skipTypewriterEffect = messageIndex <= 1
                             )
                         }

371-373: 하드코딩 문자열을 리소스로 이전

다음 텍스트는 Compose Resources로 추출해 주세요. 다국어 및 테스트 용이성에 유리합니다.

  • 버튼 라벨: "내 감정은 ${uiState.fairies[selectedIndex].emotion}이야"
  • 링크 텍스트: "다시 대화하기"
  • 플레이스홀더: "요정에게 지금 기분을 설명해보자" (2곳)

기존 테마/타이포그래피 그대로 유지되도록 stringResource + 서브스트링 포맷으로 교체를 권장합니다.

Also applies to: 380-391, 407-408, 538-539

core/network/src/commonMain/kotlin/com/nexters/emotia/network/EmotiaNetwork.kt (3)

40-43: Ktor Logging 레벨 ALL은 토큰/민감정보 노출 우려 — 릴리스 빌드에서 비활성화 권장

현재 전역으로 LogLevel.ALL을 사용하면 요청/응답 바디/헤더가 로그에 남을 수 있습니다(Authorization 헤더 포함 가능). 빌드 타입/플랫폼별로 로그를 게이팅하세요.

예시(개념 코드): KMP에서 expect/actual 또는 BuildKonfig로 디버그 플래그를 주입해 디버그에서만 설치.

if (Platform.isDebug) {
    install(Logging) {
        logger = Logger.DEFAULT
        level = LogLevel.HEADERS // 최소화 또는 BODY 금지
    }
}

86-90: get(..., token: String?) 매개변수는 더 이상 사용되지 않음 — 혼동 방지를 위해 제거 또는 Deprecate

토큰은 Auth { bearer { ... } }로 처리되고 있어 token 매개변수가 무의미합니다. API 혼동을 줄이기 위해 Deprecate 후 제거를 제안합니다.

-    suspend inline fun <reified T : Any> get(
-        path: String,
-        token: String? = null,
-    ): T = httpClient.get(path) {
-    }.body()
+    @Deprecated("토큰은 Auth 플러그인이 처리합니다. token 인자는 곧 제거 예정입니다.", ReplaceWith("get<T>(path)"))
+    suspend inline fun <reified T : Any> get(
+        path: String,
+        token: String? = null,
+    ): T = httpClient.get(path) {}.body()
+
+    // 또는 즉시 제거
+    // suspend inline fun <reified T : Any> get(path: String): T =
+    //     httpClient.get(path).body()

78-82: 기능 개선: baseUrl 파싱을 URLBuilder.takeFrom으로 일원화하고 파라미터명을 명확히 변경

다음과 같이 리팩토링을 권장드립니다. 현재 호스트명만 지정하는 hostName 대신 전체 URL을 입력받아 Ktor의 takeFrom(...)으로 한 번에 안전하게 파싱하도록 일원화하고, 파라미터명도 hostNamebaseUrl로 바꿔 가독성과 의도를 명확히 합니다.

– 변경 위치

  • core/network/src/commonMain/kotlin/com/nexters/emotia/network/EmotiaNetwork.kt

– 주요 변경사항

  1. 함수 시그니처
    • 기존
      private fun createHttpClient(hostName: String): HttpClient = HttpClient {
    • 변경
      private fun createHttpClient(baseUrl: String): HttpClient = HttpClient {
  2. defaultRequest 내 URL 설정
    • 기존
      defaultRequest {
          contentType(ContentType.Application.Json)
          url {
              protocol = URLProtocol.HTTPS
              host = hostName
          }
      }
    • 변경
      defaultRequest {
          contentType(ContentType.Application.Json)
          url {
              takeFrom(baseUrl)
          }
      }

– 호출부 수정
EmotiaNetwork.kt 29행

- val httpClient = createHttpClient(NetworkConfig.baseUrl)
+ val httpClient = createHttpClient(NetworkConfig.baseUrl)

(시그니처만 변경됐기 때문에 호출부는 그대로 유지됩니다.)

– 추가 검토
Android 쪽 BuildConfig.BASE_URL(BuildConfig.android.kt)와 iOS 쪽 NetworkConfigLocal.BASE_URL(NetworkConfig.ios.kt) 모두 스킴(https://) 포함 여부가 환경별로 다를 수 있으니, CI/CD 시크릿이나 Gradle 설정에서 BASE_URL 값에 스킴이 포함되어 있는지 한 번만 확인해주세요.

--- a/core/network/src/commonMain/kotlin/com/nexters/emotia/network/EmotiaNetwork.kt
+++ b/core/network/src/commonMain/kotlin/com/nexters/emotia/network/EmotiaNetwork.kt
@@ -29,7 +29,7 @@ class EmotiaNetwork(
     val httpClient = createHttpClient(NetworkConfig.baseUrl)
 
-    private fun createHttpClient(hostName: String): HttpClient = HttpClient {
+    private fun createHttpClient(baseUrl: String): HttpClient = HttpClient {
         ...
         defaultRequest {
             contentType(ContentType.Application.Json)
@@ -81,8 +81,5 @@ class EmotiaNetwork(
             url {
-                protocol = URLProtocol.HTTPS
-                host = hostName
+                takeFrom(baseUrl)
             }
         }
     }
composeApp/build.gradle.kts (1)

65-71: debug 빌드타입 블록이 중복 선언되어 있음 — 하나로 합치세요

debug { ... }getByName("debug") { ... }가 나뉘어 있어 유지보수성이 떨어집니다. 한 블록으로 합치는 것이 명확합니다.

-    buildTypes {
-        debug {
-            isDebuggable = true
-        }
-        getByName("debug") {
-            isMinifyEnabled = false
-        }
-    }
+    buildTypes {
+        debug {
+            isDebuggable = true
+            isMinifyEnabled = false
+        }
+    }
composeApp/src/androidMain/kotlin/com/nexters/emotia/MainActivity.kt (2)

14-16: installSplashScreen() 호출 위치는 좋으나, super.onCreate() 호출 순서 점검 권장

권장 순서는 installSplashScreen()super.onCreate()enableEdgeToEdge()setContent(...)입니다. 현재는 enableEdgeToEdge()super.onCreate()보다 앞에 와 있어, 일부 기기/테마 조합에서 윈도우 플래그 적용 타이밍 차이가 생길 수 있습니다.

     override fun onCreate(savedInstanceState: Bundle?) {
-        installSplashScreen()
-        
-        enableEdgeToEdge()
-        super.onCreate(savedInstanceState)
+        installSplashScreen()
+        super.onCreate(savedInstanceState)
+        enableEdgeToEdge()

14-15: 초기 로딩(온보딩/채팅 인트로 준비) 동안 스플래시 유지가 필요하면 setKeepOnScreenCondition을 활용하세요

채팅 인트로의 첫 메시지 프리패치/상태 초기화가 필요하다면 스플래시 유지 조건을 걸어 UX를 매끈하게 만들 수 있습니다.

예시:

val splash = installSplashScreen()
super.onCreate(savedInstanceState)
// viewModel의 준비 상태 플래그 사용 예시
splash.setKeepOnScreenCondition { !viewModel.isInitialized }
composeApp/src/androidMain/AndroidManifest.xml (1)

9-12: icon/roundIcon이 밀도 고정 리소스를 가리킴 — 일반 alias 또는 어댑티브 아이콘으로 교체 권장

현재 @mipmap/ic_launcher_xxxhdpi는 특정 밀도 전용입니다. 런처/스플래시에 동일 비트맵을 재사용하더라도, 매니페스트에는 @mipmap/ic_launcher(및 @mipmap/ic_launcher_round) 같은 일반 alias를 사용하세요. 어댑티브 아이콘(anydpi-v26) 복원 시 동적 마스크/머티리얼 유연성이 좋아집니다.

-        android:icon="@mipmap/ic_launcher_xxxhdpi"
+        android:icon="@mipmap/ic_launcher"
...
-        android:roundIcon="@mipmap/ic_launcher_xxxhdpi"
+        android:roundIcon="@mipmap/ic_launcher_round"

작성자 코멘트대로 라운드 아이콘은 추후 조정 예정이라고 하셨으니, 최소한 위 변경으로 안전한 기본값만 먼저 적용하는 것을 제안드립니다.

gradle/libs.versions.toml (1)

33-33: SplashScreen 의존성 안정 버전 사용 검토 요청

gradle/libs.versions.toml 파일의 아래 라인을 확인해 주세요:

androidx-splashscreen = "1.2.0-alpha02"

– 최신 RC 릴리즈인 1.2.0-rc01(2025-07-02) 또는 완전 안정(release) 릴리즈인 1.0.1(2023-04) 사용을 권장합니다.
– 특별히 알파 기능이 꼭 필요하다면, 해당 이유를 PR 설명에 명시해 주세요.

Possible replacements:
• androidx-splashscreen = "1.2.0-rc01"
• androidx-splashscreen = "1.0.1"

composeApp/src/androidMain/res/values/themes.xml (2)

4-8: Splash 테마 구성은 적절 — 다크 모드 대비(white flash)용 values-night 오버레이 추가 제안

현재 Splash 배경이 고정 흰색이므로 다크 모드에서 “화이트 플래시”가 발생할 수 있습니다. values-night에 동일 스타일을 정의해 어두운 배경을 지정하는 것을 권장합니다.

예시(새 파일: composeApp/src/androidMain/res/values-night/themes.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.Emotia.SplashScreen" parent="Theme.SplashScreen">
        <item name="windowSplashScreenBackground">@android:color/black</item>
        <item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash_screen</item>
        <item name="postSplashScreenTheme">@style/Theme.Emotia.Main</item>
    </style>
</resources>

추가로 필요 시 애니메이션 지속시간/아이콘 배경 컬러도 조절 가능합니다:

  • item name="windowSplashScreenAnimationDuration"
  • item name="windowSplashScreenIconBackgroundColor"

10-16: Edge-to-Edge는 WindowInsets 기반 접근 권장 — legacy translucent 플래그 제거 검토

android:windowTranslucentStatus/Navigation=true는 레거시 방식입니다. Compose 환경에서는 WindowCompat.setDecorFitsSystemWindows(window, false) + 시스템 바 inset 처리로 일관되게 Edge-to-Edge를 구현하는 편이 안정적입니다. 해당 플래그 제거 및 코드 측면 처리로의 전환을 검토해 주세요.

전환 시 다음을 점검해 주세요:

  • 루트 컨테이너에서 WindowInsets 소비 여부
  • 라이트/다크 모드에 따른 status bar 아이콘 대비(android:windowLightStatusBar)
  • 내비게이션 바 색상/아이콘 대비
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 01f014f and dc9938f.

⛔ Files ignored due to path filters (16)
  • composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_hdpi.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mdpi.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_xhdpi.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_xxhdpi.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_xxxhdpi.png is excluded by !**/*.png
  • core/designsystem/src/commonMain/composeResources/drawable/img_chatting_fairy.png is excluded by !**/*.png
📒 Files selected for processing (22)
  • .github/workflows/ace_build.yml (0 hunks)
  • .github/workflows/ace_firebase_distribution.yml (0 hunks)
  • composeApp/build.gradle.kts (1 hunks)
  • composeApp/src/androidMain/AndroidManifest.xml (1 hunks)
  • composeApp/src/androidMain/kotlin/com/nexters/emotia/MainActivity.kt (1 hunks)
  • composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml (1 hunks)
  • composeApp/src/androidMain/res/drawable/ic_launcher_background.xml (1 hunks)
  • composeApp/src/androidMain/res/drawable/ic_splash_screen.xml (1 hunks)
  • composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml (0 hunks)
  • composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml (0 hunks)
  • composeApp/src/androidMain/res/values/themes.xml (1 hunks)
  • composeApp/src/main/res/xml/network_security_config.xml (0 hunks)
  • core/data/chatting/src/commonMain/kotlin/com/nexters/emotia/core/data/chatting/datasource/ChattingRemoteDataSourceImpl.kt (0 hunks)
  • core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatBubble.kt (3 hunks)
  • core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatTextField.kt (2 hunks)
  • core/network/src/commonMain/kotlin/com/nexters/emotia/network/EmotiaNetwork.kt (1 hunks)
  • core/network/src/commonMain/kotlin/com/nexters/emotia/network/service/ChatApiService.kt (0 hunks)
  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (6 hunks)
  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingViewModel.kt (1 hunks)
  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/contract/ChattingState.kt (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
  • iosApp/iosApp/Info.plist (0 hunks)
💤 Files with no reviewable changes (8)
  • .github/workflows/ace_firebase_distribution.yml
  • iosApp/iosApp/Info.plist
  • core/network/src/commonMain/kotlin/com/nexters/emotia/network/service/ChatApiService.kt
  • composeApp/src/main/res/xml/network_security_config.xml
  • core/data/chatting/src/commonMain/kotlin/com/nexters/emotia/core/data/chatting/datasource/ChattingRemoteDataSourceImpl.kt
  • composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml
  • composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml
  • .github/workflows/ace_build.yml
🧰 Additional context used
📓 Path-based instructions (4)
composeApp/build.gradle*

📄 CodeRabbit inference engine (CLAUDE.md)

composeApp/build.gradle*: Common dependencies in commonMain.dependencies
Platform-specific dependencies in respective androidMain/iosMain blocks
Static iOS frameworks for better performance
Resource exclusions configured for Android packaging

Files:

  • composeApp/build.gradle.kts
**/build.gradle*

📄 CodeRabbit inference engine (CLAUDE.md)

Use libs.* references for dependency declarations

Files:

  • composeApp/build.gradle.kts
gradle/libs.versions.toml

📄 CodeRabbit inference engine (CLAUDE.md)

Version catalog managed in gradle/libs.versions.toml

Files:

  • gradle/libs.versions.toml
composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt: Follow expect/actual pattern for platform-specific code
Use @composable functions with @Preview annotations
Leverage Compose resources via Res.drawable.* and Res.string.*
Use expect/actual for platform-specific APIs (camera, notifications, etc.)
Use Compose Resources for strings, images, and other assets

Files:

  • composeApp/src/androidMain/kotlin/com/nexters/emotia/MainActivity.kt
🧠 Learnings (13)
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/build.gradle* : Platform-specific dependencies in respective androidMain/iosMain blocks

Applied to files:

  • composeApp/build.gradle.kts
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/build.gradle* : Common dependencies in commonMain.dependencies

Applied to files:

  • composeApp/build.gradle.kts
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt : Use Compose Resources for strings, images, and other assets

Applied to files:

  • composeApp/build.gradle.kts
  • composeApp/src/androidMain/res/drawable/ic_launcher_background.xml
  • composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml
  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt
  • composeApp/src/androidMain/res/drawable/ic_splash_screen.xml
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/build.gradle* : Resource exclusions configured for Android packaging

Applied to files:

  • composeApp/build.gradle.kts
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/build.gradle* : Static iOS frameworks for better performance

Applied to files:

  • composeApp/build.gradle.kts
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt : Use Composable functions with Preview annotations

Applied to files:

  • composeApp/build.gradle.kts
  • composeApp/src/androidMain/kotlin/com/nexters/emotia/MainActivity.kt
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt : Leverage Compose resources via Res.drawable.* and Res.string.*

Applied to files:

  • composeApp/build.gradle.kts
  • composeApp/src/androidMain/res/drawable/ic_launcher_background.xml
  • composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml
  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt
  • composeApp/src/androidMain/res/drawable/ic_splash_screen.xml
📚 Learning: 2025-08-10T08:22:45.334Z
Learnt from: sxunea
PR: Nexters/team-ace-client#22
File: feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt:26-31
Timestamp: 2025-08-10T08:22:45.334Z
Learning: In Kotlin Multiplatform (KMP) projects, `org.koin.compose.viewmodel.koinViewModel()` from Koin is now compatible with commonMain source sets and can be used across all platforms, not just Android.

Applied to files:

  • composeApp/build.gradle.kts
📚 Learning: 2025-08-10T08:22:45.334Z
Learnt from: sxunea
PR: Nexters/team-ace-client#22
File: feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt:26-31
Timestamp: 2025-08-10T08:22:45.334Z
Learning: In Kotlin Multiplatform (KMP) projects, `androidx.lifecycle.compose.collectAsStateWithLifecycle` is now compatible with commonMain source sets and can be used across all platforms, not just Android.

Applied to files:

  • composeApp/build.gradle.kts
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt : Use expect/actual for platform-specific APIs (camera, notifications, etc.)

Applied to files:

  • composeApp/build.gradle.kts
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt : Follow expect/actual pattern for platform-specific code

Applied to files:

  • composeApp/build.gradle.kts
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to gradle/libs.versions.toml : Version catalog managed in gradle/libs.versions.toml

Applied to files:

  • gradle/libs.versions.toml
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/commonMain/kotlin/**/*.kt : Use common state management patterns (ViewModel, StateFlow)

Applied to files:

  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt
🧬 Code graph analysis (2)
feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (5)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatBubble.kt (1)
  • ChatBubble (116-161)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatTypingIndicator.kt (1)
  • TypingIndicator (28-44)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/FairyCard.kt (1)
  • FairyCard (38-138)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/EmotiaButton.kt (1)
  • EmotiaButton (16-42)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatTextField.kt (1)
  • EmotiaChatTextField (93-170)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatBubble.kt (1)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/Typewriter.kt (1)
  • TypewriterText (29-72)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: Firebase App Distribution
🔇 Additional comments (10)
feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/contract/ChattingState.kt (1)

21-22: LGTM — firstMessage 필드 추가

copy/equals/serialization에도 자연스럽게 포함되어 사용성이 좋습니다. 기본값이 있어 역호환에도 문제 없습니다.

core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatTextField.kt (1)

131-135: 플레이스홀더 타이포그래피 조정 LGTM

emotia14Memotia14R로 placeholder 가독성/위계가 개선됩니다. 문제 없습니다.

core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatBubble.kt (2)

65-85: 타자기 효과 스킵 토글 추가 매우 적절

skipTypewriterEffect로 초기 노출 메시지의 애니메이션을 제어할 수 있어 요구사항에 부합합니다. 기본값을 false로 둔 것도 ABI/기본 동작 호환에 안전합니다.


156-158: 파라미터 전달 일관성 OK

ChatBubble에서 ChatBubbleTextskipTypewriterEffect를 그대로 위임하고 있어 호출부 제어가 명확합니다.

feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (2)

185-200: 초기 분기(UI 전환) 로직 의도 적합 — LGTM

messages.size <= 1인 경우 인트로(CreateRoom)를 노출하고, 첫 사용자 전송 이후 채팅 화면으로 전환되는 플로우가 요구사항과 일치합니다. 입력 활성화 조건도 인트로에서 firstMessage 수신 이후로 제한되어 UX 측면에서 적합합니다.


534-541: 인트로 입력 활성화 조건 적절

enabled = !isLoading && firstMessage.isNotEmpty()로 서버 최초 메시지 수신 전 입력을 막는 것은 자연스러운 UX입니다. 이후 본 채팅 화면에서는 showFairyPager 여부로 입력 영역을 토글하여 흐름이 깨지지 않습니다.

composeApp/build.gradle.kts (1)

26-27: SplashScreen/Core KTX 의존성 추가는 적절합니다

안드로이드 스플래시 API 사용 및 KTX 유틸 사용을 위해 필요한 의존성이며, 버전 카탈로그(libs.*) 규칙도 준수하고 있습니다.

composeApp/src/androidMain/AndroidManifest.xml (2)

13-13: Splash 테마 적용은 적절합니다

Theme.Emotia.SplashScreen 적용이 installSplashScreen()과 맞물려 정상 동작할 구성입니다. 후속 전환 테마(postSplashScreenTheme)만 실제 메인 테마와 일치하는지 재확인해 주세요.


9-13: 하드코딩된 HTTP 호출 없음 확인 — networkSecurityConfig 제거 유지 가능

검사 결과, 코드베이스 내에 실제 네트워크 호출로서의 http:// 하드코딩은 존재하지 않습니다. 발견된 항목들은 모두 XML 네임스페이스 선언이나 주석 내 문서 참조이며, 런타임 시 평문 HTTP 요청을 발생시키지 않습니다.

  • iosApp/iosApp/Info.plist (라인 2): <!DOCTYPE … "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    • XML DTD 참조
  • AndroidManifest 및 리소스 XML들:
    xmlns:android="http://schemas.android.com/apk/res/android" 등 네임스페이스 선언
  • .gitignore (라인 70, 185):
    • 주석 내 http://www.java.com/en/download/help/error_hotspot.xml (문서 링크)

따라서 networkSecurityConfig 제거가 의도한 대로 적용된 상태이며, 레거시 HTTP 호출은 더 이상 없습니다.

gradle/libs.versions.toml (1)

58-58: 의존성 alias 추가 적절 — 설치 지점과 일관성 확인

group/name/version.ref 모두 올바르며, composeApp/build.gradle.kts에서 libs.androidx.core.splashscreen을 실제로 참조하는 것으로 보입니다. 별도 이슈 없어요.

혹시 멀티모듈이라면 사용 모듈 전반에서 버전 카탈로그 alias를 일관되게 사용 중인지 점검 부탁드립니다.

Comment on lines +1 to +3
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/ic_launcher_xxxhdpi" />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Foreground를 단일 …xxxhdpi 비트맵에 고정하지 마세요 — density-agnostic 참조로 교체

ic_launcher_foreground 또한 특정 density 비트맵을 고정 참조하고 있습니다. 아래처럼 @mipmap/ic_launcher로 바꾸어 기기별 최적 밀도를 사용하도록 해 주세요.
또한 가능하면 Adaptive Icon 전경을 벡터/레이어드로 구성하는 것이 마스크/적응형 처리에 유리합니다.

-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@mipmap/ic_launcher_xxxhdpi" />
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@mipmap/ic_launcher"
+    android:gravity="center" />

참고: minSdk 24로 drawable-v24만 두어도 동작은 하지만, 아이콘/스플래시는 안드로이드 26+에서 adaptive 구성이 권장됩니다.

📝 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
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/ic_launcher_xxxhdpi" />
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/ic_launcher"
android:gravity="center" />
🤖 Prompt for AI Agents
In composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml lines
1-3, the bitmap currently references a specific density mipmap
(@mipmap/ic_launcher_xxxhdpi); replace that density-specific reference with a
density-agnostic one (e.g., @mipmap/ic_launcher) so the system picks the correct
density at runtime, and if possible convert the foreground to a vector or
layered drawable for adaptive icon support (keeping this file under drawable-v24
for API24+ behavior).

Comment on lines +2 to +3
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/ic_launcher_xxxhdpi" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

밀도 고정된 mipmap(…xxxhdpi) 직접 참조는 스케일링/블러 유발 — density-agnostic 참조 또는 Adaptive Icon으로 전환 권장

현재 @mipmap/ic_launcher_xxxhdpi를 직접 지정하여 모든 기기에서 xxxhdpi 비트맵이 스케일링됩니다. 다양한 density에서 블러링/aliasing이 발생할 수 있습니다. 최소한 density-agnostic 참조(@mipmap/ic_launcher)로 변경해 시스템이 적절한 리소스를 선택하도록 하거나, 가능한 경우 Android 권장 방식의 Adaptive Icon으로 회귀하는 것을 권장합니다.

다음과 같이 수정해 주세요(간편 개선안):

-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@mipmap/ic_launcher_xxxhdpi" />
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@mipmap/ic_launcher"
+    android:gravity="center" />

가능하면 Adaptive Icon(배경/전경 분리) 복구도 고려해 주세요:

  • res/mipmap-anydpi-v26/ic_launcher.xml(adaptive-icon) 재도입
  • Manifest의 android:iconandroid:roundIcon@mipmap/ic_launcher 계열로 지정
📝 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
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/ic_launcher_xxxhdpi" />
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/ic_launcher"
android:gravity="center" />

Comment on lines +5 to +9
<item android:gravity="center">
<bitmap
android:src="@mipmap/ic_launcher_xxxhdpi"
android:gravity="center" />
</item>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

밀도 고정 리소스(@mipmap/ic_launcher_xxxhdpi) 직접 참조는 비권장 — 일반 alias로 변경 권장

특정 밀도 전용 리소스를 직접 참조하면 다른 밀도 기기에서 스케일링 저하/블러가 날 수 있습니다. @mipmap/ic_launcher 같은 밀도-독립 alias로 바꿔주세요.

-            android:src="@mipmap/ic_launcher_xxxhdpi"
+            android:src="@mipmap/ic_launcher"

추가로, 장기적으로는 어댑티브 아이콘(adaptive-icon, anydpi-v26)을 복원해 라운드/스쿼클 등 시스템 마스크에 매끄럽게 대응하는 것을 권장합니다.

📝 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
<item android:gravity="center">
<bitmap
android:src="@mipmap/ic_launcher_xxxhdpi"
android:gravity="center" />
</item>
<item android:gravity="center">
<bitmap
android:src="@mipmap/ic_launcher"
android:gravity="center" />
</item>
🤖 Prompt for AI Agents
In composeApp/src/androidMain/res/drawable/ic_splash_screen.xml around lines
5-9, the splash XML directly references a density-specific resource
(@mipmap/ic_launcher_xxxhdpi) which can cause scaling/blur on other devices;
replace that reference with the density-independent alias (@mipmap/ic_launcher)
so the system picks the correct density version, and verify the
@mipmap/ic_launcher alias exists in mipmap resources; additionally, plan to
restore an adaptive icon in anydpi-v26 (adaptive-icon) later so system masks
(round/squircle) are handled properly.

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

Caution

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

⚠️ Outside diff range comments (1)
feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (1)

3-3: 컴파일 오류: EmotiaChatTextField import 경로가 잘못되었습니다

Kotlin import에는 FQCN이 필요합니다. 현재 import EmotiaChatTextField는 패키지 없이 선언되어 컴파일이 실패합니다. 디자인시스템 컴포넌트의 정식 패키지 경로로 교체해 주세요.

다음 diff를 적용해 주세요:

-import EmotiaChatTextField
+import com.nexters.emotia.core.designsystem.component.EmotiaChatTextField
🧹 Nitpick comments (8)
feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (8)

203-215: 내부 레이아웃에 외부 modifier 재사용 — 중복 적용 및 부작용 가능성

루트 Box에서 이미 modifier.fillMaxSize()를 적용했고, 내부 Column에서도 동일한 modifier를 다시 사용하고 있습니다. 상위에서 전달된 semantics나 테스트 태그 등이 이중 적용될 수 있으므로 내부에는 Modifier를 사용하세요.

-            Column(
-                modifier = modifier
+            Column(
+                modifier = Modifier
                     .fillMaxSize()
                     .background(
                         brush = Brush.verticalGradient(
                             colors = listOf(
                                 colors.backgroundBlue,
                                 Color.Black
                             )
                         )
                     )
                     .padding(16.dp)
                     .safeDrawingPadding() // 화면 상단의 노치 등 안전 영역 패딩
             ) {

238-248: 메시지 인덱스 계산에 O(n²) 비용 — itemsIndexed로 단순화

uiState.messages.indexOf(message)가 각 아이템마다 O(n)으로 호출되므로 전체 O(n²)입니다. itemsIndexed를 사용하면 인덱스를 바로 받아 성능과 가독성이 개선됩니다.

-                        items(
-                            items = uiState.messages,
-                            key = { message -> message.timestamp }
-                        ) { message ->
-                            val messageIndex = uiState.messages.indexOf(message)
-                            ChatBubble(
-                                text = message.text,
-                                type = message.type,
-                                messageId = message.timestamp.toString(),
-                                skipTypewriterEffect = messageIndex <= 1
-                            )
-                        }
+                        itemsIndexed(
+                            items = uiState.messages,
+                            key = { index, message -> message.timestamp }
+                        ) { index, message ->
+                            ChatBubble(
+                                text = message.text,
+                                type = message.type,
+                                messageId = message.timestamp.toString(),
+                                skipTypewriterEffect = index <= 1
+                            )
+                        }

추가로 필요한 import:

import androidx.compose.foundation.lazy.itemsIndexed

239-241: 리스트 key 안정성 점검 권장

key = { message -> message.timestamp }는 타임스탬프 충돌 시(동시 전송 등) 재활용 이슈를 유발할 수 있습니다. 고유 id가 있다면 id로 교체하고, 없다면 "$timestamp-$index" 형태로 보강하는 것을 권장합니다.


407-409: 문자열 하드코딩 제거 — Compose Resources(Res.string)로 리소스화

placeholder 문자열이 하드코딩되어 있습니다. 다국어/일관성(learned guideline: Compose Resources 활용)을 위해 string 리소스로 옮겨 주세요.

-                        placeholder = "요정에게 지금 기분을 설명해보자",
+                        placeholder = stringResource(Res.string.chat_input_placeholder),
-                placeholder = "요정에게 지금 기분을 설명해보자",
+                placeholder = stringResource(Res.string.chat_input_placeholder),

필요 import:

import org.jetbrains.compose.resources.stringResource

strings 추가(예시):

  • 키: chat_input_placeholder
  • 값(ko): 요정에게 지금 기분을 설명해보자

원하시면 리소스 파일 추가 PR 패치를 함께 드리겠습니다.

Also applies to: 541-543


486-491: 접근성(a11y): 요정 이미지 contentDescription 보강 권장

요정 이미지는 핵심 UI 요소로 보입니다. 현재 contentDescription = null이라 스크린리더에서 무시됩니다. 의미 있는 설명을 제공하거나 의도적으로 장식 이미지라면 semantics { invisibleToUser() } 등으로 명확히 처리해 주세요.

예시:

-                    contentDescription = null,
+                    contentDescription = stringResource(Res.string.cd_fairy_character),

strings 예시:

  • 키: cd_fairy_character
  • 값(ko): 요정 캐릭터

506-509: 디자인 토큰 미사용(하드코딩 색상) — 테마 컬러 사용 권장

Color(0xE62D2B38) 등 하드코딩 색상은 유지보수와 다크모드 대응에 불리합니다. 디자인시스템의 LocalEmotiaColors 혹은 지정된 토큰으로 대체해 주세요.


224-236: IME 처리 방식 일관화 제안

채팅 화면은 contentPadding에 IME 높이를 수동 반영(라인 224–236), 인트로 화면은 .imePadding()으로 처리(라인 461–465)하고 있습니다. 두 화면 모두 .imePadding()을 사용하거나 공통 유틸로 정리하면 단순하고 안전합니다.

Also applies to: 461-465


185-201: 인트로 분기 로직은 요구사항과 부합합니다

uiState.messages.size <= 1일 때 인트로 UI를 노출하는 분기, 그리고 본 채팅 진입 전 첫 두 메시지에 대한 타자기 효과 skip은 PR 목적과 일치합니다. 이후 확장성을 위해 val isIntro = ...와 같은 명명된 플래그로 추출해 두면 가독성이 더 좋아집니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dc9938f and d40a821.

📒 Files selected for processing (1)
  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (6 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt : Leverage Compose resources via Res.drawable.* and Res.string.*

Applied to files:

  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/{commonMain,androidMain,iosMain}/kotlin/**/*.kt : Use Compose Resources for strings, images, and other assets

Applied to files:

  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt
📚 Learning: 2025-07-27T10:25:59.389Z
Learnt from: CR
PR: Nexters/team-ace-client#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T10:25:59.389Z
Learning: Applies to composeApp/src/commonMain/kotlin/**/*.kt : Use common state management patterns (ViewModel, StateFlow)

Applied to files:

  • feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt
🧬 Code graph analysis (1)
feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (5)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatBubble.kt (1)
  • ChatBubble (116-161)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatTypingIndicator.kt (1)
  • TypingIndicator (28-44)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/FairyCard.kt (1)
  • FairyCard (38-138)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/EmotiaButton.kt (1)
  • EmotiaButton (16-42)
core/designsystem/src/commonMain/kotlin/com/nexters/emotia/core/designsystem/component/ChatTextField.kt (1)
  • EmotiaChatTextField (93-170)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Firebase App Distribution
🔇 Additional comments (2)
feature/chatting/src/commonMain/kotlin/com/nexters/emotia/feature/chatting/ChattingScreen.kt (2)

414-447: 스포트라이트 전환 연출과 터치 차단 레이어 구성 좋습니다

ClipOp.Difference로 원형 마스크를 적용하고, 터치 차단을 위한 투명 clickable 레이어를 둔 구성은 의도대로 동작할 것으로 보입니다. isSpotlightAnimating 전환과 내비게이션 시퀀스도 자연스럽습니다.


70-73: 검증 필요: Generated Resources 패키지 경로 일치 여부 확인

현재 ChattingScreen.kt를含む여러 모듈에서

import emotia.core.designsystem.generated.resources.Res
import emotia.core.designsystem.generated.resources.img_chatting_fairy
import emotia.core.designsystem.generated.resources.img_letter_background

와 같이 emotia.core.designsystem.generated.resources 패키지를 사용하고 있습니다.
그러나 프로젝트 네이밍 컨벤션에 따라 com.nexters.emotia.core.designsystem.generated.resources일 가능성이 제기되었으므로, 실제로 생성된 리소스 파일의 package 선언과 import 경로가 일치하는지 반드시 확인해야 합니다.

• 아래 스크립트로 빌드된/generated 디렉터리에서 패키지 선언을 검색해 보세요.

#!/usr/bin/env bash
# build/generated 또는 src/main/generated 하위에서 package 선언 확인
rg -nP 'package\s+[\w\.]+\.generated\.resources' -n build/generated -n src/main/generated

• 검증 결과 실제 패키지 경로가 com.nexters.emotia.core.designsystem.generated.resources라면,
ChattingScreen.kt 포함 모든 사용 파일의 import를 다음과 같이 변경해 주세요.

-import emotia.core.designsystem.generated.resources.Res
-import emotia.core.designsystem.generated.resources.img_chatting_fairy
-import emotia.core.designsystem.generated.resources.img_letter_background
+import com.nexters.emotia.core.designsystem.generated.resources.Res
+import com.nexters.emotia.core.designsystem.generated.resources.img_chatting_fairy
+import com.nexters.emotia.core.designsystem.generated.resources.img_letter_background

• 반대로 실제 패키지 선언이 emotia.core.designsystem.generated.resources인 경우라면,
제안된 변경은 불필요하니 그대로 유지하시면 됩니다.

위 검증 과정을 통해 import 경로가 실제 패키지 구조와 일치하는지 확인해 주시기 바랍니다.

Copy link
Collaborator

@haeti-dev haeti-dev left a comment

Choose a reason for hiding this comment

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

깔끔하네요 고생하셨습니다~

@sxunea sxunea merged commit 79b0c1a into develop Aug 22, 2025
3 checks passed
@sxunea sxunea deleted the feature/chatting-intro branch August 22, 2025 13:37
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.

[feature] splashScreen 구현 및 앱아이콘 적용 [chore] https로의 base url 변경 [feature] 채팅방 생성 초기화면 구현

3 participants