라이브 방 룰렛 - 룰렛 돌리기 기능 추가
This commit is contained in:
parent
91db6caec9
commit
9f66cb91fc
|
@ -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
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,5 +20,7 @@ enum class LiveRoomChatRawMessageType {
|
|||
@SerializedName("DONATION_STATUS")
|
||||
DONATION_STATUS,
|
||||
@SerializedName("TOGGLE_ROULETTE")
|
||||
TOGGLE_ROULETTE
|
||||
TOGGLE_ROULETTE,
|
||||
@SerializedName("ROULETTE_DONATION")
|
||||
ROULETTE_DONATION
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
|
@ -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>>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue