Skip to content

[Feat] 토닥이 진입 스플래시 및 ChatScreen 추가 - #87

Merged
oungsi2000 merged 19 commits into
feat/77-chat-viewmodelfrom
feat/78-chat-screen
Aug 10, 2026
Merged

[Feat] 토닥이 진입 스플래시 및 ChatScreen 추가#87
oungsi2000 merged 19 commits into
feat/77-chat-viewmodelfrom
feat/78-chat-screen

Conversation

@oungsi2000

@oungsi2000 oungsi2000 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

관련 이슈

#78

작업 내용

  • 토닥이 채팅 Splash와 ChatScreen을 추가하였습니다

변경사항 / 상세

  • 채팅 화면은 키보드 가시성에 따라 위로 올라가야 한다고 생각했습니다. android:windowSoftInputMode="adjustResize"를 설정하지 않으니 키보드가 올라갈 때, 패딩이 튀는 현상이 발생하여 추가했습니다.
  • 스낵바가 필요로 하는 부분이 있는데, 외부에서 snackbarHost를 주입받도록 구성했습니다. 추후 하나의 Scaffold에서 주입시키면 좋을 것 같습니다

중점 리뷰사항

  • 토닥이 진입 스플래시의 조건을 ChatEntry의 응답이 성공했을때를 기준으로 두었습니다!
  • 하나의 LazyColumn에서 예시 질문, 채팅을 다 소화하고 있는데 더 좋은 방식이 있는지 궁금합니다.

스크린샷 (선택)

Screen_Recording_20260807_014500_todakun.mp4

Summary by CodeRabbit

  • 새로운 기능

    • 카테고리별 캐릭터 안내와 인사말을 제공하는 채팅 화면을 추가했습니다.
    • 메시지 입력, 추천 질문, 새 대화, 대화 기록 이동 기능을 지원합니다.
    • 답변 생성 중 상태와 애니메이션 표시를 추가했습니다.
    • 날짜 정보를 Android 캘린더에 등록할 수 있습니다.
    • 채팅 시작 화면과 관련 안내 문구를 추가했습니다.
  • 개선

    • 키보드 표시 시 채팅 화면이 자동으로 조정됩니다.
    • 헤더 및 전송 버튼의 불필요한 클릭 효과를 제거했습니다.

@oungsi2000 oungsi2000 self-assigned this Aug 6, 2026
@oungsi2000 oungsi2000 added the feature 새로운 기능 추가 label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

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.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 514a75d5-4908-4c87-8d78-b95606b67dc5

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Compose 기반 채팅 화면과 보조 UI를 추가했습니다. ChatRoute는 ViewModel 상태와 오류를 연결합니다. 메시지 스트리밍, 그리팅 오버레이, 캘린더 등록, 키보드 레이아웃 조정을 지원합니다.

Changes

채팅 UI

