diff --git a/app/build.gradle b/app/build.gradle index f355a1b..5aa88dd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,8 +40,8 @@ android { applicationId "kr.co.vividnext.sodalive" minSdk 23 targetSdk 33 - versionCode 68 - versionName "1.10.4" + versionCode 69 + versionName "1.11.0" } buildTypes { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt index 28d7d67..04db47b 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt @@ -728,7 +728,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB copyMessage = { val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager clipboard.setPrimaryClip(ClipData.newPlainText(it, it)) - showToast("후원 메시지가 복사되었습니다.") + showToast("후원 히스토리가 복사되었습니다.") } ).show() } @@ -950,8 +950,10 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB viewModel.showRoulette { RoulettePreviewDialog( activity = this, - preview = it, - onClickSpin = { spinRoulette() }, + previewList = it, + onClickSpin = { rouletteId -> + spinRoulette(rouletteId = rouletteId) + }, layoutInflater = layoutInflater ).show() } @@ -1333,12 +1335,12 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB } } - private fun spinRoulette() { - viewModel.spinRoulette(roomId = roomId) { can, items, randomlySelectedItem -> + private fun spinRoulette(rouletteId: Long) { + viewModel.spinRoulette(roomId = roomId, rouletteId = rouletteId) { can, items, randomItem -> val rouletteRawMessage = Gson().toJson( LiveRoomChatRawMessage( type = LiveRoomChatRawMessageType.ROULETTE_DONATION, - message = randomlySelectedItem, + message = randomItem, can = can, donationMessage = "", ) @@ -1347,7 +1349,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB RouletteSpinDialog( activity = this@LiveRoomActivity, items = items, - selectedItem = randomlySelectedItem, + selectedItem = randomItem, layoutInflater = layoutInflater ) { agora.sendRawMessageToGroup( @@ -1358,7 +1360,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB LiveRoomRouletteDonationChat( profileUrl = SharedPreferenceManager.profileImage, nickname = SharedPreferenceManager.nickname, - rouletteResult = randomlySelectedItem + rouletteResult = randomItem ) ) invalidateChat() diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt index c1a4368..4be39f7 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt @@ -3,12 +3,6 @@ package kr.co.vividnext.sodalive.live.room import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.google.firebase.dynamiclinks.ShortDynamicLink -import com.google.firebase.dynamiclinks.ktx.androidParameters -import com.google.firebase.dynamiclinks.ktx.dynamicLinks -import com.google.firebase.dynamiclinks.ktx.iosParameters -import com.google.firebase.dynamiclinks.ktx.shortLinkAsync -import com.google.firebase.ktx.Firebase import com.google.gson.Gson import com.orhanobut.logger.Logger import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -36,7 +30,7 @@ import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import java.io.File -import kotlin.math.floor +import java.util.Locale class LiveRoomViewModel( private val repository: LiveRepository, @@ -480,7 +474,12 @@ class LiveRoomViewModel( ) } - fun donation(roomId: Long, can: Int, message: String, onSuccess: (LiveRoomDonationResponse?) -> Unit) { + fun donation( + roomId: Long, + can: Int, + message: String, + onSuccess: (LiveRoomDonationResponse?) -> Unit + ) { _isLoading.postValue(true) compositeDisposable.add( repository.donation(roomId, can, message, "Bearer ${SharedPreferenceManager.token}") @@ -780,7 +779,7 @@ class LiveRoomViewModel( ) } - fun showRoulette(complete: (RoulettePreview) -> Unit) { + fun showRoulette(complete: (List) -> Unit) { if (!_isLoading.value!!) { _isLoading.value = true compositeDisposable.add( @@ -797,15 +796,19 @@ class LiveRoomViewModel( val data = it.data if ( it.success && - data != null && - data.isActive && - data.items.isNotEmpty() + !data.isNullOrEmpty() ) { complete( - RoulettePreview( - data.can, - items = calculatePercentages(data.items) - ) + data + .filter { roulette -> roulette.isActive } + .filter { roulette -> roulette.items.isNotEmpty() } + .map { roulette -> + RoulettePreview( + id = roulette.id, + can = roulette.can, + items = calculatePercentages(roulette.items) + ) + } ) } else { val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요." @@ -822,12 +825,16 @@ class LiveRoomViewModel( } } - fun spinRoulette(roomId: Long, complete: (Int, List, String) -> Unit) { + fun spinRoulette( + roomId: Long, + rouletteId: Long, + complete: (Int, List, String) -> Unit + ) { if (!_isLoading.value!!) { _isLoading.value = true compositeDisposable.add( rouletteRepository.spinRoulette( - request = SpinRouletteRequest(roomId = roomId), + request = SpinRouletteRequest(roomId = roomId, rouletteId = rouletteId), token = "Bearer ${SharedPreferenceManager.token}" ) .subscribeOn(Schedulers.io()) @@ -840,11 +847,10 @@ class LiveRoomViewModel( if ( it.success && data != null && - data.isActive && data.items.isNotEmpty() ) { SharedPreferenceManager.can -= data.can - randomSelectRouletteItem(data.can, data.items, complete) + complete(data.can, data.items, data.result) } else { val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요." _toastLiveData.postValue(message) @@ -933,31 +939,11 @@ class LiveRoomViewModel( ) } - private fun randomSelectRouletteItem( - can: Int, - items: List, - complete: (Int, List, String) -> Unit - ) { - _isLoading.value = true - - val rouletteItems = mutableListOf() - items.asSequence().forEach { item -> - repeat(item.weight * 10) { - rouletteItems.add(item.title) - } - } - - _isLoading.value = false - complete(can, items, rouletteItems.random()) - } - private fun calculatePercentages(options: List): List { - val totalWeight = options.sumOf { it.weight } - val updatedOptions = options.asSequence().map { option -> - val percent = floor(option.weight.toDouble() / totalWeight * 10000) / 100 + val updatedOptions = options.map { option -> RoulettePreviewItem( title = option.title, - percent = "${String.format("%.2f", percent)}%" + percent = "${String.format(Locale.KOREAN, "%.2f", option.percentage)}%" ) }.toList() diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/donation/LiveRoomDonationMessageAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/donation/LiveRoomDonationMessageAdapter.kt index 96e69ca..fe66fc6 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/donation/LiveRoomDonationMessageAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/donation/LiveRoomDonationMessageAdapter.kt @@ -2,9 +2,11 @@ package kr.co.vividnext.sodalive.live.room.donation import android.annotation.SuppressLint import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.databinding.ItemLiveRoomDonationMessageBinding class LiveRoomDonationMessageAdapter( @@ -20,8 +22,16 @@ class LiveRoomDonationMessageAdapter( @SuppressLint("SetTextI18n") fun bind(item: LiveRoomDonationMessage) { - binding.tvNickname.text = "${item.nickname}님이" - binding.tvCanMessage.text = item.canMessage + if (item.canMessage.isNotBlank()) { + binding.tvNickname.text = "${item.nickname}님이" + binding.tvCanMessage.text = item.canMessage + binding.tvCanMessage.visibility = View.VISIBLE + binding.root.setBackgroundResource(R.drawable.bg_round_corner_5_3_333333) + } else { + binding.tvNickname.text = "${item.nickname}님의 룰렛 결과?" + binding.tvCanMessage.visibility = View.GONE + binding.root.setBackgroundResource(R.drawable.bg_round_corner_5_3_ccc25264) + } binding.tvDonationMessage.text = "\"${item.donationMessage}\"" binding.ivDelete.setOnClickListener { onClickDeleteMessage(item.uuid) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/GetRouletteResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/GetRouletteResponse.kt index 547ad30..29ee794 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/GetRouletteResponse.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/GetRouletteResponse.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.live.roulette import com.google.gson.annotations.SerializedName data class GetRouletteResponse( + @SerializedName("id") val id: Long, @SerializedName("can") val can: Int, @SerializedName("isActive") val isActive: Boolean, @SerializedName("items") val items: List @@ -10,5 +11,5 @@ data class GetRouletteResponse( data class RouletteItem( @SerializedName("title") val title: String, - @SerializedName("weight") val weight: Int + @SerializedName("percentage") val percentage: Float ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RoulettePreview.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RoulettePreview.kt index e6ed0c8..a5c5a4c 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RoulettePreview.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RoulettePreview.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.live.roulette import com.google.gson.annotations.SerializedName data class RoulettePreview( + @SerializedName("id") val id: Long, @SerializedName("can") val can: Int, @SerializedName("items") val items: List ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RoulettePreviewDialog.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RoulettePreviewDialog.kt index 6960cdd..6d410d3 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RoulettePreviewDialog.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RoulettePreviewDialog.kt @@ -8,27 +8,36 @@ import android.view.LayoutInflater import android.view.View import android.view.Window import android.view.WindowManager +import android.widget.ImageView +import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.MutableLiveData import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.DialogRoulettePreviewBinding import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.moneyFormat +import kr.co.vividnext.sodalive.live.roulette.config.RouletteSettingsViewModel.SelectedRoulette import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity class RoulettePreviewDialog( private val activity: FragmentActivity, - private val preview: RoulettePreview, + private val previewList: List, private val title: String = "", - private val onClickSpin: (() -> Unit)? = null, + private val onClickSpin: ((Long) -> Unit)? = null, layoutInflater: LayoutInflater ) { private val alertDialog: AlertDialog private val dialogView = DialogRoulettePreviewBinding.inflate(layoutInflater) + private val selectedRouletteLiveData = MutableLiveData( + SelectedRoulette.ROULETTE_1 + ) + init { val dialogBuilder = AlertDialog.Builder(activity) dialogBuilder.setView(dialogView.root) @@ -54,18 +63,189 @@ class RoulettePreviewDialog( @SuppressLint("SetTextI18n") private fun setupView() { - dialogView.tvCancel.setOnClickListener { alertDialog.dismiss() } + if (previewList.isEmpty()) { + alertDialog.dismiss() + } else { + initSelectRouletteButton() + setRouletteData(previewList[0]) + } - dialogView.tvSpinRoulette.text = "${preview.can}캔으로 룰렛 돌리기" + dialogView.tvCancel.setOnClickListener { alertDialog.dismiss() } + } + + private fun initSelectRouletteButton() { + if (previewList.size < 2) { + dialogView.llSelectRoulette.visibility = View.GONE + } else { + dialogView.llSelectRoulette.visibility = View.VISIBLE + + if (previewList.size > 2) { + dialogView.llSelectRoulette3.visibility = View.VISIBLE + } else { + dialogView.llSelectRoulette3.visibility = View.GONE + } + } + + dialogView.llSelectRoulette1.setOnClickListener { + if (selectedRouletteLiveData.value != SelectedRoulette.ROULETTE_1) { + selectedRouletteLiveData.value = SelectedRoulette.ROULETTE_1 + } + } + + dialogView.llSelectRoulette2.setOnClickListener { + if (selectedRouletteLiveData.value != SelectedRoulette.ROULETTE_2) { + selectedRouletteLiveData.value = SelectedRoulette.ROULETTE_2 + } + } + + dialogView.llSelectRoulette3.setOnClickListener { + if (selectedRouletteLiveData.value != SelectedRoulette.ROULETTE_3) { + selectedRouletteLiveData.value = SelectedRoulette.ROULETTE_3 + } + } + + selectedRouletteLiveData.observe(activity) { + deselectAllRoulette() + when (it) { + SelectedRoulette.ROULETTE_2 -> { + selectRouletteButton( + dialogView.ivSelectRoulette2, + dialogView.llSelectRoulette2, + dialogView.tvSelectRoulette2 + ) + setRouletteData(previewList[1]) + dialogView.tvCancel.setTextColor( + ContextCompat.getColor(activity, R.color.color_ffcb14) + ) + dialogView.tvCancel.setBackgroundResource( + R.drawable.bg_round_corner_10_transparent_ffcb14 + ) + } + + SelectedRoulette.ROULETTE_3 -> { + selectRouletteButton( + dialogView.ivSelectRoulette3, + dialogView.llSelectRoulette3, + dialogView.tvSelectRoulette3 + ) + setRouletteData(previewList[2]) + dialogView.tvCancel.setTextColor( + ContextCompat.getColor(activity, R.color.color_ff14d9) + ) + dialogView.tvCancel.setBackgroundResource( + R.drawable.bg_round_corner_10_transparent_ff14d9 + ) + } + + else -> { + selectRouletteButton( + dialogView.ivSelectRoulette1, + dialogView.llSelectRoulette1, + dialogView.tvSelectRoulette1 + ) + setRouletteData(previewList[0]) + dialogView.tvCancel.setTextColor( + ContextCompat.getColor(activity, R.color.color_3bb9f1) + ) + dialogView.tvCancel.setBackgroundResource( + R.drawable.bg_round_corner_10_transparent_3bb9f1 + ) + } + } + } + } + + private fun deselectAllRoulette() { + dialogView.ivSelectRoulette1.visibility = View.GONE + dialogView.ivSelectRoulette2.visibility = View.GONE + dialogView.ivSelectRoulette3.visibility = View.GONE + + dialogView.llSelectRoulette1.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b) + dialogView.tvSelectRoulette1.setTextColor( + ContextCompat.getColor( + activity, + R.color.color_3bb9f1 + ) + ) + + dialogView.llSelectRoulette2.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b) + dialogView.tvSelectRoulette2.setTextColor( + ContextCompat.getColor( + activity, + R.color.color_ffcb14 + ) + ) + + dialogView.llSelectRoulette3.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b) + dialogView.tvSelectRoulette3.setTextColor( + ContextCompat.getColor( + activity, + R.color.color_ff14d9 + ) + ) + } + + private fun selectRouletteButton( + ivSelectRoulette: ImageView, + llSelectRoulette: LinearLayout, + tvSelectRoulette: TextView + ) { + ivSelectRoulette.visibility = View.VISIBLE + llSelectRoulette.setBackgroundResource( + when (selectedRouletteLiveData.value) { + SelectedRoulette.ROULETTE_2 -> R.drawable.bg_round_corner_6_7_ffcb14 + SelectedRoulette.ROULETTE_3 -> R.drawable.bg_round_corner_6_7_ff14d9 + else -> R.drawable.bg_round_corner_6_7_3bb9f1 + } + ) + tvSelectRoulette.setTextColor( + ContextCompat.getColor( + activity, + when (selectedRouletteLiveData.value) { + SelectedRoulette.ROULETTE_2 -> R.color.black + else -> R.color.color_eeeeee + } + + ) + ) + } + + @SuppressLint("SetTextI18n") + private fun setRouletteData(roulettePreview: RoulettePreview) { + dialogView.tvSpinRoulette.text = "${roulettePreview.can}캔으로 룰렛 돌리기" dialogView.tvSpinRoulette.setOnClickListener { if (onClickSpin != null) { - onClickSpin!!() + onClickSpin!!(roulettePreview.id) } alertDialog.dismiss() } - dialogView.tvTitle.text = title.ifBlank { "룰렛" } + dialogView.tvSpinRoulette.setTextColor( + ContextCompat.getColor( + activity, + when (selectedRouletteLiveData.value) { + SelectedRoulette.ROULETTE_2 -> R.color.black + else -> R.color.white + } + ) + ) + dialogView.tvSpinRoulette.setBackgroundResource( + when (selectedRouletteLiveData.value) { + SelectedRoulette.ROULETTE_2 -> R.drawable.bg_round_corner_10_ffcb14 + SelectedRoulette.ROULETTE_3 -> R.drawable.bg_round_corner_10_ff14d9 + else -> R.drawable.bg_round_corner_10_3bb9f1 + } + ) + + dialogView.tvTitle.text = title.ifBlank { + when (selectedRouletteLiveData.value) { + SelectedRoulette.ROULETTE_2 -> "룰렛 2" + SelectedRoulette.ROULETTE_3 -> "룰렛 3" + else -> "룰렛 1" + } + } + if (onClickSpin != null) { dialogView.tvCan.visibility = View.VISIBLE dialogView.tvCan.text = SharedPreferenceManager.can.moneyFormat() @@ -80,7 +260,8 @@ class RoulettePreviewDialog( dialogView.tvCan.visibility = View.GONE } - preview.items.forEachIndexed { index, item -> + dialogView.llRouletteOptionContainer.removeAllViews() + roulettePreview.items.forEachIndexed { index, item -> dialogView.llRouletteOptionContainer.addView(createOptionView(index, item)) } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RouletteView.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RouletteView.kt index b2f7c5b..11a78ec 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RouletteView.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/RouletteView.kt @@ -74,12 +74,10 @@ class RouletteView @JvmOverloads constructor( override fun onDraw(canvas: Canvas) { super.onDraw(canvas) - val totalWeight = items.asSequence().map { it.weight }.sum() var startAngle = -90f - val shuffledColors = colors.shuffled() - items.forEachIndexed { index, (option, weight) -> - val sweepAngle = (weight / totalWeight.toFloat()) * 360f + items.forEachIndexed { index, (option, percentage) -> + val sweepAngle = (percentage / 100) * 360f fillPaint.color = shuffledColors[index] canvas.drawArc(rect, startAngle, sweepAngle, true, fillPaint) @@ -117,11 +115,10 @@ class RouletteView @JvmOverloads constructor( } private fun getAngleForOption(option: String): Float { - val totalWeight = items.asSequence().map { it.weight }.sum() var startAngle = 0f - items.forEach { (currentOption, weight) -> - val sweepAngle = (weight / totalWeight.toFloat()) * 360f + items.forEach { (currentOption, percentage) -> + val sweepAngle = (percentage / 100) * 360f if (currentOption == option) { // Return the midpoint angle of the segment return (startAngle + sweepAngle / 2) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/SpinRouletteRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/SpinRouletteRequest.kt index 4a48804..aea81c8 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/SpinRouletteRequest.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/SpinRouletteRequest.kt @@ -4,5 +4,6 @@ import com.google.gson.annotations.SerializedName data class SpinRouletteRequest( @SerializedName("roomId") val roomId: Long, + @SerializedName("rouletteId") val rouletteId: Long, @SerializedName("container") val container: String = "aos" ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/SpinRouletteResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/SpinRouletteResponse.kt new file mode 100644 index 0000000..fc40bad --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/SpinRouletteResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.live.roulette + +import com.google.gson.annotations.SerializedName + +data class SpinRouletteResponse( + @SerializedName("can") val can: Int, + @SerializedName("result") val result: String, + @SerializedName("items") val items: List +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteApi.kt index 80d2078..fc6d86b 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteApi.kt @@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.live.roulette.GetNewRouletteResponse import kr.co.vividnext.sodalive.live.roulette.GetRouletteResponse import kr.co.vividnext.sodalive.live.roulette.SpinRouletteRequest +import kr.co.vividnext.sodalive.live.roulette.SpinRouletteResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -14,37 +15,37 @@ import retrofit2.http.Path import retrofit2.http.Query interface RouletteApi { - @POST("/new-roulette") + @POST("/v2/roulette") fun createRoulette( @Body request: CreateRouletteRequest, @Header("Authorization") authHeader: String ): Single> - @PUT("/new-roulette") + @PUT("/v2/roulette") fun updateRoulette( @Body request: UpdateRouletteRequest, @Header("Authorization") authHeader: String ): Single> - @GET("/new-roulette/creator") + @GET("/v2/roulette/creator") fun getAllRoulette( @Query("creatorId") creatorId: Long, @Header("Authorization") authHeader: String ): Single>> - @GET("/new-roulette") + @GET("/v2/roulette") fun getRoulette( @Query("creatorId") creatorId: Long, @Header("Authorization") authHeader: String - ): Single> + ): Single>> - @POST("/new-roulette/spin") + @POST("/v2/roulette/spin") fun spinRoulette( @Body request: SpinRouletteRequest, @Header("Authorization") authHeader: String - ): Single> + ): Single> - @POST("/new-roulette/refund/{id}") + @POST("/v2/roulette/refund/{id}") fun refundRouletteDonation( @Path("id") id: Long, @Header("Authorization") authHeader: String diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteOption.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteOption.kt index 9c4059a..1086f69 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteOption.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteOption.kt @@ -1,3 +1,3 @@ package kr.co.vividnext.sodalive.live.roulette.config -data class RouletteOption(var title: String, var weight: Int, var percentage: String = "50.00") +data class RouletteOption(var title: String, var percentage: String = "") diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteSettingsFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteSettingsFragment.kt index ecdc7bb..16c240b 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteSettingsFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteSettingsFragment.kt @@ -156,7 +156,7 @@ class RouletteSettingsFragment : BaseFragment( viewModel.roulettePreviewLiveData.observe(viewLifecycleOwner) { RoulettePreviewDialog( activity = requireActivity(), - preview = it, + previewList = listOf(it), title = "룰렛 미리보기", layoutInflater = layoutInflater ).show() @@ -176,7 +176,7 @@ class RouletteSettingsFragment : BaseFragment( } private fun addOption() { - val newOption = RouletteOption("", 1) + val newOption = RouletteOption("", "") viewModel.addOption(newOption) } @@ -199,16 +199,17 @@ class RouletteSettingsFragment : BaseFragment( val etOption = optionView.findViewById(R.id.et_option) val tvOptionTitle = optionView.findViewById(R.id.tv_option_title) - val tvPercentage = optionView.findViewById(R.id.tv_option_percentage) - val ivMinus = optionView.findViewById(R.id.iv_minus) - val ivPlus = optionView.findViewById(R.id.iv_plus) + val etPercentage = optionView.findViewById(R.id.et_option_percentage) val tvDelete = optionView.findViewById(R.id.tv_delete) etOption.setText(option.title) tvOptionTitle.text = "옵션 ${index + 1}" - tvPercentage.text = "${option.percentage}%" - ivMinus.setOnClickListener { viewModel.subtractWeight(index) } - ivPlus.setOnClickListener { viewModel.plusWeight(index) } + + if (option.percentage.toFloat() > 0f) { + etPercentage.setText(option.percentage) + } else { + etPercentage.setText("") + } if (index == 0 || index == 1) { tvDelete.visibility = View.GONE @@ -227,6 +228,16 @@ class RouletteSettingsFragment : BaseFragment( } ) + compositeDisposable.add( + etPercentage.textChanges().skip(1) + .debounce(100, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.inputOptionPercentage(index, it.toString()) + } + ) + return optionView } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteSettingsViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteSettingsViewModel.kt index 4eeba6a..93a4d2e 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteSettingsViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/roulette/config/RouletteSettingsViewModel.kt @@ -12,7 +12,6 @@ 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 kotlin.math.floor class RouletteSettingsViewModel(private val repository: RouletteRepository) : BaseViewModel() { @@ -55,31 +54,16 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba val rouletteList = mutableListOf() - fun plusWeight(optionIndex: Int) { - val currentOption = options[optionIndex] - options[optionIndex] = currentOption.copy(weight = currentOption.weight + 1) - recalculatePercentages(options) - } - - fun subtractWeight(optionIndex: Int) { - if (options[optionIndex].weight > 1) { - val currentOption = options[optionIndex] - options[optionIndex] = currentOption.copy(weight = currentOption.weight - 1) - recalculatePercentages(options) - } - } - fun addOption(newOption: RouletteOption) { if (options.size >= 10) return - options.add(newOption) - recalculatePercentages(options) + _optionsLiveData.value = options } fun deleteOption(index: Int) { val updatedOptions = options.filterIndexed { currentIndex, _ -> currentIndex != index } removeAllAndAddOptions(updatedOptions) - recalculatePercentages(updatedOptions) + _optionsLiveData.value = updatedOptions } fun inputOption(optionIndex: Int, title: String) { @@ -87,15 +71,9 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba options[optionIndex] = currentOption.copy(title = title) } - private fun recalculatePercentages(options: List) { - val totalWeight = options.sumOf { it.weight } - val updatedOptions = options.asSequence().map { option -> - val percent = floor(option.weight.toDouble() / totalWeight * 10000) / 100 - option.copy(percentage = String.format("%.2f", percent)) - }.toList() - - removeAllAndAddOptions(updatedOptions) - _optionsLiveData.value = updatedOptions + fun inputOptionPercentage(optionIndex: Int, percentage: String) { + val currentOption = options[optionIndex] + options[optionIndex] = currentOption.copy(percentage = percentage) } fun toggleIsActive() { @@ -107,17 +85,20 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba _isLoading.value = true val items = mutableListOf() - for (option in options) { - if (option.title.trim().isEmpty()) { - _toastLiveData.value = "옵션은 빈칸을 할 수 없습니다." - _isLoading.value = false - return + if (validationOptions()) { + for (option in options) { + if (option.title.trim().isEmpty()) { + _toastLiveData.value = "옵션은 빈칸을 할 수 없습니다." + _isLoading.value = false + return + } + + items.add(RoulettePreviewItem(option.title, "${option.percentage}%")) } - items.add(RoulettePreviewItem(option.title, "${option.percentage}%")) + _roulettePreviewLiveData.postValue(RoulettePreview(0, can, items)) } - _roulettePreviewLiveData.postValue(RoulettePreview(can, items)) _isLoading.value = false } @@ -125,24 +106,41 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba if (!_isLoading.value!!) { _isLoading.value = true - if (rouletteId > 0) { - updateRoulette(onSuccess) - } else { - createRoulette(onSuccess) + if (validationOptions()) { + if (rouletteId > 0) { + updateRoulette(onSuccess) + } else { + createRoulette(onSuccess) + } } } } + private fun validationOptions(): Boolean { + var totalPercentage = 0f + for (option in options) { + if (option.title.trim().isEmpty()) { + _toastLiveData.value = "옵션은 빈칸을 할 수 없습니다." + _isLoading.value = false + return false + } + + totalPercentage += option.percentage.toFloat() + } + + if (totalPercentage != 100.0f) { + _toastLiveData.value = "확률이 100%가 아닙니다" + _isLoading.value = false + return false + } + + return true + } + private fun updateRoulette(onSuccess: (Boolean) -> Unit) { val items = mutableListOf() for (option in options) { - if (option.title.trim().isEmpty()) { - _toastLiveData.value = "옵션은 빈칸을 할 수 없습니다." - _isLoading.value = false - return - } - - items.add(RouletteItem(title = option.title, weight = option.weight)) + items.add(RouletteItem(title = option.title, percentage = option.percentage.toFloat())) } val selectedRoulette = rouletteList[_selectedRouletteLiveData.value!!.ordinal] @@ -169,24 +167,10 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba SelectedRoulette.ROULETTE_3 -> "룰렛 3" } - var isAllActive = false - - rouletteList - .filter { - it.id != selectedRoulette.id - } - .forEach { - if (it.isActive) { - isAllActive = true - } - } - val successMessage = if (isActive) { - "${selectedRouletteTitle}로 설정하였습니다." - } else if (!isAllActive) { - "${selectedRouletteTitle}이 비활성화 되었습니다." + "${selectedRouletteTitle}을 활성화 했습니다." } else { - "${selectedRouletteTitle}을 설정했습니다." + "${selectedRouletteTitle}을 비활성화 했습니다." } compositeDisposable.add( @@ -224,13 +208,7 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba private fun createRoulette(onSuccess: (Boolean) -> Unit) { val items = mutableListOf() for (option in options) { - if (option.title.trim().isEmpty()) { - _toastLiveData.value = "옵션은 빈칸을 할 수 없습니다." - _isLoading.value = false - return - } - - items.add(RouletteItem(title = option.title, weight = option.weight)) + items.add(RouletteItem(title = option.title, percentage = option.percentage.toFloat())) } val request = CreateRouletteRequest( @@ -348,10 +326,10 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba isActive = roulette.isActive val options = roulette.items.asSequence().map { item -> - RouletteOption(title = item.title, weight = item.weight) + RouletteOption(title = item.title, percentage = item.percentage.toString()) }.toList() removeAllAndAddOptions(options = options) - recalculatePercentages(options) + _optionsLiveData.value = options } else { _canLiveData.value = 0 _isActiveLiveData.value = false @@ -361,9 +339,9 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba isActive = false options.clear() - options.add(RouletteOption(title = "", weight = 1)) - options.add(RouletteOption(title = "", weight = 1)) - recalculatePercentages(options) + options.add(RouletteOption(title = "", percentage = "50.00")) + options.add(RouletteOption(title = "", percentage = "50.00")) + _optionsLiveData.value = options } } } diff --git a/app/src/main/res/drawable-xxhdpi/btn_minus_round_rect.png b/app/src/main/res/drawable-xxhdpi/btn_minus_round_rect.png deleted file mode 100644 index ae6a88e..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/btn_minus_round_rect.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/btn_plus_round_rect.png b/app/src/main/res/drawable-xxhdpi/btn_plus_round_rect.png deleted file mode 100644 index a22155e..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/btn_plus_round_rect.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_donation_message_list.png b/app/src/main/res/drawable-xxhdpi/ic_donation_message_list.png index f7e7728..818c847 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_donation_message_list.png and b/app/src/main/res/drawable-xxhdpi/ic_donation_message_list.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_select_check_black.png b/app/src/main/res/drawable-xxhdpi/ic_select_check_black.png new file mode 100644 index 0000000..c9b4901 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_select_check_black.png differ diff --git a/app/src/main/res/drawable/bg_round_corner_10_ff14d9.xml b/app/src/main/res/drawable/bg_round_corner_10_ff14d9.xml new file mode 100644 index 0000000..d6d82dd --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_ff14d9.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_10_ffcb14.xml b/app/src/main/res/drawable/bg_round_corner_10_ffcb14.xml new file mode 100644 index 0000000..a79a7e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_ffcb14.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_10_transparent_ff14d9.xml b/app/src/main/res/drawable/bg_round_corner_10_transparent_ff14d9.xml new file mode 100644 index 0000000..21898a3 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_transparent_ff14d9.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_10_transparent_ffcb14.xml b/app/src/main/res/drawable/bg_round_corner_10_transparent_ffcb14.xml new file mode 100644 index 0000000..f242c18 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_transparent_ffcb14.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_5_3_ccc25264.xml b/app/src/main/res/drawable/bg_round_corner_5_3_ccc25264.xml new file mode 100644 index 0000000..fececfc --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_5_3_ccc25264.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_ff14d9.xml b/app/src/main/res/drawable/bg_round_corner_6_7_ff14d9.xml new file mode 100644 index 0000000..acdac6a --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_6_7_ff14d9.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_ffcb14.xml b/app/src/main/res/drawable/bg_round_corner_6_7_ffcb14.xml new file mode 100644 index 0000000..2240c82 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_6_7_ffcb14.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/layout/dialog_live_room_donation_message.xml b/app/src/main/res/layout/dialog_live_room_donation_message.xml index 09ed448..3c85a4b 100644 --- a/app/src/main/res/layout/dialog_live_room_donation_message.xml +++ b/app/src/main/res/layout/dialog_live_room_donation_message.xml @@ -19,7 +19,7 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:fontFamily="@font/gmarket_sans_bold" - android:text="후원메시지" + android:text="후원 히스토리" android:textColor="@color/color_eeeeee" android:textSize="14.7sp" app:layout_constraintStart_toStartOf="parent" @@ -55,7 +55,7 @@ android:layout_marginTop="30dp" android:fontFamily="@font/gmarket_sans_medium" android:gravity="center" - android:text="후원메시지가 없습니다." + android:text="후원 히스토리가 없습니다." android:textColor="@color/color_eeeeee" android:textSize="14.7sp" android:visibility="gone" /> diff --git a/app/src/main/res/layout/dialog_roulette_preview.xml b/app/src/main/res/layout/dialog_roulette_preview.xml index d675b49..1bb295a 100644 --- a/app/src/main/res/layout/dialog_roulette_preview.xml +++ b/app/src/main/res/layout/dialog_roulette_preview.xml @@ -9,6 +9,101 @@ android:orientation="vertical" android:padding="13.3dp"> + + + + + + + + + + + + + + + + + + + + + + + + - + android:paddingVertical="16.7dp"> - + - + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e683e70..9733802 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -79,6 +79,7 @@ #D85E37 #D38C38 #59548F + #FFCB14 #4D6AA4 #2D7390 #548F7D @@ -118,4 +119,5 @@ #002ABD #312827 #F1291C + #FF14D9