라이브 방

- 하트 후원 API 연결
- 하트 후원 성공시 하트 애니메이션 호출
This commit is contained in:
klaus 2024-10-16 18:06:38 +09:00
parent 6c9ace146d
commit e964679154
7 changed files with 148 additions and 25 deletions

View File

@ -35,8 +35,8 @@ android {
applicationId "kr.co.vividnext.sodalive" applicationId "kr.co.vividnext.sodalive"
minSdk 23 minSdk 23
targetSdk 34 targetSdk 34
versionCode 117 versionCode 118
versionName "1.21.2" versionName "1.22.0"
} }
buildTypes { buildTypes {
@ -159,4 +159,6 @@ dependencies {
kapt "androidx.room:room-compiler:2.5.0" kapt "androidx.room:room-compiler:2.5.0"
implementation "androidx.room:room-ktx:2.5.0" implementation "androidx.room:room-ktx:2.5.0"
implementation "androidx.room:room-runtime:2.5.0" implementation "androidx.room:room-runtime:2.5.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
} }

View File

@ -24,9 +24,9 @@ import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse
import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse
import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest
import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest
import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse
import kr.co.vividnext.sodalive.live.room.tag.GetLiveTagResponse import kr.co.vividnext.sodalive.live.room.tag.GetLiveTagResponse
import kr.co.vividnext.sodalive.settings.ContentType
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody import okhttp3.RequestBody
import retrofit2.http.Body import retrofit2.http.Body
@ -213,4 +213,10 @@ interface LiveApi {
@Body request: CancelLiveReservationRequest, @Body request: CancelLiveReservationRequest,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<Any>> ): Single<ApiResponse<Any>>
@POST("/live/room/like-heart")
fun likeHeart(
@Body request: LiveRoomLikeHeartRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<Any>>
} }

View File

@ -17,6 +17,7 @@ import kr.co.vividnext.sodalive.live.room.donation.DeleteLiveRoomDonationMessage
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse
import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest
import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest
import kr.co.vividnext.sodalive.live.room.menu.MenuApi import kr.co.vividnext.sodalive.live.room.menu.MenuApi
import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest
import kr.co.vividnext.sodalive.user.UserApi import kr.co.vividnext.sodalive.user.UserApi
@ -242,4 +243,12 @@ class LiveRepository(
creatorId = creatorId, creatorId = creatorId,
authHeader = token authHeader = token
) )
fun likeHeart(roomId: Long, token: String) = api.likeHeart(
request = LiveRoomLikeHeartRequest(
roomId = roomId,
container = "aos"
),
authHeader = token
)
} }

View File

@ -33,6 +33,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
@ -53,6 +54,7 @@ import io.agora.rtm.RtmChannelMember
import io.agora.rtm.RtmClientListener import io.agora.rtm.RtmClientListener
import io.agora.rtm.RtmMessage import io.agora.rtm.RtmMessage
import io.agora.rtm.RtmMessageType import io.agora.rtm.RtmMessageType
import kotlinx.coroutines.launch
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.agora.Agora import kr.co.vividnext.sodalive.agora.Agora
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
@ -124,6 +126,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private var isMicrophoneMute = false private var isMicrophoneMute = false
private var isSpeaker = false private var isSpeaker = false
private var isHost = false
private var isNoChatting = false private var isNoChatting = false
private var remainingNoChattingTime = noChattingTime private var remainingNoChattingTime = noChattingTime
@ -530,8 +533,13 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
return (second * 1000).toLong() return (second * 1000).toLong()
} }
private fun addHeartAnimation(button: View) { private fun addHeartAnimation() {
// 버튼의 위치 // 버튼의 위치
val button = if (isHost) {
binding.flRouletteSettings
} else {
binding.flLikeHeart
}
val buttonPosition = IntArray(2) val buttonPosition = IntArray(2)
button.getLocationInWindow(buttonPosition) button.getLocationInWindow(buttonPosition)
@ -1004,12 +1012,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
binding.ivCreatorFollow.visibility = View.GONE binding.ivCreatorFollow.visibility = View.GONE
} }
initLikeHeartButton(isHost = response.creatorId == SharedPreferenceManager.userId) isHost = response.creatorId == SharedPreferenceManager.userId
initRouletteSettingButton(isHost = response.creatorId == SharedPreferenceManager.userId) initLikeHeartButton()
activatingRouletteButton( initRouletteSettingButton()
isHost = response.creatorId == SharedPreferenceManager.userId, activatingRouletteButton(isActiveRoulette = response.isActiveRoulette)
isActiveRoulette = response.isActiveRoulette
)
if (response.menuPan.isNotBlank()) { if (response.menuPan.isNotBlank()) {
binding.tvMenuPan.visibility = View.VISIBLE binding.tvMenuPan.visibility = View.VISIBLE
@ -1094,16 +1100,51 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
} }
private fun initLikeHeartButton(isHost: Boolean) { private fun initLikeHeartButton() {
if (!isHost) { if (!isHost) {
binding.flLikeHeart.visibility = View.VISIBLE binding.flLikeHeart.visibility = View.VISIBLE
binding.flLikeHeart.setOnClickListener { addHeartAnimation(it) } binding.flLikeHeart.setOnClickListener {
binding.flLikeHeart.isEnabled = false
viewModel.likeHeart(
roomId = roomId,
onSuccess = {
val donationRawMessage = Gson().toJson(
LiveRoomChatRawMessage(
type = LiveRoomChatRawMessageType.HEART_DONATION,
message = "",
can = 1,
signature = null,
signatureImageUrl = null,
donationMessage = null
)
)
agora.sendRawMessageToGroup(
rawMessage = donationRawMessage.toByteArray(),
onSuccess = {
handler.post {
addHeartAnimation()
lifecycleScope.launch { viewModel.addHeartDonation() }
}
},
onFailure = {
viewModel.refundDonation(roomId)
}
)
binding.flLikeHeart.isEnabled = true
},
onFailure = {
binding.flLikeHeart.isEnabled = true
}
)
}
} else { } else {
binding.flLikeHeart.visibility = View.GONE binding.flLikeHeart.visibility = View.GONE
} }
} }
private fun initRouletteSettingButton(isHost: Boolean) { private fun initRouletteSettingButton() {
if (isHost) { if (isHost) {
binding.flRouletteSettings.visibility = View.VISIBLE binding.flRouletteSettings.visibility = View.VISIBLE
binding.flRouletteSettings.setOnClickListener { binding.flRouletteSettings.setOnClickListener {
@ -1119,7 +1160,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
} }
private fun activatingRouletteButton(isHost: Boolean, isActiveRoulette: Boolean) { private fun activatingRouletteButton(isActiveRoulette: Boolean) {
if (!isHost && isActiveRoulette) { if (!isHost && isActiveRoulette) {
binding.flRoulette.visibility = View.VISIBLE binding.flRoulette.visibility = View.VISIBLE
binding.flRoulette.setOnClickListener { binding.flRoulette.setOnClickListener {
@ -1533,7 +1574,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
) )
invalidateChat() invalidateChat()
viewModel.addDonationCan(can) lifecycleScope.launch { viewModel.addDonationCan(can) }
addSignature(signature) addSignature(signature)
} }
}, },
@ -1574,7 +1615,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
) )
invalidateChat() invalidateChat()
viewModel.addDonationCan(can) lifecycleScope.launch { viewModel.addDonationCan(can) }
} }
}, },
onFailure = { onFailure = {
@ -1641,7 +1682,9 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
) )
invalidateChat() invalidateChat()
lifecycleScope.launch {
viewModel.addDonationCan(rawMessage.can) viewModel.addDonationCan(rawMessage.can)
}
if (rawMessage.signature != null) { if (rawMessage.signature != null) {
addSignature(rawMessage.signature) addSignature(rawMessage.signature)
@ -1666,9 +1709,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
LiveRoomChatRawMessageType.TOGGLE_ROULETTE -> { LiveRoomChatRawMessageType.TOGGLE_ROULETTE -> {
handler.post { handler.post {
activatingRouletteButton( activatingRouletteButton(
isHost = viewModel
.roomInfoResponse
.creatorId == SharedPreferenceManager.userId,
isActiveRoulette = rawMessage.isActiveRoulette ?: false isActiveRoulette = rawMessage.isActiveRoulette ?: false
) )
} }
@ -1684,9 +1724,18 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
) )
invalidateChat() invalidateChat()
lifecycleScope.launch {
viewModel.addDonationCan(rawMessage.can) viewModel.addDonationCan(rawMessage.can)
} }
} }
}
LiveRoomChatRawMessageType.HEART_DONATION -> {
handler.post {
addHeartAnimation()
lifecycleScope.launch { viewModel.addHeartDonation() }
}
}
else -> {} else -> {}
} }
@ -2009,7 +2058,9 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
) )
invalidateChat() invalidateChat()
lifecycleScope.launch {
viewModel.addDonationCan(message.can) viewModel.addDonationCan(message.can)
}
if (message.signature != null) { if (message.signature != null) {
addSignature(message.signature) addSignature(message.signature)

View File

@ -13,6 +13,8 @@ import com.google.gson.Gson
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.live.LiveRepository import kr.co.vividnext.sodalive.live.LiveRepository
@ -100,6 +102,8 @@ class LiveRoomViewModel(
private val blockedMemberIdList: MutableList<Long> = mutableListOf() private val blockedMemberIdList: MutableList<Long> = mutableListOf()
val mutex = Mutex()
fun getUserNickname(memberId: Int): String { fun getUserNickname(memberId: Int): String {
for (manager in roomInfoResponse.managerList) { for (manager in roomInfoResponse.managerList) {
if (manager.id.toInt() == memberId) { if (manager.id.toInt() == memberId) {
@ -562,6 +566,36 @@ class LiveRoomViewModel(
) )
} }
fun likeHeart(roomId: Long, onSuccess: () -> Unit, onFailure: () -> Unit) {
compositeDisposable.add(
repository.likeHeart(roomId, token = "Bearer ${SharedPreferenceManager.token}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success) {
SharedPreferenceManager.can -= 1
onSuccess()
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
onFailure()
}
},
{
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
onFailure()
}
)
)
}
fun donation( fun donation(
roomId: Long, roomId: Long,
can: Int, can: Int,
@ -641,9 +675,17 @@ class LiveRoomViewModel(
) )
} }
fun addDonationCan(can: Int) { suspend fun addDonationCan(can: Int) {
mutex.withLock {
_totalDonationCan.postValue(totalDonationCan.value!! + can) _totalDonationCan.postValue(totalDonationCan.value!! + can)
} }
}
suspend fun addHeartDonation() {
mutex.withLock {
_totalLikeHeart.postValue(totalLikeHeart.value!! + 1)
}
}
fun donationStatus(roomId: Long, onSuccess: (GetLiveRoomDonationStatusResponse) -> Unit) { fun donationStatus(roomId: Long, onSuccess: (GetLiveRoomDonationStatusResponse) -> Unit) {
_isLoading.value = true _isLoading.value = true

View File

@ -35,5 +35,8 @@ enum class LiveRoomChatRawMessageType {
TOGGLE_ROULETTE, TOGGLE_ROULETTE,
@SerializedName("ROULETTE_DONATION") @SerializedName("ROULETTE_DONATION")
ROULETTE_DONATION ROULETTE_DONATION,
@SerializedName("HEART_DONATION")
HEART_DONATION
} }

View File

@ -0,0 +1,10 @@
package kr.co.vividnext.sodalive.live.room.like
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class LiveRoomLikeHeartRequest(
@SerializedName("roomId") val roomId: Long,
@SerializedName("container") val container: String
)