Layer / File(s) Summary
채팅 디자인 시스템 구성
gradle/libs.versions.toml, core/designsystem/src/main/..., core/designsystem/src/main/res/...
캘린더 액션 카드와 아이콘을 추가했습니다. 헤더와 전송 버튼의 기본 ripple indication을 제거했습니다. 채팅 관련 문자열을 추가했습니다.
채팅 보조 컴포넌트
feature/chat/src/main/java/com/kikidan/chat/component/*, feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt, feature/chat/src/main/java/com/kikidan/chat/util/ToCharacterResourceId.kt, feature/chat/src/main/res/values/strings.xml
스플래시 화면, 카테고리별 인사 오버레이, 캐릭터 리소스 매핑, ThinkingIndicator를 추가했습니다.
채팅 화면 및 메시지 표시
feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt
헤더, 메시지 목록, 추천 질문, 스트리밍 상태, 그리팅 오버레이, 캘린더 등록 인텐트를 연결했습니다. Compose Preview를 추가했습니다.
채팅 상태 및 화면 진입 연결
feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt, app/src/main/AndroidManifest.xml
ViewModel 상태와 오류 이벤트를 수집하고 할당량 상태에 따라 화면을 선택합니다. 소프트 키보드 표시 시 레이아웃을 adjustResize로 설정했습니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 토닥이 진입 스플래시와 ChatScreen 추가라는 PR의 주요 변경사항을 명확하게 요약합니다.
Description check ✅ Passed PR 설명이 이슈, 작업 내용, 상세 변경사항, 중점 리뷰사항, 선택 스크린샷을 모두 포함합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

@oungsi2000
oungsi2000 force-pushed the feat/78-chat-screen branch 2 times, most recently from 4b234da to f21de29 Compare August 6, 2026 17:10
@oungsi2000

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@oungsi2000
oungsi2000 force-pushed the feat/78-chat-screen branch from 01a0c34 to 49649c0 Compare August 8, 2026 06:24
@oungsi2000
oungsi2000 force-pushed the feat/78-chat-screen branch from 49649c0 to e58e4d7 Compare August 8, 2026 08:46
@oungsi2000

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

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

⚠️ Outside diff range comments (2)
core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatHeader.kt (1)

50-63: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

[P2] 클릭 가능한 아이콘에 접근성 라벨이 없습니다.

세 아이콘 모두 contentDescription = null이며 clickableonClickLabel도 없습니다. 클릭 가능한 요소는 스크린 리더가 용도를 안내할 수 있어야 합니다. strings.xmlheader_close_content_description 등 문자열이 이미 있습니다. 이를 사용하십시오.

♿ 제안 변경 (닫기 아이콘 예시)
         Icon(
             painter = painterResource(id = R.drawable.ic_close),
-            contentDescription = null,
+            contentDescription = stringResource(R.string.header_close_content_description),
             tint = TodakunColor.gray975,

채팅/노트 아이콘에도 동일하게 적용하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatHeader.kt`
around lines 50 - 63, Update the clickable icons in TodakunChatHeader, including
the close, chat, and note icon composables, to use the existing accessibility
string resources as contentDescription values and provide matching onClickLabel
values on their clickable modifiers. Ensure each label describes the icon’s
action and remove the null descriptions.
core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatInputField.kt (1)

147-158: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

[P2] 전송 버튼에 접근성 라벨이 없습니다.

clickableonClickLabelrole이 없고, 내부 IconcontentDescriptionnull입니다. 스크린 리더 사용자는 이 요소가 전송 버튼임을 알 수 없습니다. 라벨을 추가해 주세요.

♿ 제안 변경
                 ).clickable(
                     enabled = isFilled,
                     onClick = onSendClick,
+                    onClickLabel = stringResource(R.string.todak_chat_send_message),
+                    role = Role.Button,
                     indication = null,
                     interactionSource = remember { MutableInteractionSource() },
                 ),

androidx.compose.ui.semantics.Role import와 문자열 리소스 추가가 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatInputField.kt`
around lines 147 - 158, Update the send control’s clickable modifier and inner
Icon in TodakunChatInputField to expose an accessibility label: provide the
localized send string through onClickLabel and the Icon contentDescription, and
set the clickable role to Role.Button with the required import and string
resource.
🧹 Nitpick comments (17)
core/designsystem/src/main/res/values/strings.xml (2)

51-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 리소스 이름 오타와 섹션 주석 중복을 정리하십시오.

chat_charactor_content_descriptioncharactor 철자가 잘못되었습니다. character로 바꾸십시오. 리소스 이름은 참조처가 늘어나기 전에 고치는 편이 비용이 적습니다.

36번 줄에 이미 <!-- TodakChat --> 섹션이 있습니다. 51번 줄의 <!--Todak Chat-->과 중복됩니다. 한 섹션으로 합치십시오.

♻️ 제안 변경
-    <string name="chat_charactor_content_description">토닥이 사진</string>
+    <string name="chat_character_content_description">토닥이 사진</string>

참조처도 함께 변경해야 합니다. ChatScreen.kt 306번 줄에서 사용 중입니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/designsystem/src/main/res/values/strings.xml` around lines 51 - 58,
Rename the string resource chat_charactor_content_description to
chat_character_content_description and update its reference in ChatScreen.kt.
Remove the duplicate <!--Todak Chat--> section comment near these strings,
consolidating the entries under the existing TodakChat section comment.

61-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 개행 뒤 선행 공백을 제거하십시오.

chat_greeting_*_body 문구는 모두 \n 형태입니다. 개행 다음 문자가 공백이므로 둘째 줄만 한 칸 밀려 보입니다. 의도한 들여쓰기가 아니면 공백을 제거하십시오.

♻️ 제안 변경
-    <string name="chat_greeting_relationship_body">친구, 가족, 동료 등\n 고민되는 게 있으면 전부 나한테 물어봐.</string>
+    <string name="chat_greeting_relationship_body">친구, 가족, 동료 등\n고민되는 게 있으면 전부 나한테 물어봐.</string>

(나머지 4개 문구도 동일하게 적용하십시오.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/designsystem/src/main/res/values/strings.xml` around lines 61 - 70,
Update all chat_greeting_*_body string resources, including
chat_greeting_relationship_body, chat_greeting_love_body,
chat_greeting_achievement_body, chat_greeting_money_body, and
chat_greeting_health_body, to remove the space immediately following the \n
escape so the second line starts without indentation.
feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt (1)

39-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 스플래시 분기에 modifier를 전달하지 않습니다.

ChatScreen 분기는 modifier를 사용하지만 ChatSplashScreen()은 사용하지 않습니다. 상위에서 지정한 레이아웃 제약이 스플래시에는 적용되지 않습니다. ChatSplashScreen(modifier = modifier)로 전달하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt` around lines 39 -
40, Update the ChatSplashScreen call in the showSplash branch of ChatRoute so it
receives the existing modifier parameter, matching the ChatScreen branch and
preserving the parent layout constraints.
feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt (2)

292-322: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] scaleX/scaleY 확대는 클립 경계를 넘습니다.

BoxCircleShape로 클립하므로 1.4배 확대한 이미지는 원 밖이 잘립니다. 의도한 동작이면 유지하십시오. 캐릭터 전체를 보여줄 목적이면 contentScale을 사용하는 편이 예측하기 쉽습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt` around
lines 292 - 322, Update ProfileCharacterImage so the character image remains
fully visible within the CircleShape clip instead of relying on graphicsLayer
scaleX/scaleY enlargement. Replace the category-dependent 1.4x graphicsLayer
scaling with an appropriate contentScale configuration, while preserving the
default resource and existing image layout.

330-331: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] ActionDateFormatteractionDateFormatter로 변경하세요.

private val ActionDateFormatterconst가 아니므로 ktlint property-naming 규칙에 따라 camelCase 이름이 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt` around
lines 330 - 331, Rename the non-const property ActionDateFormatter to
actionDateFormatter to comply with ktlint property-naming rules, and update all
references to the formatter accordingly.

Source: Path instructions

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

2-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 크기와 viewport의 종횡비가 어긋납니다.

width:height는 14:16 (0.875)입니다. viewportWidth:viewportHeight는 14:15.625 (0.896)입니다. 비율이 다르므로 아이콘이 세로로 약간 늘어나 보입니다. android:height15.625dp로 맞추거나 viewport를 정수 그리드(예: 14 x 16)로 재정규화하십시오.

ic_ 접두사는 벡터 파일 규칙에 맞습니다. As per path instructions: "ic_ 접두사는 SVG(벡터) 파일에 사용합니다."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/designsystem/src/main/res/drawable/ic_calendar_add.xml` around lines 2 -
5, Update the dimensions in the vector drawable definition for ic_calendar_add
so the android width/height aspect ratio matches the
viewportWidth/viewportHeight ratio. Prefer aligning android:height with the
existing 15.625 viewport height, or consistently renormalize the viewport to a
14 x 16 grid without altering the icon’s appearance.

Source: Path instructions

gradle/libs.versions.toml (1)

32-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P2] material3 버전 별칭을 제거하고 BOM 전용 별칭으로 통일하세요.

androidx-compose-material3가 Compose BOM으로 버전이 관리되는데, 같은 모듈을 libs.material3라는 고정 버전 별칭으로 다시 선언했어요. 둘을 직접 사용하지는 않지만 별도 버전 경로가 유지되어 catalog 자체가 복잡해집니다. material3 = "1.4.0"을 제거해 BOM 관리 경로만 남기세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gradle/libs.versions.toml` at line 32, Remove the material3 version alias
from the version catalog so androidx-compose-material3 remains managed
exclusively through the Compose BOM; do not alter the BOM alias or other catalog
entries.
feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt (1)

30-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 파일명과 컴포저블 이름을 맞춰 주세요.

파일명은 SplashScreen.kt이고 컴포저블은 ChatSplashScreen입니다. ChatSplashScreen.kt로 변경하면 탐색이 쉬워집니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt` around
lines 30 - 31, Rename the source file containing the ChatSplashScreen composable
from SplashScreen.kt to ChatSplashScreen.kt, keeping the composable
implementation and name unchanged.
feature/chat/src/main/java/com/kikidan/chat/util/ToCharacterResourceId.kt (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 반환 타입에 @DrawableRes를 명시하세요.

현재 반환 타입은 Int로 추론됩니다. @DrawableRes를 붙이면 잘못된 리소스 ID 전달을 린트가 잡아 줍니다.

♻️ 제안 변경
+@DrawableRes
-internal fun ChatCategory.toCharacterResourceId() =
+internal fun ChatCategory.toCharacterResourceId(): Int =
     when (this) {

androidx.annotation.DrawableRes import가 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/util/ToCharacterResourceId.kt` at
line 6, Update the ChatCategory.toCharacterResourceId function to declare its
return type as Int annotated with `@DrawableRes`, and add the required
androidx.annotation.DrawableRes import so lint can validate the returned
resource ID.
core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatActionCard.kt (4)

41-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 공개 컴포저블 이름의 철자와 파일명을 맞춰 주세요.

CalenderCalendar의 오타입니다. 파일명은 TodakunChatActionCard.kt이고 함수명은 TodakunChatCalenderActionCard로 서로 다릅니다. 공개 API이므로 사용처가 늘어나기 전에 정리하는 편이 좋습니다.

♻️ 제안 변경
-fun TodakunChatCalenderActionCard(
+fun TodakunChatCalendarActionCard(

파일명도 TodakunChatCalendarActionCard.kt로 맞추고, Preview 함수명도 함께 변경하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatActionCard.kt`
at line 41, Rename the public composable TodakunChatCalenderActionCard to
TodakunChatCalendarActionCard, rename its file to
TodakunChatCalendarActionCard.kt, and update the associated Preview function and
all references to use the corrected spelling.

Source: Path instructions


84-96: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

[P3] pressed 색상 계산을 remember로 감싸는 것을 고려하세요.

compositeOver 결과는 isPressed에만 의존합니다. 현재도 비용이 크지는 않습니다. 다만 remember(isPressed)로 감싸면 리컴포지션마다의 재계산을 피할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatActionCard.kt`
around lines 84 - 96, Update CalendarActionButton so the containerColor
calculation involving pressedOverlay.compositeOver(TodakunColor.primary600) is
memoized with remember keyed by isPressed, while preserving the existing pressed
and unpressed colors.

Source: Path instructions


61-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 대괄호 포맷을 문자열 리소스로 분리하는 편이 좋습니다.

"[$category]"는 UI 포맷을 코드에 고정합니다. 디자인 변경이나 로케일 대응 시 수정 지점이 흩어집니다. strings.xml<string name="chat_action_card_category">[%1$s]</string> 형태로 두고 stringResource로 조합하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatActionCard.kt`
around lines 61 - 65, Move the `[$category]` display format out of the
`TodakunChatActionCard` composable into a `strings.xml` resource named
`chat_action_card_category` with a `%1$s` placeholder, then use `stringResource`
when setting the `Text` value while preserving the existing category content.

98-98: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P2]ripple 비활성화 방법을 화면의 UI 요소 간에 통일해 주세요.

프로젝트는 Material3 1.4.0을 사용하므로 LocalRippleConfiguration 사용에 @OptIn(ExperimentalMaterial3Api::class)가 필요하지 않습니다. 다만 인근 SendMessageButton에서는 clickable(indication = null)로 비활성화하므로, 동일한 UI 요소에서 ripple 비활성화 방식을 통일하면 유지보수가 더 간단해집니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatActionCard.kt`
at line 98, Unify ripple disabling in TodakunChatActionCard by replacing the
LocalRippleConfiguration provider around the relevant UI element with
clickable(indication = null), matching the existing SendMessageButton approach.
Remove any now-unnecessary ExperimentalMaterial3Api opt-in related solely to
LocalRippleConfiguration, while preserving the element’s click behavior.
feature/chat/src/main/java/com/kikidan/chat/component/ChatGreetingOverlay.kt (2)

47-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] 불필요해 보이는 @OptIn을 확인해 주세요.

이 컴포저블은 Material3의 IconText만 사용합니다. 두 API는 실험적 API가 아닙니다. @OptIn(ExperimentalMaterial3Api::class)가 실제로 필요한지 확인하고, 불필요하면 import와 함께 제거하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/component/ChatGreetingOverlay.kt`
at line 47, Remove the unnecessary `@OptIn`(ExperimentalMaterial3Api::class)
annotation from the ChatGreetingOverlay composable and delete its corresponding
import, since the component only uses stable Material3 Icon and Text APIs.

139-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] toTitletoBody를 하나의 매핑으로 합치는 것을 고려하세요.

두 함수는 동일한 when 분기를 두 번 작성합니다. 카테고리가 늘어나면 두 곳을 모두 수정해야 합니다. 리소스 ID 쌍을 반환하는 단일 매핑 함수로 합치면 누락 위험이 줄어듭니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/component/ChatGreetingOverlay.kt`
around lines 139 - 157, Combine ChatCategory.toTitle and ChatCategory.toBody
into one mapping function that uses a single when over ChatCategory and returns
the corresponding title/body resource ID pair. Update callers to obtain each
resource from this shared mapping, preserving the existing localized strings and
behavior.
feature/chat/src/main/java/com/kikidan/chat/component/ThinkingIndicator.kt (2)

40-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[P3] semanticsmergeDescendants를 지정하세요.

RowcontentDescription을 설정했지만 하위 Text도 별도 노드로 남습니다. 스크린 리더가 안내 문구를 두 번 읽을 수 있습니다. semantics(mergeDescendants = true)로 노드를 병합하세요.

♿ 제안 변경
-        modifier = modifier.semantics { contentDescription = description },
+        modifier = modifier.semantics(mergeDescendants = true) { contentDescription = description },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/component/ThinkingIndicator.kt`
at line 40, Update the Row’s semantics modifier to set mergeDescendants = true
alongside the existing contentDescription, ensuring the indicator and its child
Text are exposed as a single accessibility node and the description is announced
only once.

65-75: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

[P2] background(lerp(...))가 프레임마다 리컴포지션을 유발합니다.

translationYgraphicsLayer 람다 안에서 읽으므로 draw 단계만 갱신합니다. 반면 background() 인자의 bounceProgress는 컴포지션 단계에서 읽힙니다. 애니메이션이 무한 반복되므로 각 점 Box가 매 프레임 리컴포지션됩니다. drawBehind로 옮기면 draw 단계만 갱신합니다.

♻️ 제안 변경
                 Box(
                     modifier =
                         Modifier
                             .size(ThinkingIndicatorDefaults.DotSize)
                             .graphicsLayer {
                                 translationY = -ThinkingIndicatorDefaults.BounceHeight.toPx() * bounceProgress
-                            }.clip(CircleShape)
-                            .background(
-                                lerp(TodakunColor.primary300, TodakunColor.primary700, bounceProgress),
-                            ),
+                            }.drawBehind {
+                                drawCircle(
+                                    color = lerp(TodakunColor.primary300, TodakunColor.primary700, bounceProgress),
+                                )
+                            },
                 )

androidx.compose.ui.draw.drawBehind import가 필요합니다. clipbackground import는 사용처가 없으면 제거하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/component/ThinkingIndicator.kt`
around lines 65 - 75, Update the animated dot modifier in the Box to replace
background with drawBehind, computing the lerp color inside the draw block so
bounceProgress is read during drawing rather than composition. Preserve the
existing CircleShape clipping and color interpolation, and remove or add
draw-related imports as needed while cleaning up unused background imports.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt`:
- Line 30: Update the LaunchedEffect in ChatRoute so its key uses conversationId
instead of Unit, ensuring viewModel.load(conversationId) runs whenever the
active conversation changes while preserving the existing load behavior.
- Line 28: Update the showSplash condition in ChatRoute to use
ChatState.isLoading instead of state.quota == null, so the splash ends after
load() completes on both success and failure paths while ChatScreen is still
shown with the resulting state.

In
`@feature/chat/src/main/java/com/kikidan/chat/component/ChatGreetingOverlay.kt`:
- Around line 125-134: Update the close control around the Icon in the
ChatGreetingOverlay composable so its clickable touch target meets the 48.dp
minimum while preserving the 20.dp visual CloseIconSize. Apply
minimumInteractiveComponentSize() or move clickable to an appropriately sized
outer container with design-consistent padding, keeping onCloseClick and
existing alignment behavior intact.
- Around line 60-64: Replace the empty clickable modifier on the chat greeting
overlay with pointerInput that consumes touch events without exposing a
clickable accessibility node. Remove now-unused clickable,
MutableInteractionSource, and remember imports, and simplify the background
color to Color.Black while preserving the existing transparency.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt`:
- Around line 209-217: Fix the three LazyColumn item sites in
feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt: lines 209-217,
wrap ProfileCharacterImage, Spacer, and Text in a Column; lines 219-231, replace
the forEach rendering with items(suggestions) so each chip is a separate item;
and lines 267-288, wrap the Spacer and when block in a Column.
- Around line 412-450: Update ChatScreenGreetingWithTitlePreview and
ChatScreenGreetingNoTitlePreview so they preview the greeting overlay through
the state ChatScreen actually consumes, such as setting the appropriate
selectedCategory, or preview ChatGreetingOverlay directly. Do not rely on
ChatState.greeting alone, and preserve the intended distinction between the
newline and no-newline greeting previews.
- Around line 452-469: Update ChatScreenThinkingPreview to pass
StreamingChatState.Thinking through ChatScreen’s streamingChatState parameter,
so the preview renders the ThinkingIndicator instead of the default Idle state.
- Around line 191-195: Update the LaunchedEffect scroll logic around
state.messages.size, state.streamingChatState, and imeBottom to observe actual
layout changes with snapshotFlow rather than reading listState.layoutInfo only
once. Re-scroll whenever the layout’s total item count or visible item
information changes, ensuring the final item index is used after recomposition
and streaming text growth triggers scrolling even when the item count is
unchanged.
- Around line 184-185: ChatScreen의 prevCategory 갱신이 컴포지션 본문에서 상태를 변경하지 않도록
수정하십시오. selectedCategory에서 파생되는 값이면 remember 상태를 제거하고 직접 계산하거나, 기존 상태가 필요하면
LaunchedEffect로 이동해 null이 아닌 selectedCategory만 반영하십시오. 관련 selectedCatgeory 오타도
selectedCategory로 수정하십시오.
- Around line 130-139: Update the onActionClick calendar-launch flow to convert
action.date from LocalDate to the epoch-millisecond Long required by
CalendarContract.EXTRA_EVENT_BEGIN_TIME, and catch ActivityNotFoundException
around context.startActivity to prevent crashes and show the user an appropriate
unavailable-calendar message.
- Around line 157-168: Update the ChatScreen AnimatedVisibility block to
preserve the last non-null selectedCategory for rendering during the exit
animation. Keep visibility driven by the active selectedCategory, but render
ChatGreetingOverlay from the retained category so fadeOut has content until the
animation completes.
- Around line 111-118: Update the TodakunChatHeader call in ChatScreen so
freeChatUsed receives state.quota?.used rather than state.quota?.remaining,
while preserving the existing limit fallback for freeChatTotal.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt`:
- Around line 36-37: Update the SplashScreen layout to replace the hardcoded
Color(0xFFF2F0FB) with a matching token defined in TodakunColor, removing the
direct Color import if unused. Replace the fixed vertical padding of 178.dp with
responsive spacing, such as weighted layout spacing or bounded padding, so the
image area remains visible on short screens.

In `@feature/chat/src/main/res/values/strings.xml`:
- Around line 1-4: 리소스 경계를 정리하세요. 비어 있는 feature/chat의 values/strings.xml은 삭제하고,
core/designsystem의 chat_greeting_love_title 및 chat_splash_tagline처럼 채팅 화면 전용
문자열은 feature/chat으로 이동하세요. TodakunChatInputField.placeholder처럼 공용 컴포넌트 기본값으로
재사용되는 리소스만 core/designsystem에 남기세요.

---

Outside diff comments:
In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatHeader.kt`:
- Around line 50-63: Update the clickable icons in TodakunChatHeader, including
the close, chat, and note icon composables, to use the existing accessibility
string resources as contentDescription values and provide matching onClickLabel
values on their clickable modifiers. Ensure each label describes the icon’s
action and remove the null descriptions.

In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatInputField.kt`:
- Around line 147-158: Update the send control’s clickable modifier and inner
Icon in TodakunChatInputField to expose an accessibility label: provide the
localized send string through onClickLabel and the Icon contentDescription, and
set the clickable role to Role.Button with the required import and string
resource.

---

Nitpick comments:
In
`@core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatActionCard.kt`:
- Line 41: Rename the public composable TodakunChatCalenderActionCard to
TodakunChatCalendarActionCard, rename its file to
TodakunChatCalendarActionCard.kt, and update the associated Preview function and
all references to use the corrected spelling.
- Around line 84-96: Update CalendarActionButton so the containerColor
calculation involving pressedOverlay.compositeOver(TodakunColor.primary600) is
memoized with remember keyed by isPressed, while preserving the existing pressed
and unpressed colors.
- Around line 61-65: Move the `[$category]` display format out of the
`TodakunChatActionCard` composable into a `strings.xml` resource named
`chat_action_card_category` with a `%1$s` placeholder, then use `stringResource`
when setting the `Text` value while preserving the existing category content.
- Line 98: Unify ripple disabling in TodakunChatActionCard by replacing the
LocalRippleConfiguration provider around the relevant UI element with
clickable(indication = null), matching the existing SendMessageButton approach.
Remove any now-unnecessary ExperimentalMaterial3Api opt-in related solely to
LocalRippleConfiguration, while preserving the element’s click behavior.

In `@core/designsystem/src/main/res/drawable/ic_calendar_add.xml`:
- Around line 2-5: Update the dimensions in the vector drawable definition for
ic_calendar_add so the android width/height aspect ratio matches the
viewportWidth/viewportHeight ratio. Prefer aligning android:height with the
existing 15.625 viewport height, or consistently renormalize the viewport to a
14 x 16 grid without altering the icon’s appearance.

In `@core/designsystem/src/main/res/values/strings.xml`:
- Around line 51-58: Rename the string resource
chat_charactor_content_description to chat_character_content_description and
update its reference in ChatScreen.kt. Remove the duplicate <!--Todak Chat-->
section comment near these strings, consolidating the entries under the existing
TodakChat section comment.
- Around line 61-70: Update all chat_greeting_*_body string resources, including
chat_greeting_relationship_body, chat_greeting_love_body,
chat_greeting_achievement_body, chat_greeting_money_body, and
chat_greeting_health_body, to remove the space immediately following the \n
escape so the second line starts without indentation.

In `@feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt`:
- Around line 39-40: Update the ChatSplashScreen call in the showSplash branch
of ChatRoute so it receives the existing modifier parameter, matching the
ChatScreen branch and preserving the parent layout constraints.

In
`@feature/chat/src/main/java/com/kikidan/chat/component/ChatGreetingOverlay.kt`:
- Line 47: Remove the unnecessary `@OptIn`(ExperimentalMaterial3Api::class)
annotation from the ChatGreetingOverlay composable and delete its corresponding
import, since the component only uses stable Material3 Icon and Text APIs.
- Around line 139-157: Combine ChatCategory.toTitle and ChatCategory.toBody into
one mapping function that uses a single when over ChatCategory and returns the
corresponding title/body resource ID pair. Update callers to obtain each
resource from this shared mapping, preserving the existing localized strings and
behavior.

In `@feature/chat/src/main/java/com/kikidan/chat/component/ThinkingIndicator.kt`:
- Line 40: Update the Row’s semantics modifier to set mergeDescendants = true
alongside the existing contentDescription, ensuring the indicator and its child
Text are exposed as a single accessibility node and the description is announced
only once.
- Around line 65-75: Update the animated dot modifier in the Box to replace
background with drawBehind, computing the lerp color inside the draw block so
bounceProgress is read during drawing rather than composition. Preserve the
existing CircleShape clipping and color interpolation, and remove or add
draw-related imports as needed while cleaning up unused background imports.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt`:
- Around line 292-322: Update ProfileCharacterImage so the character image
remains fully visible within the CircleShape clip instead of relying on
graphicsLayer scaleX/scaleY enlargement. Replace the category-dependent 1.4x
graphicsLayer scaling with an appropriate contentScale configuration, while
preserving the default resource and existing image layout.
- Around line 330-331: Rename the non-const property ActionDateFormatter to
actionDateFormatter to comply with ktlint property-naming rules, and update all
references to the formatter accordingly.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt`:
- Around line 30-31: Rename the source file containing the ChatSplashScreen
composable from SplashScreen.kt to ChatSplashScreen.kt, keeping the composable
implementation and name unchanged.

In `@feature/chat/src/main/java/com/kikidan/chat/util/ToCharacterResourceId.kt`:
- Line 6: Update the ChatCategory.toCharacterResourceId function to declare its
return type as Int annotated with `@DrawableRes`, and add the required
androidx.annotation.DrawableRes import so lint can validate the returned
resource ID.

In `@gradle/libs.versions.toml`:
- Line 32: Remove the material3 version alias from the version catalog so
androidx-compose-material3 remains managed exclusively through the Compose BOM;
do not alter the BOM alias or other catalog entries.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 11d94192-2570-4e55-b0ff-ed94ebf72874

📥 Commits

Reviewing files that changed from the base of the PR and between e690c2a and e58e4d7.

⛔ Files ignored due to path filters (7)
  • core/designsystem/src/main/res/drawable/img_todak_achievement_luck.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable/img_todak_chat_thumbnail.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable/img_todak_default_pose.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable/img_todak_health_luck.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable/img_todak_love_luck.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable/img_todak_money_luck.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable/img_todak_relationship_luck.png is excluded by !**/*.png
📒 Files selected for processing (14)
  • app/src/main/AndroidManifest.xml
  • core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatActionCard.kt
  • core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatHeader.kt
  • core/designsystem/src/main/java/com/kikidan/designsystem/component/chat/TodakunChatInputField.kt
  • core/designsystem/src/main/res/drawable/ic_calendar_add.xml
  • core/designsystem/src/main/res/values/strings.xml
  • feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt
  • feature/chat/src/main/java/com/kikidan/chat/component/ChatGreetingOverlay.kt
  • feature/chat/src/main/java/com/kikidan/chat/component/ThinkingIndicator.kt
  • feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt
  • feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt
  • feature/chat/src/main/java/com/kikidan/chat/util/ToCharacterResourceId.kt
  • feature/chat/src/main/res/values/strings.xml
  • gradle/libs.versions.toml

Comment thread feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt Outdated
val defaultErrorMessage = stringResource(R.string.chat_default_error)
val showSplash = state.quota == null

LaunchedEffect(Unit) { viewModel.load(conversationId) }

@coderabbitai coderabbitai Bot Aug 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

[P2] LaunchedEffect의 key가 conversationId를 반영하지 않습니다.

key가 Unit이므로 conversationId가 바뀌어도 재로드가 일어나지 않습니다. 같은 라우트 인스턴스에서 다른 대화로 이동하면 이전 대화가 그대로 남습니다.

♻️ 제안 변경
-    LaunchedEffect(Unit) { viewModel.load(conversationId) }
+    LaunchedEffect(conversationId) { viewModel.load(conversationId) }
📝 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
LaunchedEffect(Unit) { viewModel.load(conversationId) }
LaunchedEffect(conversationId) { viewModel.load(conversationId) }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt` at line 30, Update
the LaunchedEffect in ChatRoute so its key uses conversationId instead of Unit,
ensuring viewModel.load(conversationId) runs whenever the active conversation
changes while preserving the existing load behavior.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

다른 채팅창 이동 시 ViewModel 재생성 예정입니다. 해당 코드는 백스택에 ChatRoute가 남아있고 다른 화면 -> 재진입 시 최신으로 갱신하도록 하는 의도입니다

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@oungsi2000, 설명 감사합니다. 해당 탐색 구조에서는 다른 채팅창 이동 시 ChatViewModel이 재생성되고, 백스택 재진입 시 LaunchedEffect(Unit)가 다시 실행됩니다. 따라서 conversationId를 key로 변경할 필요가 없습니다. 기존 의견을 철회하겠습니다.


✏️ Learnings added
Learnt from: oungsi2000
Repo: YAPP-Github/28th-App-Team-2-Android PR: 87
File: feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt:30-30
Timestamp: 2026-08-09T06:01:24.730Z
Learning: `feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt`에서 다른 채팅창으로 이동하면 `ChatViewModel`이 재생성된다. `ChatRoute`의 `LaunchedEffect(Unit)`는 백스택에 남은 화면이 다른 화면에서 재진입할 때 최신 상태를 로드하려는 의도이며, `conversationId` 변경을 key로 관찰할 필요가 없다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Comment thread feature/chat/src/main/java/com/kikidan/chat/component/ChatGreetingOverlay.kt Outdated
Comment thread feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt
Comment thread feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt
Comment on lines +412 to +450
@Preview(showBackground = true, name = "그리팅 오버레이 (개행 있음)")
@Composable
private fun ChatScreenGreetingWithTitlePreview() {
TodakunTheme {
ChatScreen(
state =
ChatState(
suggestions = previewSuggestions,
greeting = "성취운을 알려줄게!\n커리어, 학업, 목표 등 궁금한 점이나 고민은 전부 물어봐줘.",
),
onInputChange = {},
onSendClick = {},
onSuggestionClick = {},
onNewConversationClick = {},
onCloseClick = {},
onHistoryClick = {},
)
}
}

@Preview(showBackground = true, name = "그리팅 오버레이 (개행 없음)")
@Composable
private fun ChatScreenGreetingNoTitlePreview() {
TodakunTheme {
ChatScreen(
state =
ChatState(
suggestions = previewSuggestions,
greeting = "오늘도 좋은 하루 되세요! 궁금한 것들을 물어봐줘.",
),
onInputChange = {},
onSendClick = {},
onSuggestionClick = {},
onNewConversationClick = {},
onCloseClick = {},
onHistoryClick = {},
)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

[P2] Preview 이름과 실제 렌더링이 다릅니다.

두 Preview는 ChatState.greeting만 설정합니다. ChatScreengreeting을 사용하지 않습니다. 오버레이는 selectedCategory 상태로만 표시됩니다. 따라서 이 Preview들은 그리팅 오버레이를 보여주지 못하고 진입 상태와 동일하게 렌더링됩니다.

ChatGreetingOverlay를 직접 Preview로 만들거나, ChatScreen이 오버레이 상태를 파라미터로 받도록 바꾸십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt` around
lines 412 - 450, Update ChatScreenGreetingWithTitlePreview and
ChatScreenGreetingNoTitlePreview so they preview the greeting overlay through
the state ChatScreen actually consumes, such as setting the appropriate
selectedCategory, or preview ChatGreetingOverlay directly. Do not rely on
ChatState.greeting alone, and preserve the intended distinction between the
newline and no-newline greeting previews.

Comment on lines +452 to +469
@Preview(showBackground = true, name = "생각 중 (THINKING)")
@Composable
private fun ChatScreenThinkingPreview() {
TodakunTheme {
ChatScreen(
state =
ChatState(
messages = previewMessages.take(1).toPersistentList(),
),
onInputChange = {},
onSendClick = {},
onSuggestionClick = {},
onNewConversationClick = {},
onCloseClick = {},
onHistoryClick = {},
)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

[P3] "생각 중" Preview에 스트리밍 상태가 없습니다.

이 Preview는 streamingChatState를 기본값(Idle)으로 둡니다. 그래서 ThinkingIndicator가 렌더링되지 않습니다. StreamingChatState.Thinking을 지정하십시오.

♻️ 제안 변경
                 ChatState(
                     messages = previewMessages.take(1).toPersistentList(),
+                    streamingChatState = StreamingChatState.Thinking,
                 ),
📝 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
@Preview(showBackground = true, name = "생각 중 (THINKING)")
@Composable
private fun ChatScreenThinkingPreview() {
TodakunTheme {
ChatScreen(
state =
ChatState(
messages = previewMessages.take(1).toPersistentList(),
),
onInputChange = {},
onSendClick = {},
onSuggestionClick = {},
onNewConversationClick = {},
onCloseClick = {},
onHistoryClick = {},
)
}
}
`@Preview`(showBackground = true, name = "생각 중 (THINKING)")
`@Composable`
private fun ChatScreenThinkingPreview() {
TodakunTheme {
ChatScreen(
state =
ChatState(
messages = previewMessages.take(1).toPersistentList(),
streamingChatState = StreamingChatState.Thinking,
),
onInputChange = {},
onSendClick = {},
onSuggestionClick = {},
onNewConversationClick = {},
onCloseClick = {},
onHistoryClick = {},
)
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/ChatScreen.kt` around
lines 452 - 469, Update ChatScreenThinkingPreview to pass
StreamingChatState.Thinking through ChatScreen’s streamingChatState parameter,
so the preview renders the ThinkingIndicator instead of the default Idle state.

Comment on lines +36 to +37
.background(Color(0xFFF2F0FB))
.padding(vertical = 178.dp),

@coderabbitai coderabbitai Bot Aug 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

[P2] 하드코딩 색상과 고정 세로 패딩을 조정해 주세요.

두 가지 문제가 있습니다.

  1. Color(0xFFF2F0FB)는 디자인 시스템 토큰을 우회합니다. TodakunColor에 해당 색상을 추가하고 참조하세요. 파일 내 다른 색상은 모두 TodakunColor를 사용하므로 일관성도 깨집니다.
  2. vertical = 178.dp는 고정값입니다. 화면 높이가 작은 기기에서는 weight(1f) 이미지 영역이 거의 0이 되어 캐릭터가 보이지 않습니다. 비율 기반 여백 또는 Arrangement.SpaceBetween + 상한이 있는 패딩을 검토하세요.
♻️ 제안 변경(1번 항목)
-                .background(Color(0xFFF2F0FB))
+                .background(TodakunColor.primary50)

TodakunColor에 실제 토큰명을 확인 후 반영하세요. androidx.compose.ui.graphics.Color import도 제거 대상이 됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt` around
lines 36 - 37, Update the SplashScreen layout to replace the hardcoded
Color(0xFFF2F0FB) with a matching token defined in TodakunColor, removing the
direct Color import if unused. Replace the fixed vertical padding of 178.dp with
responsive spacing, such as weighted layout spacing or bounded padding, so the
image area remains visible on short screens.

Source: Path instructions

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

디자인 시스템과 별개의 토큰입니다

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@oungsi2000 [P2] 확인했습니다. Color(0xFFF2F0FB)는 디자인 시스템과 분리된 토큰이므로 TodakunColor로 교체하라는 지적은 철회합니다.

다만 vertical = 178.dp의 작은 화면 대응 문제는 별도 항목입니다. 이 부분은 기존 코멘트에 남아 있습니다.


✏️ Learnings added
Learnt from: oungsi2000
Repo: YAPP-Github/28th-App-Team-2-Android PR: 87
File: feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt:36-37
Timestamp: 2026-08-09T06:42:33.888Z
Learning: `feature/chat/src/main/java/com/kikidan/chat/screen/SplashScreen.kt`의 `ChatSplashScreen` 배경색 `Color(0xFFF2F0FB)`는 `TodakunColor` 디자인 시스템과 분리된 토큰이다. 해당 색상을 디자인 시스템 토큰으로 교체하라고 요구하지 않는다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Comment on lines +1 to +4
<?xml version="1.0" encoding="utf-8"?>
<resources>

</resources>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 채팅 관련 문자열 리소스가 어느 모듈에 정의되어 있는지 확인
fd -t f 'strings.xml' -x sh -c 'echo "=== {} ==="; rg -n "chat_" {} || true'

Repository: YAPP-Github/28th-App-Team-2-Android

Length of output: 2181


🏁 Script executed:

#!/bin/bash
set -e

echo "=== files ==="
git ls-files | rg '(^|/)(strings\.xml|.*ChatInputField.*|.*chat.*\.kt|.*chat.*\.xml)$' | head -200

echo
echo "=== references to designsystem chat strings ==="
rg -n 'chat_greeting_love_title|chat_splash_tagline|chat_|todak_chat_place_holder_chat_input|TodakunChatInputField|chat_header_title|chat_entry_question|chat_thinking_description|chat_history_empty|chat_history_new_chat' --glob '!build/**' --glob '!**/build/**' .

echo
echo "=== strings.xml sizes ==="
for f in $(fd -t f 'strings.xml' .); do
  echo "=$f: $(wc -l < "$f") lines =="
done

Repository: YAPP-Github/28th-App-Team-2-Android

Length of output: 14510


🏁 Script executed:

#!/bin/bash
set -e

echo "=== module dependency files mentioning feature/chat or core/designsystem ==="
fd -t f '(build\.gradle(\.kts)?|settings\.gradle|gradle\.properties)$' . -e kts -e gradle -e properties -x sh -c 'echo "===== $1 ====="; rg -n "feature[:\s/]*chat|core[:\s/]*designsystem|:feature:chat|:core:designsystem" "$1" || true' sh {}

echo
echo "=== likely source files with chat string usage ==="
for f in \
  "$(git ls-files | rg 'feature/chat/.+_Chat*.kt$|feature/chat/.+Chat.*\.kt$|core/designsystem/.+\(.*Chat|core/designsystem/.+ChatInputField.*\.kt$' | head -200)"
  ; do
    if [ -n "$f" ]; then echo "===== $f ====="; sed -n '1,260p' "$f"; fi
  done

Repository: YAPP-Github/28th-App-Team-2-Android

Length of output: 871


🏁 Script executed:

#!/bin/bash
set -e

echo "===== feature/chat/build.gradle.kts ====="
sed -n '1,220p' feature/chat/build.gradle.kts

echo "===== core/designsystem/build.gradle.kts ====="
sed -n '1,220p' core/designsystem/build.gradle.kts

echo "===== app/build.gradle.kts relevant modules ====="
sed -n '30,220p' app/build.gradle.kts

Repository: YAPP-Github/28th-App-Team-2-Android

Length of output: 3883


[P2] 빈 리소스 파일 삭제와 채팅 전용 리소스 경계 정리가 필요합니다.

  1. feature/chat/src/main/res/values/strings.xml에 리소스가 없는 만큼 사용하지 않는다면 삭제합니다.
  2. core/designsystem/src/main/res/values/strings.xml에는 chat_greeting_love_title, chat_splash_tagline처럼 화면 흐름/화면별 UI 전용 리소스가 많습니다. 기능 모듈용 UI 리소스는 feature/chat에 두고, TodakunChatInputField.placeholder처럼 컴포넌트 기본값으로 재사용되는 리소스만 core/designsystem에 두도록 기준을 정리해 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@feature/chat/src/main/res/values/strings.xml` around lines 1 - 4, 리소스 경계를
정리하세요. 비어 있는 feature/chat의 values/strings.xml은 삭제하고, core/designsystem의
chat_greeting_love_title 및 chat_splash_tagline처럼 채팅 화면 전용 문자열은 feature/chat으로
이동하세요. TodakunChatInputField.placeholder처럼 공용 컴포넌트 기본값으로 재사용되는 리소스만
core/designsystem에 남기세요.

Source: Path instructions

@oungsi2000
oungsi2000 marked this pull request as ready for review August 9, 2026 08:00
oungsi2000 and others added 15 commits August 10, 2026 21:47
- MainActivity에서 Greeting/Scaffold 제거, ChatScreen 직접 호출 (설계 2-3)
- ThinkingIndicator: 점 3개 순차 애니메이션 (designsystem 미포함, chat 전용)
- ChatGreetingOverlay: 3초 자동 페이드아웃 + X 즉시 닫기, greeting 개행 유무 방어 처리 (설계 2-5)
- ChatScreen: 진입 상태(캐릭터+제목+추천칩 6개) / 대화 상태(LazyColumn, 자동 스크롤) / hiltViewModel() 배선
- CharacterAvatar: 이미지 asset 미확보로 primary100 색 원 플레이스홀더 (설계 2-7)
- @Preview 6종: 진입·그리팅(개행 있음/없음)·THINKING·TYPING·대화 이어보기

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
P1:
- ktlintCheck 4개 모듈 전부 실패 → ktlintFormat + ThinkingIndicatorDefaults의
  const val 네이밍(MinAlpha/CycleMillis → MIN_ALPHA/CYCLE_MILLIS), 자동 수정
  불가한 120자 초과 3곳(테스트 JSON 픽스처, 테스트 함수명) 수동 분리
- onSendClick이 가드 체크 전에 input을 비워 스트리밍 중/quota 소진 시
  사용자 입력이 무음 소실 → send()의 클리어 타이밍을 가드 통과 이후로 이동

P2:
- 전송 실패 시 낙관적 사용자 메시지가 PENDING으로 영구 잔존 → FAILED로 표시
- ChatScreen의 자동 스크롤이 streamingText 16ms 틱마다 animateScrollToItem을
  재시작해 덜컹거림 → scrollToItem(애니메이션 없음)으로 교체
- delta 없이(start+done만) 스트림 종료 시 빈 어시스턴트 메시지가 추가됨 →
  가드 추가, 회귀 테스트 1건 신설(기존 conversationId 전파 테스트는
  Delta 픽스처를 보강해 원래 의도대로 복원)

P3(미사용 install(SSE), ChatStreamException 패키지 위치, 서로게이트 페어
절단)는 선택 반영 대상으로 이번엔 보류.

검증: app:assembleDebug, feature:chat:assembleDebug, 전체 유닛 테스트,
ktlintCheck 전부 통과.
기존 ChatScreen을 상태를 관리하는 ChatRoute와 순수 UI 컴포넌트인 ChatScreen으로 분리한다.
ChatPhase와 별도 문자열로 파편화되어 있던 스트리밍 상태를 StreamingChatState sealed interface로 통합하여 관리한다.

- 스트리밍 에러 및 일반 에러 발생 시 스낵바를 노출하도록 사이드 이펙트 처리 로직 추가
- feature/chat 모듈에 Material 3 의존성 추가 및 에러 메시지 정의
- 캐릭터 아바타 컴포저블을 별도 함수로 추출하여 재사용성 확보

# Conflicts:
#	feature/chat/build.gradle.kts

# Conflicts:
#	gradle/libs.versions.toml
채팅 시 키보드가 입력창을 가리지 않도록 MainActivity에 `adjustResize` 설정을 추가한다.
채팅 헤더, 상태 메시지, 에러 문구 등 채팅에 필요한 문자열과
운세 카테고리별(관계, 연애, 성취, 금전, 건강) 안내 문구 및 이미지 리소스를 추가한다.
SnackbarHostState를 MainActivity로 상속하여 스낵바 표시 로직을 중앙 집중화하고, 디자인 시스템의 TodakunSnackbar를 적용한다.

- ChatGreetingOverlay와 ThinkingIndicator를 component 패키지로 이동한다.
- ChatGreetingOverlay가 ChatCategory를 직접 받아 카테고리에 맞는 제목, 본문 및 캐릭터 이미지를 출력하도록 개편한다.
- ChatScreen에서 입력창(InputField)의 높이를 동적으로 측정하여 메시지 리스트의 하단 패딩에 반영하고, 키보드(IME) 가시성 변화에 따른 스크롤 로직을 개선한다.
- 기존의 임시 캐릭터 아바타를 실제 드로어블 에셋을 사용하는 ProfileCharacterImage로 교체한다.
- SendChatMessageUseCase에 UI 개발 및 테스트를 위한 모의(Mock) 스트림 응답 로직을 추가한다.
`ChatCategory.toCharacterResourceId()`를 유틸리티 함수로 분리하여 `ChatGreetingOverlay`와 `ChatScreen`에서 공유한다.

- `ChatEntryContent`를 `ChatMessageList`의 `item`으로 통합하여 초기 진입 화면과 메시지 목록의 스크롤 상태 관리를 일원화한다.
- 선택된 카테고리에 맞는 캐릭터 이미지를 표시하고, `graphicsLayer`를 사용해 특정 카테고리 선택 시 이미지를 1.4배 확대한다.
- `prevCategory` 상태를 추가하여 카테고리 값이 `null`로 변경되어도 직전에 선택했던 캐릭터 이미지가 유지되도록 개선한다.
- `StreamingChatState`가 `Idle`이 아닌 모든 경우에 인디케이터(Thinking/Typing)를 표시하도록 로직을 수정한다.
단순히 흐려졌다 진해지던 애니메이션을 점이 위아래로 튀어 오르며 색상이 변하는 방식으로 변경하고,
우측에 '생각 중' 텍스트 레이블을 추가한다.
Figma 디자인 사양에 맞춰 도트 크기, 간격, 바운스 높이 및 타이밍을 조정한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01299PBN5qzgPtdnPH2e25rs
메시지에 액션(ChatAction)이 포함된 경우 채팅 버블 내에 `TodakunChatActionCard`를 표시하며, 날짜 형식을 지정하는 확장 함수를 추가한다. 또한 채팅 헤더와 입력창 버튼들의 리플 효과(indication)를 제거하여 디자인 가이드를 반영한다.

- `TodakunChatInputField`, `TodakunChatHeader` 내 클릭 가능한 요소에 `indication = null` 적용
- 채팅 메시지 내 `ChatAction` 유무에 따른 `TodakunChatActionCard` 렌더링 로직 추가
- `LocalDate.toActionDateText()` 확장 함수 추가 (yyyy . M . d (E) 형식)
- 스플래시 및 히스토리 화면용 문자열 리소스 추가
채팅 메시지 내에서 카테고리와 날짜 정보를 표시하고 후속 작업을 유도하는 TodakunChatActionCard를 추가한다.
CalendarActionButton은 LocalRippleConfiguration을 null로 설정해 기본 리플 효과를 제거했으며,
isPressed 상태에 따라 배경색에 gray975(16% 알파)를 composite하여 직접 눌림 피드백을 제공한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01299PBN5qzgPtdnPH2e25rs
Assistant 메시지와 Thinking/Typing 인디케이터 상단에 24.dp 간격을 추가하여 레이아웃을 개선한다.
메시지 렌더링 시 `else` 분기 대신 `MessageRole.ASSISTANT`를 명시적으로 처리하도록 변경한다.
채팅 할당량(quota) 정보를 불러오는 동안 브랜드 로고와 캐릭터 이미지가 포함된 스플래시 화면을 노출한다.
`state.quota`가 null인 초기 로딩 상태에서 ChatSplashScreen을 렌더링하도록 ChatRoute의 로직을 변경했다.
feature:chat 모듈에 정의되어 있던 채팅 UI 관련 문자열 리소스들을 core:designsystem 모듈로 옮겨 공통으로 관리한다.
이에 따라 ThinkingIndicator, ChatRoute 등 관련 컴포넌트에서 참조하는 R 클래스 패키지를 변경하고, ChatRoute의 미사용 임포트를 정리한다.
TodakunChatActionCard의 이름을 용도에 맞춰 TodakunChatCalenderActionCard로 변경한다.
카드 버튼 클릭 시 CalendarContract를 사용하여 시스템 캘린더의 일정 추가 화면으로 연결되도록 구현한다.
별도의 콜백 전달 대신 ChatScreen 내에서 직접 Intent를 시작하도록 로직을 변경한다.
코드 스타일 가이드에 맞춰 Intent 생성 부분의 들여쓰기와 줄바꿈을 조정한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01299PBN5qzgPtdnPH2e25rs
Compose의 안정성(Stability)을 보장하여 불필요한 리컴포지션을 방지하기 위해
preview 데이터 타입을 표준 List에서 PersistentList로 변경한다.
ChatState 생성을 위해 List.take()를 호출하는 부분에도 toPersistentList()를 추가하여 타입을 맞춘다.
oungsi2000 and others added 4 commits August 10, 2026 21:47
키보드 활성화 시 마지막 메시지가 입력창에 가려지지 않고 완전히 보이도록 scrollToItem에 Int.MAX_VALUE 오프셋을 적용한다.
메시지 간 간격을 20dp로 조정하고 추천 칩 표시 조건을 conversationId 존재 여부로 단순화하여 레이아웃을 다듬는다.

Co-Authored-By: Claude 3.5 Sonnet <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_chat_layout_adjustment
ChatGreetingOverlay에서 `clickable` 대신 `pointerInput`을 사용하여 하위 레이어로의 터치 이벤트 전파를 명시적으로 차단한다.
캘린더 일정 추가 시 종료 시간(`EXTRA_EVENT_END_TIME`)을 계산해 전달하고, 시스템에 캘린더 앱이 없는 경우 등에 대비해 예외 처리 및 스낵바 알림을 추가한다.
또한 헤더의 잔여 횟수 변수명을 `freeChatRemaining`으로 변경하여 의미를 명확히 하고, 스플래시 화면 노출 조건을 로딩 상태와 대화 ID 유무에 따라 세분화한다.
selectedCategory가 null로 변할 때 이전 값을 유지하기 위한 업데이트 로직을
컴포지션 단계에서 LaunchedEffect 내로 이동하여 부수 효과를 안전하게 처리한다.
사용되지 않는 빈 리소스 파일(strings.xml)을 삭제한다.
onActionClick 내의 캘린더 인텐트 설정 로직에 줄바꿈을 적용하여 가독성을 높이고,
Modifier 체인과 함수 파라미터의 스타일을 일관되게 수정한다.
@oungsi2000
oungsi2000 force-pushed the feat/78-chat-screen branch from 55798ff to ebda000 Compare August 10, 2026 12:47
@oungsi2000
oungsi2000 merged commit 7ea259d into feat/59-todak-chat Aug 10, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant