Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions lib/screens/chat_room_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,24 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
}

void _updateInputFieldHeight() {
final lineCount = '\n'.allMatches(_messageController.text).length + 1;
double newHeight = 40.h + ((lineCount - 1) * 14.h);
newHeight = newHeight.clamp(40.h, 70.h);
// '\n' 카운트 대신 TextPainter로 실제 렌더링된 시각적 라인 수 계산.
// ChatInputBar 레이아웃에서 TextField 가용 너비:
// 왼쪽 패딩(16) + + 버튼(40) + 버튼 우측 갭(8) +
// 전송 버튼 왼쪽 갭(4) + 전송 버튼(40) + 전송 오른쪽 패딩(16) +
// TextField contentPadding horizontal(12 * 2) = 148
final availableWidth = MediaQuery.of(context).size.width - 148.w;
final painter = TextPainter(
text: TextSpan(
text: _messageController.text.isEmpty ? ' ' : _messageController.text,
style: CustomTextStyles.p2.copyWith(fontWeight: FontWeight.w400, height: 1.2),
),
textDirection: TextDirection.ltr,
maxLines: null,
)..layout(maxWidth: availableWidth);

final clampedLines = painter.computeLineMetrics().length.clamp(1, 5);
double newHeight = 40.h + ((clampedLines - 1) * 15.h);
newHeight = newHeight.clamp(40.h, 130.h);
if (_inputFieldHeight != newHeight && mounted) {
setState(() => _inputFieldHeight = newHeight);
}
Expand Down
83 changes: 47 additions & 36 deletions lib/widgets/chat_input_bar.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:romrom_fe/enums/context_menu_enums.dart';
Expand Down Expand Up @@ -37,11 +38,19 @@ class ChatInputBar extends StatelessWidget {
final bottomPadding = Platform.isIOS ? 8.h + MediaQuery.of(context).padding.bottom : 21.h;
final bool sendDisabled = !hasText || isInputDisabled || isSendingMessage;

// 동적 라운드 계산: 1줄(캡슐형) → 여러 줄(20.r 둥근 사각형)
final double minH = 40.h;
final double maxH = 130.h;
final double clampedHeight = inputFieldHeight.clamp(minH, maxH);
final double t = maxH > minH ? ((clampedHeight - minH) / (maxH - minH)) : 0.0;
Comment on lines +44 to +45
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Dart null-safe, what is the return type of double.clamp(lower, upper)? Is assigning it directly to a doublevariable valid without.toDouble() or a cast?

💡 Result:

In null-safe Dart, double.clamp(lower, upper) has static return type num (it inherits num.clamp), i.e. clamp(num lowerLimit, num upperLimit) → num. [1][2]

So this is not directly assignable to a double variable without a conversion/cast:

double x = d.clamp(a, b); // compile-time error: num → double

Use either:

double x = d.clamp(a, b).toDouble();

or

double x = d.clamp(a, b) as double;

(At runtime the value will be a double when the receiver is a double, but the static type is still num.) [1][3]

Sources: [1] [2] [3]


🏁 Script executed:

cat -n lib/widgets/chat_input_bar.dart | sed -n '35,50p'

Repository: TEAM-ROMROM/RomRom-FE

Length of output: 932


🏁 Script executed:

cat -n lib/widgets/chat_input_bar.dart | head -100 | grep -E "inputFieldHeight|class.*extends"

Repository: TEAM-ROMROM/RomRom-FE

Length of output: 275


clamp 반환 타입 처리 필요 - 타입 오류 방지

Line 42에서 doubleclamp() 메서드는 static 타입으로 num을 반환합니다. double 변수에 직접 할당하면 타입 오류가 발생하므로 명시적 변환이 필요합니다.

🔧 제안 수정안
-    final double clampedHeight = inputFieldHeight.clamp(minH, maxH);
+    final double clampedHeight = inputFieldHeight.clamp(minH, maxH).toDouble();
📝 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
final double clampedHeight = inputFieldHeight.clamp(minH, maxH);
final double t = maxH > minH ? ((clampedHeight - minH) / (maxH - minH)) : 0.0;
final double clampedHeight = inputFieldHeight.clamp(minH, maxH).toDouble();
final double t = maxH > minH ? ((clampedHeight - minH) / (maxH - minH)) : 0.0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/widgets/chat_input_bar.dart` around lines 42 - 43, The clamp call returns
a num, so assign to clampedHeight using an explicit double conversion to avoid a
type error: replace the assignment in chat_input_bar.dart (variable
clampedHeight using inputFieldHeight.clamp(minH, maxH)) with a conversion such
as inputFieldHeight.clamp(minH, maxH).toDouble() (or cast with as double); also
ensure minH and maxH are doubles to keep types consistent.

final double borderRadius = lerpDouble(clampedHeight / 2, 20.r, t) ?? clampedHeight / 2;

return Container(
padding: EdgeInsets.only(top: 8.h, left: 16.w, bottom: bottomPadding),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// + 버튼 (하단 고정)
Padding(
padding: EdgeInsets.only(right: 8.0.w),
child: SizedBox(
Expand Down Expand Up @@ -78,9 +87,10 @@ class ChatInputBar extends StatelessWidget {
),
),
),
// 텍스트 입력 필드 (suffixIcon 제거)
Expanded(
child: SizedBox(
height: 40.h <= inputFieldHeight && inputFieldHeight <= 70.h ? inputFieldHeight : 40.h,
height: clampedHeight,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

고정 높이(.h)와 세로 패딩(.h) 동시 사용은 iPad 오버플로우 위험이 큽니다.

Line 84와 Line 107 조합은 가이드에서 금지한 패턴입니다. 세로 패딩은 고정 px로 바꾸는 게 안전합니다.

🔧 제안 수정안
-                  contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
+                  contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8),

As per coding guidelines: "Never combine height: N.h with padding: vertical: N.h as both scale on iPad causing overflow. Use fixed pixel padding instead."

Also applies to: 107-107

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/widgets/chat_input_bar.dart` at line 84, The ChatInputBar widget
currently combines a scaled height (clampedHeight used in the Container's
height) with scaled vertical padding (vertical padding using .h), which risks
overflow on iPad; update the build of ChatInputBar to stop using scaled vertical
padding — change any EdgeInsets.symmetric(vertical: X.h) (or similar) in the
same widget to a fixed pixel value (e.g., EdgeInsets.symmetric(vertical: X) in
raw pixels) or remove the vertical padding so it does not scale with .h, keeping
clampedHeight unchanged; locate occurrences by searching for clampedHeight and
the Container/Padding widgets inside ChatInputBar to apply the fix.

child: TextField(
controller: controller,
enabled: !isInputDisabled,
Expand All @@ -99,49 +109,50 @@ class ChatInputBar extends StatelessWidget {
hintStyle: CustomTextStyles.p2.copyWith(color: AppColors.opacity50White),
filled: true,
fillColor: AppColors.opacity10White,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(100.r), borderSide: BorderSide.none),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(borderRadius),
borderSide: BorderSide.none,
),
contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
suffixIcon: TextFieldTapRegion(
child: Material(
color: Colors.transparent,
child: ClipOval(
child: InkWell(
onTap: sendDisabled ? null : onSend,
customBorder: const CircleBorder(),
highlightColor: AppColors.buttonHighlightColorGray,
splashColor: AppColors.buttonHighlightColorGray.withValues(alpha: 0.3),
child: Container(
margin: EdgeInsets.all(4.w),
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: sendDisabled ? AppColors.secondaryBlack2 : AppColors.primaryYellow,
shape: BoxShape.circle,
),
child: Center(
child: Icon(
AppIcons.arrowUpward,
color: sendDisabled ? AppColors.secondaryBlack1 : AppColors.primaryBlack,
size: 32.w,
),
),
),
),
onSubmitted: sendDisabled ? null : (_) => onSend(),
),
),
),
// 전송 버튼 (Row 자식으로 분리, 하단 고정)
Padding(
padding: EdgeInsets.only(left: 4.w, right: 16.w),
child: SizedBox(
width: 40.w,
height: 40.w,
child: Material(
color: Colors.transparent,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

직접 Color 사용(Colors.transparent)은 가이드 위반입니다.

Line 120은 AppColors 팔레트 상수로 치환해 주세요.

As per coding guidelines: "Use AppColors for color values in Flutter (direct Color code definition prohibited)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/widgets/chat_input_bar.dart` at line 120, Replace the direct use of
Colors.transparent in the ChatInputBar widget (the color: Colors.transparent
assignment in its build/decoration) with the appropriate AppColors palette
constant; locate the color usage inside the ChatInputBar (e.g., in the
Container/BoxDecoration or TextField decoration) and swap it to the matching
AppColors value (e.g., AppColors.transparent or the designated
transparent/overlay color in AppColors) so the code follows the AppColors
guideline.

child: ClipOval(
child: InkWell(
onTap: sendDisabled ? null : onSend,
customBorder: const CircleBorder(),
highlightColor: AppColors.buttonHighlightColorGray,
splashColor: AppColors.buttonHighlightColorGray.withValues(alpha: 0.3),
child: Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: sendDisabled ? AppColors.secondaryBlack2 : AppColors.primaryYellow,
shape: BoxShape.circle,
),
child: Center(
child: Icon(
AppIcons.arrowUpward,
color: sendDisabled ? AppColors.secondaryBlack1 : AppColors.primaryBlack,
size: 32.w,
),
),
),
),
suffixIconConstraints: BoxConstraints(
minWidth: 40.w,
minHeight: 40.w,
maxWidth: 40.w,
maxHeight: 40.w,
),
),
onSubmitted: sendDisabled ? null : (_) => onSend(),
),
),
),
SizedBox(width: 16.w),
],
),
);
Expand Down
Loading