Skip to content

Conversation

@SeoHyun1024
Copy link
Collaborator

@SeoHyun1024 SeoHyun1024 commented Feb 5, 2026

#458

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • 채팅에서 이미지 전송 기능 추가
    • 전송된 이미지를 전체 화면 갤러리 뷰어로 표시
    • 메시지 입력 메뉴에 이미지 선택 옵션 추가
  • 개선사항

    • 이미지 전송 시 즉시 화면에 반영되는 낙관적 UI 적용 및 동기화 향상
    • 채팅 메시지 파싱/로딩 안정성 개선
    • 뒤로가기 동작 추가 및 메시지/카드 레이아웃 패딩·테두리 조정
    • 컨텍스트 메뉴의 터치 피드백 방식 변경 (Ripple 제거)

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

Walkthrough

이 PR은 채팅에 이미지 메시징(선택, 업로드, 전송, 표시)을 추가하고, ApiPageable 모델을 pageNumber/pageSize/offset/paged/unpaged/sort 형태로 변경하며, WebSocket 전송 및 관련 UI/컨텍스트 메뉴를 업데이트합니다.

Changes

Cohort / File(s) Summary
ApiPageable 모델 업데이트
lib/models/apis/objects/api_pageable.dart, lib/models/apis/objects/api_pageable.g.dart
ApiPageable 필드 구조를 size/number에서 pageNumber, pageSize, offset, paged, unpaged, sort로 변경. ApiSort 클래스 및 Json (de)serializers 추가. JsonKey(fromJson: ...) 헬퍼 적용.
이미지 메시징 전체 흐름
lib/screens/chat_room_screen.dart, lib/services/chat_websocket_service.dart, lib/widgets/chat_image_bubble.dart
이미지 선택(ImagePicker), 업로드(ImageApi), 낙관적 로컬 삽입, WebSocket 전송(imageUrls 포함) 및 서버 동기화 로직 추가. 이미지 메시지 렌더링용 chatImageBubble 위젯 추가.
API 통합 및 파싱 변경
lib/services/apis/chat_api.dart
ApiPageable 생성자 호출을 pageSize/pageNumber로 변경. 메시지 응답 파싱에 try-catch 추가하여 파싱 실패 시 예외 던짐.
컨텍스트 메뉴 터치 처리 변경
lib/widgets/common/romrom_context_menu.dart
메뉴 항목 렌더링에서 InkWellGestureDetector로 교체(시각적 ripple 제거).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChatScreen
    participant ImagePicker
    participant ImageApi
    participant WebSocket
    participant Server

    User->>ChatScreen: "사진 선택하기" 선택
    ChatScreen->>ImagePicker: 이미지 선택 요청
    ImagePicker-->>ChatScreen: 이미지 파일 반환
    ChatScreen->>ChatScreen: 로컬에 낙관적 이미지 메시지 생성
    ChatScreen->>ImageApi: 이미지 업로드 요청 (multipart)
    ImageApi->>Server: 업로드 전달
    Server-->>ImageApi: imageUrls 반환
    ImageApi-->>ChatScreen: imageUrls 전달
    ChatScreen->>WebSocket: payload( chatRoomId, content, type=image, imageUrls )
    WebSocket->>Server: JSON 인코딩 후 전송
    Server-->>ChatScreen: 서버 메시지(확인/동기화 데이터)
    ChatScreen->>ChatScreen: 로컬 메시지와 서버 메시지 병합/동기화
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Cassiiopeia
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경사항인 사진 첨부 후 채팅 전송 기능을 명확하게 설명하고 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 20260127_#458_사진_첨부_후_채팅_전송_기능_추가

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

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
lib/widgets/common/romrom_context_menu.dart (1)

349-382: ⚠️ Potential issue | 🟡 Minor

GestureDetector에 HitTestBehavior 설정은 선택사항 — 기술적 근거 재검토 필요

현재 코드에서 Container는 height와 padding을 명시적으로 정의하고 있으므로, 이미 padding 영역까지 포함한 전체 범위가 GestureDetector의 hit-test 범위로 설정됩니다. 따라서 기본 동작(HitTestBehavior.deferToChild)에서도 padding 영역의 탭이 정상적으로 감지됩니다.

다만 HitTestBehavior.opaque 추가는 다음 이유로 권장됩니다:

  • 코드 명확성: 의도적인 hit-test 동작을 명시적으로 표현
  • 팀 일관성: 이 파일의 다른 오버레이 GestureDetector에서 translucent를 사용하는 패턴과 통일
  • 투명 영역 처리: 명확하게 전체 padding 영역이 탭 가능함을 보장
🤖 Fix all issues with AI agents
In `@lib/screens/chat_room_screen.dart`:
- Around line 62-64: 비동기 이미지 업로드 후 위젯이 이미 dispose될 수 있어 "_sendImage()" 호출 전과 메서드
내부의 모든 setState() 호출 직전에 mounted 여부를 검사해야 합니다; 구체적으로 ImageApi().uploadImages()를
await한 뒤 즉시 context.mounted 또는 mounted를 체크하고, setState()를 호출하는 모든 위치(예:
_sendImage 내부의 상태 업데이트 블록과 에러/성공 핸들러)에서 mounted 확인을 추가해 widget이 dispose된 경우 상태
업데이트나 Navigator/Scaffold 관련 작업을 건너뛰도록 변경하세요.
- Around line 699-725: Guard against empty or null image lists before
constructing PhotoViewerMultipleImage: check message.imageUrls (or
message.imageUrls?.isEmpty) and when it's null/empty render the existing
placeholder/error widget (e.g., the SizedBox with Center and
CircularProgressIndicator or ErrorImagePlaceholder) inside the same Container;
otherwise, pass the non-empty list into PhotoViewerMultipleImage. This change
should be applied around the MessageType.image branch where
PhotoViewerMultipleImage is created (referencing PhotoViewerMultipleImage,
message.imageUrls, and the existing placeholder/errorWidget).

In `@lib/services/apis/chat_api.dart`:
- Around line 138-145: In the catch block around
jsonDecode/ChatRoomResponse.fromJson, stop including the stackTrace in the
Exception message; instead log the stackTrace separately (e.g., debugPrint or a
logger) and rethrow or throw a concise Exception like "채팅 메시지 파싱 실패" so the
error message remains brief while the stackTrace is recorded separately for
debugging; update the catch that currently does "catch (e, stackTrace) { throw
Exception('... $e $stackTrace'); }" to log stackTrace and throw a minimal
message.

In `@lib/services/chat_websocket_service.dart`:
- Around line 203-229: sendMessage currently allows MessageType.image without
ensuring imageUrls is non-null/non-empty, which can send an invalid payload; add
an early guard in sendMessage (before building/sending payload) that when type
== MessageType.image validates imageUrls != null && imageUrls.isNotEmpty and
throws an ArgumentError (or returns) if violated, otherwise proceed to include
payload['imageUrls'] = imageUrls and call _stompClient!.send as before;
reference sendMessage, MessageType.image, imageUrls, payload and the
_stompClient!.send call so the check is placed prior to creating/sending the
payload.

In `@lib/widgets/chat_image_bubble.dart`:
- Around line 40-44: Replace the hardcoded Colors.transparent used in the
Material widget's color property with the team color constant
AppColors.transparent; locate the Material constructor in chat_image_bubble.dart
(the widget where borderRadius: BorderRadius.circular(10.r), clipBehavior:
Clip.antiAlias are set) and change color: Colors.transparent to color:
AppColors.transparent so the app uses the shared color constant.

