feat(live-room): 채팅창 얼리기 기능을 추가한다
채팅 입력 제어와 룸 상태 동기화를 통합해 지연 입장자도 동일 상태를 적용한다.
This commit is contained in:
@@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.live.reservation_status.GetLiveReservationRespon
|
||||
import kr.co.vividnext.sodalive.live.room.CancelLiveRequest
|
||||
import kr.co.vividnext.sodalive.live.room.EnterOrQuitLiveRoomRequest
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomStatus
|
||||
import kr.co.vividnext.sodalive.live.room.SetChatFreezeRequest
|
||||
import kr.co.vividnext.sodalive.live.room.SetManagerOrSpeakerOrAudienceRequest
|
||||
import kr.co.vividnext.sodalive.live.room.StartLiveRequest
|
||||
import kr.co.vividnext.sodalive.live.room.create.CreateLiveRoomResponse
|
||||
@@ -186,6 +187,12 @@ interface LiveApi {
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@PUT("/live/room/info/set/chat-freeze")
|
||||
fun setChatFreeze(
|
||||
@Body request: SetChatFreezeRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/live/room/{id}/donation-list")
|
||||
fun donationStatus(
|
||||
@Path("id") id: Long,
|
||||
|
||||
@@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.live.reservation_status.CancelLiveReservationReq
|
||||
import kr.co.vividnext.sodalive.live.room.CancelLiveRequest
|
||||
import kr.co.vividnext.sodalive.live.room.EnterOrQuitLiveRoomRequest
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomStatus
|
||||
import kr.co.vividnext.sodalive.live.room.SetChatFreezeRequest
|
||||
import kr.co.vividnext.sodalive.live.room.SetManagerOrSpeakerOrAudienceRequest
|
||||
import kr.co.vividnext.sodalive.live.room.StartLiveRequest
|
||||
import kr.co.vividnext.sodalive.live.room.create.CreateLiveRoomResponse
|
||||
@@ -112,6 +113,18 @@ class LiveRepository(
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun setChatFreeze(
|
||||
roomId: Long,
|
||||
isChatFrozen: Boolean,
|
||||
token: String
|
||||
) = api.setChatFreeze(
|
||||
request = SetChatFreezeRequest(
|
||||
roomId = roomId,
|
||||
isChatFrozen = isChatFrozen
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getRoomInfo(roomId: Long, token: String) = api.getRoomInfo(roomId, authHeader = token)
|
||||
|
||||
fun getDonationMessageList(
|
||||
@@ -195,7 +208,7 @@ class LiveRepository(
|
||||
|
||||
fun setManager(roomId: Long, userId: Long, token: String) = api.setManager(
|
||||
request = SetManagerOrSpeakerOrAudienceRequest(roomId, memberId = userId),
|
||||
authHeader = token,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun creatorFollow(
|
||||
|
||||
@@ -100,6 +100,7 @@ import kr.co.vividnext.sodalive.live.room.chat.LiveRoomDonationChat
|
||||
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomJoinChat
|
||||
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomNormalChat
|
||||
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomRouletteDonationChat
|
||||
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomSystemNoticeChat
|
||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
|
||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageDialog
|
||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageViewModel
|
||||
@@ -190,6 +191,8 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
|
||||
// region 채팅 금지
|
||||
private var isNoChatting = false
|
||||
private var isChatFrozen = false
|
||||
private var hasShownInitialChatFreezeNotice = false
|
||||
private var remainingNoChattingTime = NO_CHATTING_TIME
|
||||
private val countDownTimer = object : CountDownTimer(remainingNoChattingTime * 1000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
@@ -241,6 +244,110 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
val noChatRoomList = SharedPreferenceManager.noChatRoomList
|
||||
return noChatRoomList.contains(roomId)
|
||||
}
|
||||
|
||||
private fun setChatFrozenState(isFrozen: Boolean) {
|
||||
isChatFrozen = isFrozen
|
||||
updateChatFreezeToggleUi()
|
||||
updateChatInputState()
|
||||
}
|
||||
|
||||
private fun updateChatInputState() {
|
||||
val canInputChat = isHost || !isChatFrozen
|
||||
binding.etChat.isEnabled = canInputChat
|
||||
binding.ivSend.isEnabled = canInputChat
|
||||
binding.ivSend.alpha = if (canInputChat) {
|
||||
1f
|
||||
} else {
|
||||
0.4f
|
||||
}
|
||||
|
||||
if (!canInputChat) {
|
||||
binding.etChat.clearFocus()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateChatFreezeToggleUi() {
|
||||
if (isChatFrozen) {
|
||||
binding.tvChatFreezeSwitch.text = getString(R.string.screen_live_room_chat_freeze_on_label)
|
||||
binding.tvChatFreezeSwitch.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_3bb9f1
|
||||
)
|
||||
)
|
||||
binding.tvChatFreezeSwitch
|
||||
.setBackgroundResource(R.drawable.bg_round_corner_5_3_transparent_3bb9f1)
|
||||
} else {
|
||||
binding.tvChatFreezeSwitch.text = getString(R.string.screen_live_room_chat_freeze_off_label)
|
||||
binding.tvChatFreezeSwitch.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.tvChatFreezeSwitch
|
||||
.setBackgroundResource(R.drawable.bg_round_corner_5_3_transparent_bbbbbb)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showChatFreezeWarning() {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
getString(R.string.screen_live_room_chat_freeze_warning),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun buildChatFreezeStatusMessage(isFrozen: Boolean, actorNickname: String): String {
|
||||
return getString(
|
||||
if (isFrozen) {
|
||||
R.string.screen_live_room_chat_freeze_started
|
||||
} else {
|
||||
R.string.screen_live_room_chat_freeze_ended
|
||||
},
|
||||
actorNickname
|
||||
)
|
||||
}
|
||||
|
||||
private fun addChatFreezeStatusMessage(message: String) {
|
||||
if (message.isBlank()) {
|
||||
return
|
||||
}
|
||||
chatAdapter.items.add(
|
||||
LiveRoomSystemNoticeChat(message = message)
|
||||
)
|
||||
invalidateChat()
|
||||
}
|
||||
|
||||
private fun toggleChatFreeze() {
|
||||
if (!isHost) {
|
||||
return
|
||||
}
|
||||
|
||||
val nextChatFrozen = !isChatFrozen
|
||||
viewModel.setChatFreeze(roomId = roomId, isChatFrozen = nextChatFrozen) {
|
||||
setChatFrozenState(nextChatFrozen)
|
||||
|
||||
val noticeMessage = buildChatFreezeStatusMessage(
|
||||
isFrozen = nextChatFrozen,
|
||||
actorNickname = SharedPreferenceManager.nickname
|
||||
)
|
||||
|
||||
addChatFreezeStatusMessage(noticeMessage)
|
||||
|
||||
agora.sendRawMessageToGroup(
|
||||
rawMessage = Gson().toJson(
|
||||
LiveRoomChatRawMessage(
|
||||
type = LiveRoomChatRawMessageType.TOGGLE_CHAT_FREEZE,
|
||||
message = noticeMessage,
|
||||
can = 0,
|
||||
donationMessage = "",
|
||||
isChatFrozen = nextChatFrozen
|
||||
)
|
||||
).toByteArray()
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
@@ -509,7 +616,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
)
|
||||
|
||||
binding.etChat.setOnFocusChangeListener { view, hasFocus ->
|
||||
if (isNoChatting && hasFocus) {
|
||||
if (isChatFrozen && !isHost && hasFocus) {
|
||||
showChatFreezeWarning()
|
||||
view.clearFocus()
|
||||
} else if (isNoChatting && hasFocus) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
getString(
|
||||
@@ -576,6 +686,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
|
||||
binding.tvBgSwitch.setOnClickListener { viewModel.toggleBackgroundImage() }
|
||||
binding.tvSignatureSwitch.setOnClickListener { viewModel.toggleSignatureImage() }
|
||||
binding.tvChatFreezeSwitch.setOnClickListener { toggleChatFreeze() }
|
||||
binding.tvV2vSignatureSwitch.setOnClickListener { toggleV2vCaption() }
|
||||
binding.llDonation.setOnClickListener {
|
||||
LiveRoomDonationRankingDialog(
|
||||
@@ -1105,6 +1216,20 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
}
|
||||
|
||||
isHost = response.creatorId == SharedPreferenceManager.userId
|
||||
binding.tvChatFreezeSwitch.visibility = if (isHost) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
setChatFrozenState(response.isChatFrozen)
|
||||
|
||||
if (!isHost && response.isChatFrozen && !hasShownInitialChatFreezeNotice) {
|
||||
addChatFreezeStatusMessage(
|
||||
getString(R.string.screen_live_room_chat_freeze_started)
|
||||
)
|
||||
hasShownInitialChatFreezeNotice = true
|
||||
}
|
||||
|
||||
initLikeHeartButton()
|
||||
initRouletteSettingButton()
|
||||
activatingRouletteButton(isActiveRoulette = response.isActiveRoulette)
|
||||
@@ -1572,7 +1697,9 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
val profileUrl = viewModel.getUserProfileUrl(SharedPreferenceManager.userId.toInt())
|
||||
val rank = viewModel.getUserRank(SharedPreferenceManager.userId)
|
||||
|
||||
if (isNoChatting) {
|
||||
if (isChatFrozen && !isHost) {
|
||||
showChatFreezeWarning()
|
||||
} else if (isNoChatting) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
getString(
|
||||
@@ -1981,6 +2108,28 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
)
|
||||
}
|
||||
|
||||
LiveRoomChatRawMessageType.TOGGLE_CHAT_FREEZE -> {
|
||||
if (memberId.toLong() != SharedPreferenceManager.userId) {
|
||||
handler.post {
|
||||
val frozen = message.isChatFrozen ?: false
|
||||
setChatFrozenState(frozen)
|
||||
|
||||
val statusMessage = if (message.message.isNotBlank()) {
|
||||
message.message
|
||||
} else {
|
||||
buildChatFreezeStatusMessage(
|
||||
isFrozen = frozen,
|
||||
actorNickname = nickname.ifBlank {
|
||||
viewModel.getManagerNickname()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
addChatFreezeStatusMessage(statusMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LiveRoomChatRawMessageType.ROULETTE_DONATION -> {
|
||||
handler.post {
|
||||
chatAdapter.items.add(
|
||||
|
||||
@@ -543,6 +543,50 @@ class LiveRoomViewModel(
|
||||
_isSignatureOn.value = !isSignatureOn.value!!
|
||||
}
|
||||
|
||||
fun setChatFreeze(roomId: Long, isChatFrozen: Boolean, onSuccess: () -> Unit) {
|
||||
_isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.setChatFreeze(
|
||||
roomId = roomId,
|
||||
isChatFrozen = isChatFrozen,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
if (it.success) {
|
||||
if (isRoomInfoInitialized()) {
|
||||
roomInfoResponse = roomInfoResponse.copy(isChatFrozen = isChatFrozen)
|
||||
_roomInfoLiveData.postValue(roomInfoResponse)
|
||||
}
|
||||
onSuccess()
|
||||
getRoomInfo(roomId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
SodaLiveApplicationHolder.get()
|
||||
.getString(R.string.msg_live_room_edit_update_failed)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue(
|
||||
SodaLiveApplicationHolder.get()
|
||||
.getString(R.string.msg_live_room_edit_update_failed)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun editLiveRoomInfo(
|
||||
roomId: Long,
|
||||
newTitle: String,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.live.room
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class SetChatFreezeRequest(
|
||||
@SerializedName("roomId") val roomId: Long,
|
||||
@SerializedName("isChatFrozen") val isChatFrozen: Boolean
|
||||
)
|
||||
@@ -83,13 +83,28 @@ data class LiveRoomJoinChat(
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
data class LiveRoomSystemNoticeChat(
|
||||
val message: String
|
||||
) : LiveRoomChat() {
|
||||
override var type = LiveRoomChatType.JOIN
|
||||
override fun bind(context: Context, binding: ViewBinding, onClickProfile: ((Long) -> Unit)?) {
|
||||
val itemBinding = binding as ItemLiveRoomJoinChatBinding
|
||||
itemBinding.tvJoin.setTextColor(
|
||||
ContextCompat.getColor(context, R.color.color_eeeeee)
|
||||
)
|
||||
itemBinding.tvJoin.text = message
|
||||
itemBinding.root.setBackgroundResource(R.drawable.bg_round_corner_4_7_cc004462)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
data class LiveRoomNormalChat(
|
||||
@SerializedName("userId") val userId: Long,
|
||||
@SerializedName("profileUrl") val profileUrl: String,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("rank") val rank: Int,
|
||||
@SerializedName("chat") val chat: String,
|
||||
@SerializedName("chat") val chat: String
|
||||
) : LiveRoomChat() {
|
||||
override fun bind(context: Context, binding: ViewBinding, onClickProfile: ((Long) -> Unit)?) {
|
||||
val itemBinding = binding as ItemLiveRoomChatBinding
|
||||
|
||||
@@ -12,7 +12,8 @@ data class LiveRoomChatRawMessage(
|
||||
@SerializedName("signature") val signature: LiveRoomDonationResponse? = null,
|
||||
@SerializedName("signatureImageUrl") val signatureImageUrl: String? = null,
|
||||
@SerializedName("donationMessage") val donationMessage: String?,
|
||||
@SerializedName("isActiveRoulette") val isActiveRoulette: Boolean? = null
|
||||
@SerializedName("isActiveRoulette") val isActiveRoulette: Boolean? = null,
|
||||
@SerializedName("isChatFrozen") val isChatFrozen: Boolean? = null
|
||||
)
|
||||
|
||||
enum class LiveRoomChatRawMessageType {
|
||||
@@ -31,6 +32,9 @@ enum class LiveRoomChatRawMessageType {
|
||||
@SerializedName("TOGGLE_ROULETTE")
|
||||
TOGGLE_ROULETTE,
|
||||
|
||||
@SerializedName("TOGGLE_CHAT_FREEZE")
|
||||
TOGGLE_CHAT_FREEZE,
|
||||
|
||||
@SerializedName("ROULETTE_DONATION")
|
||||
ROULETTE_DONATION,
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ data class GetRoomInfoResponse(
|
||||
@SerializedName("menuPan") val menuPan: String,
|
||||
@SerializedName("creatorLanguageCode") val creatorLanguageCode: String?,
|
||||
@SerializedName("isActiveRoulette") val isActiveRoulette: Boolean,
|
||||
@SerializedName("isChatFrozen") val isChatFrozen: Boolean = false,
|
||||
@SerializedName("isPrivateRoom") val isPrivateRoom: Boolean,
|
||||
@SerializedName("password") val password: String? = null
|
||||
)
|
||||
|
||||
@@ -268,6 +268,22 @@
|
||||
android:visibility="gone"
|
||||
tools:ignore="SmallSp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_chat_freeze_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/bg_round_corner_5_3_transparent_bbbbbb"
|
||||
android:fontFamily="@font/medium"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4.7dp"
|
||||
android:text="@string/screen_live_room_chat_freeze_off_label"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
tools:ignore="SmallSp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_signature_switch"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -462,6 +462,11 @@
|
||||
<string name="screen_live_room_menu_prefix">[Menu] </string>
|
||||
<string name="screen_live_room_leave">Leave</string>
|
||||
<string name="screen_live_room_change_listener">Change to listener</string>
|
||||
<string name="screen_live_room_chat_freeze_off_label">Freeze OFF</string>
|
||||
<string name="screen_live_room_chat_freeze_on_label">Freeze ON</string>
|
||||
<string name="screen_live_room_chat_freeze_warning">Chat is frozen. You cannot type right now.</string>
|
||||
<string name="screen_live_room_chat_freeze_started">Chat has been frozen.</string>
|
||||
<string name="screen_live_room_chat_freeze_ended">Chat freeze has been disabled.</string>
|
||||
<string name="screen_live_room_signature_off_label">Sign OFF</string>
|
||||
<string name="screen_live_room_v2v_signature_off_label">Caption OFF</string>
|
||||
<string name="screen_live_room_v2v_signature_on_label">Caption ON</string>
|
||||
|
||||
@@ -461,6 +461,11 @@
|
||||
<string name="screen_live_room_menu_prefix">[メニュー] </string>
|
||||
<string name="screen_live_room_leave">退出</string>
|
||||
<string name="screen_live_room_change_listener">リスナー変更</string>
|
||||
<string name="screen_live_room_chat_freeze_off_label">凍結 OFF</string>
|
||||
<string name="screen_live_room_chat_freeze_on_label">凍結 ON</string>
|
||||
<string name="screen_live_room_chat_freeze_warning">チャットが凍結中のため入力できません。</string>
|
||||
<string name="screen_live_room_chat_freeze_started">チャットを凍結しました。</string>
|
||||
<string name="screen_live_room_chat_freeze_ended">チャット凍結を解除しました。</string>
|
||||
<string name="screen_live_room_signature_off_label">シグ OFF</string>
|
||||
<string name="screen_live_room_v2v_signature_off_label">字幕 OFF</string>
|
||||
<string name="screen_live_room_v2v_signature_on_label">字幕 ON</string>
|
||||
|
||||
@@ -461,6 +461,11 @@
|
||||
<string name="screen_live_room_menu_prefix">[메뉴판] </string>
|
||||
<string name="screen_live_room_leave">나가기</string>
|
||||
<string name="screen_live_room_change_listener">리스너 변경</string>
|
||||
<string name="screen_live_room_chat_freeze_off_label">얼림 OFF</string>
|
||||
<string name="screen_live_room_chat_freeze_on_label">얼림 ON</string>
|
||||
<string name="screen_live_room_chat_freeze_warning">채팅창이 얼려져 있어 입력할 수 없습니다.</string>
|
||||
<string name="screen_live_room_chat_freeze_started">채팅창을 얼렸습니다.</string>
|
||||
<string name="screen_live_room_chat_freeze_ended">채팅창 얼리기를 해제했습니다</string>
|
||||
<string name="screen_live_room_signature_off_label">시그 OFF</string>
|
||||
<string name="screen_live_room_v2v_signature_off_label">자막 OFF</string>
|
||||
<string name="screen_live_room_v2v_signature_on_label">자막 ON</string>
|
||||
|
||||
Reference in New Issue
Block a user