라이브 방

- 하트 후원 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"
minSdk 23
targetSdk 34
versionCode 117
versionName "1.21.2"
versionCode 118
versionName "1.22.0"
}
buildTypes {
@ -159,4 +159,6 @@ dependencies {
kapt "androidx.room:room-compiler:2.5.0"
implementation "androidx.room:room-ktx: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.info.GetRoomInfoResponse
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.tag.GetLiveTagResponse
import kr.co.vividnext.sodalive.settings.ContentType
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
@ -213,4 +213,10 @@ interface LiveApi {
@Body request: CancelLiveReservationRequest,
@Header("Authorization") authHeader: String
): 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.LiveRoomDonationResponse
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.user.CreatorFollowRequestRequest
import kr.co.vividnext.sodalive.user.UserApi
@ -242,4 +243,12 @@ class LiveRepository(
creatorId = creatorId,
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.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.transform.CircleCropTransformation
@ -53,6 +54,7 @@ import io.agora.rtm.RtmChannelMember
import io.agora.rtm.RtmClientListener
import io.agora.rtm.RtmMessage
import io.agora.rtm.RtmMessageType
import kotlinx.coroutines.launch
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.agora.Agora
import kr.co.vividnext.sodalive.base.BaseActivity
@ -124,6 +126,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private var isMicrophoneMute = false
private var isSpeaker = false
private var isHost = false
private var isNoChatting = false
private var remainingNoChattingTime = noChattingTime
@ -530,8 +533,13 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
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)
button.getLocationInWindow(buttonPosition)
@ -1004,12 +1012,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
binding.ivCreatorFollow.visibility = View.GONE
}
initLikeHeartButton(isHost = response.creatorId == SharedPreferenceManager.userId)
initRouletteSettingButton(isHost = response.creatorId == SharedPreferenceManager.userId)
activatingRouletteButton(
isHost = response.creatorId == SharedPreferenceManager.userId,
isActiveRoulette = response.isActiveRoulette
)
isHost = response.creatorId == SharedPreferenceManager.userId
initLikeHeartButton()
initRouletteSettingButton()
activatingRouletteButton(isActiveRoulette = response.isActiveRoulette)
if (response.menuPan.isNotBlank()) {
binding.tvMenuPan.visibility = View.VISIBLE
@ -1094,16 +1100,51 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
}
}
private fun initLikeHeartButton(isHost: Boolean) {
private fun initLikeHeartButton() {
if (!isHost) {
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 {
binding.flLikeHeart.visibility = View.GONE
}
}
private fun initRouletteSettingButton(isHost: Boolean) {
private fun initRouletteSettingButton() {
if (isHost) {
binding.flRouletteSettings.visibility = View.VISIBLE
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) {
binding.flRoulette.visibility = View.VISIBLE
binding.flRoulette.setOnClickListener {
@ -1533,7 +1574,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
)
)
invalidateChat()
viewModel.addDonationCan(can)
lifecycleScope.launch { viewModel.addDonationCan(can) }
addSignature(signature)
}
},
@ -1574,7 +1615,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
)
)
invalidateChat()
viewModel.addDonationCan(can)
lifecycleScope.launch { viewModel.addDonationCan(can) }
}
},
onFailure = {
@ -1641,7 +1682,9 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
)
)
invalidateChat()
viewModel.addDonationCan(rawMessage.can)
lifecycleScope.launch {
viewModel.addDonationCan(rawMessage.can)
}
if (rawMessage.signature != null) {
addSignature(rawMessage.signature)
@ -1666,9 +1709,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
LiveRoomChatRawMessageType.TOGGLE_ROULETTE -> {
handler.post {
activatingRouletteButton(
isHost = viewModel
.roomInfoResponse
.creatorId == SharedPreferenceManager.userId,
isActiveRoulette = rawMessage.isActiveRoulette ?: false
)
}
@ -1684,7 +1724,16 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
)
)
invalidateChat()
viewModel.addDonationCan(rawMessage.can)
lifecycleScope.launch {
viewModel.addDonationCan(rawMessage.can)
}
}
}
LiveRoomChatRawMessageType.HEART_DONATION -> {
handler.post {
addHeartAnimation()
lifecycleScope.launch { viewModel.addHeartDonation() }
}
}
@ -2009,7 +2058,9 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
)
)
invalidateChat()
viewModel.addDonationCan(message.can)
lifecycleScope.launch {
viewModel.addDonationCan(message.can)
}
if (message.signature != null) {
addSignature(message.signature)

View File

@ -13,6 +13,8 @@ import com.google.gson.Gson
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
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.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.live.LiveRepository
@ -100,6 +102,8 @@ class LiveRoomViewModel(
private val blockedMemberIdList: MutableList<Long> = mutableListOf()
val mutex = Mutex()
fun getUserNickname(memberId: Int): String {
for (manager in roomInfoResponse.managerList) {
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(
roomId: Long,
can: Int,
@ -641,8 +675,16 @@ class LiveRoomViewModel(
)
}
fun addDonationCan(can: Int) {
_totalDonationCan.postValue(totalDonationCan.value!! + can)
suspend fun addDonationCan(can: Int) {
mutex.withLock {
_totalDonationCan.postValue(totalDonationCan.value!! + can)
}
}
suspend fun addHeartDonation() {
mutex.withLock {
_totalLikeHeart.postValue(totalLikeHeart.value!! + 1)
}
}
fun donationStatus(roomId: Long, onSuccess: (GetLiveRoomDonationStatusResponse) -> Unit) {

View File

@ -35,5 +35,8 @@ enum class LiveRoomChatRawMessageType {
TOGGLE_ROULETTE,
@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
)