Compare commits

..

No commits in common. "b876695adc886b4ae04b8409165a7ebc6f23ed2f" and "e52075a692f14a3009954604ca0d19e3cfed14b9" have entirely different histories.

140 changed files with 1003 additions and 4761 deletions

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

View File

@ -40,8 +40,8 @@ android {
applicationId "kr.co.vividnext.sodalive"
minSdk 23
targetSdk 33
versionCode 74
versionName "1.11.3"
versionCode 46
versionName "1.8.21"
}
buildTypes {

View File

@ -89,7 +89,6 @@
android:name=".mypage.can.charge.CanChargeActivity"
android:configChanges="orientation|screenSize|keyboardHidden" />
<activity android:name=".mypage.can.payment.CanPaymentActivity" />
<activity android:name=".mypage.can.payment.CanPaymentTempActivity" />
<activity android:name=".mypage.can.coupon.CanCouponActivity" />
<activity android:name=".live.room.create.LiveRoomCreateActivity" />
<activity android:name=".live.room.update.LiveRoomEditActivity" />
@ -134,9 +133,6 @@
<activity android:name=".audio_content.all.AudioContentRankingAllActivity" />
<activity android:name=".audio_content.all.by_theme.AudioContentAllByThemeActivity" />
<activity android:name=".live.roulette.config.RouletteConfigActivity" />
<activity android:name=".audio_content.series.SeriesListAllActivity" />
<activity android:name=".audio_content.series.detail.SeriesDetailActivity" />
<activity android:name=".audio_content.series.content.SeriesContentAllActivity" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"

View File

@ -9,7 +9,6 @@ import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaPlayer
import android.os.Build
import android.os.Handler
@ -368,7 +367,6 @@ class AudioContentPlayService :
mediaPlayer.setOnCompletionListener(this)
mediaPlayer.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)

View File

@ -1,7 +1,6 @@
package kr.co.vividnext.sodalive.audio_content.detail
import android.annotation.SuppressLint
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -14,8 +13,6 @@ import android.view.View
import android.widget.RelativeLayout
import android.widget.SeekBar
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -40,16 +37,13 @@ import kr.co.vividnext.sodalive.common.Utils
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentDetailBinding
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
import kr.co.vividnext.sodalive.mypage.auth.Auth
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentTempActivity
import kr.co.vividnext.sodalive.report.ReportType
import org.koin.android.ext.android.inject
import kotlin.math.ceil
class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBinding>(
ActivityAudioContentDetailBinding::inflate
@ -69,10 +63,6 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
private var refresh = false
private var title = ""
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
private lateinit var audioContent: GetAudioContentDetailResponse
private lateinit var orderType: OrderType
@SuppressLint("SetTextI18n")
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
@ -97,14 +87,6 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
finish()
}
activityResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
contentOrder(audioContent, orderType)
}
}
bindData()
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
}
@ -451,11 +433,7 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
}
private fun setupCommentArea(response: GetAudioContentDetailResponse) {
if (
response.isCommentAvailable &&
response.contentUrl.isNotBlank() &&
response.releaseDate == null
) {
if (response.isCommentAvailable) {
binding.llDonation.visibility = View.VISIBLE
binding.llComment.visibility = View.VISIBLE
binding.tvCommentCount.text = "${response.commentCount}"
@ -534,29 +512,12 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
binding.tvReleaseDate.visibility = View.GONE
binding.llPurchase.visibility = View.VISIBLE
binding.llPurchasePrice.visibility = View.VISIBLE
binding.tvPrice.text = response.price.toString()
binding.llPurchase.background = ContextCompat.getDrawable(
applicationContext,
R.drawable.bg_round_corner_5_3_3bb9f1
)
binding.ivCan.visibility = if (SharedPreferenceManager.userId == 17958L) {
View.GONE
} else {
View.VISIBLE
}
binding.tvPrice.text = if (SharedPreferenceManager.userId == 17958L) {
(response.price * 110).moneyFormat()
} else {
response.price.moneyFormat()
}
binding.tvUnit.text = if (SharedPreferenceManager.userId == 17958L) {
"원으로"
} else {
"캔으로"
}
binding.tvStrPurchaseOrRental.text = if (response.isOnlyRental) {
" 대여하기"
} else {
@ -696,54 +657,45 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
binding.tvTag.visibility = View.GONE
}
if (response.contentUrl.isNotBlank() && response.releaseDate == null) {
binding.svActionButtons.visibility = View.VISIBLE
binding.llLike.visibility = View.VISIBLE
binding.ivLike.setImageResource(
if (response.isLike) {
R.drawable.ic_audio_content_heart_pressed
binding.ivLike.setImageResource(
if (response.isLike) {
R.drawable.ic_audio_content_heart_pressed
} else {
R.drawable.ic_audio_content_heart_normal
}
)
binding.tvLike.text = "${response.likeCount}"
binding.llLike.setOnClickListener {
viewModel.likeContent(contentId = audioContentId) {
val likeCount = binding.tvLike.text.toString().toInt()
if (it) {
binding.tvLike.text = "${likeCount + 1}"
binding.ivLike.setImageResource(R.drawable.ic_audio_content_heart_pressed)
} else {
R.drawable.ic_audio_content_heart_normal
}
)
binding.tvLike.text = "${response.likeCount}"
binding.llLike.setOnClickListener {
viewModel.likeContent(contentId = audioContentId) {
val likeCount = binding.tvLike.text.toString().toInt()
if (it) {
binding.tvLike.text = "${likeCount + 1}"
binding.ivLike.setImageResource(R.drawable.ic_audio_content_heart_pressed)
binding.tvLike.text = if (likeCount - 1 < 0) {
"0"
} else {
binding.tvLike.text = if (likeCount - 1 < 0) {
"0"
} else {
"${likeCount - 1}"
}
binding.ivLike.setImageResource(R.drawable.ic_audio_content_heart_normal)
"${likeCount - 1}"
}
binding.ivLike.setImageResource(R.drawable.ic_audio_content_heart_normal)
}
}
}
binding.tvShare.visibility = View.VISIBLE
binding.tvShare.setOnClickListener {
viewModel.shareAudioContent(
audioContentId = audioContentId,
contentImage = response.coverImageUrl,
contentTitle = "${response.title} - ${response.creator.nickname}"
) {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_TEXT, it)
binding.tvShare.setOnClickListener {
viewModel.shareAudioContent(
audioContentId = audioContentId,
contentImage = response.coverImageUrl,
contentTitle = "${response.title} - ${response.creator.nickname}"
) {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_TEXT, it)
val shareIntent = Intent.createChooser(intent, "오디오콘텐츠 공유")
startActivity(shareIntent)
}
val shareIntent = Intent.createChooser(intent, "오디오콘텐츠 공유")
startActivity(shareIntent)
}
} else {
binding.svActionButtons.visibility = View.GONE
binding.llLike.visibility = View.GONE
binding.tvShare.visibility = View.GONE
}
if (response.totalContentCount != null && response.remainingContentCount != null) {
@ -882,43 +834,18 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
binding.rlPreviewAlert.visibility = View.GONE
if (SharedPreferenceManager.userId == 17958L) {
this@AudioContentDetailActivity.audioContent = audioContent
this@AudioContentDetailActivity.orderType = orderType
activityResultLauncher.launch(
Intent(applicationContext, CanPaymentTempActivity::class.java).apply {
putExtra("title", audioContent.title)
putExtra(
"can",
if (orderType == OrderType.RENTAL) {
ceil(audioContent.price * 0.6).toInt()
} else {
audioContent.price
}
)
}
)
} else {
contentOrder(audioContent, orderType)
viewModel.order(
contentId = audioContent.contentId,
orderType = orderType
) {
val intent = Intent(applicationContext, CanChargeActivity::class.java)
intent.putExtra(Constants.EXTRA_GO_TO_PREV_PAGE, true)
startActivity(intent)
}
},
).show(screenWidth)
}
private fun contentOrder(
audioContent: GetAudioContentDetailResponse,
orderType: OrderType
) {
viewModel.order(
contentId = audioContent.contentId,
orderType = orderType
) {
val intent = Intent(applicationContext, CanChargeActivity::class.java)
intent.putExtra(Constants.EXTRA_GO_TO_PREV_PAGE, true)
startActivity(intent)
}
}
inner class AudioContentReceiver : BroadcastReceiver() {
@SuppressLint("SetTextI18n")
override fun onReceive(context: Context?, intent: Intent?) {

View File

@ -1,11 +1,13 @@
package kr.co.vividnext.sodalive.audio_content.main
import android.annotation.SuppressLint
import android.app.Service
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.LinearLayout
import android.widget.Toast
import androidx.core.content.ContextCompat
@ -27,19 +29,18 @@ import kr.co.vividnext.sodalive.audio_content.main.curation.AudioContentMainCura
import kr.co.vividnext.sodalive.audio_content.main.curation.AudioContentMainCurationViewModel
import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentThemeAdapter
import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentViewModel
import kr.co.vividnext.sodalive.audio_content.main.new_content_upload_creator.AudioContentMainNewContentCreatorAdapter
import kr.co.vividnext.sodalive.audio_content.main.new_content_upload_creator.AudioContentMainNewContentCreatorViewModel
import kr.co.vividnext.sodalive.audio_content.main.order.AudioContentMainOrderListViewModel
import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingAdapter
import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingViewModel
import kr.co.vividnext.sodalive.audio_content.main.recommend_series.AudioContentMainRecommendSeriesViewModel
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListActivity
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentMainBinding
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
import kr.co.vividnext.sodalive.explorer.profile.series.UserProfileSeriesListAdapter
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import kr.co.vividnext.sodalive.settings.notification.MemberRole
@ -49,8 +50,8 @@ import kotlin.math.roundToInt
class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
FragmentAudioContentMainBinding::inflate
) {
private val recommendSeriesViewModel: AudioContentMainRecommendSeriesViewModel by inject()
private lateinit var seriesAdapter: UserProfileSeriesListAdapter
private val newContentCreatorViewModel: AudioContentMainNewContentCreatorViewModel by inject()
private lateinit var newContentCreatorAdapter: AudioContentMainNewContentCreatorAdapter
private val bannerViewModel: AudioContentMainBannerViewModel by inject()
private lateinit var bannerAdapter: AudioContentMainBannerAdapter
@ -79,7 +80,7 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
newContentViewModel.getNewContentOfTheme("전체")
contentRankingViewModel.getContentRanking()
contentRankingViewModel.getContentRankingSortType()
recommendSeriesViewModel.getRecommendSeriesList()
newContentCreatorViewModel.getNewContentUploadCreatorList()
}
private fun setupView() {
@ -97,7 +98,7 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
binding.llUploadContent.visibility = View.GONE
}
setupRecommendSeries()
setupNewContentCreator()
setupBanner()
setupOrderList()
setupNewContentTheme()
@ -106,6 +107,17 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
setupContentRanking()
setupCuration()
binding.swipeRefreshLayout.setOnRefreshListener {
binding.swipeRefreshLayout.isRefreshing = false
curationViewModel.refresh()
bannerViewModel.getMainBannerList()
newContentViewModel.getThemeList()
newContentViewModel.getNewContentOfTheme("전체")
contentRankingViewModel.getContentRanking()
contentRankingViewModel.getContentRankingSortType()
newContentCreatorViewModel.getNewContentUploadCreatorList()
}
binding.llShortPlay.setOnClickListener {
startActivity(
Intent(requireContext(), AudioContentAllByThemeActivity::class.java).apply {
@ -123,33 +135,20 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
}
}
private fun setupRecommendSeries() {
seriesAdapter = UserProfileSeriesListAdapter(
onClickItem = {
startActivity(
Intent(requireContext(), SeriesDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, it)
}
)
},
onClickCreator = {
startActivity(
Intent(requireContext(), UserProfileActivity::class.java).apply {
putExtra(Constants.EXTRA_USER_ID, it)
}
)
},
isVisibleCreator = true
)
private fun setupNewContentCreator() {
newContentCreatorAdapter = AudioContentMainNewContentCreatorAdapter {
val intent = Intent(requireContext(), UserProfileActivity::class.java)
intent.putExtra(Constants.EXTRA_USER_ID, it)
startActivity(intent)
}
val recyclerView = binding.rvRecommendSeries
recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
binding.rvNewContentCreator.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
binding.rvNewContentCreator.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
@ -161,28 +160,28 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 6.7f.dpToPx().toInt()
outRect.right = 10.7f.dpToPx().toInt()
}
seriesAdapter.itemCount - 1 -> {
newContentCreatorAdapter.itemCount - 1 -> {
outRect.left = 10.7f.dpToPx().toInt()
outRect.right = 0
outRect.left = 6.7f.dpToPx().toInt()
}
else -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 6.7f.dpToPx().toInt()
outRect.left = 10.7f.dpToPx().toInt()
outRect.right = 10.7f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = seriesAdapter
binding.rvNewContentCreator.adapter = newContentCreatorAdapter
recommendSeriesViewModel.seriesListLiveData.observe(viewLifecycleOwner) {
seriesAdapter.addItems(it)
binding.llRecommendSeries.visibility = if (
seriesAdapter.itemCount <= 0 && it.isEmpty()
newContentCreatorViewModel.newContentUploadCreatorListLiveData.observe(viewLifecycleOwner) {
newContentCreatorAdapter.addItems(it)
binding.rvNewContentCreator.visibility = if (
newContentCreatorAdapter.itemCount <= 0 && it.isEmpty()
) {
View.GONE
} else {
@ -190,14 +189,9 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
}
}
recommendSeriesViewModel.toastLiveData.observe(viewLifecycleOwner) {
newContentCreatorViewModel.toastLiveData.observe(viewLifecycleOwner) {
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
}
binding.llRecommendSeriesRefresh.setOnClickListener {
seriesAdapter.clear()
recommendSeriesViewModel.getRecommendSeriesList()
}
}
private fun setupBanner() {

View File

@ -61,7 +61,6 @@ class AudioContentMainNewContentViewModel(
}
fun getNewContentOfTheme(theme: String) {
_isLoading.value = true
compositeDisposable.add(
repository.getNewContentOfTheme(
theme = if (theme == "전체") {
@ -86,13 +85,10 @@ class AudioContentMainNewContentViewModel(
)
}
}
_isLoading.value = false
},
{
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
_isLoading.value = false
}
)
)

View File

@ -0,0 +1,53 @@
package kr.co.vividnext.sodalive.audio_content.main.new_content_upload_creator
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.main.GetNewContentUploadCreator
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainNewContentCreatorBinding
class AudioContentMainNewContentCreatorAdapter(
private val onClickItem: (Long) -> Unit
) : RecyclerView.Adapter<AudioContentMainNewContentCreatorAdapter.ViewHolder>() {
private val items = mutableListOf<GetNewContentUploadCreator>()
inner class ViewHolder(
private val binding: ItemAudioContentMainNewContentCreatorBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetNewContentUploadCreator) {
binding.tvNewContentCreator.text = item.creatorNickname
binding.ivNewContentCreator.load(item.creatorProfileImageUrl) {
crossfade(true)
placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation())
}
binding.root.setOnClickListener { onClickItem(item.creatorId) }
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
ItemAudioContentMainNewContentCreatorBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun getItemCount() = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
@SuppressLint("NotifyDataSetChanged")
fun addItems(items: List<GetNewContentUploadCreator>) {
this.items.clear()
this.items.addAll(items)
notifyDataSetChanged()
}
}

View File

@ -1,42 +1,44 @@
package kr.co.vividnext.sodalive.audio_content.main.recommend_series
package kr.co.vividnext.sodalive.audio_content.main.new_content_upload_creator
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.audio_content.series.SeriesRepository
import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
import kr.co.vividnext.sodalive.audio_content.main.GetNewContentUploadCreator
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class AudioContentMainRecommendSeriesViewModel(
private val repository: SeriesRepository
class AudioContentMainNewContentCreatorViewModel(
private val repository: AudioContentRepository
) : BaseViewModel() {
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _seriesListLiveData = MutableLiveData<List<GetSeriesListResponse.SeriesListItem>>()
val seriesListLiveData: LiveData<List<GetSeriesListResponse.SeriesListItem>>
get() = _seriesListLiveData
private var _newContentUploadCreatorListLiveData =
MutableLiveData<List<GetNewContentUploadCreator>>()
val newContentUploadCreatorListLiveData: LiveData<List<GetNewContentUploadCreator>>
get() = _newContentUploadCreatorListLiveData
fun getRecommendSeriesList() {
fun getNewContentUploadCreatorList() {
compositeDisposable.add(
repository
.getRecommendSeriesList(token = "Bearer ${SharedPreferenceManager.token}")
repository.getNewContentUploadCreatorList(
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
_seriesListLiveData.value = it.data!!
_newContentUploadCreatorListLiveData.postValue(it.data!!)
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"추천 시리즈를 불러오지 못했습니다. 다시 시도해 주세요.\n" +
"크리에이터 리스트를 불러오지 못했습니다. 다시 시도해 주세요.\n" +
"계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
)
}
@ -45,7 +47,7 @@ class AudioContentMainRecommendSeriesViewModel(
{
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue(
"추천 시리즈를 불러오지 못했습니다. 다시 시도해 주세요.\n" +
"크리에이터 리스트를 불러오지 못했습니다. 다시 시도해 주세요.\n" +
"계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
)
}

View File

@ -173,17 +173,17 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
R.color.color_eeeeee
)
)
binding.llCommentYes.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
binding.llCommentYes.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
binding.ivCommentNo.visibility = View.GONE
binding.tvCommentNo.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_3bb9f1
R.color.color_9970ff
)
)
binding.llCommentNo.setBackgroundResource(
R.drawable.bg_round_corner_6_7_13181b_3bb9f1
R.drawable.bg_round_corner_6_7_1f1734_9970ff
)
} else {
binding.ivCommentNo.visibility = View.VISIBLE
@ -193,17 +193,17 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
R.color.color_eeeeee
)
)
binding.llCommentNo.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
binding.llCommentNo.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
binding.ivCommentYes.visibility = View.GONE
binding.tvCommentYes.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_3bb9f1
R.color.color_9970ff
)
)
binding.llCommentYes
.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b_3bb9f1)
.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734_9970ff)
}
}
@ -223,17 +223,17 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
if (it) {
binding.ivAgeAll.visibility = View.GONE
binding.llAgeAll.setBackgroundResource(
R.drawable.bg_round_corner_6_7_13181b
R.drawable.bg_round_corner_6_7_1f1734
)
binding.tvAgeAll.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_3bb9f1
R.color.color_9970ff
)
)
binding.ivAge19.visibility = View.VISIBLE
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
binding.tvAge19.setTextColor(
ContextCompat.getColor(
applicationContext,
@ -242,17 +242,17 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
)
} else {
binding.ivAge19.visibility = View.GONE
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734)
binding.tvAge19.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_3bb9f1
R.color.color_9970ff
)
)
binding.ivAgeAll.visibility = View.VISIBLE
binding.llAgeAll.setBackgroundResource(
R.drawable.bg_round_corner_6_7_3bb9f1
R.drawable.bg_round_corner_6_7_9970ff
)
binding.tvAgeAll.setTextColor(
ContextCompat.getColor(

View File

@ -4,17 +4,14 @@ import android.app.Activity
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import coil.load
import coil.transform.CircleCropTransformation
import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.DialogAudioContentOrderConfirmBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kotlin.math.ceil
class AudioContentOrderConfirmDialog(
@ -61,35 +58,16 @@ class AudioContentOrderConfirmDialog(
}
dialogView.tvDuration.text = duration
if (SharedPreferenceManager.userId == 17958L) {
dialogView.ivCan.visibility = View.GONE
dialogView.tvPrice.text = if (orderType == OrderType.RENTAL && !isOnlyRental) {
"${(ceil(price * 0.6).toInt() * 110).moneyFormat()}"
} else {
"${(price * 110).moneyFormat()}"
}
dialogView.tvPrice.text = if (orderType == OrderType.RENTAL && !isOnlyRental) {
"${ceil(price * 0.6).toInt()}"
} else {
dialogView.ivCan.visibility = View.VISIBLE
dialogView.tvPrice.text = if (orderType == OrderType.RENTAL && !isOnlyRental) {
ceil(price * 0.6).toInt().moneyFormat()
} else {
price.moneyFormat()
}
"$price"
}
if (SharedPreferenceManager.userId == 17958L) {
dialogView.tvNotice.text = if (orderType == OrderType.RENTAL) {
"콘텐츠를 대여하시겠습니까?"
} else {
"콘텐츠를 소장하시겠습니까?"
}
dialogView.tvNotice.text = if (orderType == OrderType.RENTAL) {
"콘텐츠를 대여하시겠습니까?\n아래 캔이 차감됩니다."
} else {
dialogView.tvNotice.text = if (orderType == OrderType.RENTAL) {
"콘텐츠를 대여하시겠습니까?\n아래 캔이 차감됩니다."
} else {
"콘텐츠를 소장하시겠습니까?\n아래 캔이 차감됩니다."
}
"콘텐츠를 소장하시겠습니까?\n아래 캔이 차감됩니다."
}
dialogView.tvCancel.setOnClickListener {

View File

@ -1,14 +1,11 @@
package kr.co.vividnext.sodalive.audio_content.order
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentOrderBinding
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kotlin.math.ceil
class AudioContentOrderFragment(
@ -29,33 +26,15 @@ class AudioContentOrderFragment(
return binding.root
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (SharedPreferenceManager.userId == 17958L) {
binding.ivKeepCan.visibility = View.GONE
binding.ivRentalCan.visibility = View.GONE
} else {
binding.ivKeepCan.visibility = View.VISIBLE
binding.ivRentalCan.visibility = View.VISIBLE
}
if (isOnlyRental) {
if (SharedPreferenceManager.userId == 17958L) {
binding.tvRental.text = "${(price * 110).moneyFormat()}"
} else {
binding.tvRental.text = price.moneyFormat()
}
binding.tvRental.text = "$price"
binding.rlKeep.visibility = View.GONE
} else {
if (SharedPreferenceManager.userId == 17958L) {
binding.tvKeep.text = "${(price * 110).moneyFormat()}"
binding.tvRental.text = "${(ceil(price * 0.6).toInt() * 110).moneyFormat()}"
} else {
binding.tvKeep.text = price.moneyFormat()
binding.tvRental.text = ceil(price * 0.6).toInt().moneyFormat()
}
binding.tvKeep.text = "$price"
binding.tvRental.text = "${ceil(price * 0.6).toInt()}"
binding.rlKeep.visibility = View.VISIBLE
binding.llKeep.setOnClickListener {

View File

@ -1,26 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series
import com.google.gson.annotations.SerializedName
data class GetSeriesListResponse(
@SerializedName("totalCount") val totalCount: Int,
@SerializedName("items") val items: List<SeriesListItem>
) {
data class SeriesListItem(
@SerializedName("seriesId") val seriesId: Long,
@SerializedName("title") val title: String,
@SerializedName("coverImage") val coverImage: String,
@SerializedName("publishedDaysOfWeek") val publishedDaysOfWeek: String,
@SerializedName("isComplete") val isComplete: Boolean,
@SerializedName("creator") val creator: SeriesListItemCreator,
@SerializedName("numberOfContent") val numberOfContent: Int,
@SerializedName("isNew") val isNew: Boolean,
@SerializedName("isPopular") val isPopular: Boolean
)
data class SeriesListItemCreator(
@SerializedName("creatorId") val creatorId: Long,
@SerializedName("nickname") val nickname: String,
@SerializedName("profileImage") val profileImage: String
)
}

View File

@ -1,40 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesContentListResponse
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesDetailResponse
import kr.co.vividnext.sodalive.common.ApiResponse
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Path
import retrofit2.http.Query
interface SeriesApi {
@GET("/audio-content/series")
fun getSeriesList(
@Query("creatorId") creatorId: Long,
@Query("sortType") sortType: SeriesListAllViewModel.SeriesSortType,
@Query("page") page: Int,
@Query("size") size: Int,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetSeriesListResponse>>
@GET("/audio-content/series/{id}")
fun getSeriesDetail(
@Path("id") seriesId: Long,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetSeriesDetailResponse>>
@GET("/audio-content/series/{id}/content")
fun getSeriesContentList(
@Path("id") seriesId: Long,
@Query("page") page: Int,
@Query("size") size: Int,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetSeriesContentListResponse>>
@GET("/audio-content/series/recommend")
fun getRecommendSeriesList(
@Header("Authorization") authHeader: String
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
}

View File

@ -1,104 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.CircleCropTransformation
import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemSeriesListBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class SeriesListAdapter(
private val itemWidth: Int,
private val onClickItem: (Long) -> Unit,
private val onClickCreator: (Long) -> Unit,
private val isVisibleCreator: Boolean
) : RecyclerView.Adapter<SeriesListAdapter.ViewHolder>() {
val items = mutableListOf<GetSeriesListResponse.SeriesListItem>()
inner class ViewHolder(
private val binding: ItemSeriesListBinding
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(item: GetSeriesListResponse.SeriesListItem) {
val lp = binding.ivCover.layoutParams as ConstraintLayout.LayoutParams
lp.width = itemWidth
lp.height = itemWidth * 432 / 306
binding.ivCover.layoutParams = lp
binding.ivCover.load(item.coverImage) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(5f.dpToPx()))
}
binding.tvTitle.text = item.title
binding.tvSeriesContentCount.text = "${item.numberOfContent}"
binding.tvPublishedDaysOfWeek.text = item.publishedDaysOfWeek
binding.tvNew.visibility = if (item.isNew) {
View.VISIBLE
} else {
View.GONE
}
binding.tvPopular.visibility = if (item.isPopular) {
View.VISIBLE
} else {
View.GONE
}
if (item.isComplete) {
binding.tvNew.visibility = View.GONE
binding.tvComplete.visibility = View.VISIBLE
} else {
binding.tvComplete.visibility = View.GONE
}
if (isVisibleCreator) {
binding.llCreator.visibility = View.VISIBLE
binding.tvCreator.text = item.creator.nickname
binding.ivCreator.load(item.creator.profileImage) {
crossfade(true)
placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation())
}
binding.llCreator.setOnClickListener { onClickCreator(item.creator.creatorId) }
} else {
binding.llCreator.visibility = View.GONE
}
binding.root.setOnClickListener { onClickItem(item.seriesId) }
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
ItemSeriesListBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.count()
@SuppressLint("NotifyDataSetChanged")
fun addItems(items: List<GetSeriesListResponse.SeriesListItem>) {
this.items.addAll(items)
notifyDataSetChanged()
}
fun clear() {
this.items.clear()
}
}

View File

@ -1,118 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.DifferentSpacingItemDecoration
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.ActivitySeriesListAllBinding
import org.koin.android.ext.android.inject
import kotlin.math.roundToInt
class SeriesListAllActivity : BaseActivity<ActivitySeriesListAllBinding>(
ActivitySeriesListAllBinding::inflate
) {
private val viewModel: SeriesListAllViewModel by inject()
private lateinit var loadingDialog: LoadingDialog
private lateinit var seriesAdapter: SeriesListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val creatorId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0)
if (creatorId <= 0) {
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
finish()
}
bindData()
viewModel.creatorId = creatorId
viewModel.getSeriesList()
}
override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater)
binding.toolbar.tvBack.text = "시리즈 전체보기"
binding.toolbar.tvBack.setOnClickListener { finish() }
seriesAdapter = SeriesListAdapter(
itemWidth = ((screenWidth - (13.3 * 3)) / 3).roundToInt(),
onClickItem = {
startActivity(
Intent(applicationContext, SeriesDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, it)
}
)
},
onClickCreator = {},
isVisibleCreator = false
)
val spanCount = 3
val horizontalSpacing = 20
val verticalSpacing = 100
val recyclerView = binding.rvSeriesAll
recyclerView.layoutManager = GridLayoutManager(this, spanCount)
recyclerView.addItemDecoration(
DifferentSpacingItemDecoration(
spanCount = spanCount,
horizontalSpacing = horizontalSpacing,
verticalSpacing = verticalSpacing,
includeEdge = true
)
)
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
.findLastCompletelyVisibleItemPosition()
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
// 스크롤이 끝에 도달했는지 확인
if (!recyclerView.canScrollVertically(1) &&
lastVisibleItemPosition == itemTotalCount
) {
viewModel.getSeriesList()
}
}
})
recyclerView.adapter = seriesAdapter
}
private fun bindData() {
viewModel.toastLiveData.observe(this) {
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
}
viewModel.isLoading.observe(this) {
if (it) {
loadingDialog.show(screenWidth, "")
} else {
loadingDialog.dismiss()
}
}
viewModel.seriesListLiveData.observe(this) {
if (viewModel.page - 1 == 1) {
seriesAdapter.clear()
binding.rvSeriesAll.scrollToPosition(0)
}
seriesAdapter.addItems(it)
}
}
}

View File

@ -1,80 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.gson.annotations.SerializedName
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class SeriesListAllViewModel(private val repository: SeriesRepository) : BaseViewModel() {
enum class SeriesSortType {
@SerializedName("NEWEST") NEWEST,
@SerializedName("POPULAR") POPULAR
}
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
private var _seriesListLiveData = MutableLiveData<List<GetSeriesListResponse.SeriesListItem>>()
val seriesListLiveData: LiveData<List<GetSeriesListResponse.SeriesListItem>>
get() = _seriesListLiveData
var creatorId = 0L
var isLast = false
var page = 1
private val size = 10
fun getSeriesList() {
if (!_isLoading.value!! && !isLast) {
_isLoading.value = true
compositeDisposable.add(
repository.getSeriesList(
creatorId = creatorId,
sortType = SeriesSortType.NEWEST,
page = page,
size = size,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
page += 1
if (it.data.items.isNotEmpty()) {
_seriesListLiveData.value = it.data.items
} else {
isLast = true
}
} else {
if (it.message != null) {
_toastLiveData.value = it.message
} else {
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
}
}
_isLoading.value = false
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}
}

View File

@ -1,36 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series
class SeriesRepository(private val api: SeriesApi) {
fun getSeriesList(
creatorId: Long,
sortType: SeriesListAllViewModel.SeriesSortType,
page: Int,
size: Int,
token: String
) = api.getSeriesList(
creatorId = creatorId,
sortType = sortType,
page = page - 1,
size = size,
authHeader = token
)
fun getSeriesDetail(seriesId: Long, token: String) = api.getSeriesDetail(
seriesId = seriesId,
authHeader = token
)
fun getSeriesContentList(
seriesId: Long,
page: Int,
size: Int,
token: String
) = api.getSeriesContentList(
seriesId = seriesId,
page = page - 1,
size = size,
authHeader = token
)
fun getRecommendSeriesList(token: String) = api.getRecommendSeriesList(authHeader = token)
}

View File

@ -1,77 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.content
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesContentListItem
import kr.co.vividnext.sodalive.databinding.ItemSeriesContentBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class SeriesContentAdapter(
private val onClickItem: (Long) -> Unit
) : RecyclerView.Adapter<SeriesContentAdapter.ViewHolder>() {
val items = mutableListOf<GetSeriesContentListItem>()
inner class ViewHolder(
private val binding: ItemSeriesContentBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetSeriesContentListItem) {
binding.ivCover.load(item.coverImage) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
}
binding.tvTitle.text = item.title
binding.tvDuration.text = item.duration
binding.tvPrice.visibility = View.GONE
binding.tvOwned.visibility = View.GONE
binding.tvRented.visibility = View.GONE
binding.tvPriceFree.visibility = View.GONE
if (item.isOwned) {
binding.tvOwned.visibility = View.VISIBLE
} else if (item.isRented) {
binding.tvRented.visibility = View.VISIBLE
} else if (item.price > 0) {
binding.tvPrice.text = "${item.price}"
binding.tvPrice.visibility = View.VISIBLE
} else {
binding.tvPriceFree.visibility = View.VISIBLE
}
binding.root.setOnClickListener { onClickItem(item.contentId) }
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
ItemSeriesContentBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun getItemCount() = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
@SuppressLint("NotifyDataSetChanged")
fun addItems(items: List<GetSeriesContentListItem>) {
this.items.addAll(items)
notifyDataSetChanged()
}
fun clear() {
this.items.clear()
}
}

View File

@ -1,108 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.content
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.ActivitySeriesContentAllBinding
import org.koin.android.ext.android.inject
class SeriesContentAllActivity : BaseActivity<ActivitySeriesContentAllBinding>(
ActivitySeriesContentAllBinding::inflate
) {
private val viewModel: SeriesContentAllViewModel by inject()
private lateinit var loadingDialog: LoadingDialog
private lateinit var adapter: SeriesContentAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val seriesId = intent.getLongExtra(Constants.EXTRA_SERIES_ID, 0)
if (seriesId <= 0) {
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
finish()
}
bindData()
viewModel.seriesId = seriesId
viewModel.getSeriesContentList()
}
override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater)
val seriesTitle = intent.getStringExtra(Constants.EXTRA_SERIES_TITLE) ?: ""
binding.toolbar.tvBack.text = if (seriesTitle.isNotBlank()) {
"$seriesTitle - 전체회차 듣기"
} else {
" 전체회차 듣기"
}
binding.toolbar.tvBack.setOnClickListener { finish() }
adapter = SeriesContentAdapter {
startActivity(
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
}
)
}
binding.rvSeriesContentAll.layoutManager = LinearLayoutManager(
applicationContext,
LinearLayoutManager.VERTICAL,
false
)
binding.rvSeriesContentAll.addItemDecoration(
DividerItemDecoration(
applicationContext,
DividerItemDecoration.VERTICAL
)
)
binding.rvSeriesContentAll.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
.findLastCompletelyVisibleItemPosition()
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
// 스크롤이 끝에 도달했는지 확인
if (!recyclerView.canScrollVertically(1) &&
lastVisibleItemPosition == itemTotalCount
) {
viewModel.getSeriesContentList()
}
}
})
binding.rvSeriesContentAll.adapter = adapter
}
fun bindData() {
viewModel.toastLiveData.observe(this) {
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
}
viewModel.isLoading.observe(this) {
if (it) {
loadingDialog.show(screenWidth, "")
} else {
loadingDialog.dismiss()
}
}
viewModel.seriesContentListLiveData.observe(this) {
adapter.addItems(it)
}
}
}

View File

@ -1,73 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.content
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.audio_content.series.SeriesRepository
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesContentListItem
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class SeriesContentAllViewModel(private val repository: SeriesRepository) : BaseViewModel() {
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
private var _seriesContentListLiveData = MutableLiveData<List<GetSeriesContentListItem>>()
val seriesContentListLiveData: LiveData<List<GetSeriesContentListItem>>
get() = _seriesContentListLiveData
var seriesId = 0L
var page = 1
private var pageSize = 10
private var isLast = false
fun getSeriesContentList() {
if (!_isLoading.value!! && !isLast) {
_isLoading.value = true
compositeDisposable.add(
repository.getSeriesContentList(
seriesId = seriesId,
page = page,
size = pageSize,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
page += 1
if (it.data.items.isNotEmpty()) {
_seriesContentListLiveData.value = it.data.items
} else {
isLast = true
}
} else {
if (it.message != null) {
_toastLiveData.value = it.message
} else {
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
}
}
_isLoading.value = false
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}
}

View File

@ -1,22 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.detail
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
data class GetSeriesContentListResponse(
@SerializedName("totalCount") val totalCount: Int,
@SerializedName("items") val items: List<GetSeriesContentListItem>
)
@Parcelize
data class GetSeriesContentListItem(
@SerializedName("contentId") val contentId: Long,
@SerializedName("title") val title: String,
@SerializedName("coverImage") val coverImage: String,
@SerializedName("releaseDate") val releaseDate: String,
@SerializedName("duration") val duration: String,
@SerializedName("price") val price: Int,
@SerializedName("isRented") var isRented: Boolean,
@SerializedName("isOwned") var isOwned: Boolean
) : Parcelable

View File

@ -1,36 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.detail
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class GetSeriesDetailResponse(
@SerializedName("seriesId") val seriesId: Long,
@SerializedName("title") val title: String,
@SerializedName("coverImage") val coverImage: String,
@SerializedName("introduction") val introduction: String,
@SerializedName("genre") val genre: String,
@SerializedName("isAdult") val isAdult: Boolean,
@SerializedName("writer") val writer: String?,
@SerializedName("studio") val studio: String?,
@SerializedName("publishedDate") val publishedDate: String,
@SerializedName("creator") val creator: GetSeriesDetailCreator,
@SerializedName("rentalMinPrice") var rentalMinPrice: Int,
@SerializedName("rentalMaxPrice") var rentalMaxPrice: Int,
@SerializedName("rentalPeriod") val rentalPeriod: Int,
@SerializedName("minPrice") var minPrice: Int,
@SerializedName("maxPrice") var maxPrice: Int,
@SerializedName("keywordList") var keywordList: List<String>,
@SerializedName("publishedDaysOfWeek") var publishedDaysOfWeek: String,
@SerializedName("contentList") val contentList: List<GetSeriesContentListItem>,
@SerializedName("contentCount") val contentCount: Int
) : Parcelable {
@Parcelize
data class GetSeriesDetailCreator(
@SerializedName("creatorId") val creatorId: Long,
@SerializedName("nickname") val nickname: String,
@SerializedName("profileImage") val profileImage: String,
@SerializedName("isFollow") var isFollow: Boolean
) : Parcelable
}

View File

@ -1,212 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.detail
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import coil.load
import coil.size.Scale
import coil.transform.BlurTransformation
import coil.transform.CircleCropTransformation
import coil.transform.RoundedCornersTransformation
import com.google.android.material.chip.Chip
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.tabs.TabLayout
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivitySeriesDetailBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject
class SeriesDetailActivity : BaseActivity<ActivitySeriesDetailBinding>(
ActivitySeriesDetailBinding::inflate
) {
private val viewModel: SeriesDetailViewModel by inject()
private lateinit var loadingDialog: LoadingDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val seriesId = intent.getLongExtra(Constants.EXTRA_SERIES_ID, 0)
if (seriesId <= 0) {
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
finish()
}
bindData()
viewModel.seriesId = seriesId
viewModel.getSeriesDetail()
}
override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater)
binding.ivBack.setOnClickListener { finish() }
setupTab()
}
private fun setupTab() {
val tabs = binding.tabs
tabs.addTab(tabs.newTab().setText("").setTag("home"))
tabs.addTab(tabs.newTab().setText("작품소개").setTag("introduction"))
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
val tag = tab.tag as String
changeFragment(tag)
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
}
private fun changeFragment(tag: String) {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = when (tag) {
"introduction" -> SeriesDetailIntroductionFragment()
else -> SeriesDetailHomeFragment()
}
val bundle = Bundle()
bundle.putParcelable(Constants.EXTRA_SERIES, viewModel.seriesDetailResponse)
fragment.arguments = bundle
fragmentTransaction.replace(R.id.container, fragment, tag)
fragmentTransaction.setPrimaryNavigationFragment(fragment)
fragmentTransaction.setReorderingAllowed(true)
fragmentTransaction.commitNow()
}
private fun bindData() {
viewModel.toastLiveData.observe(this) {
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
}
viewModel.isLoading.observe(this) {
if (it) {
loadingDialog.show(screenWidth, "")
} else {
loadingDialog.dismiss()
}
}
viewModel.seriesDetailLiveData.observe(this) {
setSeriesBg(it.coverImage)
setSeriesInfo(it)
setSeriesCreator(it.creator)
setSeriesKeywordChipList(it.keywordList)
changeFragment("home")
}
}
private fun setSeriesKeywordChipList(keywordList: List<String>) {
binding.chipGroup.isSingleLine = true
binding.chipGroup.isHorizontalScrollBarEnabled = false
for (keyword in keywordList) {
val chip = Chip(this)
chip.text = keyword
chip.isClickable = false
chip.isCheckable = false
chip.textSize = 12f
chip.chipStrokeWidth = 0f
chip.setChipBackgroundColorResource(R.color.color_222222)
val shapeAppearanceModel = ShapeAppearanceModel.Builder()
.setAllCornerSizes(26.7f.dpToPx())
.build()
chip.shapeAppearanceModel = shapeAppearanceModel
chip.setPadding(0, 0, 0, 0)
chip.setTextColor(ContextCompat.getColor(applicationContext, R.color.color_d2d2d2))
chip.typeface = ResourcesCompat.getFont(applicationContext, R.font.gmarket_sans_medium)
binding.chipGroup.addView(chip)
}
}
@SuppressLint("SetTextI18n")
private fun setSeriesInfo(seriesDetail: GetSeriesDetailResponse) {
binding.ivCover.load(seriesDetail.coverImage) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(5f.dpToPx()))
}
binding.tvTitle.text = seriesDetail.title
binding.tvGenre.text = seriesDetail.genre
binding.tvPublishedDaysOfWeek.text = "${seriesDetail.publishedDaysOfWeek} 연재"
if (seriesDetail.isAdult) {
binding.tvAge19.visibility = View.VISIBLE
binding.tvAgeAll.visibility = View.GONE
} else {
binding.tvAge19.visibility = View.GONE
binding.tvAgeAll.visibility = View.VISIBLE
}
}
private fun setSeriesCreator(creator: GetSeriesDetailResponse.GetSeriesDetailCreator) {
binding.tvNickname.text = creator.nickname
binding.ivProfile.load(creator.profileImage) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(CircleCropTransformation())
}
if (SharedPreferenceManager.userId != creator.creatorId) {
binding.ivFollow.visibility = View.VISIBLE
binding.ivFollow.setImageResource(
if (creator.isFollow) {
R.drawable.btn_following_big
} else {
R.drawable.btn_follow_big
}
)
} else {
binding.ivFollow.visibility = View.GONE
}
binding.ivFollow.setOnClickListener {
if (creator.isFollow) {
viewModel.unFollow(creator.creatorId) {
creator.isFollow = false
binding.ivFollow.setImageResource(R.drawable.btn_follow_big)
}
} else {
viewModel.follow(creator.creatorId) {
creator.isFollow = true
binding.ivFollow.setImageResource(R.drawable.btn_following_big)
}
}
}
}
private fun setSeriesBg(coverImage: String) {
binding.ivBg.load(coverImage) {
transformations(
BlurTransformation(
this@SeriesDetailActivity,
25f,
2.5f
)
)
scale(Scale.FILL)
}
}
}

View File

@ -1,83 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.detail
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.series.content.SeriesContentAdapter
import kr.co.vividnext.sodalive.audio_content.series.content.SeriesContentAllActivity
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.databinding.FragmentSeriesDetailHomeBinding
class SeriesDetailHomeFragment : BaseFragment<FragmentSeriesDetailHomeBinding>(
FragmentSeriesDetailHomeBinding::inflate
) {
private var seriesDetailResponse: GetSeriesDetailResponse? = null
private lateinit var adapter: SeriesContentAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments != null) {
seriesDetailResponse = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requireArguments().getParcelable(
Constants.EXTRA_SERIES,
GetSeriesDetailResponse::class.java
)
} else {
requireArguments().getParcelable(Constants.EXTRA_SERIES)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (seriesDetailResponse != null) {
setContent()
}
}
@SuppressLint("SetTextI18n")
private fun setContent() {
binding.tvTotalCount.text = "(${seriesDetailResponse!!.contentCount})"
binding.llContentAll.setOnClickListener {
startActivity(
Intent(requireActivity(), SeriesContentAllActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, seriesDetailResponse!!.seriesId)
putExtra(Constants.EXTRA_SERIES_TITLE, seriesDetailResponse!!.title)
}
)
}
adapter = SeriesContentAdapter {
startActivity(
Intent(requireActivity(), AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
}
)
}
binding.rvContent.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.VERTICAL,
false
)
binding.rvContent.addItemDecoration(
DividerItemDecoration(
requireContext(),
DividerItemDecoration.VERTICAL
)
)
binding.rvContent.adapter = adapter
adapter.addItems(seriesDetailResponse!!.contentList)
}
}

View File

@ -1,142 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.detail
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.shape.ShapeAppearanceModel
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.databinding.FragmentSeriesDetailIntroductionBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class SeriesDetailIntroductionFragment : BaseFragment<FragmentSeriesDetailIntroductionBinding>(
FragmentSeriesDetailIntroductionBinding::inflate
) {
private var seriesDetailResponse: GetSeriesDetailResponse? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments != null) {
seriesDetailResponse = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requireArguments().getParcelable(
Constants.EXTRA_SERIES,
GetSeriesDetailResponse::class.java
)
} else {
requireArguments().getParcelable(Constants.EXTRA_SERIES)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (seriesDetailResponse != null) {
setSeriesKeywordChipList(seriesDetailResponse!!.keywordList)
setSeriesIntroduction(seriesDetailResponse!!.introduction)
setSeriesPrice()
setSeriesInfo()
}
}
private fun setSeriesPrice() {
val rentalMinPrice = seriesDetailResponse!!.rentalMinPrice
val rentalMaxPrice = seriesDetailResponse!!.rentalMaxPrice
val minPrice = seriesDetailResponse!!.minPrice
val maxPrice = seriesDetailResponse!!.maxPrice
binding.tvRentalPrice.text = if (rentalMinPrice == rentalMaxPrice) {
if (rentalMaxPrice == 0) {
"무료(15일)"
} else {
"$rentalMaxPrice(15일)"
}
} else {
"${if (rentalMinPrice == 0) "무료" else rentalMinPrice} ~ ${rentalMaxPrice}캔 (15일)"
}
binding.tvPrice.text = if (minPrice == maxPrice) {
if (maxPrice == 0) {
"무료"
} else {
"$maxPrice"
}
} else {
"${if (minPrice == 0) "무료" else minPrice} ~ ${maxPrice}"
}
}
@SuppressLint("SetTextI18n")
private fun setSeriesInfo() {
binding.tvGenre.text = seriesDetailResponse!!.genre
binding.tvIsAdult.text = if (seriesDetailResponse!!.isAdult) {
"19세 이상"
} else {
"전체연령가"
}
binding.tvPublishedDate.text = seriesDetailResponse!!.publishedDate
binding.tvPublishedDaysOfWeek.text =
if (seriesDetailResponse!!.publishedDaysOfWeek == "랜덤") {
seriesDetailResponse!!.publishedDaysOfWeek
} else {
"${seriesDetailResponse!!.publishedDaysOfWeek}요일"
}
if (seriesDetailResponse!!.writer != null) {
binding.tvWriter.visibility = View.VISIBLE
binding.tvWriterLabel.visibility = View.VISIBLE
binding.tvWriter.text = seriesDetailResponse!!.writer
} else {
binding.tvWriter.visibility = View.GONE
binding.tvWriterLabel.visibility = View.GONE
}
if (seriesDetailResponse!!.studio != null) {
binding.tvStudio.visibility = View.VISIBLE
binding.tvStudioLabel.visibility = View.VISIBLE
binding.tvStudio.text = seriesDetailResponse!!.studio
} else {
binding.tvStudio.visibility = View.GONE
binding.tvStudioLabel.visibility = View.GONE
}
}
private fun setSeriesIntroduction(introduction: String) {
binding.tvIntroduce.text = introduction
}
private fun setSeriesKeywordChipList(keywordList: List<String>) {
binding.chipGroup.isHorizontalScrollBarEnabled = false
for (keyword in keywordList) {
val chip = Chip(requireActivity())
chip.text = keyword
chip.isClickable = false
chip.isCheckable = false
chip.textSize = 12f
chip.chipStrokeWidth = 0f
chip.setChipBackgroundColorResource(R.color.color_222222)
val shapeAppearanceModel = ShapeAppearanceModel.Builder()
.setAllCornerSizes(26.7f.dpToPx())
.build()
chip.shapeAppearanceModel = shapeAppearanceModel
chip.setEnsureMinTouchTargetSize(false)
chip.setPadding(0, 0, 0, 0)
chip.setTextColor(ContextCompat.getColor(requireContext(), R.color.color_d2d2d2))
chip.typeface = ResourcesCompat.getFont(requireContext(), R.font.gmarket_sans_medium)
binding.chipGroup.addView(chip)
}
}
}

View File

@ -1,131 +0,0 @@
package kr.co.vividnext.sodalive.audio_content.series.detail
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.audio_content.series.SeriesRepository
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.user.UserRepository
class SeriesDetailViewModel(
private val repository: SeriesRepository,
private val userRepository: UserRepository
) : BaseViewModel() {
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
private var _seriesDetailLiveData = MutableLiveData<GetSeriesDetailResponse>()
val seriesDetailLiveData: LiveData<GetSeriesDetailResponse>
get() = _seriesDetailLiveData
var seriesId = 0L
lateinit var seriesDetailResponse: GetSeriesDetailResponse
fun getSeriesDetail() {
_isLoading.value = true
compositeDisposable.add(
repository.getSeriesDetail(
seriesId = seriesId,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
seriesDetailResponse = it.data
_seriesDetailLiveData.value = seriesDetailResponse
} else {
if (it.message != null) {
_toastLiveData.value = it.message
} else {
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
}
}
_isLoading.value = false
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
fun follow(creatorId: Long, onSuccess: () -> Unit) {
_isLoading.value = true
compositeDisposable.add(
userRepository.creatorFollow(
creatorId,
"Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
onSuccess()
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
}
_isLoading.value = false
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
fun unFollow(creatorId: Long, onSuccess: () -> Unit) {
_isLoading.value = true
compositeDisposable.add(
userRepository.creatorUnFollow(
creatorId,
"Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
onSuccess()
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
}
_isLoading.value = false
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}

View File

@ -20,17 +20,14 @@ object Constants {
const val EXTRA_DATA = "extra_data"
const val EXTRA_TERMS = "extra_terms"
const val EXTRA_EVENT = "extra_event"
const val EXTRA_SERIES = "extra_series"
const val EXTRA_NOTICE = "extra_notice"
const val EXTRA_ROOM_ID = "extra_room_id"
const val EXTRA_USER_ID = "extra_user_id"
const val EXTRA_THEME_ID = "extra_theme_id"
const val EXTRA_SERIES_ID = "extra_series_id"
const val EXTRA_NICKNAME = "extra_nickname"
const val EXTRA_MESSAGE_ID = "extra_message_id"
const val EXTRA_ROOM_DETAIL = "extra_room_detail"
const val EXTRA_MESSAGE_BOX = "extra_message_box"
const val EXTRA_SERIES_TITLE = "extra_series_title"
const val EXTRA_TEXT_MESSAGE = "extra_text_message"
const val EXTRA_LIVE_TIME_NOW = "extra_live_time_now"
const val EXTRA_RESULT_ROULETTE = "extra_result_roulette"

View File

@ -1,39 +0,0 @@
package kr.co.vividnext.sodalive.common
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class DifferentSpacingItemDecoration(
private val spanCount: Int,
private val horizontalSpacing: Int,
private val verticalSpacing: Int,
private val includeEdge: Boolean
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view) // 아이템의 위치
val column = position % spanCount // 아이템의 열 위치
if (includeEdge) {
outRect.left = horizontalSpacing - column * horizontalSpacing / spanCount
outRect.right = (column + 1) * horizontalSpacing / spanCount
if (position < spanCount) {
outRect.top = verticalSpacing
}
outRect.bottom = verticalSpacing
} else {
outRect.left = column * horizontalSpacing / spanCount
outRect.right = horizontalSpacing - (column + 1) * horizontalSpacing / spanCount
if (position >= spanCount) {
outRect.top = verticalSpacing
}
}
}
}

View File

@ -19,16 +19,11 @@ import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailViewModel
import kr.co.vividnext.sodalive.audio_content.main.banner.AudioContentMainBannerViewModel
import kr.co.vividnext.sodalive.audio_content.main.curation.AudioContentMainCurationViewModel
import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentViewModel
import kr.co.vividnext.sodalive.audio_content.main.new_content_upload_creator.AudioContentMainNewContentCreatorViewModel
import kr.co.vividnext.sodalive.audio_content.main.order.AudioContentMainOrderListViewModel
import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingViewModel
import kr.co.vividnext.sodalive.audio_content.main.recommend_series.AudioContentMainRecommendSeriesViewModel
import kr.co.vividnext.sodalive.audio_content.modify.AudioContentModifyViewModel
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListViewModel
import kr.co.vividnext.sodalive.audio_content.series.SeriesApi
import kr.co.vividnext.sodalive.audio_content.series.SeriesListAllViewModel
import kr.co.vividnext.sodalive.audio_content.series.SeriesRepository
import kr.co.vividnext.sodalive.audio_content.series.content.SeriesContentAllViewModel
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailViewModel
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadViewModel
import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeViewModel
import kr.co.vividnext.sodalive.common.ApiBuilder
@ -82,10 +77,7 @@ import kr.co.vividnext.sodalive.mypage.can.CanRepository
import kr.co.vividnext.sodalive.mypage.can.charge.iap.CanChargeIapViewModel
import kr.co.vividnext.sodalive.mypage.can.charge.pg.CanChargePgViewModel
import kr.co.vividnext.sodalive.mypage.can.coupon.CanCouponViewModel
import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentTempRepository
import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentTempViewModel
import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentViewModel
import kr.co.vividnext.sodalive.mypage.can.payment.CanTempApi
import kr.co.vividnext.sodalive.mypage.can.status.CanStatusViewModel
import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateViewModel
import kr.co.vividnext.sodalive.mypage.profile.nickname.NicknameUpdateViewModel
@ -124,7 +116,6 @@ import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
class AppDI(private val context: Context, isDebugMode: Boolean) {
private val baseUrl = BuildConfig.BASE_URL
@ -147,9 +138,6 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
OkHttpClient().newBuilder()
.addInterceptor(logging)
.authenticator(TokenAuthenticator(get()))
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build()
}
@ -163,14 +151,12 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
}
single { ApiBuilder().build(get(), CanApi::class.java) }
single { ApiBuilder().build(get(), CanTempApi::class.java) }
single { ApiBuilder().build(get(), AuthApi::class.java) }
single { ApiBuilder().build(get(), UserApi::class.java) }
single { ApiBuilder().build(get(), MenuApi::class.java) }
single { ApiBuilder().build(get(), LiveApi::class.java) }
single { ApiBuilder().build(get(), TermsApi::class.java) }
single { ApiBuilder().build(get(), EventApi::class.java) }
single { ApiBuilder().build(get(), SeriesApi::class.java) }
single { ApiBuilder().build(get(), ReportApi::class.java) }
single { ApiBuilder().build(get(), LiveRecommendApi::class.java) }
single { ApiBuilder().build(get(), ExplorerApi::class.java) }
@ -195,7 +181,6 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { CanStatusViewModel(get()) }
viewModel { CanChargePgViewModel(get()) }
viewModel { CanPaymentViewModel(get()) }
viewModel { CanPaymentTempViewModel(get()) }
viewModel { LiveRoomDetailViewModel(get()) }
viewModel { LiveRoomCreateViewModel(get()) }
viewModel { LiveTagViewModel(get()) }
@ -210,14 +195,11 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { VoiceMessageViewModel(get()) }
viewModel { VoiceMessageWriteViewModel(get()) }
viewModel { SelectMessageRecipientViewModel(get(), get()) }
viewModel { SignOutViewModel(get()) }
viewModel { NoticeViewModel(get()) }
viewModel { EventViewModel(get()) }
viewModel { NotificationSettingsViewModel(get()) }
viewModel { SettingsViewModel(get()) }
viewModel { SeriesDetailViewModel(get(), get()) }
viewModel { SeriesListAllViewModel(get()) }
viewModel { SeriesContentAllViewModel(get()) }
viewModel { SignOutViewModel(get()) }
viewModel { TextMessageDetailViewModel(get()) }
viewModel { LiveReservationStatusViewModel(get()) }
viewModel { AudioContentMainBannerViewModel(get()) }
@ -225,7 +207,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { AudioContentMainCurationViewModel(get()) }
viewModel { AudioContentMainOrderListViewModel(get()) }
viewModel { AudioContentMainNewContentViewModel(get()) }
viewModel { AudioContentMainRecommendSeriesViewModel(get()) }
viewModel { AudioContentMainNewContentCreatorViewModel(get()) }
viewModel { AudioContentViewModel(get()) }
viewModel { AudioContentOrderListViewModel(get()) }
viewModel { AudioContentUploadViewModel(get()) }
@ -256,13 +238,11 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
private val repositoryModule = module {
factory { UserRepository(get()) }
factory { TermsRepository(get()) }
factory { SeriesRepository(get()) }
factory { LiveRepository(get(), get(), get()) }
factory { EventRepository(get()) }
factory { LiveRecommendRepository(get()) }
factory { AuthRepository(get()) }
factory { CanRepository(get()) }
factory { CanPaymentTempRepository(get()) }
factory { LiveTagRepository(get()) }
factory { ReportRepository(get()) }
factory { ExplorerRepository(get()) }

View File

@ -121,11 +121,7 @@ class ExplorerAdapter(
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
if (item.creators.isNotEmpty()) {
holder.bind(items[position])
}
holder.bind(items[position])
}
override fun getItemCount() = items.size

View File

@ -1,7 +1,6 @@
package kr.co.vividnext.sodalive.explorer.profile
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse
data class GetCreatorProfileResponse(
@ -21,8 +20,6 @@ data class GetCreatorProfileResponse(
val cheers: GetCheersResponse,
@SerializedName("activitySummary")
val activitySummary: GetCreatorActivitySummary,
@SerializedName("seriesList")
val seriesList: List<GetSeriesListResponse.SeriesListItem>,
@SerializedName("isBlock")
val isBlock: Boolean
)

View File

@ -26,10 +26,6 @@ import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.AudioContentActivity
import kr.co.vividnext.sodalive.audio_content.AudioContentAdapter
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.audio_content.series.SeriesListAdapter
import kr.co.vividnext.sodalive.audio_content.series.SeriesListAllActivity
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog
@ -46,7 +42,6 @@ import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAda
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewActivity
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewActivity
import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListActivity
import kr.co.vividnext.sodalive.explorer.profile.series.UserProfileSeriesListAdapter
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.loadUrl
import kr.co.vividnext.sodalive.extensions.moneyFormat
@ -75,7 +70,6 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
private lateinit var loadingDialog: LoadingDialog
private lateinit var liveAdapter: UserProfileLiveAdapter
private lateinit var audioContentAdapter: AudioContentAdapter
private lateinit var seriesAdapter: UserProfileSeriesListAdapter
private lateinit var donationAdapter: UserProfileDonationAdapter
private lateinit var cheersAdapter: UserProfileCheersAdapter
@ -123,7 +117,6 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
setupLiveView()
setupDonationView()
setupFanTalkView()
setupSeriesListView()
setupAudioContentListView()
setupCreatorCommunityView()
}
@ -422,65 +415,6 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
rvCheers.adapter = cheersAdapter
}
private fun setupSeriesListView() {
binding.layoutCreatorChannelSeries.tvAll.setOnClickListener {
startActivity(
Intent(applicationContext, SeriesListAllActivity::class.java).apply {
putExtra(Constants.EXTRA_USER_ID, userId)
}
)
}
val recyclerView = binding.layoutCreatorChannelSeries.rvSeries
recyclerView.layoutManager = LinearLayoutManager(
applicationContext,
LinearLayoutManager.HORIZONTAL,
false
)
seriesAdapter = UserProfileSeriesListAdapter(
onClickItem = {
startActivity(
Intent(applicationContext, SeriesDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, it)
}
)
},
onClickCreator = {},
isVisibleCreator = false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 6.7f.dpToPx().toInt()
}
seriesAdapter.itemCount - 1 -> {
outRect.right = 0
outRect.left = 6.7f.dpToPx().toInt()
}
else -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 6.7f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = seriesAdapter
}
private fun setupAudioContentListView() {
binding.layoutUserProfileAudioContent.tvAll.setOnClickListener {
val intent = Intent(applicationContext, AudioContentActivity::class.java)
@ -583,7 +517,6 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
viewModel.creatorProfileLiveData.observe(this) {
setCheers(it.cheers)
setSeriesList(it.seriesList)
setCreatorProfile(it.creator)
setAudioContentList(it.contentList)
setLiveRoomList(it.liveRoomList)
@ -617,19 +550,6 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
}
}
@SuppressLint("NotifyDataSetChanged")
private fun setSeriesList(seriesList: List<GetSeriesListResponse.SeriesListItem>) {
if (seriesList.isNotEmpty()) {
binding.layoutCreatorChannelSeries.root.visibility = View.VISIBLE
} else {
binding.layoutCreatorChannelSeries.root.visibility = View.GONE
}
seriesAdapter.items.clear()
seriesAdapter.items.addAll(seriesList)
seriesAdapter.notifyDataSetChanged()
}
@SuppressLint("SetTextI18n")
private fun setCreatorProfile(creator: CreatorResponse) {
val layoutUserProfile = binding.layoutUserProfile

View File

@ -1,98 +0,0 @@
package kr.co.vividnext.sodalive.explorer.profile.series
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.CircleCropTransformation
import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.databinding.ItemSeriesListBigBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class UserProfileSeriesListAdapter(
private val onClickItem: (Long) -> Unit,
private val onClickCreator: (Long) -> Unit,
private val isVisibleCreator: Boolean
) : RecyclerView.Adapter<UserProfileSeriesListAdapter.ViewHolder>() {
val items = mutableListOf<GetSeriesListResponse.SeriesListItem>()
inner class ViewHolder(
private val binding: ItemSeriesListBigBinding
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(item: GetSeriesListResponse.SeriesListItem) {
binding.ivCover.load(item.coverImage) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(5f.dpToPx()))
}
binding.tvTitle.text = item.title
binding.tvSeriesContentCount.text = "${item.numberOfContent}"
binding.tvPublishedDaysOfWeek.text = item.publishedDaysOfWeek
binding.tvNew.visibility = if (item.isNew) {
View.VISIBLE
} else {
View.GONE
}
binding.tvPopular.visibility = if (item.isPopular) {
View.VISIBLE
} else {
View.GONE
}
if (item.isComplete) {
binding.tvNew.visibility = View.GONE
binding.tvComplete.visibility = View.VISIBLE
} else {
binding.tvComplete.visibility = View.GONE
}
if (isVisibleCreator) {
binding.llCreator.visibility = View.VISIBLE
binding.tvCreator.text = item.creator.nickname
binding.ivCreator.load(item.creator.profileImage) {
crossfade(true)
placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation())
}
binding.llCreator.setOnClickListener { onClickCreator(item.creator.creatorId) }
} else {
binding.llCreator.visibility = View.GONE
}
binding.root.setOnClickListener { onClickItem(item.seriesId) }
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
ItemSeriesListBigBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.count()
@SuppressLint("NotifyDataSetChanged")
fun addItems(items: List<GetSeriesListResponse.SeriesListItem>) {
this.items.addAll(items)
notifyDataSetChanged()
}
fun clear() {
this.items.clear()
}
}

View File

@ -21,7 +21,6 @@ import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationStatusResp
import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationTotalResponse
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessage
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse
import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse
import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest
import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse
@ -157,11 +156,11 @@ interface LiveApi {
@Header("Authorization") authHeader: String
): Single<ApiResponse<Any>>
@POST("/live/room/donation/v2")
@POST("/live/room/donation")
fun donation(
@Body request: LiveRoomDonationRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<LiveRoomDonationResponse>>
): Single<ApiResponse<String>>
@POST("/live/room/donation/refund/{id}")
fun refundDonation(

View File

@ -129,6 +129,8 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
val intent = Intent(requireContext(), LiveRoomCreateActivity::class.java)
activityResultLauncher.launch(intent)
}
binding.swipeRefreshLayout.setOnRefreshListener { refreshSummary() }
}
private fun refreshSummary() {
@ -138,6 +140,8 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
message = "라이브를 불러오고 있습니다."
viewModel.getSummary()
binding.swipeRefreshLayout.isRefreshing = false
}
@SuppressLint("NotifyDataSetChanged")
@ -180,7 +184,7 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
.setIndicatorVisibility(View.GONE)
.setIndicatorSliderColor(
ContextCompat.getColor(requireContext(), R.color.color_909090),
ContextCompat.getColor(requireContext(), R.color.color_3bb9f1)
ContextCompat.getColor(requireContext(), R.color.color_9970ff)
)
.setIndicatorSliderWidth(4f.dpToPx().toInt(), 10f.dpToPx().toInt())
.setIndicatorHeight(4f.dpToPx().toInt())
@ -283,11 +287,6 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
startActivity(Intent(requireContext(), LiveNowAllActivity::class.java))
}
binding
.layoutLiveNow
.llRefresh
.setOnClickListener { refreshSummary() }
val recyclerView = binding
.layoutLiveNow
.rvSudaNow
@ -487,7 +486,7 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
.setIndicatorVisibility(View.GONE)
.setIndicatorSliderColor(
ContextCompat.getColor(requireContext(), R.color.color_909090),
ContextCompat.getColor(requireContext(), R.color.color_3bb9f1)
ContextCompat.getColor(requireContext(), R.color.color_9970ff)
)
.setIndicatorSliderWidth(4f.dpToPx().toInt(), 10f.dpToPx().toInt())
.setIndicatorHeight(4f.dpToPx().toInt())

View File

@ -14,7 +14,6 @@ import kr.co.vividnext.sodalive.live.room.create.CreateLiveRoomResponse
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse
import kr.co.vividnext.sodalive.live.room.donation.DeleteLiveRoomDonationMessage
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse
import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest
import kr.co.vividnext.sodalive.live.room.menu.MenuApi
import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest
@ -164,7 +163,7 @@ class LiveRepository(
can: Int,
message: String,
token: String
): Single<ApiResponse<LiveRoomDonationResponse>> {
): Single<ApiResponse<String>> {
return api.donation(
request = LiveRoomDonationRequest(
roomId = roomId,

View File

@ -68,7 +68,6 @@ import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageDialog
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageViewModel
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRankingDialog
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse
import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse
import kr.co.vividnext.sodalive.live.room.profile.LiveRoomProfileDialog
import kr.co.vividnext.sodalive.live.room.profile.LiveRoomProfileListAdapter
@ -125,16 +124,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
}
}
private val signatureList = mutableListOf<LiveRoomDonationResponse>()
private var signature: LiveRoomDonationResponse? = null
set(value) {
field = value
if (field != null) {
showSignatureImage()
}
}
private var isShowSignatureImage = false
private val countDownTimer = object : CountDownTimer(remainingNoChattingTime * 1000, 1000) {
@ -479,7 +468,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
binding.tvMenuPan.setOnClickListener { viewModel.toggleShowMenuPan() }
binding.tvBgSwitch.setOnClickListener { viewModel.toggleBackgroundImage() }
binding.tvSignatureSwitch.setOnClickListener { viewModel.toggleSignatureImage() }
binding.llDonation.setOnClickListener {
LiveRoomDonationRankingDialog(
activity = this,
@ -638,31 +626,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
}
}
viewModel.isSignatureOn.observe(this) {
if (it) {
binding.tvSignatureSwitch.text = "시그 ON"
binding.tvSignatureSwitch.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_3bb9f1
)
)
binding.tvSignatureSwitch
.setBackgroundResource(R.drawable.bg_round_corner_5_3_transparent_3bb9f1)
} else {
binding.tvSignatureSwitch.text = "시그 OFF"
binding.tvSignatureSwitch.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_eeeeee
)
)
binding.tvSignatureSwitch
.setBackgroundResource(R.drawable.bg_round_corner_5_3_transparent_bbbbbb)
binding.ivSignature.visibility = View.GONE
}
}
viewModel.isLoading.observe(this) {
if (it) {
loadingDialog.show(screenWidth)
@ -728,7 +691,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
copyMessage = {
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText(it, it))
showToast("후원 히스토리가 복사되었습니다.")
showToast("후원 메시지가 복사되었습니다.")
}
).show()
}
@ -796,6 +759,21 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
binding.ivEdit.visibility = View.GONE
}
binding.ivShare.setOnClickListener {
viewModel.shareRoomLink(
response.roomId,
response.isPrivateRoom,
response.password
) {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_TEXT, it)
val shareIntent = Intent.createChooser(intent, "라이브 공유")
startActivity(shareIntent)
}
}
if (response.creatorId == SharedPreferenceManager.userId) {
binding.llViewUsers.visibility = View.VISIBLE
binding.llViewUsers.setOnClickListener { roomProfileDialog.show() }
@ -950,10 +928,8 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
viewModel.showRoulette {
RoulettePreviewDialog(
activity = this,
previewList = it,
onClickSpin = { rouletteId ->
spinRoulette(rouletteId = rouletteId)
},
preview = it,
onClickSpin = { spinRoulette() },
layoutInflater = layoutInflater
).show()
}
@ -1294,14 +1270,13 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private fun donation(can: Int, message: String) {
val rawMessage = "${can}캔을 후원하셨습니다.\uD83D\uDCB0\uD83E\uDE99"
viewModel.donation(roomId, can, message) { signature ->
viewModel.donation(roomId, can, message) { signatureImage ->
val donationRawMessage = Gson().toJson(
LiveRoomChatRawMessage(
type = LiveRoomChatRawMessageType.DONATION,
message = rawMessage,
can = can,
signature = signature,
signatureImageUrl = signature?.imageUrl,
signatureImageUrl = signatureImage,
donationMessage = message
)
)
@ -1325,7 +1300,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
)
invalidateChat()
viewModel.addDonationCan(can)
addSignature(signature)
addSignatureImage(signatureImage)
}
},
onFailure = {
@ -1335,12 +1310,12 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
}
}
private fun spinRoulette(rouletteId: Long) {
viewModel.spinRoulette(roomId = roomId, rouletteId = rouletteId) { can, items, randomItem ->
private fun spinRoulette() {
viewModel.spinRoulette(roomId = roomId) { can, items, randomlySelectedItem ->
val rouletteRawMessage = Gson().toJson(
LiveRoomChatRawMessage(
type = LiveRoomChatRawMessageType.ROULETTE_DONATION,
message = randomItem,
message = randomlySelectedItem,
can = can,
donationMessage = "",
)
@ -1349,7 +1324,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
RouletteSpinDialog(
activity = this@LiveRoomActivity,
items = items,
selectedItem = randomItem,
selectedItem = randomlySelectedItem,
layoutInflater = layoutInflater
) {
agora.sendRawMessageToGroup(
@ -1360,7 +1335,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
LiveRoomRouletteDonationChat(
profileUrl = SharedPreferenceManager.profileImage,
nickname = SharedPreferenceManager.nickname,
rouletteResult = randomItem
rouletteResult = randomlySelectedItem
)
)
invalidateChat()
@ -1432,12 +1407,9 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
)
invalidateChat()
viewModel.addDonationCan(rawMessage.can)
if (rawMessage.signature != null) {
addSignature(rawMessage.signature)
} else if (rawMessage.signatureImageUrl != null) {
addSignatureImage(rawMessage.signatureImageUrl)
}
addSignatureImage(
imageUrl = rawMessage.signatureImageUrl ?: ""
)
}
}
@ -1790,47 +1762,14 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
}
}
private fun addSignature(signature: LiveRoomDonationResponse?) {
if (signature != null) {
if (!isShowSignatureImage) {
this.signature = signature
isShowSignatureImage = true
} else {
signatureList.add(signature)
}
}
}
private fun showSignatureImage() {
if (signature != null) {
if (viewModel.isSignatureOn.value!!) {
Glide
.with(this)
.load(signature!!.imageUrl)
.into(binding.ivSignature)
if (signatureImageUrl.isNotBlank()) {
Glide
.with(this)
.load(signatureImageUrl)
.into(binding.ivSignature)
binding.ivSignature.visibility = View.VISIBLE
}
handler.postDelayed({
if (signatureList.isNotEmpty()) {
signature = signatureList.removeAt(0)
} else {
signature = null
isShowSignatureImage = false
binding.ivSignature.setImageDrawable(null)
binding.ivSignature.visibility = View.GONE
}
}, signature!!.time * 1000L)
} else if (signatureImageUrl.isNotBlank()) {
if (viewModel.isSignatureOn.value!!) {
Glide
.with(this)
.load(signatureImageUrl)
.into(binding.ivSignature)
binding.ivSignature.visibility = View.VISIBLE
}
binding.ivSignature.visibility = View.VISIBLE
handler.postDelayed({
if (signatureImageUrlList.isNotEmpty()) {
signatureImageUrl = signatureImageUrlList.removeAt(0)

View File

@ -3,6 +3,12 @@ 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
@ -11,7 +17,6 @@ import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.live.LiveRepository
import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationStatusResponse
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse
import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse
import kr.co.vividnext.sodalive.live.room.menu.GetMenuPresetResponse
import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse
@ -30,7 +35,7 @@ import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.util.Locale
import kotlin.math.floor
class LiveRoomViewModel(
private val repository: LiveRepository,
@ -78,10 +83,6 @@ class LiveRoomViewModel(
val isBgOn: LiveData<Boolean>
get() = _isBgOn
private var _isSignatureOn = MutableLiveData(true)
val isSignatureOn: LiveData<Boolean>
get() = _isSignatureOn
lateinit var getRealPathFromURI: (Uri) -> String?
fun getUserNickname(memberId: Int): String {
@ -247,6 +248,43 @@ class LiveRoomViewModel(
)
}
fun shareRoomLink(
roomId: Long,
isPrivateRoom: Boolean,
password: String?,
onSuccess: (String) -> Unit
) {
_isLoading.value = true
Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
link = Uri.parse("https://sodalive.net/?room_id=$roomId")
domainUriPrefix = "https://sodalive.page.link"
androidParameters { }
iosParameters("kr.co.vividnext.sodalive") {
appStoreId = "6461721697"
}
}.addOnSuccessListener {
val uri = it.shortLink
if (uri != null) {
val message = if (isPrivateRoom) {
"${SharedPreferenceManager.nickname}님이 귀하를 " +
"소다라이브의 비공개라이브에 초대하였습니다.\n" +
"※ 라이브 참여: $uri\n" +
"(입장 비밀번호 : $password)"
} else {
"${SharedPreferenceManager.nickname}님이 귀하를 " +
"소다라이브의 공개라이브에 초대하였습니다.\n" +
"※ 라이브 참여: $uri"
}
onSuccess(message)
}
}.addOnFailureListener {
_toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")
}.addOnCompleteListener {
_isLoading.value = false
}
}
fun creatorFollow(creatorId: Long, roomId: Long, isGetUserProfile: Boolean = false) {
_isLoading.value = true
compositeDisposable.add(
@ -340,10 +378,6 @@ class LiveRoomViewModel(
_isBgOn.value = !isBgOn.value!!
}
fun toggleSignatureImage() {
_isSignatureOn.value = !isSignatureOn.value!!
}
fun editLiveRoomInfo(
roomId: Long,
newTitle: String,
@ -474,12 +508,7 @@ class LiveRoomViewModel(
)
}
fun donation(
roomId: Long,
can: Int,
message: String,
onSuccess: (LiveRoomDonationResponse?) -> Unit
) {
fun donation(roomId: Long, can: Int, message: String, onSuccess: (String) -> Unit) {
_isLoading.postValue(true)
compositeDisposable.add(
repository.donation(roomId, can, message, "Bearer ${SharedPreferenceManager.token}")
@ -490,7 +519,7 @@ class LiveRoomViewModel(
_isLoading.value = false
if (it.success) {
SharedPreferenceManager.can -= can
onSuccess(it.data)
onSuccess(it.data ?: "")
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
@ -779,7 +808,7 @@ class LiveRoomViewModel(
)
}
fun showRoulette(complete: (List<RoulettePreview>) -> Unit) {
fun showRoulette(complete: (RoulettePreview) -> Unit) {
if (!_isLoading.value!!) {
_isLoading.value = true
compositeDisposable.add(
@ -796,19 +825,15 @@ class LiveRoomViewModel(
val data = it.data
if (
it.success &&
!data.isNullOrEmpty()
data != null &&
data.isActive &&
data.items.isNotEmpty()
) {
complete(
data
.filter { roulette -> roulette.isActive }
.filter { roulette -> roulette.items.isNotEmpty() }
.map { roulette ->
RoulettePreview(
id = roulette.id,
can = roulette.can,
items = calculatePercentages(roulette.items)
)
}
RoulettePreview(
data.can,
items = calculatePercentages(data.items)
)
)
} else {
val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
@ -825,16 +850,12 @@ class LiveRoomViewModel(
}
}
fun spinRoulette(
roomId: Long,
rouletteId: Long,
complete: (Int, List<RouletteItem>, String) -> Unit
) {
fun spinRoulette(roomId: Long, complete: (Int, List<RouletteItem>, String) -> Unit) {
if (!_isLoading.value!!) {
_isLoading.value = true
compositeDisposable.add(
rouletteRepository.spinRoulette(
request = SpinRouletteRequest(roomId = roomId, rouletteId = rouletteId),
request = SpinRouletteRequest(roomId = roomId),
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
@ -847,10 +868,11 @@ class LiveRoomViewModel(
if (
it.success &&
data != null &&
data.isActive &&
data.items.isNotEmpty()
) {
SharedPreferenceManager.can -= data.can
complete(data.can, data.items, data.result)
randomSelectRouletteItem(data.can, data.items, complete)
} else {
val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
_toastLiveData.postValue(message)
@ -939,11 +961,31 @@ 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> {
val updatedOptions = options.map { option ->
val totalWeight = options.sumOf { it.weight }
val updatedOptions = options.asSequence().map { option ->
val percent = floor(option.weight.toDouble() / totalWeight * 10000) / 100
RoulettePreviewItem(
title = option.title,
percent = "${String.format(Locale.KOREAN, "%.2f", option.percentage)}%"
percent = "${String.format("%.2f", percent)}%"
)
}.toList()

View File

@ -1,13 +1,11 @@
package kr.co.vividnext.sodalive.live.room.chat
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse
data class LiveRoomChatRawMessage(
@SerializedName("type") val type: LiveRoomChatRawMessageType,
@SerializedName("message") val message: String,
@SerializedName("can") val can: Int,
@SerializedName("signature") val signature: LiveRoomDonationResponse? = null,
@SerializedName("signatureImageUrl") val signatureImageUrl: String? = null,
@SerializedName("donationMessage") val donationMessage: String?,
@SerializedName("isActiveRoulette") val isActiveRoulette: Boolean? = null

View File

@ -17,6 +17,5 @@ data class CreateLiveRoomRequest(
@SerializedName("password") val password: String? = null,
@SerializedName("menuPanId") val menuPanId: Long = 0,
@SerializedName("menuPan") val menuPan: String = "",
@SerializedName("isActiveMenuPan") val isActiveMenuPan: Boolean = false,
@SerializedName("isAvailableJoinCreator") val isAvailableJoinCreator: Boolean = true
@SerializedName("isActiveMenuPan") val isActiveMenuPan: Boolean = false
)

View File

@ -322,14 +322,6 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
binding.llSelectMenu3.setOnClickListener {
viewModel.selectMenuPreset(LiveRoomCreateViewModel.SelectedMenu.MENU_3)
}
binding.llAvailableJoinCreatorY.setOnClickListener {
viewModel.setAvailableJoinCreator(true)
}
binding.llAvailableJoinCreatorN.setOnClickListener {
viewModel.setAvailableJoinCreator(false)
}
}
@SuppressLint("SetTextI18n")
@ -655,46 +647,6 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
viewModel.menuLiveData.observe(this) {
binding.etMenu.setText(it)
}
viewModel.isAvailableJoinCreatorLiveData.observe(this) {
if (it) {
binding.ivAvailableJoinCreatorN.visibility = View.GONE
binding.llAvailableJoinCreatorN.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
binding.tvAvailableJoinCreatorN.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_3bb9f1
)
)
binding.ivAvailableJoinCreatorY.visibility = View.VISIBLE
binding.llAvailableJoinCreatorY.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
binding.tvAvailableJoinCreatorY.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_eeeeee
)
)
} else {
binding.ivAvailableJoinCreatorY.visibility = View.GONE
binding.llAvailableJoinCreatorY.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
binding.tvAvailableJoinCreatorY.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_3bb9f1
)
)
binding.ivAvailableJoinCreatorN.visibility = View.VISIBLE
binding.llAvailableJoinCreatorN.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
binding.tvAvailableJoinCreatorN.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.color_eeeeee
)
)
}
}
}
}

View File

@ -71,10 +71,6 @@ class LiveRoomCreateViewModel(
val isActivateMenuLiveData: LiveData<Boolean>
get() = _isActivateMenuLiveData
private val _isAvailableJoinCreatorLiveData = MutableLiveData(true)
val isAvailableJoinCreatorLiveData: LiveData<Boolean>
get() = _isAvailableJoinCreatorLiveData
private val _menuLiveData = MutableLiveData("")
val menuLiveData: LiveData<String>
get() = _menuLiveData
@ -151,8 +147,7 @@ class LiveRoomCreateViewModel(
} else {
""
},
isActiveMenuPan = _isActivateMenuLiveData.value!!,
isAvailableJoinCreator = _isAvailableJoinCreatorLiveData.value!!
isActiveMenuPan = _isActivateMenuLiveData.value!!
)
val requestJson = Gson().toJson(request)
@ -260,10 +255,6 @@ class LiveRoomCreateViewModel(
_isAdultLiveData.value = isAdult
}
fun setAvailableJoinCreator(isAvailableJoinCreator: Boolean) {
_isAvailableJoinCreatorLiveData.value = isAvailableJoinCreator
}
fun getRecentInfo(onSuccess: (GetRecentRoomInfoResponse) -> Unit) {
_isLoading.value = true
compositeDisposable.add(

View File

@ -111,14 +111,6 @@ class LiveRoomDonationDialog(
intent.putExtra(Constants.EXTRA_GO_TO_PREV_PAGE, true)
activity.startActivity(intent)
}
dialogView.tvCharge.setOnClickListener {
bottomSheetDialog.dismiss()
val intent = Intent(activity, CanChargeActivity::class.java)
intent.putExtra(Constants.EXTRA_GO_TO_PREV_PAGE, true)
activity.startActivity(intent)
}
}
@SuppressLint("SetTextI18n")

View File

@ -2,11 +2,9 @@ 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(
@ -22,16 +20,8 @@ class LiveRoomDonationMessageAdapter(
@SuppressLint("SetTextI18n")
fun bind(item: LiveRoomDonationMessage) {
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.tvNickname.text = "${item.nickname}님이"
binding.tvCanMessage.text = item.canMessage
binding.tvDonationMessage.text = "\"${item.donationMessage}\""
binding.ivDelete.setOnClickListener { onClickDeleteMessage(item.uuid) }

View File

@ -1,8 +0,0 @@
package kr.co.vividnext.sodalive.live.room.donation
import com.google.gson.annotations.SerializedName
data class LiveRoomDonationResponse(
@SerializedName("imageUrl") val imageUrl: String,
@SerializedName("time") val time: Int
)

View File

@ -26,7 +26,7 @@ class LiveTagAdapter(
fun bind(item: GetLiveTagResponse) {
if (selectedTags.contains(item.tag)) {
binding.ivTagChecked.visibility = View.VISIBLE
binding.tvTag.setTextColor(ContextCompat.getColor(context, R.color.color_3bb9f1))
binding.tvTag.setTextColor(ContextCompat.getColor(context, R.color.color_9970ff))
isChecked = true
} else {
binding.ivTagChecked.visibility = View.GONE
@ -50,7 +50,7 @@ class LiveTagAdapter(
binding.tvTag.setTextColor(
ContextCompat.getColor(
context,
R.color.color_3bb9f1
R.color.color_9970ff
)
)
} else {

View File

@ -3,7 +3,6 @@ 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<RouletteItem>
@ -11,5 +10,5 @@ data class GetRouletteResponse(
data class RouletteItem(
@SerializedName("title") val title: String,
@SerializedName("percentage") val percentage: Float
@SerializedName("weight") val weight: Int
)

View File

@ -1,14 +1,11 @@
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<RoulettePreviewItem>
val can: Int,
val items: List<RoulettePreviewItem>
)
data class RoulettePreviewItem(
@SerializedName("title") val title: String,
@SerializedName("percent") val percent: String
val title: String,
val percent: String
)

View File

@ -8,36 +8,27 @@ 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 previewList: List<RoulettePreview>,
private val preview: RoulettePreview,
private val title: String = "",
private val onClickSpin: ((Long) -> Unit)? = null,
private val onClickSpin: (() -> 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)
@ -63,189 +54,18 @@ class RoulettePreviewDialog(
@SuppressLint("SetTextI18n")
private fun setupView() {
if (previewList.isEmpty()) {
alertDialog.dismiss()
} else {
initSelectRouletteButton()
setRouletteData(previewList[0])
}
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.text = "${preview.can}캔으로 룰렛 돌리기"
dialogView.tvSpinRoulette.setOnClickListener {
if (onClickSpin != null) {
onClickSpin!!(roulettePreview.id)
onClickSpin!!()
}
alertDialog.dismiss()
}
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"
}
}
dialogView.tvTitle.text = title.ifBlank { "룰렛" }
if (onClickSpin != null) {
dialogView.tvCan.visibility = View.VISIBLE
dialogView.tvCan.text = SharedPreferenceManager.can.moneyFormat()
@ -260,8 +80,7 @@ class RoulettePreviewDialog(
dialogView.tvCan.visibility = View.GONE
}
dialogView.llRouletteOptionContainer.removeAllViews()
roulettePreview.items.forEachIndexed { index, item ->
preview.items.forEachIndexed { index, item ->
dialogView.llRouletteOptionContainer.addView(createOptionView(index, item))
}
}

View File

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

View File

@ -4,6 +4,5 @@ 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"
)

View File

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

View File

@ -1,3 +1,3 @@
package kr.co.vividnext.sodalive.live.roulette.config
data class RouletteOption(var title: String, var percentage: String = "")
data class RouletteOption(var title: String, var weight: Int, var percentage: String = "50.00")

View File

@ -26,7 +26,6 @@ import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.FragmentRouletteSettingsBinding
import kr.co.vividnext.sodalive.live.roulette.RoulettePreviewDialog
import org.koin.android.ext.android.inject
import java.util.Locale
import java.util.concurrent.TimeUnit
class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
@ -104,7 +103,6 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
}
}
@SuppressLint("SetTextI18n")
private fun bindData() {
viewModel.selectedRouletteLiveData.observe(viewLifecycleOwner) {
deselectAllRoulette()
@ -158,22 +156,12 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
viewModel.roulettePreviewLiveData.observe(viewLifecycleOwner) {
RoulettePreviewDialog(
activity = requireActivity(),
previewList = listOf(it),
preview = it,
title = "룰렛 미리보기",
layoutInflater = layoutInflater
).show()
}
viewModel.totalPercentageLiveData.observe(viewLifecycleOwner) {
binding.tvTotalPercentage.text = "( ${
String.format(
Locale.KOREAN,
"%.2f%%",
it
)
} )"
}
compositeDisposable.add(
binding.etSetPrice.textChanges().skip(1)
.debounce(100, TimeUnit.MILLISECONDS)
@ -188,7 +176,7 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
}
private fun addOption() {
val newOption = RouletteOption("", "")
val newOption = RouletteOption("", 1)
viewModel.addOption(newOption)
}
@ -211,21 +199,16 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
val etOption = optionView.findViewById<EditText>(R.id.et_option)
val tvOptionTitle = optionView.findViewById<TextView>(R.id.tv_option_title)
val etPercentage = optionView.findViewById<EditText>(R.id.et_option_percentage)
val tvPercentage = optionView.findViewById<TextView>(R.id.tv_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)
etOption.setText(option.title)
tvOptionTitle.text = "옵션 ${index + 1}"
try {
if (option.percentage.toFloat() > 0f) {
etPercentage.setText(option.percentage)
} else {
etPercentage.setText("")
}
} catch (e: Exception) {
etPercentage.setText("")
}
tvPercentage.text = "${option.percentage}%"
ivMinus.setOnClickListener { viewModel.subtractWeight(index) }
ivPlus.setOnClickListener { viewModel.plusWeight(index) }
if (index == 0 || index == 1) {
tvDelete.visibility = View.GONE
@ -244,16 +227,6 @@ 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
}
@ -275,7 +248,7 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
binding.tvSelectRoulette2.setTextColor(
ContextCompat.getColor(
requireContext(),
R.color.color_ffcb14
R.color.color_3bb9f1
)
)
} else {
@ -283,7 +256,7 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
binding.tvSelectRoulette2.setTextColor(
ContextCompat.getColor(
requireContext(),
R.color.color_ff14d9
R.color.color_555555
)
)
}
@ -313,20 +286,11 @@ class RouletteSettingsFragment : BaseFragment<FragmentRouletteSettingsBinding>(
tvSelectRoulette: TextView
) {
ivSelectRoulette.visibility = View.VISIBLE
llSelectRoulette.setBackgroundResource(
when (viewModel.selectedRouletteLiveData.value) {
RouletteSettingsViewModel.SelectedRoulette.ROULETTE_2 -> R.drawable.bg_round_corner_6_7_ffcb14
RouletteSettingsViewModel.SelectedRoulette.ROULETTE_3 -> R.drawable.bg_round_corner_6_7_ff14d9
else -> R.drawable.bg_round_corner_6_7_3bb9f1
}
)
llSelectRoulette.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
tvSelectRoulette.setTextColor(
ContextCompat.getColor(
requireContext(),
when (viewModel.selectedRouletteLiveData.value) {
RouletteSettingsViewModel.SelectedRoulette.ROULETTE_2 -> R.color.black
else -> R.color.color_eeeeee
}
R.color.color_eeeeee
)
)
}

View File

@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.live.roulette.RouletteItem
import kr.co.vividnext.sodalive.live.roulette.RoulettePreview
import kr.co.vividnext.sodalive.live.roulette.RoulettePreviewItem
import kr.co.vividnext.sodalive.live.roulette.RouletteRepository
import kotlin.math.floor
class RouletteSettingsViewModel(private val repository: RouletteRepository) : BaseViewModel() {
@ -47,10 +48,6 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
val selectedRouletteLiveData: LiveData<SelectedRoulette>
get() = _selectedRouletteLiveData
private val _totalPercentageLiveData = MutableLiveData(0f)
val totalPercentageLiveData: LiveData<Float>
get() = _totalPercentageLiveData
var can = 0
var isActive = false
private var rouletteId = 0L
@ -58,17 +55,31 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
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) {
if (options.size >= 10) return
options.add(newOption)
_optionsLiveData.value = options
calculateTotalPercentage()
recalculatePercentages(options)
}
fun deleteOption(index: Int) {
val updatedOptions = options.filterIndexed { currentIndex, _ -> currentIndex != index }
removeAllAndAddOptions(updatedOptions)
_optionsLiveData.value = updatedOptions
recalculatePercentages(updatedOptions)
}
fun inputOption(optionIndex: Int, title: String) {
@ -76,10 +87,15 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
options[optionIndex] = currentOption.copy(title = title)
}
fun inputOptionPercentage(optionIndex: Int, percentage: String) {
val currentOption = options[optionIndex]
options[optionIndex] = currentOption.copy(percentage = percentage)
calculateTotalPercentage()
private fun recalculatePercentages(options: List<RouletteOption>) {
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 toggleIsActive() {
@ -91,20 +107,17 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
_isLoading.value = true
val items = mutableListOf<RoulettePreviewItem>()
if (validationOptions()) {
for (option in options) {
if (option.title.trim().isEmpty()) {
_toastLiveData.value = "옵션은 빈칸을 할 수 없습니다."
_isLoading.value = false
return
}
items.add(RoulettePreviewItem(option.title, "${option.percentage}%"))
for (option in options) {
if (option.title.trim().isEmpty()) {
_toastLiveData.value = "옵션은 빈칸을 할 수 없습니다."
_isLoading.value = false
return
}
_roulettePreviewLiveData.postValue(RoulettePreview(0, can, items))
items.add(RoulettePreviewItem(option.title, "${option.percentage}%"))
}
_roulettePreviewLiveData.postValue(RoulettePreview(can, items))
_isLoading.value = false
}
@ -112,41 +125,24 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
if (!_isLoading.value!!) {
_isLoading.value = true
if (validationOptions()) {
if (rouletteId > 0) {
updateRoulette(onSuccess)
} else {
createRoulette(onSuccess)
}
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<RouletteItem>()
for (option in options) {
items.add(RouletteItem(title = option.title, percentage = option.percentage.toFloat()))
if (option.title.trim().isEmpty()) {
_toastLiveData.value = "옵션은 빈칸을 할 수 없습니다."
_isLoading.value = false
return
}
items.add(RouletteItem(title = option.title, weight = option.weight))
}
val selectedRoulette = rouletteList[_selectedRouletteLiveData.value!!.ordinal]
@ -173,10 +169,24 @@ 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}을 활성화 했습니다."
"${selectedRouletteTitle}로 설정하였습니다."
} else if (!isAllActive) {
"${selectedRouletteTitle}이 비활성화 되었습니다."
} else {
"${selectedRouletteTitle}을 비활성화 했습니다."
"${selectedRouletteTitle}설정했습니다."
}
compositeDisposable.add(
@ -214,7 +224,13 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
private fun createRoulette(onSuccess: (Boolean) -> Unit) {
val items = mutableListOf<RouletteItem>()
for (option in options) {
items.add(RouletteItem(title = option.title, percentage = option.percentage.toFloat()))
if (option.title.trim().isEmpty()) {
_toastLiveData.value = "옵션은 빈칸을 할 수 없습니다."
_isLoading.value = false
return
}
items.add(RouletteItem(title = option.title, weight = option.weight))
}
val request = CreateRouletteRequest(
@ -332,10 +348,10 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
isActive = roulette.isActive
val options = roulette.items.asSequence().map { item ->
RouletteOption(title = item.title, percentage = item.percentage.toString())
RouletteOption(title = item.title, weight = item.weight)
}.toList()
removeAllAndAddOptions(options = options)
_optionsLiveData.value = options
recalculatePercentages(options)
} else {
_canLiveData.value = 0
_isActiveLiveData.value = false
@ -345,30 +361,15 @@ class RouletteSettingsViewModel(private val repository: RouletteRepository) : Ba
isActive = false
options.clear()
options.add(RouletteOption(title = "", percentage = ""))
options.add(RouletteOption(title = "", percentage = ""))
_optionsLiveData.value = options
options.add(RouletteOption(title = "", weight = 1))
options.add(RouletteOption(title = "", weight = 1))
recalculatePercentages(options)
}
calculateTotalPercentage()
}
}
private fun removeAllAndAddOptions(options: List<RouletteOption>) {
this.options.clear()
this.options.addAll(options)
calculateTotalPercentage()
}
private fun calculateTotalPercentage() {
val totalPercent = options.map {
try {
it.percentage.toFloat()
} catch (e: Exception) {
0f
}
}.sum()
_totalPercentageLiveData.value = totalPercent
}
}

View File

@ -10,6 +10,7 @@ import com.google.android.material.tabs.TabLayout
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivityCanChargeBinding
import kr.co.vividnext.sodalive.mypage.can.charge.iap.CanChargeIapFragment
import kr.co.vividnext.sodalive.mypage.can.charge.pg.CanChargePgFragment
@ -41,10 +42,7 @@ class CanChargeActivity : BaseActivity<ActivityCanChargeBinding>(
)
supportFragmentManager.beginTransaction()
.replace(
R.id.fl_container,
CanChargePgFragment()
).commit()
.replace(R.id.fl_container, CanChargeIapFragment()).commit()
}
override fun setupView() {
@ -58,29 +56,33 @@ class CanChargeActivity : BaseActivity<ActivityCanChargeBinding>(
}
private fun setupTabs() {
val tabs = binding.tabs
if (SharedPreferenceManager.isAuth) {
val tabs = binding.tabs
tabs.visibility = View.VISIBLE
tabs.addTab(tabs.newTab().setText("PG"))
tabs.addTab(tabs.newTab().setText("인 앱 결제"))
tabs.visibility = View.VISIBLE
tabs.addTab(tabs.newTab().setText("인 앱 결제"))
tabs.addTab(tabs.newTab().setText("PG"))
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.position) {
0 -> supportFragmentManager.beginTransaction()
.replace(R.id.fl_container, CanChargePgFragment()).commit()
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.position) {
0 -> supportFragmentManager.beginTransaction()
.replace(R.id.fl_container, CanChargeIapFragment()).commit()
1 -> supportFragmentManager.beginTransaction()
.replace(R.id.fl_container, CanChargeIapFragment()).commit()
1 -> supportFragmentManager.beginTransaction()
.replace(R.id.fl_container, CanChargePgFragment()).commit()
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
} else {
binding.tabs.visibility = View.GONE
}
}
fun selectCan(model: CanResponse) {

View File

@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.mypage.can.charge.iap
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.os.Bundle
import android.os.Handler
@ -41,6 +42,10 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
private lateinit var purchaseUpdateListener: PurchasesUpdatedListener
fun safeContext(): Context? {
return if (isAdded) context else null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -51,9 +56,29 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
setupBillingClient()
}
override fun onDestroy() {
override fun onStart() {
super.onStart()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingServiceDisconnected() {
viewModel.showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.")
viewModel.setLoading(false)
}
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
queryAndConsumeUnconsumedPurchases()
} else {
viewModel.showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.")
viewModel.setLoading(false)
}
}
})
}
override fun onStop() {
super.onStop()
billingClient.endConnection()
super.onDestroy()
}
private fun bindData() {
@ -146,22 +171,6 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
.build()
loadingDialog.show(screenWidth)
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingServiceDisconnected() {
viewModel.showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.")
viewModel.setLoading(false)
}
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
queryAndConsumeUnconsumedPurchases()
} else {
viewModel.showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.")
viewModel.setLoading(false)
}
}
})
}
@SuppressLint("NotifyDataSetChanged")
@ -210,9 +219,7 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
if (purchaseList.isNotEmpty()) {
for (purchase in purchaseList) {
if (!purchase.isAcknowledged) {
consumePurchase(purchase) {
queryAvailableCans()
}
consumePurchase(purchase)
}
}
} else {
@ -222,14 +229,14 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
}
}
private fun consumePurchase(purchase: Purchase, onSuccess: () -> Unit) {
private fun consumePurchase(purchase: Purchase) {
val params = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.consumeAsync(params) { billingResult, _ ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
onSuccess()
queryAvailableCans()
}
}
}

View File

@ -1,237 +0,0 @@
package kr.co.vividnext.sodalive.mypage.can.payment
import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import com.google.gson.Gson
import com.orhanobut.logger.Logger
import kr.co.bootpay.android.Bootpay
import kr.co.bootpay.android.events.BootpayEventListener
import kr.co.bootpay.android.models.BootUser
import kr.co.bootpay.android.models.Payload
import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivityCanPaymentBinding
import kr.co.vividnext.sodalive.extensions.fontSpan
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
import org.koin.android.ext.android.inject
class CanPaymentTempActivity : BaseActivity<ActivityCanPaymentBinding>(
ActivityCanPaymentBinding::inflate
) {
enum class PaymentMethod(val method: String) {
CARD("카드"), BANK("계좌이체"), PHONE("휴대폰")
}
private val viewModel: CanPaymentTempViewModel by inject()
private val handler = Handler(Looper.getMainLooper())
private lateinit var loadingDialog: LoadingDialog
private lateinit var title: String
private var can: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindData()
}
@SuppressLint("SetTextI18n")
override fun setupView() {
title = intent.getStringExtra("title") ?: ""
can = intent.getIntExtra("can", 0)
loadingDialog = LoadingDialog(this, layoutInflater)
if (title.isBlank() || can <= 0) {
Toast.makeText(
applicationContext,
"다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다.",
Toast.LENGTH_LONG
).show()
finish()
}
binding.toolbar.tvBack.text = "결제하기"
binding.toolbar.tvBack.setOnClickListener { finish() }
binding.ivCan.visibility = View.GONE
binding.tvAlert.visibility = View.GONE
binding.tvChargeCanTitle.text = title
binding.tvPrice.text = (can * 110).moneyFormat()
binding.tvPaymentPrice.text = "${(can * 110).moneyFormat()}".fontSpan(
ResourcesCompat.getFont(applicationContext, R.font.gmarket_sans_light),
""
)
binding.tvAgree.setOnClickListener {
binding.tvAgree.isSelected = !binding.tvAgree.isSelected
}
binding.tvPayment.setOnClickListener {
if (viewModel.paymentMethodLiveData.value == null) {
Toast.makeText(
applicationContext,
"결제수단을 선택해 주세요.",
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
if (!binding.tvAgree.isSelected) {
Toast.makeText(
applicationContext,
"결제 진행에 동의하셔야 결제가 가능합니다.",
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
requestCharge()
}
binding.tvMethodCard.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.CARD) }
binding.tvMethodBank.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.BANK) }
binding.tvMethodPhone.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.PHONE) }
}
private fun allPaymentMethodSelectFalse() {
paymentMethodSelectFalse(binding.tvMethodBank)
paymentMethodSelectFalse(binding.tvMethodCard)
paymentMethodSelectFalse(binding.tvMethodPhone)
}
private fun paymentMethodSelectFalse(view: TextView) {
view.typeface = ResourcesCompat.getFont(
applicationContext,
R.font.gmarket_sans_medium
)
view.setTextColor(ContextCompat.getColor(applicationContext, R.color.color_eeeeee))
view.setBackgroundResource(R.drawable.bg_round_corner_10_232323_777777)
}
private fun paymentMethodSelect(view: TextView) {
view.typeface = ResourcesCompat.getFont(
applicationContext,
R.font.gmarket_sans_bold
)
view.setTextColor(ContextCompat.getColor(applicationContext, R.color.color_3bb9f1))
view.setBackgroundResource(R.drawable.bg_round_corner_10_13181b_3bb9f1)
}
private fun requestCharge() {
viewModel.chargeCan(
can = can,
paymentGateway = PaymentGateway.PG,
onSuccess = {
requestPayment(chargeId = it)
},
onFailure = {
Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
}
)
}
private fun requestPayment(chargeId: Long) {
val user = BootUser()
.setId("${SharedPreferenceManager.userId}")
.setUsername(SharedPreferenceManager.nickname)
val payload = Payload()
.setApplicationId(BuildConfig.BOOTPAY_APP_ID)
.setOrderId("$chargeId")
.setOrderName(title)
.setPrice((can * 110).toDouble())
.setTaxFree(0.toDouble())
.setPg("세틀뱅크")
.setMethod(viewModel.paymentMethodLiveData.value!!.method)
.setUser(user)
Bootpay.init(this, this)
.setPayload(payload)
.setEventListener(object : BootpayEventListener {
override fun onCancel(data: String) {
Logger.e("onCancel: $data")
}
override fun onError(data: String) {
Logger.e("onError: $data")
Toast.makeText(applicationContext, data, Toast.LENGTH_LONG).show()
}
override fun onClose() {
Logger.e("onClose")
Bootpay.removePaymentWindow()
}
override fun onIssued(data: String) {
Logger.e("onIssued: $data")
}
override fun onConfirm(data: String): Boolean {
Logger.e("onConfirm: $data")
return true
}
override fun onDone(data: String) {
Logger.e("onDone: $data")
handler.post {
verifyPayment(data)
Bootpay.removePaymentWindow()
}
}
}).requestPayment()
}
private fun verifyPayment(data: String) {
val bootpayResponse = Gson().fromJson(data, BootpayResponse::class.java)
val request = VerifyRequest(bootpayResponse.data.receiptId, bootpayResponse.data.orderId)
viewModel.verify(
request,
onSuccess = {
SharedPreferenceManager.can += can
setResult(RESULT_OK)
finish()
},
onFailure = {
Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
}
)
}
private fun bindData() {
viewModel.paymentMethodLiveData.observe(this) {
allPaymentMethodSelectFalse()
if (it != null) {
when (it) {
PaymentMethod.CARD -> paymentMethodSelect(binding.tvMethodCard)
PaymentMethod.BANK -> paymentMethodSelect(binding.tvMethodBank)
PaymentMethod.PHONE -> paymentMethodSelect(binding.tvMethodPhone)
}
}
}
viewModel.isLoading.observe(this) {
if (it) {
loadingDialog.show(screenWidth, "")
} else {
loadingDialog.dismiss()
}
}
}
}

View File

@ -1,15 +0,0 @@
package kr.co.vividnext.sodalive.mypage.can.payment
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
class CanPaymentTempRepository(private val api: CanTempApi) {
fun chargeCan(
request: ChargeTempRequest,
token: String
) = api.chargeCan(request, authHeader = token)
fun verify(
request: VerifyRequest,
token: String
) = api.verifyCharge(request, authHeader = token)
}

View File

@ -1,88 +0,0 @@
package kr.co.vividnext.sodalive.mypage.can.payment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
class CanPaymentTempViewModel(private val repository: CanPaymentTempRepository) : BaseViewModel() {
private val _paymentMethodLiveData = MutableLiveData<CanPaymentTempActivity.PaymentMethod?>()
val paymentMethodLiveData: LiveData<CanPaymentTempActivity.PaymentMethod?>
get() = _paymentMethodLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
fun chargeCan(
can: Int,
paymentGateway: PaymentGateway,
onSuccess: (Long) -> Unit,
onFailure: (String) -> Unit
) {
_isLoading.value = true
val request = ChargeTempRequest(can, can * 110, paymentGateway)
compositeDisposable.add(
repository.chargeCan(request, "Bearer ${SharedPreferenceManager.token}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success && it.data != null) {
onSuccess(it.data.chargeId)
} else {
if (it.message != null) {
onFailure(it.message)
} else {
onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
fun verify(request: VerifyRequest, onSuccess: () -> Unit, onFailure: (String) -> Unit) {
_isLoading.value = true
compositeDisposable.add(
repository.verify(
request = request,
"Bearer ${SharedPreferenceManager.token}"
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success) {
onSuccess()
} else {
if (it.message != null) {
onFailure(it.message)
} else {
onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
fun setPaymentMethod(paymentMethod: CanPaymentTempActivity.PaymentMethod) {
_paymentMethodLiveData.value = paymentMethod
}
}

View File

@ -1,30 +0,0 @@
package kr.co.vividnext.sodalive.mypage.can.payment
import com.google.gson.annotations.SerializedName
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeResponse
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.POST
interface CanTempApi {
@POST("/charge/temp")
fun chargeCan(
@Body chargeRequest: ChargeTempRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<ChargeResponse>>
@POST("/charge/temp/verify")
fun verifyCharge(
@Body request: VerifyRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<Any>>
}
data class ChargeTempRequest(
@SerializedName("can") val can: Int,
@SerializedName("price") val price: Int,
@SerializedName("paymentGateway") val paymentGateway: PaymentGateway
)

View File

@ -23,7 +23,7 @@ class MemberTagAdapter(
fun bind(item: MemberTagResponse) {
if (selectedTags.contains(item.tag)) {
binding.ivTagChecked.visibility = View.VISIBLE
binding.ivTag.setBackgroundResource(R.drawable.bg_round_corner_30_3bb9f1)
binding.ivTag.setBackgroundResource(R.drawable.bg_round_corner_30_9970ff)
isChecked = true
} else {
binding.ivTagChecked.visibility = View.GONE
@ -44,7 +44,7 @@ class MemberTagAdapter(
if (onItemClick(item.tag, isChecked)) {
if (isChecked) {
binding.ivTagChecked.visibility = View.VISIBLE
binding.ivTag.setBackgroundResource(R.drawable.bg_round_corner_30_3bb9f1)
binding.ivTag.setBackgroundResource(R.drawable.bg_round_corner_30_9970ff)
} else {
binding.ivTagChecked.visibility = View.GONE
binding.ivTag.background = null

View File

@ -34,13 +34,8 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding
super.onCreate(savedInstanceState)
val lp = binding.ivText.layoutParams as ConstraintLayout.LayoutParams
lp.topMargin = screenHeight * 302 / 2337
lp.topMargin = screenHeight * 787 / 2337
binding.ivText.layoutParams = lp
val lp2 = binding.ivText2.layoutParams as ConstraintLayout.LayoutParams
lp2.bottomMargin = screenHeight * 195 / 2337
binding.ivText2.layoutParams = lp2
setupRemoteConfig()
fetchAndroidLatestVersion()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 B

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,8 +0,0 @@
<?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

@ -1,8 +0,0 @@
<?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

@ -4,5 +4,5 @@
<corners android:radius="10dp" />
<stroke
android:width="1dp"
android:color="@color/color_ff14d9" />
android:color="@color/color_9970ff" />
</shape>

View File

@ -1,8 +0,0 @@
<?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

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

View File

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

View File

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

View File

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

View File

@ -1,8 +0,0 @@
<?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="26.7dp" />
<stroke
android:width="1dp"
android:color="@color/color_909090" />
</shape>

View File

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

View File

@ -3,5 +3,5 @@
<corners android:radius="30dp" />
<stroke
android:width="1dp"
android:color="@color/color_3bb9f1" />
android:color="@color/color_9970ff" />
</shape>

View File

@ -1,8 +0,0 @@
<?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

@ -1,8 +0,0 @@
<?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

@ -1,8 +0,0 @@
<?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

@ -4,5 +4,5 @@
<corners android:radius="16.7dp" />
<stroke
android:width="1dp"
android:color="@color/color_3bb9f1" />
android:color="@color/color_9970ff" />
</shape>

View File

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

View File

@ -295,7 +295,6 @@
tools:text="매버릭 팔레트 (feat. J-DRAGON)" />
<ScrollView
android:id="@+id/sv_action_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp">
@ -546,7 +545,6 @@
android:visibility="gone">
<ImageView
android:id="@+id/iv_can"
android:layout_width="16.7dp"
android:layout_height="16.7dp"
android:contentDescription="@null"
@ -563,7 +561,6 @@
tools:text="300" />
<TextView
android:id="@+id/tv_unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_light"

View File

@ -51,7 +51,7 @@
android:layout_width="106.7dp"
android:layout_height="106.7dp"
android:adjustViewBounds="true"
android:background="@drawable/bg_round_corner_13_3_13181b"
android:background="@drawable/bg_round_corner_13_3_3e3358"
android:contentDescription="@null"
android:padding="13.3dp"
android:src="@drawable/ic_logo" />
@ -209,7 +209,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_13181b"
android:background="@drawable/bg_round_corner_6_7_1f1734"
android:gravity="center"
android:paddingVertical="14.3dp">
@ -228,7 +228,7 @@
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:text="전체 연령"
android:textColor="@color/color_3bb9f1"
android:textColor="@color/color_9970ff"
android:textSize="14.7sp" />
</LinearLayout>
@ -238,7 +238,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_13181b"
android:background="@drawable/bg_round_corner_6_7_1f1734"
android:gravity="center"
android:paddingVertical="14.3dp">
@ -257,7 +257,7 @@
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:text="19세 이상"
android:textColor="@color/color_3bb9f1"
android:textColor="@color/color_9970ff"
android:textSize="14.7sp" />
</LinearLayout>
</LinearLayout>
@ -300,7 +300,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_13181b"
android:background="@drawable/bg_round_corner_6_7_1f1734"
android:gravity="center"
android:paddingVertical="14.3dp">
@ -319,7 +319,7 @@
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:text="댓글 가능"
android:textColor="@color/color_3bb9f1"
android:textColor="@color/color_9970ff"
android:textSize="14.7sp" />
</LinearLayout>
@ -329,7 +329,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_13181b"
android:background="@drawable/bg_round_corner_6_7_1f1734"
android:gravity="center"
android:paddingVertical="14.3dp">
@ -348,7 +348,7 @@
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:text="댓글 불가"
android:textColor="@color/color_3bb9f1"
android:textColor="@color/color_9970ff"
android:textSize="14.7sp" />
</LinearLayout>
</LinearLayout>
@ -368,11 +368,11 @@
android:layout_height="50dp"
android:layout_marginEnd="6.7dp"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_13181b_3bb9f1"
android:background="@drawable/bg_round_corner_6_7_1f1734_9970ff"
android:fontFamily="@font/gmarket_sans_bold"
android:gravity="center"
android:text="취소"
android:textColor="@color/color_3bb9f1"
android:textColor="@color/color_9970ff"
android:textSize="18.3sp" />
<TextView
@ -381,7 +381,7 @@
android:layout_height="50dp"
android:layout_marginStart="6.7dp"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_6_7_3bb9f1"
android:background="@drawable/bg_round_corner_6_7_9970ff"
android:fontFamily="@font/gmarket_sans_bold"
android:gravity="center"
android:text="수정"

View File

@ -33,32 +33,29 @@
android:paddingHorizontal="13.3dp"
android:paddingVertical="23.3dp">
<LinearLayout
<ImageView
android:id="@+id/ic_can"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:contentDescription="@null"
android:src="@drawable/ic_can"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_can"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="13.3dp"
android:contentDescription="@null"
android:src="@drawable/ic_can" />
<TextView
android:id="@+id/tv_charge_can_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:gravity="center"
android:textColor="@color/color_eeeeee"
android:textSize="15.3sp"
tools:text="5000 캔 + 1000 캔" />
</LinearLayout>
<TextView
android:id="@+id/tv_charge_can_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:fontFamily="@font/gmarket_sans_bold"
android:gravity="center"
android:textColor="@color/color_eeeeee"
android:textSize="15.3sp"
app:layout_constraintBottom_toBottomOf="@+id/ic_can"
app:layout_constraintStart_toEndOf="@+id/ic_can"
app:layout_constraintTop_toTopOf="@+id/ic_can"
tools:text="5000 캔 + 1000 캔" />
<LinearLayout
android:id="@+id/ll_price"
@ -174,7 +171,6 @@
app:drawableStartCompat="@drawable/ic_select" />
<TextView
android:id="@+id/tv_alert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="26.7dp"

View File

@ -88,7 +88,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="13.3dp"
android:layout_marginTop="60dp"
android:background="@drawable/bg_round_corner_6_7_3bb9f1"
android:background="@drawable/bg_round_corner_6_7_9970ff"
android:fontFamily="@font/gmarket_sans_bold"
android:gravity="center"
android:paddingVertical="16dp"
@ -102,13 +102,13 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="93dp"
android:background="@drawable/bg_round_corner_8_transparent_3bb9f1"
android:background="@drawable/bg_round_corner_8_transparent_9970ff"
android:drawablePadding="13.3dp"
android:paddingHorizontal="18.7dp"
android:paddingVertical="10.7dp"
android:text="고객센터로 문의하기"
android:textColor="@color/color_3bb9f1"
app:drawableStartCompat="@drawable/ic_headphones_blue" />
android:textColor="@color/color_9970ff"
app:drawableStartCompat="@drawable/ic_headphones_purple" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -29,10 +29,9 @@
android:layout_gravity="center"
android:layout_marginTop="28.3dp"
android:fontFamily="@font/gmarket_sans_medium"
android:text="현재 참여 가능한 라이브 방송이 없거나\n연령제한으로 입장이 불가능합니다.\n본인인증을 해보거나 채널을 팔로잉하고\n라이브 방송 알림을 받아보세요."
android:text="🙀현재 참여 가능한 라이브 방송이 없거나\n연령제한으로 입장이 불가능합니다.\n본인인증을 해보거나 채널을 팔로잉하고\n라이브 방송 알림을 받아보세요."
android:textColor="@color/color_bbbbbb"
android:textSize="13sp"
android:lineSpacingExtra="8dp"
android:textSize="15sp"
android:visibility="gone" />
</LinearLayout>

View File

@ -63,10 +63,9 @@
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center"
android:text="지금 예약중인 라이브가 없습니다.\n다른 날짜의 라이브를 예약하고 참여해 보세요."
android:text="지금 예약중인 라이브가 없습니다.\n채널을 팔로잉 하고 라이브 알림을 받아 보세요."
android:textColor="@color/color_bbbbbb"
android:textSize="13sp"
android:lineSpacingExtra="8dp"
android:textSize="10.7sp"
tools:ignore="SmallSp" />
</LinearLayout>
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show More