feat(chat-ui): 채팅 쿼터 안내 액션 모델을 정리한다
This commit is contained in:
@@ -7,7 +7,6 @@ package kr.co.vividnext.sodalive.chat.talk.room
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.TextUtils
|
||||
@@ -37,10 +36,16 @@ sealed class ChatListItem {
|
||||
data class UserMessage(val data: ChatMessage) : ChatListItem()
|
||||
data class AiMessage(val data: ChatMessage, val displayName: String? = null) : ChatListItem()
|
||||
data class Notice(val text: String) : ChatListItem()
|
||||
data class QuotaNotice(val timeText: String? = null) : ChatListItem()
|
||||
object QuotaNotice : ChatListItem()
|
||||
object TypingIndicator : ChatListItem()
|
||||
}
|
||||
|
||||
enum class ChatQuotaNoticeAction {
|
||||
REWARDED_AD,
|
||||
PURCHASE_10_CAN,
|
||||
PURCHASE_20_CAN
|
||||
}
|
||||
|
||||
class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
// 타이핑 인디케이터 표시용 정보(캐릭터 이름/프로필)
|
||||
@@ -65,6 +70,9 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
fun onPurchaseMessage(message: ChatMessage)
|
||||
fun onOpenPurchasedImage(message: ChatMessage)
|
||||
fun onPurchaseQuota()
|
||||
fun onQuotaNoticeAction(action: ChatQuotaNoticeAction) {
|
||||
onPurchaseQuota()
|
||||
}
|
||||
}
|
||||
|
||||
private var callback: Callback? = null
|
||||
@@ -79,7 +87,6 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
const val VIEW_TYPE_NOTICE = 3
|
||||
const val VIEW_TYPE_TYPING_INDICATOR = 4
|
||||
const val VIEW_TYPE_QUOTA_NOTICE = 5
|
||||
private const val PAYLOAD_KEY_QUOTA_TIME = "payload_quota_time"
|
||||
|
||||
/**
|
||||
* [list]와 [position]을 기준으로 그룹화 여부와 해당 아이템이 그룹의 마지막인지 계산한다.
|
||||
@@ -155,16 +162,22 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
return when (item) {
|
||||
is ChatListItem.UserMessage -> {
|
||||
val data = item.data
|
||||
if (data.messageId != 0L) data.messageId
|
||||
else (data.localId?.hashCode()?.toLong()
|
||||
?: ("user:" + data.createdAt + data.message.hashCode()).hashCode().toLong())
|
||||
if (data.messageId != 0L) {
|
||||
data.messageId
|
||||
} else {
|
||||
data.localId?.hashCode()?.toLong()
|
||||
?: ("user:" + data.createdAt + data.message.hashCode()).hashCode().toLong()
|
||||
}
|
||||
}
|
||||
|
||||
is ChatListItem.AiMessage -> {
|
||||
val data = item.data
|
||||
if (data.messageId != 0L) data.messageId
|
||||
else (data.localId?.hashCode()?.toLong()
|
||||
?: ("ai:" + data.createdAt + data.message.hashCode()).hashCode().toLong())
|
||||
if (data.messageId != 0L) {
|
||||
data.messageId
|
||||
} else {
|
||||
data.localId?.hashCode()?.toLong()
|
||||
?: ("ai:" + data.createdAt + data.message.hashCode()).hashCode().toLong()
|
||||
}
|
||||
}
|
||||
|
||||
is ChatListItem.Notice -> ("notice:" + item.text).hashCode().toLong()
|
||||
@@ -241,18 +254,6 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
val n = newItems[newItemPosition]
|
||||
return o == n
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
|
||||
val o = old[oldItemPosition]
|
||||
val n = newItems[newItemPosition]
|
||||
// QuotaNotice의 timeText만 변경된 경우 부분 갱신 payload 반환
|
||||
if (o is ChatListItem.QuotaNotice && n is ChatListItem.QuotaNotice) {
|
||||
if (o.timeText != n.timeText) {
|
||||
return Bundle().apply { putString(PAYLOAD_KEY_QUOTA_TIME, n.timeText) }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
})
|
||||
items.clear()
|
||||
items.addAll(newItems)
|
||||
@@ -405,31 +406,13 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
}
|
||||
|
||||
is QuotaNoticeViewHolder -> {
|
||||
val item = currItem as ChatListItem.QuotaNotice
|
||||
holder.bind(item.timeText) {
|
||||
callback?.onPurchaseQuota()
|
||||
holder.bind { action ->
|
||||
callback?.onQuotaNoticeAction(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: RecyclerView.ViewHolder,
|
||||
position: Int,
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
if (payloads.isNotEmpty()) {
|
||||
if (holder is QuotaNoticeViewHolder) {
|
||||
val bundle = payloads.find { it is Bundle } as? Bundle
|
||||
if (bundle?.containsKey(PAYLOAD_KEY_QUOTA_TIME) == true) {
|
||||
holder.updateTimeText(bundle.getString(PAYLOAD_KEY_QUOTA_TIME))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onBindViewHolder(holder, position, payloads)
|
||||
}
|
||||
|
||||
// region ViewHolders
|
||||
|
||||
/** 사용자 메시지 뷰홀더: 시간 포맷팅, 상태(투명도) 표시 */
|
||||
@@ -672,25 +655,16 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
// endregion
|
||||
|
||||
/** 쿼터 안내 메시지 뷰홀더: 제목/남은시간 + 구매 버튼 */
|
||||
/** 쿼터 안내 메시지 뷰홀더: 광고 보기 + 캔 구매 버튼 */
|
||||
class QuotaNoticeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val tvTime: TextView = itemView.findViewById(R.id.tv_time)
|
||||
private val btnPurchase: View = itemView.findViewById(R.id.ll_purchase)
|
||||
private val btnRewardedAd: View = itemView.findViewById(R.id.ll_rewarded_ad)
|
||||
private val btnPurchase10Can: View = itemView.findViewById(R.id.ll_purchase_10_can)
|
||||
private val btnPurchase20Can: View = itemView.findViewById(R.id.ll_purchase_20_can)
|
||||
|
||||
fun bind(timeText: String?, onPurchase: () -> Unit) {
|
||||
updateTimeText(timeText)
|
||||
btnPurchase.setOnClickListener { onPurchase() }
|
||||
}
|
||||
|
||||
fun updateTimeText(timeText: String?) {
|
||||
if (timeText.isNullOrBlank()) {
|
||||
if (tvTime.visibility != View.GONE) tvTime.visibility = View.GONE
|
||||
} else {
|
||||
if (tvTime.visibility != View.VISIBLE) tvTime.visibility = View.VISIBLE
|
||||
if (tvTime.text?.toString() != timeText) {
|
||||
tvTime.text = timeText
|
||||
}
|
||||
}
|
||||
fun bind(onAction: (ChatQuotaNoticeAction) -> Unit) {
|
||||
btnRewardedAd.setOnClickListener { onAction(ChatQuotaNoticeAction.REWARDED_AD) }
|
||||
btnPurchase10Can.setOnClickListener { onAction(ChatQuotaNoticeAction.PURCHASE_10_CAN) }
|
||||
btnPurchase20Can.setOnClickListener { onAction(ChatQuotaNoticeAction.PURCHASE_20_CAN) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,13 +40,15 @@ class ChatMessageAdapterTest {
|
||||
ChatListItem.UserMessage(ChatMessage(1, "hi", "", mine = true, createdAt = 1L)),
|
||||
ChatListItem.AiMessage(ChatMessage(2, "hello", "", mine = false, createdAt = 2L)),
|
||||
ChatListItem.Notice("notice"),
|
||||
ChatListItem.QuotaNotice,
|
||||
ChatListItem.TypingIndicator
|
||||
)
|
||||
adapter.setItemsForTest(list)
|
||||
assertEquals(ChatMessageAdapter.VIEW_TYPE_USER_MESSAGE, adapter.getItemViewType(0))
|
||||
assertEquals(ChatMessageAdapter.VIEW_TYPE_AI_MESSAGE, adapter.getItemViewType(1))
|
||||
assertEquals(ChatMessageAdapter.VIEW_TYPE_NOTICE, adapter.getItemViewType(2))
|
||||
assertEquals(ChatMessageAdapter.VIEW_TYPE_TYPING_INDICATOR, adapter.getItemViewType(3))
|
||||
assertEquals(ChatMessageAdapter.VIEW_TYPE_QUOTA_NOTICE, adapter.getItemViewType(3))
|
||||
assertEquals(ChatMessageAdapter.VIEW_TYPE_TYPING_INDICATOR, adapter.getItemViewType(4))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user