룰렛 변경

- 확률 수동 설정
- 여러개의 룰렛이 켜져있을 때 선택하여 돌리기
- 후원 히스토리에 룰렛 히스토리 추가
This commit is contained in:
klaus 2024-05-10 17:43:26 +09:00
parent 9df08cdf24
commit 85ccc18485
29 changed files with 519 additions and 182 deletions

View File

@ -40,8 +40,8 @@ android {
applicationId "kr.co.vividnext.sodalive" applicationId "kr.co.vividnext.sodalive"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 68 versionCode 69
versionName "1.10.4" versionName "1.11.0"
} }
buildTypes { buildTypes {

View File

@ -728,7 +728,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
copyMessage = { copyMessage = {
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText(it, it)) clipboard.setPrimaryClip(ClipData.newPlainText(it, it))
showToast("후원 메시지가 복사되었습니다.") showToast("후원 히스토리가 복사되었습니다.")
} }
).show() ).show()
} }
@ -950,8 +950,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
viewModel.showRoulette { viewModel.showRoulette {
RoulettePreviewDialog( RoulettePreviewDialog(
activity = this, activity = this,
preview = it, previewList = it,
onClickSpin = { spinRoulette() }, onClickSpin = { rouletteId ->
spinRoulette(rouletteId = rouletteId)
},
layoutInflater = layoutInflater layoutInflater = layoutInflater
).show() ).show()
} }
@ -1333,12 +1335,12 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
} }
private fun spinRoulette() { private fun spinRoulette(rouletteId: Long) {
viewModel.spinRoulette(roomId = roomId) { can, items, randomlySelectedItem -> viewModel.spinRoulette(roomId = roomId, rouletteId = rouletteId) { can, items, randomItem ->
val rouletteRawMessage = Gson().toJson( val rouletteRawMessage = Gson().toJson(
LiveRoomChatRawMessage( LiveRoomChatRawMessage(
type = LiveRoomChatRawMessageType.ROULETTE_DONATION, type = LiveRoomChatRawMessageType.ROULETTE_DONATION,
message = randomlySelectedItem, message = randomItem,
can = can, can = can,
donationMessage = "", donationMessage = "",
) )
@ -1347,7 +1349,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
RouletteSpinDialog( RouletteSpinDialog(
activity = this@LiveRoomActivity, activity = this@LiveRoomActivity,
items = items, items = items,
selectedItem = randomlySelectedItem, selectedItem = randomItem,
layoutInflater = layoutInflater layoutInflater = layoutInflater
) { ) {
agora.sendRawMessageToGroup( agora.sendRawMessageToGroup(
@ -1358,7 +1360,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
LiveRoomRouletteDonationChat( LiveRoomRouletteDonationChat(
profileUrl = SharedPreferenceManager.profileImage, profileUrl = SharedPreferenceManager.profileImage,
nickname = SharedPreferenceManager.nickname, nickname = SharedPreferenceManager.nickname,
rouletteResult = randomlySelectedItem rouletteResult = randomItem
) )
) )
invalidateChat() invalidateChat()

View File

@ -3,12 +3,6 @@ package kr.co.vividnext.sodalive.live.room
import android.net.Uri import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData 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.google.gson.Gson
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
@ -36,7 +30,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.math.floor import java.util.Locale
class LiveRoomViewModel( class LiveRoomViewModel(
private val repository: LiveRepository, 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) _isLoading.postValue(true)
compositeDisposable.add( compositeDisposable.add(
repository.donation(roomId, can, message, "Bearer ${SharedPreferenceManager.token}") repository.donation(roomId, can, message, "Bearer ${SharedPreferenceManager.token}")
@ -780,7 +779,7 @@ class LiveRoomViewModel(
) )
} }
fun showRoulette(complete: (RoulettePreview) -> Unit) { fun showRoulette(complete: (List<RoulettePreview>) -> Unit) {
if (!_isLoading.value!!) { if (!_isLoading.value!!) {
_isLoading.value = true _isLoading.value = true
compositeDisposable.add( compositeDisposable.add(
@ -797,15 +796,19 @@ class LiveRoomViewModel(
val data = it.data val data = it.data
if ( if (
it.success && it.success &&
data != null && !data.isNullOrEmpty()
data.isActive &&
data.items.isNotEmpty()
) { ) {
complete( complete(
data
.filter { roulette -> roulette.isActive }
.filter { roulette -> roulette.items.isNotEmpty() }
.map { roulette ->
RoulettePreview( RoulettePreview(
data.can, id = roulette.id,
items = calculatePercentages(data.items) can = roulette.can,
items = calculatePercentages(roulette.items)
) )
}
) )
} else { } else {
val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요." val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
@ -822,12 +825,16 @@ class LiveRoomViewModel(
} }
} }
fun spinRoulette(roomId: Long, complete: (Int, List<RouletteItem>, String) -> Unit) { fun spinRoulette(
roomId: Long,
rouletteId: Long,
complete: (Int, List<RouletteItem>, String) -> Unit
) {
if (!_isLoading.value!!) { if (!_isLoading.value!!) {
_isLoading.value = true _isLoading.value = true
compositeDisposable.add( compositeDisposable.add(
rouletteRepository.spinRoulette( rouletteRepository.spinRoulette(
request = SpinRouletteRequest(roomId = roomId), request = SpinRouletteRequest(roomId = roomId, rouletteId = rouletteId),
token = "Bearer ${SharedPreferenceManager.token}" token = "Bearer ${SharedPreferenceManager.token}"
) )
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@ -840,11 +847,10 @@ class LiveRoomViewModel(
if ( if (
it.success && it.success &&
data != null && data != null &&
data.isActive &&
data.items.isNotEmpty() data.items.isNotEmpty()
) { ) {
SharedPreferenceManager.can -= data.can SharedPreferenceManager.can -= data.can
randomSelectRouletteItem(data.can, data.items, complete) complete(data.can, data.items, data.result)
} else { } else {
val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요." val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
_toastLiveData.postValue(message) _toastLiveData.postValue(message)
@ -933,31 +939,11 @@ class LiveRoomViewModel(
) )
} }
private fun randomSelectRouletteItem(
can: Int,
items: List<RouletteItem>,
complete: (Int, List<RouletteItem>, String) -> Unit
) {
_isLoading.value = true
val rouletteItems = mutableListOf<String>()
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<RouletteItem>): List<RoulettePreviewItem> { private fun calculatePercentages(options: List<RouletteItem>): List<RoulettePreviewItem> {
val totalWeight = options.sumOf { it.weight } val updatedOptions = options.map { option ->
val updatedOptions = options.asSequence().map { option ->
val percent = floor(option.weight.toDouble() / totalWeight * 10000) / 100
RoulettePreviewItem( RoulettePreviewItem(
title = option.title, title = option.title,
percent = "${String.format("%.2f", percent)}%" percent = "${String.format(Locale.KOREAN, "%.2f", option.percentage)}%"
) )
}.toList() }.toList()

View File

@ -2,9 +2,11 @@ package kr.co.vividnext.sodalive.live.room.donation
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemLiveRoomDonationMessageBinding import kr.co.vividnext.sodalive.databinding.ItemLiveRoomDonationMessageBinding
class LiveRoomDonationMessageAdapter( class LiveRoomDonationMessageAdapter(
@ -20,8 +22,16 @@ class LiveRoomDonationMessageAdapter(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun bind(item: LiveRoomDonationMessage) { fun bind(item: LiveRoomDonationMessage) {
if (item.canMessage.isNotBlank()) {
binding.tvNickname.text = "${item.nickname}님이" binding.tvNickname.text = "${item.nickname}님이"
binding.tvCanMessage.text = item.canMessage 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.tvDonationMessage.text = "\"${item.donationMessage}\""
binding.ivDelete.setOnClickListener { onClickDeleteMessage(item.uuid) } binding.ivDelete.setOnClickListener { onClickDeleteMessage(item.uuid) }

View File

@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.live.roulette
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class GetRouletteResponse( data class GetRouletteResponse(
@SerializedName("id") val id: Long,
@SerializedName("can") val can: Int, @SerializedName("can") val can: Int,
@SerializedName("isActive") val isActive: Boolean, @SerializedName("isActive") val isActive: Boolean,
@SerializedName("items") val items: List<RouletteItem> @SerializedName("items") val items: List<RouletteItem>
@ -10,5 +11,5 @@ data class GetRouletteResponse(
data class RouletteItem( data class RouletteItem(
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("weight") val weight: Int @SerializedName("percentage") val percentage: Float
) )

View File

@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.live.roulette
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class RoulettePreview( data class RoulettePreview(
@SerializedName("id") val id: Long,
@SerializedName("can") val can: Int, @SerializedName("can") val can: Int,
@SerializedName("items") val items: List<RoulettePreviewItem> @SerializedName("items") val items: List<RoulettePreviewItem>
) )

View File

@ -8,27 +8,36 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.MutableLiveData
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.DialogRoulettePreviewBinding import kr.co.vividnext.sodalive.databinding.DialogRoulettePreviewBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat 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 import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
class RoulettePreviewDialog( class RoulettePreviewDialog(
private val activity: FragmentActivity, private val activity: FragmentActivity,
private val preview: RoulettePreview, private val previewList: List<RoulettePreview>,
private val title: String = "", private val title: String = "",
private val onClickSpin: (() -> Unit)? = null, private val onClickSpin: ((Long) -> Unit)? = null,
layoutInflater: LayoutInflater layoutInflater: LayoutInflater
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog
private val dialogView = DialogRoulettePreviewBinding.inflate(layoutInflater) private val dialogView = DialogRoulettePreviewBinding.inflate(layoutInflater)
private val selectedRouletteLiveData = MutableLiveData(
SelectedRoulette.ROULETTE_1
)
init { init {
val dialogBuilder = AlertDialog.Builder(activity) val dialogBuilder = AlertDialog.Builder(activity)
dialogBuilder.setView(dialogView.root) dialogBuilder.setView(dialogView.root)
@ -54,18 +63,189 @@ class RoulettePreviewDialog(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun setupView() { 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 { dialogView.tvSpinRoulette.setOnClickListener {
if (onClickSpin != null) { if (onClickSpin != null) {
onClickSpin!!() onClickSpin!!(roulettePreview.id)
} }
alertDialog.dismiss() 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) { if (onClickSpin != null) {
dialogView.tvCan.visibility = View.VISIBLE dialogView.tvCan.visibility = View.VISIBLE
dialogView.tvCan.text = SharedPreferenceManager.can.moneyFormat() dialogView.tvCan.text = SharedPreferenceManager.can.moneyFormat()
@ -80,7 +260,8 @@ class RoulettePreviewDialog(
dialogView.tvCan.visibility = View.GONE dialogView.tvCan.visibility = View.GONE
} }
preview.items.forEachIndexed { index, item -> dialogView.llRouletteOptionContainer.removeAllViews()
roulettePreview.items.forEachIndexed { index, item ->
dialogView.llRouletteOptionContainer.addView(createOptionView(index, item)) dialogView.llRouletteOptionContainer.addView(createOptionView(index, item))
} }
} }

View File

@ -74,12 +74,10 @@ class RouletteView @JvmOverloads constructor(
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
super.onDraw(canvas) super.onDraw(canvas)
val totalWeight = items.asSequence().map { it.weight }.sum()
var startAngle = -90f var startAngle = -90f
val shuffledColors = colors.shuffled() val shuffledColors = colors.shuffled()
items.forEachIndexed { index, (option, weight) -> items.forEachIndexed { index, (option, percentage) ->
val sweepAngle = (weight / totalWeight.toFloat()) * 360f val sweepAngle = (percentage / 100) * 360f
fillPaint.color = shuffledColors[index] fillPaint.color = shuffledColors[index]
canvas.drawArc(rect, startAngle, sweepAngle, true, fillPaint) canvas.drawArc(rect, startAngle, sweepAngle, true, fillPaint)
@ -117,11 +115,10 @@ class RouletteView @JvmOverloads constructor(
} }
private fun getAngleForOption(option: String): Float { private fun getAngleForOption(option: String): Float {
val totalWeight = items.asSequence().map { it.weight }.sum()
var startAngle = 0f var startAngle = 0f
items.forEach { (currentOption, weight) -> items.forEach { (currentOption, percentage) ->
val sweepAngle = (weight / totalWeight.toFloat()) * 360f val sweepAngle = (percentage / 100) * 360f
if (currentOption == option) { if (currentOption == option) {
// Return the midpoint angle of the segment // Return the midpoint angle of the segment
return (startAngle + sweepAngle / 2) return (startAngle + sweepAngle / 2)

View File

@ -4,5 +4,6 @@ import com.google.gson.annotations.SerializedName
data class SpinRouletteRequest( data class SpinRouletteRequest(
@SerializedName("roomId") val roomId: Long, @SerializedName("roomId") val roomId: Long,
@SerializedName("rouletteId") val rouletteId: Long,
@SerializedName("container") val container: String = "aos" @SerializedName("container") val container: String = "aos"
) )

View File

@ -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<RouletteItem>
)

View File

@ -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.GetNewRouletteResponse
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 kr.co.vividnext.sodalive.live.roulette.SpinRouletteRequest
import kr.co.vividnext.sodalive.live.roulette.SpinRouletteResponse
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
@ -14,37 +15,37 @@ import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
interface RouletteApi { interface RouletteApi {
@POST("/new-roulette") @POST("/v2/roulette")
fun createRoulette( fun createRoulette(
@Body request: CreateRouletteRequest, @Body request: CreateRouletteRequest,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<Any>> ): Single<ApiResponse<Any>>
@PUT("/new-roulette") @PUT("/v2/roulette")
fun updateRoulette( fun updateRoulette(
@Body request: UpdateRouletteRequest, @Body request: UpdateRouletteRequest,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<Any>> ): Single<ApiResponse<Any>>
@GET("/new-roulette/creator") @GET("/v2/roulette/creator")
fun getAllRoulette( fun getAllRoulette(
@Query("creatorId") creatorId: Long, @Query("creatorId") creatorId: Long,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<List<GetNewRouletteResponse>>> ): Single<ApiResponse<List<GetNewRouletteResponse>>>
@GET("/new-roulette") @GET("/v2/roulette")
fun getRoulette( fun getRoulette(
@Query("creatorId") creatorId: Long, @Query("creatorId") creatorId: Long,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<GetRouletteResponse>> ): Single<ApiResponse<List<GetRouletteResponse>>>
@POST("/new-roulette/spin") @POST("/v2/roulette/spin")
fun spinRoulette( fun spinRoulette(
@Body request: SpinRouletteRequest, @Body request: SpinRouletteRequest,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<GetRouletteResponse>> ): Single<ApiResponse<SpinRouletteResponse>>
@POST("/new-roulette/refund/{id}") @POST("/v2/roulette/refund/{id}")
fun refundRouletteDonation( fun refundRouletteDonation(
@Path("id") id: Long, @Path("id") id: Long,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String

View File

@ -1,3 +1,3 @@
package kr.co.vividnext.sodalive.live.roulette.config 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 = "")

View File

@ -156,7 +156,7 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
viewModel.roulettePreviewLiveData.observe(viewLifecycleOwner) { viewModel.roulettePreviewLiveData.observe(viewLifecycleOwner) {
RoulettePreviewDialog( RoulettePreviewDialog(
activity = requireActivity(), activity = requireActivity(),
preview = it, previewList = listOf(it),
title = "룰렛 미리보기", title = "룰렛 미리보기",
layoutInflater = layoutInflater layoutInflater = layoutInflater
).show() ).show()
@ -176,7 +176,7 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
} }
private fun addOption() { private fun addOption() {
val newOption = RouletteOption("", 1) val newOption = RouletteOption("", "")
viewModel.addOption(newOption) viewModel.addOption(newOption)
} }
@ -199,16 +199,17 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
val etOption = optionView.findViewById<EditText>(R.id.et_option) val etOption = optionView.findViewById<EditText>(R.id.et_option)
val tvOptionTitle = optionView.findViewById<TextView>(R.id.tv_option_title) val tvOptionTitle = optionView.findViewById<TextView>(R.id.tv_option_title)
val tvPercentage = optionView.findViewById<TextView>(R.id.tv_option_percentage) val etPercentage = optionView.findViewById<EditText>(R.id.et_option_percentage)
val ivMinus = optionView.findViewById<ImageView>(R.id.iv_minus)
val ivPlus = optionView.findViewById<ImageView>(R.id.iv_plus)
val tvDelete = optionView.findViewById<TextView>(R.id.tv_delete) val tvDelete = optionView.findViewById<TextView>(R.id.tv_delete)
etOption.setText(option.title) etOption.setText(option.title)
tvOptionTitle.text = "옵션 ${index + 1}" tvOptionTitle.text = "옵션 ${index + 1}"
tvPercentage.text = "${option.percentage}%"
ivMinus.setOnClickListener { viewModel.subtractWeight(index) } if (option.percentage.toFloat() > 0f) {
ivPlus.setOnClickListener { viewModel.plusWeight(index) } etPercentage.setText(option.percentage)
} else {
etPercentage.setText("")
}
if (index == 0 || index == 1) { if (index == 0 || index == 1) {
tvDelete.visibility = View.GONE tvDelete.visibility = View.GONE
@ -227,6 +228,16 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
} }
) )
compositeDisposable.add(
etPercentage.textChanges().skip(1)
.debounce(100, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe {
viewModel.inputOptionPercentage(index, it.toString())
}
)
return optionView return optionView
} }

View File

@ -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.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 kotlin.math.floor
class RouletteSettingsViewModel(private val repository: RouletteRepository) : BaseViewModel() { class RouletteSettingsViewModel(private val repository: RouletteRepository) : BaseViewModel() {
@ -55,31 +54,16 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
val rouletteList = mutableListOf<GetNewRouletteResponse>() val rouletteList = mutableListOf<GetNewRouletteResponse>()
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) { fun addOption(newOption: RouletteOption) {
if (options.size >= 10) return if (options.size >= 10) return
options.add(newOption) options.add(newOption)
recalculatePercentages(options) _optionsLiveData.value = options
} }
fun deleteOption(index: Int) { fun deleteOption(index: Int) {
val updatedOptions = options.filterIndexed { currentIndex, _ -> currentIndex != index } val updatedOptions = options.filterIndexed { currentIndex, _ -> currentIndex != index }
removeAllAndAddOptions(updatedOptions) removeAllAndAddOptions(updatedOptions)
recalculatePercentages(updatedOptions) _optionsLiveData.value = updatedOptions
} }
fun inputOption(optionIndex: Int, title: String) { fun inputOption(optionIndex: Int, title: String) {
@ -87,15 +71,9 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
options[optionIndex] = currentOption.copy(title = title) options[optionIndex] = currentOption.copy(title = title)
} }
private fun recalculatePercentages(options: List<RouletteOption>) { fun inputOptionPercentage(optionIndex: Int, percentage: String) {
val totalWeight = options.sumOf { it.weight } val currentOption = options[optionIndex]
val updatedOptions = options.asSequence().map { option -> options[optionIndex] = currentOption.copy(percentage = percentage)
val percent = floor(option.weight.toDouble() / totalWeight * 10000) / 100
option.copy(percentage = String.format("%.2f", percent))
}.toList()
removeAllAndAddOptions(updatedOptions)
_optionsLiveData.value = updatedOptions
} }
fun toggleIsActive() { fun toggleIsActive() {
@ -107,6 +85,7 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
_isLoading.value = true _isLoading.value = true
val items = mutableListOf<RoulettePreviewItem>() val items = mutableListOf<RoulettePreviewItem>()
if (validationOptions()) {
for (option in options) { for (option in options) {
if (option.title.trim().isEmpty()) { if (option.title.trim().isEmpty()) {
_toastLiveData.value = "옵션은 빈칸을 할 수 없습니다." _toastLiveData.value = "옵션은 빈칸을 할 수 없습니다."
@ -117,7 +96,9 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
items.add(RoulettePreviewItem(option.title, "${option.percentage}%")) items.add(RoulettePreviewItem(option.title, "${option.percentage}%"))
} }
_roulettePreviewLiveData.postValue(RoulettePreview(can, items)) _roulettePreviewLiveData.postValue(RoulettePreview(0, can, items))
}
_isLoading.value = false _isLoading.value = false
} }
@ -125,6 +106,7 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
if (!_isLoading.value!!) { if (!_isLoading.value!!) {
_isLoading.value = true _isLoading.value = true
if (validationOptions()) {
if (rouletteId > 0) { if (rouletteId > 0) {
updateRoulette(onSuccess) updateRoulette(onSuccess)
} else { } else {
@ -132,17 +114,33 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
} }
} }
} }
}
private fun updateRoulette(onSuccess: (Boolean) -> Unit) { private fun validationOptions(): Boolean {
val items = mutableListOf<RouletteItem>() var totalPercentage = 0f
for (option in options) { for (option in options) {
if (option.title.trim().isEmpty()) { if (option.title.trim().isEmpty()) {
_toastLiveData.value = "옵션은 빈칸을 할 수 없습니다." _toastLiveData.value = "옵션은 빈칸을 할 수 없습니다."
_isLoading.value = false _isLoading.value = false
return return false
} }
items.add(RouletteItem(title = option.title, weight = option.weight)) 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<RouletteItem>()
for (option in options) {
items.add(RouletteItem(title = option.title, percentage = option.percentage.toFloat()))
} }
val selectedRoulette = rouletteList[_selectedRouletteLiveData.value!!.ordinal] val selectedRoulette = rouletteList[_selectedRouletteLiveData.value!!.ordinal]
@ -169,24 +167,10 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
SelectedRoulette.ROULETTE_3 -> "룰렛 3" SelectedRoulette.ROULETTE_3 -> "룰렛 3"
} }
var isAllActive = false
rouletteList
.filter {
it.id != selectedRoulette.id
}
.forEach {
if (it.isActive) {
isAllActive = true
}
}
val successMessage = if (isActive) { val successMessage = if (isActive) {
"${selectedRouletteTitle}로 설정하였습니다." "${selectedRouletteTitle}을 활성화 했습니다."
} else if (!isAllActive) {
"${selectedRouletteTitle}이 비활성화 되었습니다."
} else { } else {
"${selectedRouletteTitle}설정했습니다." "${selectedRouletteTitle}을 비활성화 했습니다."
} }
compositeDisposable.add( compositeDisposable.add(
@ -224,13 +208,7 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
private fun createRoulette(onSuccess: (Boolean) -> Unit) { private fun createRoulette(onSuccess: (Boolean) -> Unit) {
val items = mutableListOf<RouletteItem>() val items = mutableListOf<RouletteItem>()
for (option in options) { for (option in options) {
if (option.title.trim().isEmpty()) { items.add(RouletteItem(title = option.title, percentage = option.percentage.toFloat()))
_toastLiveData.value = "옵션은 빈칸을 할 수 없습니다."
_isLoading.value = false
return
}
items.add(RouletteItem(title = option.title, weight = option.weight))
} }
val request = CreateRouletteRequest( val request = CreateRouletteRequest(
@ -348,10 +326,10 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
isActive = roulette.isActive isActive = roulette.isActive
val options = roulette.items.asSequence().map { item -> val options = roulette.items.asSequence().map { item ->
RouletteOption(title = item.title, weight = item.weight) RouletteOption(title = item.title, percentage = item.percentage.toString())
}.toList() }.toList()
removeAllAndAddOptions(options = options) removeAllAndAddOptions(options = options)
recalculatePercentages(options) _optionsLiveData.value = options
} else { } else {
_canLiveData.value = 0 _canLiveData.value = 0
_isActiveLiveData.value = false _isActiveLiveData.value = false
@ -361,9 +339,9 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
isActive = false isActive = false
options.clear() options.clear()
options.add(RouletteOption(title = "", weight = 1)) options.add(RouletteOption(title = "", percentage = "50.00"))
options.add(RouletteOption(title = "", weight = 1)) options.add(RouletteOption(title = "", percentage = "50.00"))
recalculatePercentages(options) _optionsLiveData.value = options
} }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_ff14d9" />
<corners android:radius="10dp" />
<stroke
android:width="1dp"
android:color="@color/color_ff14d9" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_ffcb14" />
<corners android:radius="10dp" />
<stroke
android:width="1dp"
android:color="@color/color_ffcb14" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent" />
<corners android:radius="10dp" />
<stroke
android:width="1dp"
android:color="@color/color_ff14d9" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent" />
<corners android:radius="10dp" />
<stroke
android:width="1dp"
android:color="@color/color_ffcb14" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_ccc25264" />
<corners android:radius="5.3dp" />
<stroke
android:width="1dp"
android:color="@color/color_ccc25264" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_ff14d9" />
<corners android:radius="6.7dp" />
<stroke
android:width="1dp"
android:color="@color/color_ff14d9" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_ffcb14" />
<corners android:radius="6.7dp" />
<stroke
android:width="1dp"
android:color="@color/color_ffcb14" />
</shape>

View File

@ -19,7 +19,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:fontFamily="@font/gmarket_sans_bold" android:fontFamily="@font/gmarket_sans_bold"
android:text="후원메시지" android:text="후원 히스토리"
android:textColor="@color/color_eeeeee" android:textColor="@color/color_eeeeee"
android:textSize="14.7sp" android:textSize="14.7sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -55,7 +55,7 @@
android:layout_marginTop="30dp" android:layout_marginTop="30dp"
android:fontFamily="@font/gmarket_sans_medium" android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center" android:gravity="center"
android:text="후원메시지가 없습니다." android:text="후원 히스토리가 없습니다."
android:textColor="@color/color_eeeeee" android:textColor="@color/color_eeeeee"
android:textSize="14.7sp" android:textSize="14.7sp"
android:visibility="gone" /> android:visibility="gone" />

View File

@ -9,6 +9,101 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="13.3dp"> android:padding="13.3dp">
<LinearLayout
android:id="@+id/ll_select_roulette"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="26.7dp"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/ll_select_roulette_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_13181b"
android:gravity="center"
android:paddingVertical="14.3dp">
<ImageView
android:id="@+id/iv_select_roulette_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6.7dp"
android:contentDescription="@null"
android:src="@drawable/ic_select_check"
android:visibility="gone" />
<TextView
android:id="@+id/tv_select_roulette_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:text="룰렛 1"
android:textColor="@color/color_3bb9f1"
android:textSize="14.7sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_select_roulette_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_13181b"
android:gravity="center"
android:paddingVertical="14.3dp">
<ImageView
android:id="@+id/iv_select_roulette_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6.7dp"
android:contentDescription="@null"
android:src="@drawable/ic_select_check_black"
android:visibility="gone" />
<TextView
android:id="@+id/tv_select_roulette_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:text="룰렛 2"
android:textColor="@color/color_ffcb14"
android:textSize="14.7sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_select_roulette_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_13181b"
android:gravity="center"
android:paddingVertical="14.3dp"
android:visibility="gone">
<ImageView
android:id="@+id/iv_select_roulette_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6.7dp"
android:contentDescription="@null"
android:src="@drawable/ic_select_check"
android:visibility="gone" />
<TextView
android:id="@+id/tv_select_roulette_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:text="룰렛 3"
android:textColor="@color/color_ff14d9"
android:textSize="14.7sp" />
</LinearLayout>
</LinearLayout>
<RelativeLayout <RelativeLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -60,33 +60,39 @@
android:theme="@style/EditTextStyle" android:theme="@style/EditTextStyle"
tools:ignore="LabelFor" /> tools:ignore="LabelFor" />
<TextView <LinearLayout
android:id="@+id/tv_option_percentage"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_round_corner_6_7_222222" android:background="@drawable/bg_round_corner_6_7_222222"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center" android:gravity="center"
android:minWidth="60dp"
android:orientation="horizontal"
android:paddingHorizontal="13.3dp" android:paddingHorizontal="13.3dp"
android:paddingVertical="16.7dp" android:paddingVertical="16.7dp">
<EditText
android:id="@+id/et_option_percentage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:fontFamily="@font/gmarket_sans_medium"
android:hint="0.00"
android:importantForAutofill="no"
android:lines="1"
android:maxLength="5"
android:inputType="numberDecimal|numberSigned"
android:text=""
android:textColor="@color/color_eeeeee" android:textColor="@color/color_eeeeee"
android:textSize="14.7sp" android:textColorHint="@color/color_eeeeee"
tools:text="50%" /> android:textSize="14.7sp" />
<ImageView <TextView
android:id="@+id/iv_minus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:fontFamily="@font/gmarket_sans_medium"
android:contentDescription="@null" android:text="%"
android:src="@drawable/btn_minus_round_rect" /> android:textColor="@color/color_eeeeee"
android:textSize="14.7sp" />
<ImageView </LinearLayout>
android:id="@+id/iv_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/btn_plus_round_rect" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -79,6 +79,7 @@
<color name="color_d85e37">#D85E37</color> <color name="color_d85e37">#D85E37</color>
<color name="color_d38c38">#D38C38</color> <color name="color_d38c38">#D38C38</color>
<color name="color_59548f">#59548F</color> <color name="color_59548f">#59548F</color>
<color name="color_ffcb14">#FFCB14</color>
<color name="color_4d6aa4">#4D6AA4</color> <color name="color_4d6aa4">#4D6AA4</color>
<color name="color_2d7390">#2D7390</color> <color name="color_2d7390">#2D7390</color>
<color name="color_548f7d">#548F7D</color> <color name="color_548f7d">#548F7D</color>
@ -118,4 +119,5 @@
<color name="color_002abd">#002ABD</color> <color name="color_002abd">#002ABD</color>
<color name="color_312827">#312827</color> <color name="color_312827">#312827</color>
<color name="color_f1291c">#F1291C</color> <color name="color_f1291c">#F1291C</color>
<color name="color_ff14d9">#FF14D9</color>
</resources> </resources>