라이브 방 룰렛 - 룰렛 돌리기 기능 추가

This commit is contained in:
klaus 2023-12-02 05:11:55 +09:00
parent 91db6caec9
commit 9f66cb91fc
9 changed files with 263 additions and 2 deletions

View File

@ -63,6 +63,7 @@ import kr.co.vividnext.sodalive.live.room.chat.LiveRoomDonationChat
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomDonationStatusChat
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.donation.LiveRoomDonationDialog
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageDialog
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageViewModel
@ -880,7 +881,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
RoulettePreviewDialog(
activity = this,
preview = it,
onClickSpin = {},
onClickSpin = { spinRoulette() },
layoutInflater = layoutInflater
).show()
}
@ -1248,6 +1249,40 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
}
}
private fun spinRoulette() {
viewModel.spinRoulette(roomId = roomId) { can, randomlySelectedItem ->
val rawMessage = "[$randomlySelectedItem] 당첨!"
val rouletteRawMessage = Gson().toJson(
LiveRoomChatRawMessage(
type = LiveRoomChatRawMessageType.ROULETTE_DONATION,
message = rawMessage,
can = can,
donationMessage = "",
)
)
agora.sendRawMessageToGroup(
rawMessage = rouletteRawMessage.toByteArray(),
onSuccess = {
handler.post {
chatAdapter.items.add(
LiveRoomRouletteDonationChat(
profileUrl = SharedPreferenceManager.profileImage,
nickname = SharedPreferenceManager.nickname,
chat = rawMessage
)
)
invalidateChat()
viewModel.addDonationCan(can)
}
},
onFailure = {
viewModel.refundRouletteDonation(roomId)
}
)
}
}
private fun joinChannel(roomInfo: GetRoomInfoResponse) {
loadingDialog.show(width = screenWidth, message = "라이브에 입장하고 있습니다.")
@ -1330,6 +1365,20 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
)
}
}
LiveRoomChatRawMessageType.ROULETTE_DONATION -> {
handler.post {
chatAdapter.items.add(
LiveRoomRouletteDonationChat(
profileUrl = profileUrl,
nickname = nickname,
chat = rawMessage.message
)
)
invalidateChat()
viewModel.addDonationCan(rawMessage.can)
}
}
}
} else {
val chat = message.text

View File

@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.live.roulette.RouletteItem
import kr.co.vividnext.sodalive.live.roulette.RoulettePreview
import kr.co.vividnext.sodalive.live.roulette.RoulettePreviewItem
import kr.co.vividnext.sodalive.live.roulette.RouletteRepository
import kr.co.vividnext.sodalive.live.roulette.SpinRouletteRequest
import kr.co.vividnext.sodalive.report.ReportRepository
import kr.co.vividnext.sodalive.report.ReportRequest
import kr.co.vividnext.sodalive.report.ReportType
@ -33,6 +34,7 @@ import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import kotlin.random.Random
class LiveRoomViewModel(
private val repository: LiveRepository,
@ -823,6 +825,108 @@ class LiveRoomViewModel(
}
}
fun spinRoulette(roomId: Long, complete: (Int, String) -> Unit) {
if (!_isLoading.value!!) {
_isLoading.value = true
compositeDisposable.add(
rouletteRepository.spinRoulette(
request = SpinRouletteRequest(roomId = roomId),
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
val data = it.data
if (
it.success &&
data != null &&
data.isActive &&
data.items.isNotEmpty()
) {
SharedPreferenceManager.can -= data.can
randomSelectRouletteItem(data.can, data.items, complete)
} else {
val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
_toastLiveData.postValue(message)
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("룰렛을 사용할 수 없습니다. 다시 시도해 주세요.")
}
)
)
}
}
fun refundRouletteDonation(roomId: Long) {
_isLoading.postValue(true)
compositeDisposable.add(
rouletteRepository.refundRouletteDonation(
roomId,
"Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success) {
_toastLiveData.postValue(
"후원에 실패했습니다.\n다시 후원해주세요.\n" +
"계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
)
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
)
}
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue(
"후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
)
}
)
)
}
private fun randomSelectRouletteItem(
can: Int,
items: List<RouletteItem>,
complete: (Int, String) -> Unit
) {
_isLoading.value = true
val rouletteItemTitles = items.asSequence().map { it.title }.toList()
val cumulativeWeights = items.runningFold(0) { sum, item ->
sum + item.weight
}
val totalWeight = items.asSequence().map { it.weight }.sum()
val randomValue = Random.nextInt(0, totalWeight)
for (index in 1 until cumulativeWeights.size) {
if (randomValue < cumulativeWeights[index]) {
_isLoading.value = false
complete(can, rouletteItemTitles[index - 1])
return
}
}
_isLoading.value = false
complete(can, rouletteItemTitles.last())
}
private fun calculatePercentages(options: List<RouletteItem>): List<RoulettePreviewItem> {
val totalWeight = options.sumOf { it.weight }
val updatedOptions = options.asSequence().map { option ->

View File

@ -174,6 +174,7 @@ data class LiveRoomNormalChat(
itemBinding.tvNickname.text = nickname
itemBinding.ivBg.visibility = View.VISIBLE
itemBinding.ivRoulette.visibility = View.GONE
itemBinding.tvCreatorOrManager.visibility = View.GONE
when (rank + 1) {
@ -288,6 +289,7 @@ data class LiveRoomDonationChat(
itemBinding.ivCan.visibility = View.VISIBLE
itemBinding.ivBg.visibility = View.GONE
itemBinding.ivCrown.visibility = View.GONE
itemBinding.ivRoulette.visibility = View.GONE
itemBinding.tvCreatorOrManager.visibility = View.GONE
if (donationMessage.isNotBlank()) {
@ -334,3 +336,61 @@ data class LiveRoomDonationChat(
itemBinding.root.setPadding(33)
}
}
data class LiveRoomRouletteDonationChat(
@SerializedName("profileUrl") val profileUrl: String,
@SerializedName("nickname") val nickname: String,
@SerializedName("chat") val chat: String
) : LiveRoomChat() {
override fun bind(context: Context, binding: ViewBinding, onClickProfile: ((Long) -> Unit)?) {
val itemBinding = binding as ItemLiveRoomChatBinding
val spChat = SpannableString(chat)
spChat.setSpan(
ForegroundColorSpan(
ContextCompat.getColor(
context,
R.color.color_ffe500
)
),
0,
chat.indexOf("]", 0, true) + 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
val spNickname = SpannableString("${nickname}님의 룰렛 결과?")
spNickname.setSpan(
CustomTypefaceSpan(
ResourcesCompat.getFont(
context,
R.font.gmarket_sans_medium
)
),
0,
nickname.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
itemBinding.ivProfile.load(profileUrl) {
crossfade(true)
placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.3f.dpToPx()))
}
itemBinding.tvChat.text = spChat
itemBinding.tvNickname.text = spNickname
itemBinding.ivProfile.setOnClickListener {}
itemBinding.ivCan.visibility = View.GONE
itemBinding.ivBg.visibility = View.GONE
itemBinding.ivCrown.visibility = View.GONE
itemBinding.tvCreatorOrManager.visibility = View.GONE
itemBinding.ivRoulette.visibility = View.VISIBLE
itemBinding.tvDonationMessage.visibility = View.GONE
itemBinding.llMessageBg.setPadding(0)
itemBinding.llMessageBg.background = null
itemBinding.root.setBackgroundResource(R.drawable.bg_round_corner_6_7_c25264)
itemBinding.root.setPadding(33)
}
}

View File

@ -20,5 +20,7 @@ enum class LiveRoomChatRawMessageType {
@SerializedName("DONATION_STATUS")
DONATION_STATUS,
@SerializedName("TOGGLE_ROULETTE")
TOGGLE_ROULETTE
TOGGLE_ROULETTE,
@SerializedName("ROULETTE_DONATION")
ROULETTE_DONATION
}

View File

@ -1,5 +1,7 @@
package kr.co.vividnext.sodalive.live.roulette
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.live.roulette.config.CreateOrUpdateRouletteRequest
import kr.co.vividnext.sodalive.live.roulette.config.RouletteApi
@ -16,4 +18,16 @@ class RouletteRepository(private val api: RouletteApi) {
creatorId = creatorId,
authHeader = token
)
fun spinRoulette(request: SpinRouletteRequest, token: String) = api.spinRoulette(
request = request,
authHeader = token
)
fun refundRouletteDonation(roomId: Long, token: String): Single<ApiResponse<Any>> {
return api.refundRouletteDonation(
id = roomId,
authHeader = token
)
}
}

View File

@ -0,0 +1,8 @@
package kr.co.vividnext.sodalive.live.roulette
import com.google.gson.annotations.SerializedName
data class SpinRouletteRequest(
@SerializedName("roomId") val roomId: Long,
@SerializedName("container") val container: String = "aos"
)

View File

@ -3,10 +3,12 @@ package kr.co.vividnext.sodalive.live.roulette.config
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.live.roulette.GetRouletteResponse
import kr.co.vividnext.sodalive.live.roulette.SpinRouletteRequest
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
interface RouletteApi {
@ -21,4 +23,16 @@ interface RouletteApi {
@Query("creatorId") creatorId: Long,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetRouletteResponse>>
@POST("/roulette/spin")
fun spinRoulette(
@Body request: SpinRouletteRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetRouletteResponse>>
@POST("/roulette/refund/{id}")
fun refundRouletteDonation(
@Path("id") id: Long,
@Header("Authorization") authHeader: String
): Single<ApiResponse<Any>>
}

View File

@ -39,6 +39,15 @@
android:layout_gravity="end|bottom"
android:contentDescription="@null"
android:visibility="gone" />
<ImageView
android:id="@+id/iv_roulette"
android:layout_width="16.7dp"
android:layout_height="16.7dp"
android:layout_gravity="end|bottom"
android:contentDescription="@null"
android:src="@drawable/ic_roulette"
android:visibility="gone" />
</FrameLayout>
<LinearLayout

View File

@ -101,4 +101,5 @@
<color name="color_3bb9f1">#3BB9F1</color>
<color name="color_2e6279">#2E6279</color>
<color name="color_cf5c37">#CF5C37</color>
<color name="color_ffe500">#FFE500</color>
</resources>