fix(chat-room): 채팅 아이템 UI, 메시지 입력 창 UI
- 채팅 아이템이 화면을 벗어나는 버그 수정 - 메시지 입력창 글자크기 14sp, rounded corner 32dp
This commit is contained in:
		@@ -35,6 +35,20 @@ sealed class ChatListItem {
 | 
			
		||||
 | 
			
		||||
class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 | 
			
		||||
 | 
			
		||||
    // 타이핑 인디케이터 표시용 정보(캐릭터 이름/프로필)
 | 
			
		||||
    private var typingName: String? = null
 | 
			
		||||
    private var typingProfileUrl: String? = null
 | 
			
		||||
 | 
			
		||||
    fun setTypingInfo(name: String?, profileUrl: String?) {
 | 
			
		||||
        typingName = name
 | 
			
		||||
        typingProfileUrl = profileUrl
 | 
			
		||||
        // 표시 중이면 인디케이터 UI를 갱신
 | 
			
		||||
        if (isRecyclerViewAttached) {
 | 
			
		||||
            val idx = findTypingIndicatorIndex()
 | 
			
		||||
            if (idx >= 0) notifyItemChanged(idx)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 테스트/비연결 환경에서 notify* 호출로 인한 NPE 방지용 플래그
 | 
			
		||||
    private var isRecyclerViewAttached: Boolean = false
 | 
			
		||||
 | 
			
		||||
@@ -287,7 +301,7 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            is TypingIndicatorViewHolder -> {
 | 
			
		||||
                holder.bind()
 | 
			
		||||
                holder.bind(typingName, typingProfileUrl)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -300,8 +314,10 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 | 
			
		||||
    ) : RecyclerView.ViewHolder(binding.root) {
 | 
			
		||||
        fun bind(data: ChatMessage, showTime: Boolean, isGrouped: Boolean) {
 | 
			
		||||
            binding.tvMessage.text = data.message
 | 
			
		||||
            // 화면 너비의 65%를 최대 폭으로 적용
 | 
			
		||||
            binding.tvMessage.maxWidth = (itemView.resources.displayMetrics.widthPixels * 0.65f).toInt()
 | 
			
		||||
            binding.tvTime.text = formatMessageTime(data.createdAt)
 | 
			
		||||
            binding.tvTime.visibility = if (showTime) View.VISIBLE else View.INVISIBLE
 | 
			
		||||
            binding.tvTime.visibility = if (showTime) View.VISIBLE else View.GONE
 | 
			
		||||
 | 
			
		||||
            // 상태에 따른 시각적 피드백: 전송중은 약간 투명, 실패는 더 투명
 | 
			
		||||
            val alpha = when (data.status) {
 | 
			
		||||
@@ -365,8 +381,9 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 | 
			
		||||
    ) : RecyclerView.ViewHolder(binding.root) {
 | 
			
		||||
        fun bind(data: ChatMessage, displayName: String?, isGrouped: Boolean, showTime: Boolean) {
 | 
			
		||||
            binding.tvMessage.text = data.message
 | 
			
		||||
            binding.tvMessage.maxWidth = (itemView.resources.displayMetrics.widthPixels * 0.65f).toInt()
 | 
			
		||||
            binding.tvTime.text = formatMessageTime(data.createdAt)
 | 
			
		||||
            binding.tvTime.visibility = if (showTime) View.VISIBLE else View.INVISIBLE
 | 
			
		||||
            binding.tvTime.visibility = if (showTime) View.VISIBLE else View.GONE
 | 
			
		||||
 | 
			
		||||
            // 그룹화: isGrouped가 true면 프로필/이름 숨김
 | 
			
		||||
            binding.ivProfile.visibility = if (isGrouped) View.INVISIBLE else View.VISIBLE
 | 
			
		||||
@@ -438,7 +455,12 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 | 
			
		||||
        private val binding: ItemChatTypingIndicatorBinding
 | 
			
		||||
    ) : RecyclerView.ViewHolder(binding.root) {
 | 
			
		||||
 | 
			
		||||
        fun bind() {
 | 
			
		||||
        fun bind(name: String?, profileUrl: String?) {
 | 
			
		||||
            // 이름/프로필 표시
 | 
			
		||||
            binding.tvName.text = name ?: ""
 | 
			
		||||
            binding.tvName.visibility = View.VISIBLE
 | 
			
		||||
            binding.ivProfile.visibility = View.VISIBLE
 | 
			
		||||
            loadProfileImage(binding.ivProfile, profileUrl)
 | 
			
		||||
            startTypingAnimation()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -492,7 +514,7 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 | 
			
		||||
    override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
 | 
			
		||||
        super.onViewAttachedToWindow(holder)
 | 
			
		||||
        if (holder is TypingIndicatorViewHolder) {
 | 
			
		||||
            holder.bind()
 | 
			
		||||
            holder.bind(typingName, typingProfileUrl)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.chat.talk.room
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.graphics.Rect
 | 
			
		||||
import android.text.Editable
 | 
			
		||||
import android.text.TextWatcher
 | 
			
		||||
import android.view.View
 | 
			
		||||
@@ -18,6 +19,7 @@ import kr.co.vividnext.sodalive.base.BaseActivity
 | 
			
		||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterType
 | 
			
		||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
 | 
			
		||||
import kr.co.vividnext.sodalive.databinding.ActivityChatRoomBinding
 | 
			
		||||
import kr.co.vividnext.sodalive.extensions.dpToPx
 | 
			
		||||
import org.koin.android.ext.android.inject
 | 
			
		||||
 | 
			
		||||
class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
 | 
			
		||||
@@ -85,6 +87,9 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
 | 
			
		||||
    fun setCharacterInfo(info: CharacterInfo) {
 | 
			
		||||
        characterInfo = info
 | 
			
		||||
        bindHeader(info)
 | 
			
		||||
        if (this::chatAdapter.isInitialized) {
 | 
			
		||||
            chatAdapter.setTypingInfo(info.name, info.profileImageUrl)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** 5.3: 헤더 바인딩 구현 (프로필, 이름, 타입 배지) */
 | 
			
		||||
@@ -122,6 +127,37 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
 | 
			
		||||
        }
 | 
			
		||||
        binding.rvMessages.layoutManager = layoutManager
 | 
			
		||||
 | 
			
		||||
        binding.rvMessages.addItemDecoration(object : RecyclerView.ItemDecoration() {
 | 
			
		||||
            override fun getItemOffsets(
 | 
			
		||||
                outRect: Rect,
 | 
			
		||||
                view: View,
 | 
			
		||||
                parent: RecyclerView,
 | 
			
		||||
                state: RecyclerView.State
 | 
			
		||||
            ) {
 | 
			
		||||
                super.getItemOffsets(outRect, view, parent, state)
 | 
			
		||||
 | 
			
		||||
                outRect.left = 13.3f.dpToPx().toInt()
 | 
			
		||||
                outRect.right = 13.3f.dpToPx().toInt()
 | 
			
		||||
 | 
			
		||||
                when (parent.getChildAdapterPosition(view)) {
 | 
			
		||||
                    0 -> {
 | 
			
		||||
                        outRect.top = 0
 | 
			
		||||
                        outRect.bottom = 10f.dpToPx().toInt()
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    chatAdapter.itemCount - 1 -> {
 | 
			
		||||
                        outRect.top = 10f.dpToPx().toInt()
 | 
			
		||||
                        outRect.bottom = 0
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    else -> {
 | 
			
		||||
                        outRect.top = 10f.dpToPx().toInt()
 | 
			
		||||
                        outRect.bottom = 10f.dpToPx().toInt()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        chatAdapter = ChatMessageAdapter().apply {
 | 
			
		||||
            // RecyclerView에 연결하기 전에 안정 ID를 활성화해야 함
 | 
			
		||||
            setHasStableIds(true)
 | 
			
		||||
@@ -133,6 +169,12 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
 | 
			
		||||
        }
 | 
			
		||||
        binding.rvMessages.adapter = chatAdapter
 | 
			
		||||
 | 
			
		||||
        // 현재 보유 중인 캐릭터 프로필/이름을 타이핑 인디케이터에도 반영
 | 
			
		||||
        chatAdapter.setTypingInfo(
 | 
			
		||||
            characterInfo?.name,
 | 
			
		||||
            characterInfo?.profileImageUrl
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        // 상단 도달 시 이전 메시지 로드
 | 
			
		||||
        binding.rvMessages.addOnScrollListener(object : RecyclerView.OnScrollListener() {
 | 
			
		||||
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
<!-- AI 메시지용 배경: color_111111 10% 투명, 코너: 4dp 16dp 16dp 16dp (TL, TR, BR, BL) -->
 | 
			
		||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
android:shape="rectangle">
 | 
			
		||||
<solid android:color="#1A111111" />
 | 
			
		||||
<solid android:color="#33111111" />
 | 
			
		||||
<corners
 | 
			
		||||
    android:topLeftRadius="4dp"
 | 
			
		||||
    android:topRightRadius="16dp"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- 입력창 배경: 기존 bg_round_corner_999_263238.xml 활용 -->
 | 
			
		||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <item android:drawable="@drawable/bg_round_corner_999_263238" />
 | 
			
		||||
</selector>
 | 
			
		||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <solid android:color="#263238" />
 | 
			
		||||
    <corners android:radius="32dp" />
 | 
			
		||||
</shape>
 | 
			
		||||
 
 | 
			
		||||
@@ -191,18 +191,22 @@
 | 
			
		||||
        <EditText
 | 
			
		||||
            android:id="@+id/et_message"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="48dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginEnd="8dp"
 | 
			
		||||
            android:background="@drawable/bg_chat_input"
 | 
			
		||||
            android:fontFamily="@font/pretendard_regular"
 | 
			
		||||
            android:hint="@string/chat_input_placeholder"
 | 
			
		||||
            android:imeOptions="actionSend|flagNoEnterAction"
 | 
			
		||||
            android:importantForAutofill="no"
 | 
			
		||||
            android:inputType="textMultiLine"
 | 
			
		||||
            android:maxLength="200"
 | 
			
		||||
            android:maxLines="4"
 | 
			
		||||
            android:paddingStart="16dp"
 | 
			
		||||
            android:paddingEnd="16dp"
 | 
			
		||||
            android:minHeight="48dp"
 | 
			
		||||
            android:paddingHorizontal="16dp"
 | 
			
		||||
            android:paddingVertical="8dp"
 | 
			
		||||
            android:textColor="#FFFFFFFF"
 | 
			
		||||
            android:textColorHint="#80FFFFFF"
 | 
			
		||||
            android:textSize="14sp"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintEnd_toStartOf="@id/iv_send"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!--
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?><!--
 | 
			
		||||
  AI 메시지 아이템 레이아웃 (3.3)
 | 
			
		||||
  - 왼쪽 정렬된 메시지 버블
 | 
			
		||||
  - 프로필 이미지, 이름(조건부), 메시지, 시간(오른쪽, 조건부)
 | 
			
		||||
@@ -9,11 +8,7 @@
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:paddingStart="12dp"
 | 
			
		||||
    android:paddingEnd="12dp"
 | 
			
		||||
    android:paddingTop="6dp"
 | 
			
		||||
    android:paddingBottom="6dp">
 | 
			
		||||
    android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
    <!-- 프로필 이미지 (그룹 첫 메시지에서만 보임) -->
 | 
			
		||||
    <ImageView
 | 
			
		||||
@@ -21,36 +16,36 @@
 | 
			
		||||
        android:layout_width="32dp"
 | 
			
		||||
        android:layout_height="32dp"
 | 
			
		||||
        android:layout_marginEnd="8dp"
 | 
			
		||||
        android:contentDescription="@string/a11y_ai_profile_image"
 | 
			
		||||
        android:scaleType="centerCrop"
 | 
			
		||||
        android:src="@drawable/ic_placeholder_profile"
 | 
			
		||||
        android:contentDescription="@string/a11y_ai_profile_image"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="parent" />
 | 
			
		||||
        app:layout_constraintTop_toTopOf="@id/message_group" />
 | 
			
		||||
 | 
			
		||||
    <!-- 메시지 그룹: 이름 + 메시지 버블 -->
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
        android:id="@+id/message_group"
 | 
			
		||||
        android:layout_width="0dp"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginStart="8dp"
 | 
			
		||||
        android:orientation="vertical"
 | 
			
		||||
        app:layout_constraintStart_toEndOf="@id/iv_profile"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        app:layout_constraintEnd_toStartOf="@id/tv_time"
 | 
			
		||||
        app:layout_constraintHorizontal_bias="0"
 | 
			
		||||
        android:layout_marginEnd="8dp">
 | 
			
		||||
        app:layout_constraintStart_toEndOf="@id/iv_profile"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent">
 | 
			
		||||
 | 
			
		||||
        <!-- 보낸이 이름 (그룹의 첫 메시지에서만 보임) -->
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/tv_name"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:textColor="@android:color/white"
 | 
			
		||||
            android:textSize="12sp"
 | 
			
		||||
            android:includeFontPadding="false"
 | 
			
		||||
            android:layout_marginBottom="2dp"
 | 
			
		||||
            android:ellipsize="end"
 | 
			
		||||
            android:fontFamily="@font/pretendard_regular"
 | 
			
		||||
            android:includeFontPadding="false"
 | 
			
		||||
            android:maxLines="1"
 | 
			
		||||
            android:ellipsize="end" />
 | 
			
		||||
            android:textColor="@android:color/white"
 | 
			
		||||
            android:textSize="12sp" />
 | 
			
		||||
 | 
			
		||||
        <!-- 메시지 버블 컨테이너 -->
 | 
			
		||||
        <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
@@ -58,27 +53,26 @@
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:background="@drawable/bg_chat_ai_message"
 | 
			
		||||
            android:paddingStart="14dp"
 | 
			
		||||
            android:paddingEnd="14dp"
 | 
			
		||||
            android:paddingTop="10dp"
 | 
			
		||||
            android:paddingBottom="10dp">
 | 
			
		||||
            android:paddingHorizontal="10dp"
 | 
			
		||||
            android:paddingVertical="8dp">
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                android:id="@+id/tv_message"
 | 
			
		||||
                android:layout_width="wrap_content"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:textColor="@android:color/white"
 | 
			
		||||
                android:textSize="15sp"
 | 
			
		||||
                android:lineSpacingExtra="2dp"
 | 
			
		||||
                android:includeFontPadding="false"
 | 
			
		||||
                android:hyphenationFrequency="normal"
 | 
			
		||||
                android:breakStrategy="balanced"
 | 
			
		||||
                android:ellipsize="end"
 | 
			
		||||
                android:fontFamily="@font/pretendard_regular"
 | 
			
		||||
                android:hyphenationFrequency="normal"
 | 
			
		||||
                android:includeFontPadding="false"
 | 
			
		||||
                android:lineSpacingExtra="2dp"
 | 
			
		||||
                android:maxLines="1000"
 | 
			
		||||
                app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
                android:textColor="@android:color/white"
 | 
			
		||||
                android:textSize="15sp"
 | 
			
		||||
                app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
                app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                app:layout_constraintEnd_toEndOf="parent" />
 | 
			
		||||
                app:layout_constraintTop_toTopOf="parent" />
 | 
			
		||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
 | 
			
		||||
@@ -87,12 +81,13 @@
 | 
			
		||||
        android:id="@+id/tv_time"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginStart="4dp"
 | 
			
		||||
        android:fontFamily="@font/pretendard_regular"
 | 
			
		||||
        android:includeFontPadding="false"
 | 
			
		||||
        android:textColor="@color/color_777777"
 | 
			
		||||
        android:textSize="12sp"
 | 
			
		||||
        android:includeFontPadding="false"
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="@id/message_group"
 | 
			
		||||
        app:layout_constraintStart_toEndOf="@id/message_group"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        android:layout_marginStart="8dp" />
 | 
			
		||||
        app:layout_constraintStart_toEndOf="@id/message_group" />
 | 
			
		||||
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!--
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?><!--
 | 
			
		||||
  타이핑 인디케이터 아이템 레이아웃 (3.4)
 | 
			
		||||
  - AI 메시지 레이아웃과 동일한 좌측 정렬 구조
 | 
			
		||||
  - 메시지 영역 대신 3개 점(•••) 애니메이션 표시
 | 
			
		||||
@@ -9,10 +8,6 @@
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:paddingStart="12dp"
 | 
			
		||||
    android:paddingEnd="12dp"
 | 
			
		||||
    android:paddingTop="6dp"
 | 
			
		||||
    android:paddingBottom="6dp"
 | 
			
		||||
    android:contentDescription="@string/a11y_typing">
 | 
			
		||||
 | 
			
		||||
    <!-- 프로필 이미지 (AI) -->
 | 
			
		||||
@@ -21,35 +16,35 @@
 | 
			
		||||
        android:layout_width="32dp"
 | 
			
		||||
        android:layout_height="32dp"
 | 
			
		||||
        android:layout_marginEnd="8dp"
 | 
			
		||||
        android:contentDescription="@string/a11y_ai_profile_image"
 | 
			
		||||
        android:scaleType="centerCrop"
 | 
			
		||||
        android:src="@drawable/ic_placeholder_profile"
 | 
			
		||||
        android:contentDescription="@string/a11y_ai_profile_image"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="parent" />
 | 
			
		||||
        app:layout_constraintTop_toTopOf="@id/message_group" />
 | 
			
		||||
 | 
			
		||||
    <!-- 이름 + 타이핑 버블 그룹 -->
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
        android:id="@+id/message_group"
 | 
			
		||||
        android:layout_width="0dp"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginStart="8dp"
 | 
			
		||||
        android:layout_marginEnd="56dp"
 | 
			
		||||
        android:orientation="vertical"
 | 
			
		||||
        app:layout_constraintStart_toEndOf="@id/iv_profile"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        android:layout_marginEnd="56dp">
 | 
			
		||||
        app:layout_constraintStart_toEndOf="@id/iv_profile"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent">
 | 
			
		||||
 | 
			
		||||
        <!-- 보낸이 이름 (필요 시 표시) -->
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/tv_name"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:textColor="@android:color/white"
 | 
			
		||||
            android:textSize="12sp"
 | 
			
		||||
            android:includeFontPadding="false"
 | 
			
		||||
            android:layout_marginBottom="2dp"
 | 
			
		||||
            android:ellipsize="end"
 | 
			
		||||
            android:includeFontPadding="false"
 | 
			
		||||
            android:maxLines="1"
 | 
			
		||||
            android:ellipsize="end" />
 | 
			
		||||
            android:textColor="@android:color/white"
 | 
			
		||||
            android:textSize="12sp" />
 | 
			
		||||
 | 
			
		||||
        <!-- 타이핑 표시 컨테이너 (AI 버블 배경 재사용 가능) -->
 | 
			
		||||
        <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
@@ -57,10 +52,8 @@
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:background="@drawable/bg_chat_ai_message"
 | 
			
		||||
            android:paddingStart="14dp"
 | 
			
		||||
            android:paddingEnd="14dp"
 | 
			
		||||
            android:paddingTop="10dp"
 | 
			
		||||
            android:paddingBottom="10dp">
 | 
			
		||||
            android:paddingHorizontal="14dp"
 | 
			
		||||
            android:paddingVertical="10dp">
 | 
			
		||||
 | 
			
		||||
            <!-- 3개 점 애니메이션 -->
 | 
			
		||||
            <LinearLayout
 | 
			
		||||
@@ -68,42 +61,42 @@
 | 
			
		||||
                android:layout_width="wrap_content"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:orientation="horizontal"
 | 
			
		||||
                app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
                app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
                app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                app:layout_constraintEnd_toEndOf="parent">
 | 
			
		||||
                app:layout_constraintTop_toTopOf="parent">
 | 
			
		||||
 | 
			
		||||
                <TextView
 | 
			
		||||
                    android:id="@+id/dot1"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="\u2022"
 | 
			
		||||
                    android:textSize="18sp"
 | 
			
		||||
                    android:textColor="@android:color/white"
 | 
			
		||||
                    android:animation="@anim/typing_dots_animation"
 | 
			
		||||
                    android:includeFontPadding="false"
 | 
			
		||||
                    android:animation="@anim/typing_dots_animation" />
 | 
			
		||||
                    android:text="\u2022"
 | 
			
		||||
                    android:textColor="@android:color/white"
 | 
			
		||||
                    android:textSize="18sp" />
 | 
			
		||||
 | 
			
		||||
                <TextView
 | 
			
		||||
                    android:id="@+id/dot2"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:layout_marginStart="4dp"
 | 
			
		||||
                    android:text="\u2022"
 | 
			
		||||
                    android:textSize="18sp"
 | 
			
		||||
                    android:textColor="@android:color/white"
 | 
			
		||||
                    android:animation="@anim/typing_dots_animation"
 | 
			
		||||
                    android:includeFontPadding="false"
 | 
			
		||||
                    android:animation="@anim/typing_dots_animation" />
 | 
			
		||||
                    android:text="\u2022"
 | 
			
		||||
                    android:textColor="@android:color/white"
 | 
			
		||||
                    android:textSize="18sp" />
 | 
			
		||||
 | 
			
		||||
                <TextView
 | 
			
		||||
                    android:id="@+id/dot3"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:layout_marginStart="4dp"
 | 
			
		||||
                    android:text="\u2022"
 | 
			
		||||
                    android:textSize="18sp"
 | 
			
		||||
                    android:textColor="@android:color/white"
 | 
			
		||||
                    android:animation="@anim/typing_dots_animation"
 | 
			
		||||
                    android:includeFontPadding="false"
 | 
			
		||||
                    android:animation="@anim/typing_dots_animation" />
 | 
			
		||||
                    android:text="\u2022"
 | 
			
		||||
                    android:textColor="@android:color/white"
 | 
			
		||||
                    android:textSize="18sp" />
 | 
			
		||||
            </LinearLayout>
 | 
			
		||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!--
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?><!--
 | 
			
		||||
  사용자 메시지 아이템 레이아웃
 | 
			
		||||
  - 오른쪽 정렬된 메시지 버블
 | 
			
		||||
  - 버블의 왼쪽에 시간 표시
 | 
			
		||||
@@ -11,8 +10,8 @@
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:paddingStart="12dp"
 | 
			
		||||
    android:paddingEnd="12dp"
 | 
			
		||||
    android:paddingTop="6dp"
 | 
			
		||||
    android:paddingEnd="12dp"
 | 
			
		||||
    android:paddingBottom="6dp">
 | 
			
		||||
 | 
			
		||||
    <!-- 전송 시간: 버블의 왼쪽에 표시 -->
 | 
			
		||||
@@ -20,13 +19,13 @@
 | 
			
		||||
        android:id="@+id/tv_time"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginEnd="4dp"
 | 
			
		||||
        android:fontFamily="@font/pretendard_regular"
 | 
			
		||||
        android:includeFontPadding="false"
 | 
			
		||||
        android:textColor="@color/color_777777"
 | 
			
		||||
        android:textSize="12sp"
 | 
			
		||||
        android:includeFontPadding="false"
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="@id/message_container"
 | 
			
		||||
        app:layout_constraintEnd_toStartOf="@id/message_container"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        android:layout_marginEnd="8dp"/>
 | 
			
		||||
        app:layout_constraintEnd_toStartOf="@id/message_container" />
 | 
			
		||||
 | 
			
		||||
    <!-- 메시지 버블 컨테이너: 오른쪽 정렬 -->
 | 
			
		||||
    <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
@@ -34,30 +33,28 @@
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:background="@drawable/bg_chat_user_message"
 | 
			
		||||
        android:paddingStart="14dp"
 | 
			
		||||
        android:paddingEnd="14dp"
 | 
			
		||||
        android:paddingTop="10dp"
 | 
			
		||||
        android:paddingBottom="10dp"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        android:paddingHorizontal="14dp"
 | 
			
		||||
        android:paddingVertical="10dp"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toEndOf="@id/tv_time">
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent">
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/tv_message"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:textColor="@android:color/white"
 | 
			
		||||
            android:textSize="15sp"
 | 
			
		||||
            android:lineSpacingExtra="2dp"
 | 
			
		||||
            android:includeFontPadding="false"
 | 
			
		||||
            android:hyphenationFrequency="normal"
 | 
			
		||||
            android:breakStrategy="balanced"
 | 
			
		||||
            android:ellipsize="end"
 | 
			
		||||
            android:fontFamily="@font/pretendard_regular"
 | 
			
		||||
            android:hyphenationFrequency="normal"
 | 
			
		||||
            android:includeFontPadding="false"
 | 
			
		||||
            android:lineSpacingExtra="2dp"
 | 
			
		||||
            android:maxLines="1000"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            android:textColor="@android:color/white"
 | 
			
		||||
            android:textSize="15sp"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent" />
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent" />
 | 
			
		||||
 | 
			
		||||
        <!-- 재전송 버튼: FAILED 상태일 때만 보임 -->
 | 
			
		||||
        <ImageView
 | 
			
		||||
@@ -66,11 +63,11 @@
 | 
			
		||||
            android:layout_height="20dp"
 | 
			
		||||
            android:layout_marginStart="6dp"
 | 
			
		||||
            android:layout_marginTop="-8dp"
 | 
			
		||||
            android:src="@android:drawable/ic_popup_sync"
 | 
			
		||||
            android:background="@android:color/transparent"
 | 
			
		||||
            android:contentDescription="@string/action_retry"
 | 
			
		||||
            android:src="@android:drawable/ic_popup_sync"
 | 
			
		||||
            android:visibility="gone"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent" />
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent" />
 | 
			
		||||
    </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user