Comment on lines 138 to 145
if (response.statusCode >= 200 && response.statusCode < 300) {
final Map<String, dynamic> responseData = jsonDecode(response.body);
chatRoomResponse = ChatRoomResponse.fromJson(responseData);
debugPrint('채팅 메시지 조회 성공: ${chatRoomResponse.messages?.content.length ?? 0}개');
try {
final Map<String, dynamic> responseData = jsonDecode(response.body);
chatRoomResponse = ChatRoomResponse.fromJson(responseData);
debugPrint('채팅 메시지 조회 성공: ${chatRoomResponse.messages?.content.length ?? 0}개');
} catch (e, stackTrace) {
throw Exception('채팅 메시지 파싱 실패: $e $stackTrace');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

예외 메시지에 stackTrace 포함 지양
stackTrace를 예외 메시지에 붙이면 사용자 노출/로그 노이즈 위험이 큽니다. 로그로 분리하고 예외 메시지는 간결하게 유지하세요.

🔧 수정 제안
       try {
         final Map<String, dynamic> responseData = jsonDecode(response.body);
         chatRoomResponse = ChatRoomResponse.fromJson(responseData);
         debugPrint('채팅 메시지 조회 성공: ${chatRoomResponse.messages?.content.length ?? 0}개');
       } catch (e, stackTrace) {
-        throw Exception('채팅 메시지 파싱 실패: $e $stackTrace');
+        debugPrint('채팅 메시지 파싱 실패: $e\n$stackTrace');
+        throw Exception('채팅 메시지 파싱 실패');
       }
📝 Committable suggestion

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

Suggested change
if (response.statusCode >= 200 && response.statusCode < 300) {
final Map<String, dynamic> responseData = jsonDecode(response.body);
chatRoomResponse = ChatRoomResponse.fromJson(responseData);
debugPrint('채팅 메시지 조회 성공: ${chatRoomResponse.messages?.content.length ?? 0}개');
try {
final Map<String, dynamic> responseData = jsonDecode(response.body);
chatRoomResponse = ChatRoomResponse.fromJson(responseData);
debugPrint('채팅 메시지 조회 성공: ${chatRoomResponse.messages?.content.length ?? 0}개');
} catch (e, stackTrace) {
throw Exception('채팅 메시지 파싱 실패: $e $stackTrace');
}
if (response.statusCode >= 200 && response.statusCode < 300) {
try {
final Map<String, dynamic> responseData = jsonDecode(response.body);
chatRoomResponse = ChatRoomResponse.fromJson(responseData);
debugPrint('채팅 메시지 조회 성공: ${chatRoomResponse.messages?.content.length ?? 0}개');
} catch (e, stackTrace) {
debugPrint('채팅 메시지 파싱 실패: $e\n$stackTrace');
throw Exception('채팅 메시지 파싱 실패');
}
🤖 Prompt for AI Agents
In `@lib/services/apis/chat_api.dart` around lines 138 - 145, In the catch block
around jsonDecode/ChatRoomResponse.fromJson, stop including the stackTrace in
the Exception message; instead log the stackTrace separately (e.g., debugPrint
or a logger) and rethrow or throw a concise Exception like "채팅 메시지 파싱 실패" so the
error message remains brief while the stackTrace is recorded separately for
debugging; update the catch that currently does "catch (e, stackTrace) { throw
Exception('... $e $stackTrace'); }" to log stackTrace and throw a minimal
message.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

✅ 프로젝트 빌드 성공

APK 빌드가 완료되었습니다.

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

🤖 Fix all issues with AI agents
In `@lib/screens/chat_room_screen.dart`:
- Around line 740-757: The outgoing message branch incorrectly applies
BoxConstraints(maxWidth: 264.w, maxHeight: 264.h) causing long text in the text
bubble to be clipped; update the ternary so that chatImageBubble(message.type ==
MessageType.image) keeps the maxHeight constraint inside the image branch (or
within chatImageBubble) while the text Container uses only
BoxConstraints(maxWidth: 264.w) (remove maxHeight) so long text can wrap; locate
the ternary around message.type, the chatImageBubble call, and the outgoing
Container with constraints to make this change.
🧹 Nitpick comments (2)
lib/widgets/chat_image_bubble.dart (1)

29-30: placeholder SizedBox 크기에 ScreenUtil 미적용

bubble 영역의 위젯들은 264.w, 10.r 등 ScreenUtil을 사용하고 있지만, placeholder의 SizedBox(width: 32, height: 32)는 논리 픽셀 고정값입니다. 일관성을 위해 .w를 적용하는 것을 권장합니다.

♻️ 수정 제안
-            placeholder: (_, __) => const Center(
-              child: SizedBox(width: 32, height: 32, child: CircularProgressIndicator(color: AppColors.primaryYellow)),
+            placeholder: (_, __) => Center(
+              child: SizedBox(width: 32.w, height: 32.w, child: CircularProgressIndicator(color: AppColors.primaryYellow)),
             ),

bubble 내 placeholder도 동일하게:

-                child: SizedBox(
-                  width: 32,
-                  height: 32,
+                child: SizedBox(
+                  width: 32.w,
+                  height: 32.w,

Also applies to: 56-58

lib/services/chat_websocket_service.dart (1)

220-220: enum 직렬화 방식 개선 제안

type.toString().split('.').last.toUpperCase()보다 Dart 2.17+의 .name 속성을 사용하면 더 간결하고 안전합니다.

♻️ 수정 제안
-      'type': type.toString().split('.').last.toUpperCase(),
+      'type': type.name.toUpperCase(),

Comment on lines +740 to +757
message.type == MessageType.image
? chatImageBubble(context, message)
: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
constraints: BoxConstraints(maxWidth: 264.w, maxHeight: 264.h),
decoration: BoxDecoration(
color: AppColors.primaryYellow,
borderRadius: BorderRadius.circular(10.r),
),
child: Text(
message.content ?? '',
style: CustomTextStyles.p2.copyWith(
color: AppColors.textColorBlack,
fontWeight: FontWeight.w400,
height: 1.2,
),
),
),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

발신 텍스트 메시지에 maxHeight: 264.h 제약이 적용되어 긴 텍스트가 잘릴 수 있습니다

수신 메시지(Line 703)에는 maxWidth만 설정되어 있지만, 발신 메시지(Line 744)에는 maxHeight: 264.h도 추가되어 있습니다. 이미지 버블과 텍스트 버블이 ternary로 분기되면서 maxHeight가 텍스트 Container에도 적용된 것으로 보입니다. 긴 텍스트 메시지가 264.h에서 잘리게 됩니다.

🔧 수정 제안
                 message.type == MessageType.image
                     ? chatImageBubble(context, message)
                     : Container(
                         padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
-                        constraints: BoxConstraints(maxWidth: 264.w, maxHeight: 264.h),
+                        constraints: BoxConstraints(maxWidth: 264.w),
                         decoration: BoxDecoration(
🤖 Prompt for AI Agents
In `@lib/screens/chat_room_screen.dart` around lines 740 - 757, The outgoing
message branch incorrectly applies BoxConstraints(maxWidth: 264.w, maxHeight:
264.h) causing long text in the text bubble to be clipped; update the ternary so
that chatImageBubble(message.type == MessageType.image) keeps the maxHeight
constraint inside the image branch (or within chatImageBubble) while the text
Container uses only BoxConstraints(maxWidth: 264.w) (remove maxHeight) so long
text can wrap; locate the ternary around message.type, the chatImageBubble call,
and the outgoing Container with constraints to make this change.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

✅ 프로젝트 빌드 성공

APK 빌드가 완료되었습니다.

Copy link
Member

@Cassiiopeia Cassiiopeia left a comment

Choose a reason for hiding this comment

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

확인했습니다

Comment on lines -350 to 351
InkWell(
GestureDetector(
onTap: () {
Copy link
Member

Choose a reason for hiding this comment

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

이게뭐예요? InkWell?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Screenshot 2026-02-09 at 12 12 02 AM

버튼 눌렀을 때 잉크 처럼 퍼지는 애니메이션 들어가있는 위젯인데 눌렀을 떼 테두리가 생격서 GestureDetector로 교체햇습니다

@SeoHyun1024 SeoHyun1024 merged commit 0a74fda into main Feb 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants