라이브 방 룰렛 - 룰렛 돌리기 기능 추가
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.LiveRoomDonationStatusChat
|
||||||
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomJoinChat
|
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.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.LiveRoomDonationDialog
|
||||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageDialog
|
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageDialog
|
||||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageViewModel
|
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageViewModel
|
||||||
|
@ -880,7 +881,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||||
RoulettePreviewDialog(
|
RoulettePreviewDialog(
|
||||||
activity = this,
|
activity = this,
|
||||||
preview = it,
|
preview = it,
|
||||||
onClickSpin = {},
|
onClickSpin = { spinRoulette() },
|
||||||
layoutInflater = layoutInflater
|
layoutInflater = layoutInflater
|
||||||
).show()
|
).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) {
|
private fun joinChannel(roomInfo: GetRoomInfoResponse) {
|
||||||
loadingDialog.show(width = screenWidth, message = "라이브에 입장하고 있습니다.")
|
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 {
|
} else {
|
||||||
val chat = message.text
|
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.RoulettePreview
|
||||||
import kr.co.vividnext.sodalive.live.roulette.RoulettePreviewItem
|
import kr.co.vividnext.sodalive.live.roulette.RoulettePreviewItem
|
||||||
import kr.co.vividnext.sodalive.live.roulette.RouletteRepository
|
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.ReportRepository
|
||||||
import kr.co.vividnext.sodalive.report.ReportRequest
|
import kr.co.vividnext.sodalive.report.ReportRequest
|
||||||
import kr.co.vividnext.sodalive.report.ReportType
|
import kr.co.vividnext.sodalive.report.ReportType
|
||||||
|
@ -33,6 +34,7 @@ import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
class LiveRoomViewModel(
|
class LiveRoomViewModel(
|
||||||
private val repository: LiveRepository,
|
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> {
|
private fun calculatePercentages(options: List<RouletteItem>): List<RoulettePreviewItem> {
|
||||||
val totalWeight = options.sumOf { it.weight }
|
val totalWeight = options.sumOf { it.weight }
|
||||||
val updatedOptions = options.asSequence().map { option ->
|
val updatedOptions = options.asSequence().map { option ->
|
||||||
|
|
|
@ -174,6 +174,7 @@ data class LiveRoomNormalChat(
|
||||||
itemBinding.tvNickname.text = nickname
|
itemBinding.tvNickname.text = nickname
|
||||||
|
|
||||||
itemBinding.ivBg.visibility = View.VISIBLE
|
itemBinding.ivBg.visibility = View.VISIBLE
|
||||||
|
itemBinding.ivRoulette.visibility = View.GONE
|
||||||
itemBinding.tvCreatorOrManager.visibility = View.GONE
|
itemBinding.tvCreatorOrManager.visibility = View.GONE
|
||||||
|
|
||||||
when (rank + 1) {
|
when (rank + 1) {
|
||||||
|
@ -288,6 +289,7 @@ data class LiveRoomDonationChat(
|
||||||
itemBinding.ivCan.visibility = View.VISIBLE
|
itemBinding.ivCan.visibility = View.VISIBLE
|
||||||
itemBinding.ivBg.visibility = View.GONE
|
itemBinding.ivBg.visibility = View.GONE
|
||||||
itemBinding.ivCrown.visibility = View.GONE
|
itemBinding.ivCrown.visibility = View.GONE
|
||||||
|
itemBinding.ivRoulette.visibility = View.GONE
|
||||||
itemBinding.tvCreatorOrManager.visibility = View.GONE
|
itemBinding.tvCreatorOrManager.visibility = View.GONE
|
||||||
|
|
||||||
if (donationMessage.isNotBlank()) {
|
if (donationMessage.isNotBlank()) {
|
||||||
|
@ -334,3 +336,61 @@ data class LiveRoomDonationChat(
|
||||||
itemBinding.root.setPadding(33)
|
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")
|
@SerializedName("DONATION_STATUS")
|
||||||
DONATION_STATUS,
|
DONATION_STATUS,
|
||||||
@SerializedName("TOGGLE_ROULETTE")
|
@SerializedName("TOGGLE_ROULETTE")
|
||||||
TOGGLE_ROULETTE
|
TOGGLE_ROULETTE,
|
||||||
|
@SerializedName("ROULETTE_DONATION")
|
||||||
|
ROULETTE_DONATION
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package kr.co.vividnext.sodalive.live.roulette
|
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.CreateOrUpdateRouletteRequest
|
||||||
import kr.co.vividnext.sodalive.live.roulette.config.RouletteApi
|
import kr.co.vividnext.sodalive.live.roulette.config.RouletteApi
|
||||||
|
|
||||||
|
@ -16,4 +18,16 @@ class RouletteRepository(private val api: RouletteApi) {
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
authHeader = token
|
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 io.reactivex.rxjava3.core.Single
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.live.roulette.GetRouletteResponse
|
import kr.co.vividnext.sodalive.live.roulette.GetRouletteResponse
|
||||||
|
import kr.co.vividnext.sodalive.live.roulette.SpinRouletteRequest
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Path
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface RouletteApi {
|
interface RouletteApi {
|
||||||
|
@ -21,4 +23,16 @@ interface RouletteApi {
|
||||||
@Query("creatorId") creatorId: Long,
|
@Query("creatorId") creatorId: Long,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<GetRouletteResponse>>
|
): 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:layout_gravity="end|bottom"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:visibility="gone" />
|
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>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
@ -101,4 +101,5 @@
|
||||||
<color name="color_3bb9f1">#3BB9F1</color>
|
<color name="color_3bb9f1">#3BB9F1</color>
|
||||||
<color name="color_2e6279">#2E6279</color>
|
<color name="color_2e6279">#2E6279</color>
|
||||||
<color name="color_cf5c37">#CF5C37</color>
|
<color name="color_cf5c37">#CF5C37</color>
|
||||||
|
<color name="color_ffe500">#FFE500</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue