feat(creator): 커뮤니티 구매와 메뉴 동작을 정리한다
This commit is contained in:
@@ -6,12 +6,4 @@ sealed interface CommunityChange {
|
|||||||
data object Created : CommunityChange
|
data object Created : CommunityChange
|
||||||
|
|
||||||
data object Updated : CommunityChange
|
data object Updated : CommunityChange
|
||||||
|
|
||||||
data class Deleted(
|
|
||||||
val postId: Long
|
|
||||||
) : CommunityChange
|
|
||||||
|
|
||||||
data class PinChanged(
|
|
||||||
val postId: Long
|
|
||||||
) : CommunityChange
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
|||||||
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelBinding
|
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelBinding
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityPostMenuBottomSheetDialog
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityPostMenuBottomSheetDialog
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityReportDialog
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.modify.CreatorCommunityModifyActivity
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.modify.CreatorCommunityModifyActivity
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.modify.ModifyCommunityPostRequest
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.modify.ModifyCommunityPostRequest
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
|
||||||
@@ -60,8 +61,9 @@ import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
|||||||
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragment
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragment
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityMenuItem
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
||||||
@@ -96,6 +98,7 @@ class CreatorChannelActivity :
|
|||||||
|
|
||||||
private val liveViewModel: LiveViewModel by inject()
|
private val liveViewModel: LiveViewModel by inject()
|
||||||
private val creatorCommunityRepository: CreatorCommunityRepository by inject()
|
private val creatorCommunityRepository: CreatorCommunityRepository by inject()
|
||||||
|
private val creatorChannelRepository: CreatorChannelRepository by inject()
|
||||||
private var creatorId: Long = 0L
|
private var creatorId: Long = 0L
|
||||||
private var currentHeader: CreatorChannelHeaderUiModel? = null
|
private var currentHeader: CreatorChannelHeaderUiModel? = null
|
||||||
private var homeActionDelegate: CreatorChannelHomeFragment.HomeActionDelegate? = null
|
private var homeActionDelegate: CreatorChannelHomeFragment.HomeActionDelegate? = null
|
||||||
@@ -107,6 +110,7 @@ class CreatorChannelActivity :
|
|||||||
private var isOwnerFabAnimating: Boolean = false
|
private var isOwnerFabAnimating: Boolean = false
|
||||||
private var isDonationFloatingButtonVisible: Boolean = false
|
private var isDonationFloatingButtonVisible: Boolean = false
|
||||||
private var isFanTalkFixedPlusVisible: Boolean = false
|
private var isFanTalkFixedPlusVisible: Boolean = false
|
||||||
|
private var isCommunityPostMutating: Boolean = false
|
||||||
private lateinit var loadingDialog: LoadingDialog
|
private lateinit var loadingDialog: LoadingDialog
|
||||||
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
|
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
|
||||||
private val liveActionCoordinator: LiveActionCoordinator by lazy {
|
private val liveActionCoordinator: LiveActionCoordinator by lazy {
|
||||||
@@ -126,13 +130,6 @@ class CreatorChannelActivity :
|
|||||||
resolveCommunityActivityResult(CommunityActivityResultSource.Write, result.resultCode)
|
resolveCommunityActivityResult(CommunityActivityResultSource.Write, result.resultCode)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private val communityPostModifyLauncher = registerForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult()
|
|
||||||
) { result ->
|
|
||||||
handleCommunityChange(
|
|
||||||
resolveCommunityActivityResult(CommunityActivityResultSource.Modify, result.resultCode)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
private val communityDetailLauncher = registerForActivityResult(
|
private val communityDetailLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) { result ->
|
) { result ->
|
||||||
@@ -140,6 +137,13 @@ class CreatorChannelActivity :
|
|||||||
resolveCommunityActivityResult(CommunityActivityResultSource.Detail, result.resultCode)
|
resolveCommunityActivityResult(CommunityActivityResultSource.Detail, result.resultCode)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
private val communityModifyLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
handleCommunityChange(
|
||||||
|
resolveCommunityActivityResult(CommunityActivityResultSource.Modify, result.resultCode)
|
||||||
|
)
|
||||||
|
}
|
||||||
private val fanTalkWriteLauncher = registerForActivityResult(
|
private val fanTalkWriteLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) { result ->
|
) { result ->
|
||||||
@@ -627,6 +631,142 @@ class CreatorChannelActivity :
|
|||||||
handleCommunityAction(CommunityActionCommand.PostDetail(postId), communityDetailLauncher::launch)
|
handleCommunityAction(CommunityActionCommand.PostDetail(postId), communityDetailLauncher::launch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem) {
|
||||||
|
if (!item.showMore) return
|
||||||
|
|
||||||
|
CreatorCommunityPostMenuBottomSheetDialog(
|
||||||
|
isFixed = item.isPinned,
|
||||||
|
isCreator = item.isOwner,
|
||||||
|
onClickPin = { updateCreatorChannelCommunityPostFixed(item) },
|
||||||
|
onClickModify = {
|
||||||
|
if (item.isOwner) {
|
||||||
|
communityModifyLauncher.launch(
|
||||||
|
Intent(this, CreatorCommunityModifyActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_COMMUNITY_POST_ID, item.postId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClickDelete = { showDeleteCreatorChannelCommunityPostDialog(item) },
|
||||||
|
onClickReport = {
|
||||||
|
CreatorCommunityReportDialog(
|
||||||
|
this,
|
||||||
|
layoutInflater
|
||||||
|
) { reason -> reportCreatorChannelCommunityPost(item, reason) }.show(screenWidth)
|
||||||
|
}
|
||||||
|
).show(supportFragmentManager, CreatorCommunityPostMenuBottomSheetDialog::class.java.simpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCreatorChannelCommunityPostFixed(item: CreatorChannelCommunityMenuItem) {
|
||||||
|
if (!item.isOwner || isCommunityPostMutating) return
|
||||||
|
|
||||||
|
isCommunityPostMutating = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
creatorCommunityRepository.updateCommunityPostFixed(item.postId, !item.isPinned, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isCommunityPostMutating = false
|
||||||
|
if (response.success) {
|
||||||
|
handleCommunityChange(CommunityChange.Updated)
|
||||||
|
} else {
|
||||||
|
showCommunityMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isCommunityPostMutating = false
|
||||||
|
showCommunityMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDeleteCreatorChannelCommunityPostDialog(item: CreatorChannelCommunityMenuItem) {
|
||||||
|
if (!item.isOwner) return
|
||||||
|
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.screen_creator_community_delete_title),
|
||||||
|
desc = getString(R.string.screen_creator_community_delete_desc),
|
||||||
|
confirmButtonTitle = getString(R.string.confirm_delete_title),
|
||||||
|
confirmButtonClick = { deleteCreatorChannelCommunityPost(item) },
|
||||||
|
cancelButtonTitle = getString(R.string.cancel)
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteCreatorChannelCommunityPost(item: CreatorChannelCommunityMenuItem) {
|
||||||
|
if (!item.isOwner || isCommunityPostMutating) return
|
||||||
|
|
||||||
|
val request = Gson().toJson(
|
||||||
|
ModifyCommunityPostRequest(
|
||||||
|
creatorCommunityId = item.postId,
|
||||||
|
isActive = false
|
||||||
|
)
|
||||||
|
).toRequestBody("text/plain".toMediaType())
|
||||||
|
isCommunityPostMutating = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
creatorCommunityRepository.modifyCommunityPost(postImage = null, request = request, token = authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isCommunityPostMutating = false
|
||||||
|
if (response.success) {
|
||||||
|
handleCommunityChange(CommunityChange.Updated)
|
||||||
|
} else {
|
||||||
|
showCommunityMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isCommunityPostMutating = false
|
||||||
|
showCommunityMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reportCreatorChannelCommunityPost(
|
||||||
|
item: CreatorChannelCommunityMenuItem,
|
||||||
|
reason: String
|
||||||
|
) {
|
||||||
|
if (item.isOwner || !item.showMore || reason.isBlank() || isCommunityPostMutating) return
|
||||||
|
|
||||||
|
isCommunityPostMutating = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
creatorChannelRepository.reportCommunityPost(item.postId, reason, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isCommunityPostMutating = false
|
||||||
|
if (response.success) {
|
||||||
|
showToast(
|
||||||
|
if (response.message.isNullOrBlank()) {
|
||||||
|
getString(R.string.character_comment_report_submitted)
|
||||||
|
} else {
|
||||||
|
response.message
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
showCommunityMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isCommunityPostMutating = false
|
||||||
|
showCommunityMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showCommunityMutationFailureToast(message: String? = null) {
|
||||||
|
showToast(message.takeUnless { it.isNullOrBlank() } ?: getString(R.string.common_error_unknown))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun authToken(): String = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
|
||||||
override fun onCreatorChannelFanTalkContentChanged() {
|
override fun onCreatorChannelFanTalkContentChanged() {
|
||||||
updateViewPagerHeight {
|
updateViewPagerHeight {
|
||||||
postCheckCreatorChannelCurrentTabNeedsMore()
|
postCheckCreatorChannelCurrentTabNeedsMore()
|
||||||
@@ -697,93 +837,6 @@ class CreatorChannelActivity :
|
|||||||
).show(screenWidth)
|
).show(screenWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatorChannelCommunityOwnerMoreClicked(item: CreatorChannelCommunityPostUiModel) {
|
|
||||||
CreatorCommunityPostMenuBottomSheetDialog(
|
|
||||||
isFixed = item.isPinned,
|
|
||||||
isCreator = true,
|
|
||||||
onClickPin = {
|
|
||||||
updateCreatorChannelCommunityPostFixed(item)
|
|
||||||
},
|
|
||||||
onClickModify = {
|
|
||||||
communityPostModifyLauncher.launch(
|
|
||||||
Intent(this, CreatorCommunityModifyActivity::class.java).apply {
|
|
||||||
putExtra(Constants.EXTRA_COMMUNITY_POST_ID, item.postId)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClickDelete = {
|
|
||||||
showCreatorChannelCommunityDeleteDialog(item)
|
|
||||||
},
|
|
||||||
onClickReport = {}
|
|
||||||
).show(supportFragmentManager, CreatorCommunityPostMenuBottomSheetDialog::class.java.simpleName)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showCreatorChannelCommunityDeleteDialog(item: CreatorChannelCommunityPostUiModel) {
|
|
||||||
V2ModalDialog(
|
|
||||||
activity = this,
|
|
||||||
layoutInflater = layoutInflater,
|
|
||||||
title = getString(R.string.screen_creator_community_delete_title),
|
|
||||||
desc = getString(R.string.screen_creator_community_delete_desc),
|
|
||||||
confirmButtonTitle = getString(R.string.confirm_delete_title),
|
|
||||||
confirmButtonClick = {
|
|
||||||
deleteCreatorChannelCommunityPost(item)
|
|
||||||
},
|
|
||||||
cancelButtonTitle = getString(R.string.cancel),
|
|
||||||
cancelButtonClick = {}
|
|
||||||
).show(screenWidth)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateCreatorChannelCommunityPostFixed(item: CreatorChannelCommunityPostUiModel) {
|
|
||||||
compositeDisposable.add(
|
|
||||||
creatorCommunityRepository.updateCommunityPostFixed(
|
|
||||||
postId = item.postId,
|
|
||||||
isFixed = !item.isPinned,
|
|
||||||
token = authToken()
|
|
||||||
)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
{ response ->
|
|
||||||
if (response.success) {
|
|
||||||
handleCommunityChange(CommunityChange.PinChanged(item.postId))
|
|
||||||
} else {
|
|
||||||
response.message?.let(::showToast)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ error -> error.message?.let(::showToast) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteCreatorChannelCommunityPost(item: CreatorChannelCommunityPostUiModel) {
|
|
||||||
val request = ModifyCommunityPostRequest(
|
|
||||||
creatorCommunityId = item.postId,
|
|
||||||
isActive = false
|
|
||||||
)
|
|
||||||
val requestJson = Gson().toJson(request)
|
|
||||||
compositeDisposable.add(
|
|
||||||
creatorCommunityRepository.modifyCommunityPost(
|
|
||||||
postImage = null,
|
|
||||||
request = requestJson.toRequestBody("text/plain".toMediaType()),
|
|
||||||
token = authToken()
|
|
||||||
)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
{ response ->
|
|
||||||
if (response.success) {
|
|
||||||
handleCommunityChange(CommunityChange.Deleted(item.postId))
|
|
||||||
} else {
|
|
||||||
response.message?.let(::showToast)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ error -> error.message?.let(::showToast) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authToken(): String = "Bearer ${SharedPreferenceManager.token}"
|
|
||||||
|
|
||||||
private fun refreshCreatorChannelCommunity() {
|
private fun refreshCreatorChannelCommunity() {
|
||||||
findCommunityFragment()?.onCreatorChannelCommunityRefreshRequested()
|
findCommunityFragment()?.onCreatorChannelCommunityRefreshRequested()
|
||||||
}
|
}
|
||||||
@@ -795,9 +848,10 @@ class CreatorChannelActivity :
|
|||||||
homeActionDelegate?.refreshHome()
|
homeActionDelegate?.refreshHome()
|
||||||
refreshCreatorChannelCommunity()
|
refreshCreatorChannelCommunity()
|
||||||
}
|
}
|
||||||
CommunityChange.Updated,
|
CommunityChange.Updated -> {
|
||||||
is CommunityChange.Deleted,
|
homeActionDelegate?.refreshHome()
|
||||||
is CommunityChange.PinChanged -> refreshCreatorChannelCommunity()
|
refreshCreatorChannelCommunity()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioConte
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityMenuItem
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
|
||||||
@@ -28,7 +29,8 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
|||||||
onSeriesClick = ::onSeriesClicked,
|
onSeriesClick = ::onSeriesClicked,
|
||||||
onDonationClick = ::onDonationClicked,
|
onDonationClick = ::onDonationClicked,
|
||||||
onSectionChevronClick = ::onSectionChevronClicked,
|
onSectionChevronClick = ::onSectionChevronClicked,
|
||||||
onCommunityClick = ::onCommunityClicked
|
onCommunityClick = ::onCommunityClicked,
|
||||||
|
onCommunityMoreClick = ::onCommunityMoreClicked
|
||||||
)
|
)
|
||||||
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
|
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
|
||||||
private val host: Host
|
private val host: Host
|
||||||
@@ -134,6 +136,10 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
|||||||
host.onCreatorChannelCommunityPostClicked(postId)
|
host.onCreatorChannelCommunityPostClicked(postId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onCommunityMoreClicked(item: CreatorChannelCommunityMenuItem) {
|
||||||
|
host.onCreatorChannelCommunityMoreClicked(item)
|
||||||
|
}
|
||||||
|
|
||||||
private fun onCurrentLiveClicked(live: CreatorChannelLiveResponse) {
|
private fun onCurrentLiveClicked(live: CreatorChannelLiveResponse) {
|
||||||
host.onCreatorChannelCurrentLiveClicked(live)
|
host.onCreatorChannelCurrentLiveClicked(live)
|
||||||
}
|
}
|
||||||
@@ -150,6 +156,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
|||||||
fun onCreatorChannelDonationClicked()
|
fun onCreatorChannelDonationClicked()
|
||||||
fun onCreatorChannelHomeTabRequested(tab: CreatorChannelTab)
|
fun onCreatorChannelHomeTabRequested(tab: CreatorChannelTab)
|
||||||
fun onCreatorChannelCommunityPostClicked(postId: Long)
|
fun onCreatorChannelCommunityPostClicked(postId: Long)
|
||||||
|
fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem)
|
||||||
fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse)
|
fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
import kr.co.vividnext.sodalive.databinding.FragmentCreatorChannelCommunityBinding
|
import kr.co.vividnext.sodalive.databinding.FragmentCreatorChannelCommunityBinding
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityContentItem
|
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityMediaPlayerManager
|
|
||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityMenuItem
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityViewMode
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityViewMode
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.ui.CreatorChannelCommunityGridAdapter
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.ui.CreatorChannelCommunityGridAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.ui.calculateCreatorChannelCommunityGridItemSize
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.ui.calculateCreatorChannelCommunityGridItemSize
|
||||||
@@ -29,14 +27,11 @@ class CreatorChannelCommunityFragment : BaseFragment<FragmentCreatorChannelCommu
|
|||||||
private val viewModel: CreatorChannelCommunityViewModel by viewModel()
|
private val viewModel: CreatorChannelCommunityViewModel by viewModel()
|
||||||
private val listAdapter = CreatorChannelCommunityListAdapter(
|
private val listAdapter = CreatorChannelCommunityListAdapter(
|
||||||
onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) },
|
onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) },
|
||||||
onPlayClick = { item -> toggleCommunityAudio(item) },
|
onMoreClick = { item -> host.onCreatorChannelCommunityMoreClicked(item) }
|
||||||
onOwnerMoreClick = { item -> host.onCreatorChannelCommunityOwnerMoreClicked(item) },
|
|
||||||
isPlayingContent = { postId -> mediaPlayerManager?.isPlayingContent(postId) == true }
|
|
||||||
)
|
)
|
||||||
private val gridAdapter = CreatorChannelCommunityGridAdapter(
|
private val gridAdapter = CreatorChannelCommunityGridAdapter(
|
||||||
onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) }
|
onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) }
|
||||||
)
|
)
|
||||||
private var mediaPlayerManager: CreatorCommunityMediaPlayerManager? = null
|
|
||||||
private var currentContentState: CreatorChannelCommunityUiState.Content? = null
|
private var currentContentState: CreatorChannelCommunityUiState.Content? = null
|
||||||
private var lastContentLayoutKey: CreatorChannelCommunityContentLayoutKey? = null
|
private var lastContentLayoutKey: CreatorChannelCommunityContentLayoutKey? = null
|
||||||
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
|
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
|
||||||
@@ -45,7 +40,6 @@ class CreatorChannelCommunityFragment : BaseFragment<FragmentCreatorChannelCommu
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
mediaPlayerManager = CreatorCommunityMediaPlayerManager(requireContext()) { listAdapter.notifyDataSetChanged() }
|
|
||||||
bindLoading()
|
bindLoading()
|
||||||
setupCommunityList()
|
setupCommunityList()
|
||||||
setupClickListeners()
|
setupClickListeners()
|
||||||
@@ -53,19 +47,12 @@ class CreatorChannelCommunityFragment : BaseFragment<FragmentCreatorChannelCommu
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
mediaPlayerManager?.stopContent()
|
|
||||||
mediaPlayerManager = null
|
|
||||||
currentContentState = null
|
currentContentState = null
|
||||||
lastContentLayoutKey = null
|
lastContentLayoutKey = null
|
||||||
binding.rvCreatorChannelCommunity.adapter = null
|
binding.rvCreatorChannelCommunity.adapter = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
mediaPlayerManager?.pauseContent()
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupCommunityList() = with(binding.rvCreatorChannelCommunity) {
|
private fun setupCommunityList() = with(binding.rvCreatorChannelCommunity) {
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
adapter = listAdapter
|
adapter = listAdapter
|
||||||
@@ -220,16 +207,11 @@ class CreatorChannelCommunityFragment : BaseFragment<FragmentCreatorChannelCommu
|
|||||||
layoutCreatorChannelCommunityEmpty.updatePadding(bottom = bottomPadding)
|
layoutCreatorChannelCommunityEmpty.updatePadding(bottom = bottomPadding)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleCommunityAudio(item: CreatorChannelCommunityPostUiModel) {
|
|
||||||
val audioUrl = item.audioUrl ?: return
|
|
||||||
mediaPlayerManager?.toggleContent(CreatorCommunityContentItem(item.postId, audioUrl))
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Host {
|
interface Host {
|
||||||
fun isCreatorChannelOwner(): Boolean
|
fun isCreatorChannelOwner(): Boolean
|
||||||
fun onCreatorChannelCommunityContentChanged()
|
fun onCreatorChannelCommunityContentChanged()
|
||||||
fun onCreatorChannelCommunityPostClicked(postId: Long)
|
fun onCreatorChannelCommunityPostClicked(postId: Long)
|
||||||
fun onCreatorChannelCommunityOwnerMoreClicked(item: CreatorChannelCommunityPostUiModel)
|
fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -23,10 +23,14 @@ import com.bumptech.glide.request.RequestListener
|
|||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelCommunityDetailBinding
|
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelCommunityDetailBinding
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityPostMenuBottomSheetDialog
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityReportDialog
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PurchaseCommunityPostDialog
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PurchaseCommunityPostDialog
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityContentItem
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityContentItem
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityMediaPlayerManager
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityMediaPlayerManager
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.modify.CreatorCommunityModifyActivity
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
@@ -40,6 +44,12 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
private val viewModel: CreatorChannelCommunityDetailViewModel by viewModel()
|
private val viewModel: CreatorChannelCommunityDetailViewModel by viewModel()
|
||||||
|
private val modifyLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK && postId > 0L) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
viewModel.loadDetail(postId)
|
||||||
|
}
|
||||||
|
}
|
||||||
private val replyLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val replyLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == Activity.RESULT_OK && postId > 0L) {
|
if (result.resultCode == Activity.RESULT_OK && postId > 0L) {
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(Activity.RESULT_OK)
|
||||||
@@ -65,6 +75,11 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.btnCreatorChannelCommunityDetailBack.setOnClickListener { finish() }
|
binding.btnCreatorChannelCommunityDetailBack.setOnClickListener { finish() }
|
||||||
|
binding.btnCreatorChannelCommunityDetailMore.setOnClickListener {
|
||||||
|
val content = viewModel.detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@setOnClickListener
|
||||||
|
showPostMenu(content.post)
|
||||||
|
}
|
||||||
binding.layoutCreatorChannelCommunityDetailImageContainer.clipToOutline = true
|
binding.layoutCreatorChannelCommunityDetailImageContainer.clipToOutline = true
|
||||||
binding.layoutCreatorChannelCommunityDetailImageContainer.outlineProvider = roundedOutlineProvider()
|
binding.layoutCreatorChannelCommunityDetailImageContainer.outlineProvider = roundedOutlineProvider()
|
||||||
binding.rvCreatorChannelCommunityDetailComments.layoutManager = LinearLayoutManager(this)
|
binding.rvCreatorChannelCommunityDetailComments.layoutManager = LinearLayoutManager(this)
|
||||||
@@ -103,6 +118,13 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
viewModel.consumePostChangedEvent()
|
viewModel.consumePostChangedEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModel.postDeletedEventLiveData.observe(this) { deleted ->
|
||||||
|
if (deleted == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
viewModel.consumePostDeletedEvent()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
viewModel.toastLiveData.observe(this) { event ->
|
viewModel.toastLiveData.observe(this) { event ->
|
||||||
event.consume()?.let { toast ->
|
event.consume()?.let { toast ->
|
||||||
val message = toast.message ?: toast.resId?.let(::getString)
|
val message = toast.message ?: toast.resId?.let(::getString)
|
||||||
@@ -150,6 +172,7 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
}
|
}
|
||||||
tvCreatorChannelCommunityDetailNickname.text = post.creatorNickname
|
tvCreatorChannelCommunityDetailNickname.text = post.creatorNickname
|
||||||
tvCreatorChannelCommunityDetailTime.text = post.createdAtText
|
tvCreatorChannelCommunityDetailTime.text = post.createdAtText
|
||||||
|
btnCreatorChannelCommunityDetailMore.isVisible = post.showMore
|
||||||
tvCreatorChannelCommunityDetailBody.text = post.content
|
tvCreatorChannelCommunityDetailBody.text = post.content
|
||||||
layoutCreatorChannelCommunityDetailReaction.isVisible = post.showReaction || content.isCommentAvailable
|
layoutCreatorChannelCommunityDetailReaction.isVisible = post.showReaction || content.isCommentAvailable
|
||||||
tvCreatorChannelCommunityDetailLikeCount.text = post.likeCount.moneyFormat()
|
tvCreatorChannelCommunityDetailLikeCount.text = post.likeCount.moneyFormat()
|
||||||
@@ -163,9 +186,6 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
ivCreatorChannelCommunityDetailHeart.setImageResource(
|
ivCreatorChannelCommunityDetailHeart.setImageResource(
|
||||||
if (post.isLiked) R.drawable.ic_feed_community_heart_fill else R.drawable.ic_feed_community_heart
|
if (post.isLiked) R.drawable.ic_feed_community_heart_fill else R.drawable.ic_feed_community_heart
|
||||||
)
|
)
|
||||||
layoutCreatorChannelCommunityDetailPrice.isVisible = post.isLocked
|
|
||||||
tvCreatorChannelCommunityDetailPrice.text = post.price.moneyFormat()
|
|
||||||
tvCreatorChannelCommunityDetailPurchased.isVisible = post.existOrdered && post.price > 0
|
|
||||||
layoutCreatorChannelCommunityDetailImageContainer.isVisible = post.imageUrl != null || post.showPaywall
|
layoutCreatorChannelCommunityDetailImageContainer.isVisible = post.imageUrl != null || post.showPaywall
|
||||||
layoutCreatorChannelCommunityDetailImageContainer.layoutParams =
|
layoutCreatorChannelCommunityDetailImageContainer.layoutParams =
|
||||||
layoutCreatorChannelCommunityDetailImageContainer.layoutParams.apply {
|
layoutCreatorChannelCommunityDetailImageContainer.layoutParams.apply {
|
||||||
@@ -296,6 +316,37 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
).show(screenWidth)
|
).show(screenWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showPostMenu(post: CreatorChannelCommunityPostDetailUiModel) {
|
||||||
|
CreatorCommunityPostMenuBottomSheetDialog(
|
||||||
|
isFixed = post.isPinned,
|
||||||
|
isCreator = post.isOwner,
|
||||||
|
onClickPin = viewModel::updateCommunityPostFixed,
|
||||||
|
onClickModify = {
|
||||||
|
modifyLauncher.launch(
|
||||||
|
Intent(this, CreatorCommunityModifyActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_COMMUNITY_POST_ID, post.postId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClickDelete = ::showDeletePostDialog,
|
||||||
|
onClickReport = {
|
||||||
|
CreatorCommunityReportDialog(this, layoutInflater, viewModel::reportCommunityPost).show(screenWidth)
|
||||||
|
}
|
||||||
|
).show(supportFragmentManager, CreatorCommunityPostMenuBottomSheetDialog::class.java.simpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDeletePostDialog() {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.screen_creator_community_delete_title),
|
||||||
|
desc = getString(R.string.screen_creator_community_delete_desc),
|
||||||
|
confirmButtonTitle = getString(R.string.confirm_delete_title),
|
||||||
|
confirmButtonClick = viewModel::deleteCommunityPost,
|
||||||
|
cancelButtonTitle = getString(R.string.cancel)
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showDeleteCommentDialog(commentId: Long) {
|
private fun showDeleteCommentDialog(commentId: Long) {
|
||||||
V2ModalDialog(
|
V2ModalDialog(
|
||||||
activity = this,
|
activity = this,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel.community.detail
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.google.gson.Gson
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
@@ -12,12 +13,15 @@ import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
|||||||
import kr.co.vividnext.sodalive.common.ToastMessage
|
import kr.co.vividnext.sodalive.common.ToastMessage
|
||||||
import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter
|
import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.modify.ModifyCommunityPostRequest
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelEvent
|
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelEvent
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentsResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentsResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityPostDetailResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityPostDetailResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityReplyResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityReplyResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
||||||
class CreatorChannelCommunityDetailViewModel(
|
class CreatorChannelCommunityDetailViewModel(
|
||||||
private val repository: CreatorChannelRepository,
|
private val repository: CreatorChannelRepository,
|
||||||
@@ -37,6 +41,10 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
val postChangedEventLiveData: LiveData<Boolean>
|
val postChangedEventLiveData: LiveData<Boolean>
|
||||||
get() = _postChangedEventLiveData
|
get() = _postChangedEventLiveData
|
||||||
|
|
||||||
|
private val _postDeletedEventLiveData = MutableLiveData<Boolean>()
|
||||||
|
val postDeletedEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _postDeletedEventLiveData
|
||||||
|
|
||||||
private val _toastLiveData = MutableLiveData<CreatorChannelEvent<ToastMessage>>()
|
private val _toastLiveData = MutableLiveData<CreatorChannelEvent<ToastMessage>>()
|
||||||
val toastLiveData: LiveData<CreatorChannelEvent<ToastMessage>>
|
val toastLiveData: LiveData<CreatorChannelEvent<ToastMessage>>
|
||||||
get() = _toastLiveData
|
get() = _toastLiveData
|
||||||
@@ -49,6 +57,7 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
private var isSendingComment = false
|
private var isSendingComment = false
|
||||||
private var isModifyingComment = false
|
private var isModifyingComment = false
|
||||||
private var isPurchasingPost = false
|
private var isPurchasingPost = false
|
||||||
|
private var isMutatingPost = false
|
||||||
|
|
||||||
fun loadDetail(postId: Long) {
|
fun loadDetail(postId: Long) {
|
||||||
if (postId <= 0) return
|
if (postId <= 0) return
|
||||||
@@ -181,6 +190,10 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
_postChangedEventLiveData.value = false
|
_postChangedEventLiveData.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun consumePostDeletedEvent() {
|
||||||
|
_postDeletedEventLiveData.value = false
|
||||||
|
}
|
||||||
|
|
||||||
fun startCommentEdit(commentId: Long, comment: String) {
|
fun startCommentEdit(commentId: Long, comment: String) {
|
||||||
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
if (commentId <= 0) return
|
if (commentId <= 0) return
|
||||||
@@ -245,6 +258,97 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateCommunityPostFixed() {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
if (!content.post.isOwner || isMutatingPost) return
|
||||||
|
|
||||||
|
isMutatingPost = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.updateCommunityPostFixed(postId, !content.post.isPinned, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isMutatingPost = false
|
||||||
|
if (response.success) {
|
||||||
|
_postChangedEventLiveData.value = true
|
||||||
|
loadDetail(postId)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isMutatingPost = false
|
||||||
|
showMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteCommunityPost() {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
if (!content.post.isOwner || isMutatingPost) return
|
||||||
|
|
||||||
|
val request = Gson().toJson(
|
||||||
|
ModifyCommunityPostRequest(
|
||||||
|
creatorCommunityId = postId,
|
||||||
|
isActive = false
|
||||||
|
)
|
||||||
|
).toRequestBody("text/plain".toMediaType())
|
||||||
|
isMutatingPost = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyCommunityPost(postImage = null, request = request, token = authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isMutatingPost = false
|
||||||
|
if (response.success) {
|
||||||
|
_postDeletedEventLiveData.value = true
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isMutatingPost = false
|
||||||
|
showMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reportCommunityPost(reason: String) {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
if (reason.isBlank() || content.post.isOwner || !content.post.showMore || isMutatingPost) return
|
||||||
|
|
||||||
|
isMutatingPost = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.reportCommunityPost(postId, reason, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isMutatingPost = false
|
||||||
|
if (response.success) {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
if (response.message.isNullOrBlank()) {
|
||||||
|
ToastMessage(resId = R.string.character_comment_report_submitted)
|
||||||
|
} else {
|
||||||
|
ToastMessage(message = response.message)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isMutatingPost = false
|
||||||
|
showMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun submitCommentEdit(
|
private fun submitCommentEdit(
|
||||||
content: CreatorChannelCommunityDetailUiState.Content,
|
content: CreatorChannelCommunityDetailUiState.Content,
|
||||||
commentId: Long,
|
commentId: Long,
|
||||||
@@ -425,25 +529,30 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CreatorChannelCommunityPostDetailResponse.toUiModel() = CreatorChannelCommunityPostDetailUiModel(
|
private fun CreatorChannelCommunityPostDetailResponse.toUiModel(): CreatorChannelCommunityPostDetailUiModel {
|
||||||
postId = postId,
|
val isOwner = creatorId == SharedPreferenceManager.userId
|
||||||
creatorId = creatorId,
|
return CreatorChannelCommunityPostDetailUiModel(
|
||||||
creatorNickname = creatorNickname,
|
postId = postId,
|
||||||
creatorProfileUrl = creatorProfileUrl,
|
creatorId = creatorId,
|
||||||
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
creatorNickname = creatorNickname,
|
||||||
content = content,
|
creatorProfileUrl = creatorProfileUrl,
|
||||||
imageUrl = imageUrl.takeUnless { isLocked() },
|
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
||||||
audioUrl = audioUrl.takeUnless { isLocked() },
|
content = content,
|
||||||
price = price,
|
imageUrl = imageUrl.takeUnless { isLocked() },
|
||||||
existOrdered = existOrdered,
|
audioUrl = audioUrl.takeUnless { isLocked() },
|
||||||
likeCount = likeCount,
|
price = price,
|
||||||
commentCount = commentCount,
|
existOrdered = existOrdered,
|
||||||
isLiked = isLiked,
|
likeCount = likeCount,
|
||||||
isPinned = isPinned,
|
commentCount = commentCount,
|
||||||
isLocked = isLocked(),
|
isLiked = isLiked,
|
||||||
showPaywall = isLocked(),
|
isPinned = isPinned,
|
||||||
showReaction = !isLocked()
|
isOwner = isOwner,
|
||||||
)
|
showMore = isOwner || price == 0 || existOrdered,
|
||||||
|
isLocked = isLocked(),
|
||||||
|
showPaywall = isLocked(),
|
||||||
|
showReaction = !isLocked()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun CreatorChannelCommunityPostDetailResponse.isLocked(): Boolean {
|
private fun CreatorChannelCommunityPostDetailResponse.isLocked(): Boolean {
|
||||||
return price > 0 && !existOrdered && creatorId != SharedPreferenceManager.userId
|
return price > 0 && !existOrdered && creatorId != SharedPreferenceManager.userId
|
||||||
@@ -526,6 +635,8 @@ data class CreatorChannelCommunityPostDetailUiModel(
|
|||||||
val commentCount: Int,
|
val commentCount: Int,
|
||||||
val isLiked: Boolean,
|
val isLiked: Boolean,
|
||||||
val isPinned: Boolean,
|
val isPinned: Boolean,
|
||||||
|
val isOwner: Boolean,
|
||||||
|
val showMore: Boolean,
|
||||||
val isLocked: Boolean,
|
val isLocked: Boolean,
|
||||||
val showPaywall: Boolean,
|
val showPaywall: Boolean,
|
||||||
val showReaction: Boolean
|
val showReaction: Boolean
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ private fun CreatorChannelCommunityPostResponse.toCommunityPostUiModel(
|
|||||||
val isLocked = price > 0 && !existOrdered && !isOwner
|
val isLocked = price > 0 && !existOrdered && !isOwner
|
||||||
val showOwnerActions = isOwner && creatorId == currentUserId
|
val showOwnerActions = isOwner && creatorId == currentUserId
|
||||||
val visibleImageUrl = imageUrl.takeUnless { isLocked }
|
val visibleImageUrl = imageUrl.takeUnless { isLocked }
|
||||||
val showPlayButton = !isLocked && !audioUrl.isNullOrBlank() && !visibleImageUrl.isNullOrBlank()
|
|
||||||
return CreatorChannelCommunityPostUiModel(
|
return CreatorChannelCommunityPostUiModel(
|
||||||
postId = postId,
|
postId = postId,
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
@@ -30,7 +29,6 @@ private fun CreatorChannelCommunityPostResponse.toCommunityPostUiModel(
|
|||||||
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
||||||
content = content,
|
content = content,
|
||||||
imageUrl = visibleImageUrl,
|
imageUrl = visibleImageUrl,
|
||||||
audioUrl = audioUrl,
|
|
||||||
price = price,
|
price = price,
|
||||||
existOrdered = existOrdered,
|
existOrdered = existOrdered,
|
||||||
likeCount = likeCount,
|
likeCount = likeCount,
|
||||||
@@ -40,11 +38,17 @@ private fun CreatorChannelCommunityPostResponse.toCommunityPostUiModel(
|
|||||||
showNotice = isPinned,
|
showNotice = isPinned,
|
||||||
isPinned = isPinned,
|
isPinned = isPinned,
|
||||||
isLocked = isLocked,
|
isLocked = isLocked,
|
||||||
showOwnerMore = showOwnerActions,
|
|
||||||
showOwnerTopPrice = showOwnerActions && price > 0,
|
showOwnerTopPrice = showOwnerActions && price > 0,
|
||||||
showPlayButton = showPlayButton,
|
|
||||||
gridPreviewText = content.toGridPreviewText(),
|
gridPreviewText = content.toGridPreviewText(),
|
||||||
imageMode = toImageMode(isLocked, visibleImageUrl)
|
imageMode = toImageMode(isLocked, visibleImageUrl),
|
||||||
|
menuItem = CreatorChannelCommunityMenuItem(
|
||||||
|
postId = postId,
|
||||||
|
creatorId = creatorId,
|
||||||
|
currentUserId = currentUserId,
|
||||||
|
price = price,
|
||||||
|
existOrdered = existOrdered,
|
||||||
|
isPinned = isPinned
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.model
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityMenuItem(
|
||||||
|
val postId: Long,
|
||||||
|
val creatorId: Long,
|
||||||
|
val currentUserId: Long,
|
||||||
|
val price: Int,
|
||||||
|
val existOrdered: Boolean,
|
||||||
|
val isPinned: Boolean
|
||||||
|
) {
|
||||||
|
val isOwner: Boolean
|
||||||
|
get() = creatorId == currentUserId
|
||||||
|
|
||||||
|
val showMore: Boolean
|
||||||
|
get() = isOwner || price == 0 || existOrdered
|
||||||
|
}
|
||||||
@@ -32,7 +32,6 @@ data class CreatorChannelCommunityPostUiModel(
|
|||||||
val createdAtText: String,
|
val createdAtText: String,
|
||||||
val content: String,
|
val content: String,
|
||||||
val imageUrl: String?,
|
val imageUrl: String?,
|
||||||
val audioUrl: String?,
|
|
||||||
val price: Int,
|
val price: Int,
|
||||||
val existOrdered: Boolean,
|
val existOrdered: Boolean,
|
||||||
val likeCount: Int,
|
val likeCount: Int,
|
||||||
@@ -42,9 +41,8 @@ data class CreatorChannelCommunityPostUiModel(
|
|||||||
val showNotice: Boolean,
|
val showNotice: Boolean,
|
||||||
val isPinned: Boolean,
|
val isPinned: Boolean,
|
||||||
val isLocked: Boolean,
|
val isLocked: Boolean,
|
||||||
val showOwnerMore: Boolean,
|
|
||||||
val showOwnerTopPrice: Boolean,
|
val showOwnerTopPrice: Boolean,
|
||||||
val showPlayButton: Boolean,
|
|
||||||
val gridPreviewText: String,
|
val gridPreviewText: String,
|
||||||
val imageMode: CreatorChannelCommunityImageMode
|
val imageMode: CreatorChannelCommunityImageMode,
|
||||||
|
val menuItem: CreatorChannelCommunityMenuItem
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,14 +19,13 @@ import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelCommunityListBindi
|
|||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityMenuItem
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.calculateFeedCommunityImageHeight
|
import kr.co.vividnext.sodalive.v2.widget.feed.calculateFeedCommunityImageHeight
|
||||||
|
|
||||||
class CreatorChannelCommunityListAdapter(
|
class CreatorChannelCommunityListAdapter(
|
||||||
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
||||||
private val onPlayClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
private val onMoreClick: (CreatorChannelCommunityMenuItem) -> Unit = {}
|
||||||
private val onOwnerMoreClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
|
||||||
private val isPlayingContent: (Long) -> Boolean = { false }
|
|
||||||
) : RecyclerView.Adapter<CreatorChannelCommunityListAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<CreatorChannelCommunityListAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var items: List<CreatorChannelCommunityPostUiModel> = emptyList()
|
private var items: List<CreatorChannelCommunityPostUiModel> = emptyList()
|
||||||
@@ -40,9 +39,7 @@ class CreatorChannelCommunityListAdapter(
|
|||||||
return ViewHolder(
|
return ViewHolder(
|
||||||
ItemCreatorChannelCommunityListBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
ItemCreatorChannelCommunityListBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
onPostClick,
|
onPostClick,
|
||||||
onPlayClick,
|
onMoreClick
|
||||||
onOwnerMoreClick,
|
|
||||||
isPlayingContent
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,9 +52,7 @@ class CreatorChannelCommunityListAdapter(
|
|||||||
class ViewHolder(
|
class ViewHolder(
|
||||||
private val binding: ItemCreatorChannelCommunityListBinding,
|
private val binding: ItemCreatorChannelCommunityListBinding,
|
||||||
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
||||||
private val onPlayClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
private val onMoreClick: (CreatorChannelCommunityMenuItem) -> Unit
|
||||||
private val onOwnerMoreClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
|
||||||
private val isPlayingContent: (Long) -> Boolean
|
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -86,7 +81,7 @@ class CreatorChannelCommunityListAdapter(
|
|||||||
|
|
||||||
val visibleImageUrl = item.imageUrl.takeUnless { item.isLocked }
|
val visibleImageUrl = item.imageUrl.takeUnless { item.isLocked }
|
||||||
layoutCreatorChannelCommunityListImageContainer.isVisible =
|
layoutCreatorChannelCommunityListImageContainer.isVisible =
|
||||||
visibleImageUrl != null || item.isLocked || item.showPlayButton
|
visibleImageUrl != null || item.isLocked
|
||||||
ivCreatorChannelCommunityListImage.isVisible = visibleImageUrl != null
|
ivCreatorChannelCommunityListImage.isVisible = visibleImageUrl != null
|
||||||
resetCommunityImageHeight()
|
resetCommunityImageHeight()
|
||||||
if (visibleImageUrl != null) {
|
if (visibleImageUrl != null) {
|
||||||
@@ -119,19 +114,14 @@ class CreatorChannelCommunityListAdapter(
|
|||||||
}
|
}
|
||||||
layoutCreatorChannelCommunityListLockedOverlay.isVisible = item.isLocked
|
layoutCreatorChannelCommunityListLockedOverlay.isVisible = item.isLocked
|
||||||
ivCreatorChannelCommunityListLock.isVisible = item.isLocked
|
ivCreatorChannelCommunityListLock.isVisible = item.isLocked
|
||||||
tvCreatorChannelCommunityListLockedPrice.isVisible = item.isLocked
|
layoutCreatorChannelCommunityListLockedPrice.isVisible = item.isLocked
|
||||||
tvCreatorChannelCommunityListLockedPrice.text = item.price.moneyFormat()
|
tvCreatorChannelCommunityListLockedPrice.text = item.price.moneyFormat()
|
||||||
ivCreatorChannelCommunityListPlay.isVisible = item.showPlayButton
|
|
||||||
ivCreatorChannelCommunityListPlay.setImageResource(
|
|
||||||
if (isPlayingContent(item.postId)) R.drawable.ic_player_pause else R.drawable.ic_new_player_play
|
|
||||||
)
|
|
||||||
ivCreatorChannelCommunityListPlay.setOnClickListener { onPlayClick(item) }
|
|
||||||
|
|
||||||
layoutCreatorChannelCommunityListTopActions.isVisible = item.showOwnerMore || item.showOwnerTopPrice
|
layoutCreatorChannelCommunityListTopActions.isVisible = item.showOwnerTopPrice || item.menuItem.showMore
|
||||||
layoutCreatorChannelCommunityListTopPrice.isVisible = item.showOwnerTopPrice
|
layoutCreatorChannelCommunityListTopPrice.isVisible = item.showOwnerTopPrice
|
||||||
tvCreatorChannelCommunityListTopPrice.text = item.price.moneyFormat()
|
tvCreatorChannelCommunityListTopPrice.text = item.price.moneyFormat()
|
||||||
ivCreatorChannelCommunityListOwnerMore.isVisible = item.showOwnerMore
|
btnCreatorChannelCommunityListMore.isVisible = item.menuItem.showMore
|
||||||
ivCreatorChannelCommunityListOwnerMore.setOnClickListener { onOwnerMoreClick(item) }
|
btnCreatorChannelCommunityListMore.setOnClickListener { onMoreClick(item.menuItem) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetCommunityImageHeight() {
|
private fun resetCommunityImageHeight() {
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ data class CreatorChannelCommunityPostResponse(
|
|||||||
@SerializedName("dateUtc") val dateUtc: String,
|
@SerializedName("dateUtc") val dateUtc: String,
|
||||||
@SerializedName("existOrdered") val existOrdered: Boolean,
|
@SerializedName("existOrdered") val existOrdered: Boolean,
|
||||||
@SerializedName("likeCount") val likeCount: Int,
|
@SerializedName("likeCount") val likeCount: Int,
|
||||||
@SerializedName("commentCount") val commentCount: Int
|
@SerializedName("commentCount") val commentCount: Int,
|
||||||
|
@SerializedName("isPinned") val isPinned: Boolean,
|
||||||
|
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
|
|||||||
@@ -185,6 +185,11 @@ class CreatorChannelRepository(
|
|||||||
token = token
|
token = token
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun reportCommunityPost(postId: Long, reason: String, token: String) = reportRepository.report(
|
||||||
|
request = ReportRequest(ReportType.COMMUNITY_POST, reason, communityPostId = postId),
|
||||||
|
token = token
|
||||||
|
)
|
||||||
|
|
||||||
fun deleteFanTalk(fanTalkId: Long, token: String) = explorerRepository.modifyCheers(
|
fun deleteFanTalk(fanTalkId: Long, token: String) = explorerRepository.modifyCheers(
|
||||||
request = PutModifyCheersRequest(cheersId = fanTalkId, isActive = false),
|
request = PutModifyCheersRequest(cheersId = fanTalkId, isActive = false),
|
||||||
token = token
|
token = token
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ fun CreatorChannelHomeResponse.toUiContent(currentMemberId: Long): CreatorChanne
|
|||||||
?.let { add(CreatorChannelHomeSection.Schedules(it)) }
|
?.let { add(CreatorChannelHomeSection.Schedules(it)) }
|
||||||
audioContents.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.AudioContents(it)) }
|
audioContents.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.AudioContents(it)) }
|
||||||
series.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Series(it)) }
|
series.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Series(it)) }
|
||||||
communities.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Communities(it)) }
|
communities.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { add(CreatorChannelHomeSection.Communities(it, currentMemberId)) }
|
||||||
add(CreatorChannelHomeSection.FanTalk(fanTalk))
|
add(CreatorChannelHomeSection.FanTalk(fanTalk))
|
||||||
introduce.takeIf { it.isNotBlank() }?.let { add(CreatorChannelHomeSection.Introduce(it)) }
|
introduce.takeIf { it.isNotBlank() }?.let { add(CreatorChannelHomeSection.Introduce(it)) }
|
||||||
add(CreatorChannelHomeSection.Activity(activity))
|
add(CreatorChannelHomeSection.Activity(activity))
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ sealed interface CreatorChannelHomeSection {
|
|||||||
data class Schedules(val schedules: List<CreatorChannelScheduleResponse>) : CreatorChannelHomeSection
|
data class Schedules(val schedules: List<CreatorChannelScheduleResponse>) : CreatorChannelHomeSection
|
||||||
data class AudioContents(val audioContents: List<CreatorChannelAudioContentResponse>) : CreatorChannelHomeSection
|
data class AudioContents(val audioContents: List<CreatorChannelAudioContentResponse>) : CreatorChannelHomeSection
|
||||||
data class Series(val series: List<CreatorChannelSeriesResponse>) : CreatorChannelHomeSection
|
data class Series(val series: List<CreatorChannelSeriesResponse>) : CreatorChannelHomeSection
|
||||||
data class Communities(val communities: List<CreatorChannelCommunityPostResponse>) : CreatorChannelHomeSection
|
data class Communities(
|
||||||
|
val communities: List<CreatorChannelCommunityPostResponse>,
|
||||||
|
val currentMemberId: Long
|
||||||
|
) : CreatorChannelHomeSection
|
||||||
data class FanTalk(val fanTalk: CreatorChannelFanTalkSummaryResponse) : CreatorChannelHomeSection
|
data class FanTalk(val fanTalk: CreatorChannelFanTalkSummaryResponse) : CreatorChannelHomeSection
|
||||||
data class Introduce(val introduce: String) : CreatorChannelHomeSection
|
data class Introduce(val introduce: String) : CreatorChannelHomeSection
|
||||||
data class Activity(val activity: CreatorChannelActivityResponse) : CreatorChannelHomeSection
|
data class Activity(val activity: CreatorChannelActivityResponse) : CreatorChannelHomeSection
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelCommunityP
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityMenuItem
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeSection
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeSection
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedCommunityView
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedCommunityView
|
||||||
@@ -43,7 +44,8 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit = {},
|
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit = {},
|
||||||
private val onDonationClick: () -> Unit = {},
|
private val onDonationClick: () -> Unit = {},
|
||||||
private val onSectionChevronClick: (CreatorChannelTab) -> Unit = {},
|
private val onSectionChevronClick: (CreatorChannelTab) -> Unit = {},
|
||||||
private val onCommunityClick: (Long) -> Unit = {}
|
private val onCommunityClick: (Long) -> Unit = {},
|
||||||
|
private val onCommunityMoreClick: (CreatorChannelCommunityMenuItem) -> Unit = {}
|
||||||
) : RecyclerView.Adapter<CreatorChannelHomeSectionAdapter.SectionViewHolder>() {
|
) : RecyclerView.Adapter<CreatorChannelHomeSectionAdapter.SectionViewHolder>() {
|
||||||
|
|
||||||
private var items: List<CreatorChannelHomeSection> = emptyList()
|
private var items: List<CreatorChannelHomeSection> = emptyList()
|
||||||
@@ -65,7 +67,8 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
onSeriesClick,
|
onSeriesClick,
|
||||||
onDonationClick,
|
onDonationClick,
|
||||||
onSectionChevronClick,
|
onSectionChevronClick,
|
||||||
onCommunityClick
|
onCommunityClick,
|
||||||
|
onCommunityMoreClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +86,8 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit,
|
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit,
|
||||||
private val onDonationClick: () -> Unit,
|
private val onDonationClick: () -> Unit,
|
||||||
private val onSectionChevronClick: (CreatorChannelTab) -> Unit,
|
private val onSectionChevronClick: (CreatorChannelTab) -> Unit,
|
||||||
private val onCommunityClick: (Long) -> Unit
|
private val onCommunityClick: (Long) -> Unit,
|
||||||
|
private val onCommunityMoreClick: (CreatorChannelCommunityMenuItem) -> Unit
|
||||||
) : RecyclerView.ViewHolder(view) {
|
) : RecyclerView.ViewHolder(view) {
|
||||||
private val title: TextView? = view.findViewById(R.id.tv_section_title)
|
private val title: TextView? = view.findViewById(R.id.tv_section_title)
|
||||||
private val sectionTitleChevron: ImageView? = view.findViewById(R.id.iv_section_title_chevron)
|
private val sectionTitleChevron: ImageView? = view.findViewById(R.id.iv_section_title_chevron)
|
||||||
@@ -366,6 +370,14 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
communityMoreButton?.setOnClickListener { onSectionChevronClick(CreatorChannelTab.Community) }
|
communityMoreButton?.setOnClickListener { onSectionChevronClick(CreatorChannelTab.Community) }
|
||||||
val visibleCommunities = item.communities.take(MAX_COMMUNITY_ITEM_COUNT)
|
val visibleCommunities = item.communities.take(MAX_COMMUNITY_ITEM_COUNT)
|
||||||
visibleCommunities.forEachIndexed { index, community ->
|
visibleCommunities.forEachIndexed { index, community ->
|
||||||
|
val menuItem = CreatorChannelCommunityMenuItem(
|
||||||
|
postId = community.postId,
|
||||||
|
creatorId = community.creatorId,
|
||||||
|
currentUserId = item.currentMemberId,
|
||||||
|
price = community.price,
|
||||||
|
existOrdered = community.existOrdered,
|
||||||
|
isPinned = community.isPinned
|
||||||
|
)
|
||||||
val communityWidthDp = calculateCreatorChannelCommunityCardWidthDp(
|
val communityWidthDp = calculateCreatorChannelCommunityCardWidthDp(
|
||||||
itemView.resources.configuration.screenWidthDp
|
itemView.resources.configuration.screenWidthDp
|
||||||
)
|
)
|
||||||
@@ -381,7 +393,8 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
setHideEmptyTextRows(true)
|
setHideEmptyTextRows(true)
|
||||||
bind(community.toFeedCommunityItem())
|
bind(community.toFeedCommunityItem(showMore = menuItem.showMore))
|
||||||
|
setOnMoreClick { onCommunityMoreClick(menuItem) }
|
||||||
}
|
}
|
||||||
bindCommunityImages(row, community)
|
bindCommunityImages(row, community)
|
||||||
row.layoutParams = LinearLayout.LayoutParams(
|
row.layoutParams = LinearLayout.LayoutParams(
|
||||||
@@ -443,7 +456,9 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CreatorChannelCommunityPostResponse.toFeedCommunityItem(): FeedItem.Community = FeedItem.Community(
|
private fun CreatorChannelCommunityPostResponse.toFeedCommunityItem(
|
||||||
|
showMore: Boolean
|
||||||
|
): FeedItem.Community = FeedItem.Community(
|
||||||
feedId = postId.toString(),
|
feedId = postId.toString(),
|
||||||
creatorId = creatorId.toString(),
|
creatorId = creatorId.toString(),
|
||||||
creatorName = creatorNickname,
|
creatorName = creatorNickname,
|
||||||
@@ -458,7 +473,11 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
audioUrl = audioUrl,
|
audioUrl = audioUrl,
|
||||||
price = price,
|
price = price,
|
||||||
existOrdered = existOrdered,
|
existOrdered = existOrdered,
|
||||||
showKeyword = false
|
isPinned = isPinned,
|
||||||
|
isCommentAvailable = isCommentAvailable,
|
||||||
|
hideReactionWhenLocked = true,
|
||||||
|
showKeyword = false,
|
||||||
|
showMore = showMore
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun bindIntroduce(item: CreatorChannelHomeSection.Introduce) {
|
private fun bindIntroduce(item: CreatorChannelHomeSection.Introduce) {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class FeedCommunityView @JvmOverloads constructor(
|
|||||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
private var profileImage: ImageView? = null
|
private var profileImage: ImageView? = null
|
||||||
|
private var noticeRow: View? = null
|
||||||
|
private var moreButton: View? = null
|
||||||
private var creatorText: TextView? = null
|
private var creatorText: TextView? = null
|
||||||
private var createdAtText: TextView? = null
|
private var createdAtText: TextView? = null
|
||||||
private var bodyText: TextView? = null
|
private var bodyText: TextView? = null
|
||||||
@@ -27,15 +29,21 @@ class FeedCommunityView @JvmOverloads constructor(
|
|||||||
private var communityImage: ImageView? = null
|
private var communityImage: ImageView? = null
|
||||||
private var paidOverlay: View? = null
|
private var paidOverlay: View? = null
|
||||||
private var priceText: TextView? = null
|
private var priceText: TextView? = null
|
||||||
|
private var reactionRow: View? = null
|
||||||
|
private var commentIcon: ImageView? = null
|
||||||
private var commentCountText: TextView? = null
|
private var commentCountText: TextView? = null
|
||||||
|
private var likeIcon: ImageView? = null
|
||||||
private var likeCountText: TextView? = null
|
private var likeCountText: TextView? = null
|
||||||
private var currentItem: FeedItem.Community? = null
|
private var currentItem: FeedItem.Community? = null
|
||||||
private var clickListener: ((FeedItem) -> Unit)? = null
|
private var clickListener: ((FeedItem) -> Unit)? = null
|
||||||
|
private var moreClickListener: ((FeedItem.Community) -> Unit)? = null
|
||||||
private var hideEmptyTextRows: Boolean = false
|
private var hideEmptyTextRows: Boolean = false
|
||||||
|
|
||||||
override fun onFinishInflate() {
|
override fun onFinishInflate() {
|
||||||
super.onFinishInflate()
|
super.onFinishInflate()
|
||||||
|
noticeRow = findViewById(R.id.ll_feed_community_notice)
|
||||||
profileImage = findViewById(R.id.iv_feed_community_profile)
|
profileImage = findViewById(R.id.iv_feed_community_profile)
|
||||||
|
moreButton = findViewById(R.id.btn_feed_community_more)
|
||||||
creatorText = findViewById(R.id.tv_feed_community_creator)
|
creatorText = findViewById(R.id.tv_feed_community_creator)
|
||||||
createdAtText = findViewById(R.id.tv_feed_community_created_at)
|
createdAtText = findViewById(R.id.tv_feed_community_created_at)
|
||||||
bodyText = findViewById(R.id.tv_feed_community_body)
|
bodyText = findViewById(R.id.tv_feed_community_body)
|
||||||
@@ -43,7 +51,10 @@ class FeedCommunityView @JvmOverloads constructor(
|
|||||||
communityImage = findViewById(R.id.iv_feed_community_image)
|
communityImage = findViewById(R.id.iv_feed_community_image)
|
||||||
paidOverlay = findViewById(R.id.ll_feed_community_paid_overlay)
|
paidOverlay = findViewById(R.id.ll_feed_community_paid_overlay)
|
||||||
priceText = findViewById(R.id.tv_feed_community_price)
|
priceText = findViewById(R.id.tv_feed_community_price)
|
||||||
|
reactionRow = findViewById(R.id.ll_feed_community_reaction)
|
||||||
|
commentIcon = findViewById(R.id.iv_feed_community_comment)
|
||||||
commentCountText = findViewById(R.id.tv_feed_community_comment_count)
|
commentCountText = findViewById(R.id.tv_feed_community_comment_count)
|
||||||
|
likeIcon = findViewById(R.id.iv_feed_community_like)
|
||||||
likeCountText = findViewById(R.id.tv_feed_community_like_count)
|
likeCountText = findViewById(R.id.tv_feed_community_like_count)
|
||||||
clipToOutline = true
|
clipToOutline = true
|
||||||
outlineProvider = roundedOutlineProvider(CARD_RADIUS_DP)
|
outlineProvider = roundedOutlineProvider(CARD_RADIUS_DP)
|
||||||
@@ -59,7 +70,8 @@ class FeedCommunityView @JvmOverloads constructor(
|
|||||||
requireNotNull(createdAtText).text = item.createdAtText
|
requireNotNull(createdAtText).text = item.createdAtText
|
||||||
requireNotNull(bodyText).text = item.bodyText
|
requireNotNull(bodyText).text = item.bodyText
|
||||||
requireNotNull(bodyText).visibility = visibilityForText(item.bodyText)
|
requireNotNull(bodyText).visibility = visibilityForText(item.bodyText)
|
||||||
val isLocked = item.price > 0 && !item.existOrdered
|
requireNotNull(noticeRow).isVisible = item.isPinned
|
||||||
|
val isLocked = item.isLocked
|
||||||
val hasImage = !item.imageUrl.isNullOrBlank()
|
val hasImage = !item.imageUrl.isNullOrBlank()
|
||||||
requireNotNull(communityImageContainer).isVisible = hasImage || isLocked
|
requireNotNull(communityImageContainer).isVisible = hasImage || isLocked
|
||||||
resetCommunityImageHeight()
|
resetCommunityImageHeight()
|
||||||
@@ -69,9 +81,15 @@ class FeedCommunityView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
requireNotNull(paidOverlay).isVisible = isLocked
|
requireNotNull(paidOverlay).isVisible = isLocked
|
||||||
requireNotNull(priceText).text = item.price.toString()
|
requireNotNull(priceText).text = item.price.toString()
|
||||||
|
requireNotNull(reactionRow).isVisible = item.showReaction
|
||||||
|
requireNotNull(commentIcon).isVisible = item.showComment
|
||||||
requireNotNull(commentCountText).text = item.commentCount.toString()
|
requireNotNull(commentCountText).text = item.commentCount.toString()
|
||||||
|
requireNotNull(commentCountText).isVisible = item.showComment
|
||||||
|
requireNotNull(likeIcon).isVisible = item.showReaction
|
||||||
requireNotNull(likeCountText).text = item.likeCount.toString()
|
requireNotNull(likeCountText).text = item.likeCount.toString()
|
||||||
|
requireNotNull(likeCountText).isVisible = item.showReaction
|
||||||
applyClickState(item)
|
applyClickState(item)
|
||||||
|
applyMoreClickState(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun profileImageView(): ImageView = requireNotNull(profileImage)
|
fun profileImageView(): ImageView = requireNotNull(profileImage)
|
||||||
@@ -116,6 +134,11 @@ class FeedCommunityView @JvmOverloads constructor(
|
|||||||
currentItem?.let(::applyClickState)
|
currentItem?.let(::applyClickState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setOnMoreClick(listener: ((FeedItem.Community) -> Unit)?) {
|
||||||
|
moreClickListener = listener
|
||||||
|
currentItem?.let(::applyMoreClickState)
|
||||||
|
}
|
||||||
|
|
||||||
fun setHideEmptyTextRows(hide: Boolean) {
|
fun setHideEmptyTextRows(hide: Boolean) {
|
||||||
hideEmptyTextRows = hide
|
hideEmptyTextRows = hide
|
||||||
currentItem?.let(::bind)
|
currentItem?.let(::bind)
|
||||||
@@ -130,6 +153,14 @@ class FeedCommunityView @JvmOverloads constructor(
|
|||||||
isClickable = listener != null
|
isClickable = listener != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun applyMoreClickState(item: FeedItem.Community) {
|
||||||
|
val listener = moreClickListener
|
||||||
|
requireNotNull(moreButton).isVisible = item.showMore
|
||||||
|
requireNotNull(moreButton).setOnClickListener(
|
||||||
|
if (listener == null) null else View.OnClickListener { listener(item) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateRootWidth(width: Int) {
|
private fun updateRootWidth(width: Int) {
|
||||||
val currentLayoutParams = layoutParams
|
val currentLayoutParams = layoutParams
|
||||||
layoutParams = if (currentLayoutParams == null) {
|
layoutParams = if (currentLayoutParams == null) {
|
||||||
|
|||||||
@@ -47,6 +47,19 @@ sealed class FeedItem(open val feedId: String, val variant: FeedVariant) {
|
|||||||
val audioUrl: String? = null,
|
val audioUrl: String? = null,
|
||||||
val price: Int = 0,
|
val price: Int = 0,
|
||||||
val existOrdered: Boolean = false,
|
val existOrdered: Boolean = false,
|
||||||
val showKeyword: Boolean = true
|
val isPinned: Boolean = false,
|
||||||
) : FeedItem(feedId, FeedVariant.Community)
|
val isCommentAvailable: Boolean = true,
|
||||||
|
val hideReactionWhenLocked: Boolean = false,
|
||||||
|
val showKeyword: Boolean = true,
|
||||||
|
val showMore: Boolean = false
|
||||||
|
) : FeedItem(feedId, FeedVariant.Community) {
|
||||||
|
val isLocked: Boolean
|
||||||
|
get() = price > 0 && !existOrdered
|
||||||
|
|
||||||
|
val showReaction: Boolean
|
||||||
|
get() = !hideReactionWhenLocked || !isLocked
|
||||||
|
|
||||||
|
val showComment: Boolean
|
||||||
|
get() = showReaction && isCommentAvailable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
app:layout_constraintEnd_toStartOf="@id/layout_creator_channel_community_detail_price"
|
app:layout_constraintEnd_toStartOf="@id/btn_creator_channel_community_detail_more"
|
||||||
app:layout_constraintStart_toEndOf="@id/iv_creator_channel_community_detail_profile"
|
app:layout_constraintStart_toEndOf="@id/iv_creator_channel_community_detail_profile"
|
||||||
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_community_detail_profile"
|
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_community_detail_profile"
|
||||||
tools:text="크리에이터" />
|
tools:text="크리에이터" />
|
||||||
@@ -91,36 +91,17 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_community_detail_nickname"
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_community_detail_nickname"
|
||||||
tools:text="2분 전" />
|
tools:text="2분 전" />
|
||||||
|
|
||||||
<LinearLayout
|
<ImageButton
|
||||||
android:id="@+id/layout_creator_channel_community_detail_price"
|
android:id="@+id/btn_creator_channel_community_detail_more"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="42dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="42dp"
|
||||||
android:background="@drawable/bg_creator_channel_community_price"
|
android:background="@android:color/transparent"
|
||||||
android:gravity="center"
|
android:contentDescription="@string/read_more"
|
||||||
android:orientation="horizontal"
|
android:src="@drawable/ic_seemore_vertical"
|
||||||
android:paddingHorizontal="@dimen/spacing_4"
|
|
||||||
android:paddingVertical="2dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_community_detail_profile"
|
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_community_detail_profile"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="18dp"
|
|
||||||
android:layout_height="18dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_bar_cash" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_creator_channel_community_detail_price"
|
|
||||||
style="@style/Typography.Caption3"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="2dp"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
tools:text="300" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -222,22 +203,6 @@
|
|||||||
tools:text="Audio"
|
tools:text="Audio"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_creator_channel_community_detail_purchased"
|
|
||||||
style="@style/Typography.Caption2"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginTop="@dimen/spacing_8"
|
|
||||||
android:background="@drawable/bg_creator_channel_community_price"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:paddingHorizontal="@dimen/spacing_8"
|
|
||||||
android:paddingVertical="@dimen/spacing_4"
|
|
||||||
android:text="@string/creator_channel_community_purchased_badge"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_creator_channel_community_detail_reaction"
|
android:id="@+id/layout_creator_channel_community_detail_reaction"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -122,13 +122,13 @@
|
|||||||
tools:text="300" />
|
tools:text="300" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageView
|
<ImageButton
|
||||||
android:id="@+id/iv_creator_channel_community_list_owner_more"
|
android:id="@+id/btn_creator_channel_community_list_more"
|
||||||
android:layout_width="24dp"
|
android:layout_width="42dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="42dp"
|
||||||
android:layout_marginStart="@dimen/spacing_4"
|
android:background="@android:color/transparent"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@string/read_more"
|
||||||
android:src="@drawable/ic_new_more"
|
android:src="@drawable/ic_seemore_vertical"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -187,32 +187,34 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tv_creator_channel_community_list_locked_price"
|
android:id="@+id/layout_creator_channel_community_list_locked_price"
|
||||||
style="@style/Typography.Body3"
|
|
||||||
android:layout_width="70dp"
|
android:layout_width="70dp"
|
||||||
android:layout_height="36dp"
|
android:layout_height="36dp"
|
||||||
android:layout_marginTop="@dimen/spacing_4"
|
android:layout_marginTop="@dimen/spacing_4"
|
||||||
android:background="@drawable/bg_creator_channel_community_price"
|
android:background="@drawable/bg_creator_channel_community_price"
|
||||||
android:drawableStart="@drawable/ic_bar_cash"
|
|
||||||
android:drawablePadding="@dimen/spacing_6"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:includeFontPadding="false"
|
android:orientation="horizontal"
|
||||||
android:textColor="@color/black"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:text="30"
|
tools:visibility="visible">
|
||||||
tools:visibility="visible" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/iv_creator_channel_community_list_play"
|
android:layout_width="18dp"
|
||||||
android:layout_width="48dp"
|
android:layout_height="18dp"
|
||||||
android:layout_height="48dp"
|
android:contentDescription="@null"
|
||||||
android:layout_gravity="center"
|
android:src="@drawable/ic_bar_cash" />
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_new_player_play"
|
<TextView
|
||||||
android:visibility="gone"
|
android:id="@+id/tv_creator_channel_community_list_locked_price"
|
||||||
tools:visibility="visible" />
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_6"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
tools:text="30" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -8,7 +8,33 @@
|
|||||||
android:padding="@dimen/spacing_14">
|
android:padding="@dimen/spacing_14">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_feed_community_notice"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/spacing_14"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_pin" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/creator_channel_community_notice"
|
||||||
|
android:textColor="@color/green_400" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="42dp"
|
android:layout_height="42dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
@@ -22,9 +48,10 @@
|
|||||||
tools:src="@drawable/ic_launcher_background" />
|
tools:src="@drawable/ic_launcher_background" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_8"
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:layout_weight="1"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -50,6 +77,16 @@
|
|||||||
android:textColor="@color/gray_500"
|
android:textColor="@color/gray_500"
|
||||||
tools:text="2분 전" />
|
tools:text="2분 전" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_feed_community_more"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/read_more"
|
||||||
|
android:src="@drawable/ic_seemore_vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -125,6 +162,7 @@
|
|||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_feed_community_reaction"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:layout_marginTop="@dimen/spacing_16"
|
android:layout_marginTop="@dimen/spacing_16"
|
||||||
@@ -132,6 +170,7 @@
|
|||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:id="@+id/iv_feed_community_comment"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
@@ -148,6 +187,7 @@
|
|||||||
tools:text="5" />
|
tools:text="5" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:id="@+id/iv_feed_community_like"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
android:layout_marginStart="15dp"
|
android:layout_marginStart="15dp"
|
||||||
|
|||||||
@@ -44,13 +44,4 @@ class CommunityChangeTest {
|
|||||||
|
|
||||||
assertEquals(List(CommunityActivityResultSource.entries.size) { CommunityChange.Ignored }, changes)
|
assertEquals(List(CommunityActivityResultSource.entries.size) { CommunityChange.Ignored }, changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `삭제와 고정 변경은 post ID를 가진 명시적 변경으로 표현한다`() {
|
|
||||||
val deleted = CommunityChange.Deleted(postId = 31L)
|
|
||||||
val pinChanged = CommunityChange.PinChanged(postId = 32L)
|
|
||||||
|
|
||||||
assertEquals(31L, deleted.postId)
|
|
||||||
assertEquals(32L, pinChanged.postId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,164 @@ class CreatorChannelActivitySourceTest {
|
|||||||
assertTrue(source.contains("private fun handleCommunityChange(change: CommunityChange)"))
|
assertTrue(source.contains("private fun handleCommunityChange(change: CommunityChange)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `홈과 커뮤니티 list 더보기는 동일 menu item을 Activity Host까지 전달한다`() {
|
||||||
|
val adapter = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt"
|
||||||
|
).readText()
|
||||||
|
val homeFragment = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt"
|
||||||
|
).readText()
|
||||||
|
val communityFragment = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt"
|
||||||
|
).readText()
|
||||||
|
val activity = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
adapter.contains("private val onCommunityMoreClick: (CreatorChannelCommunityMenuItem) -> Unit = {}")
|
||||||
|
)
|
||||||
|
assertTrue(adapter.contains("currentUserId = item.currentMemberId"))
|
||||||
|
assertTrue(adapter.contains("showMore = menuItem.showMore"))
|
||||||
|
assertTrue(adapter.contains("setOnMoreClick { onCommunityMoreClick(menuItem) }"))
|
||||||
|
assertTrue(adapter.contains("row.setOnClickListener { onCommunityClick(community.postId) }"))
|
||||||
|
assertTrue(homeFragment.contains("onCommunityMoreClick = ::onCommunityMoreClicked"))
|
||||||
|
assertTrue(homeFragment.contains("host.onCreatorChannelCommunityMoreClicked(item)"))
|
||||||
|
assertTrue(
|
||||||
|
homeFragment.contains("fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem)")
|
||||||
|
)
|
||||||
|
assertTrue(communityFragment.contains("host.onCreatorChannelCommunityMoreClicked(item)"))
|
||||||
|
assertTrue(
|
||||||
|
communityFragment.contains("fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem)")
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
activity.contains(
|
||||||
|
"override fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem)"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `커뮤니티 더보기는 하나의 Activity callback에서 권한별 기존 메뉴와 수정 결과를 연결한다`() {
|
||||||
|
val source = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
||||||
|
).readText()
|
||||||
|
val handlerSource = sourceSection(
|
||||||
|
source = source,
|
||||||
|
startMarker = "override fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem)",
|
||||||
|
endMarker = "override fun onCreatorChannelFanTalkContentChanged"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
1,
|
||||||
|
Regex(
|
||||||
|
Regex.escape(
|
||||||
|
"override fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem)"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.findAll(source)
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
assertTrue(handlerSource.contains("if (!item.showMore) return"))
|
||||||
|
assertTrue(handlerSource.contains("CreatorCommunityPostMenuBottomSheetDialog("))
|
||||||
|
assertTrue(handlerSource.contains("isFixed = item.isPinned"))
|
||||||
|
assertTrue(handlerSource.contains("isCreator = item.isOwner"))
|
||||||
|
assertTrue(handlerSource.contains("onClickPin = { updateCreatorChannelCommunityPostFixed(item) }"))
|
||||||
|
assertTrue(handlerSource.contains("onClickDelete = { showDeleteCreatorChannelCommunityPostDialog(item) }"))
|
||||||
|
assertTrue(handlerSource.contains("CreatorCommunityReportDialog("))
|
||||||
|
assertTrue(handlerSource.contains("if (item.isOwner)"))
|
||||||
|
assertFalse(handlerSource.contains("isCreatorChannelOwner()"))
|
||||||
|
assertTrue(source.contains("private val communityModifyLauncher = registerForActivityResult"))
|
||||||
|
assertTrue(source.contains("CommunityActivityResultSource.Modify"))
|
||||||
|
assertTrue(source.contains("CreatorCommunityModifyActivity::class.java"))
|
||||||
|
assertTrue(source.contains("putExtra(Constants.EXTRA_COMMUNITY_POST_ID, item.postId)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `커뮤니티 고정과 삭제 mutation은 역할과 중복을 재확인하고 성공만 목록을 갱신한다`() {
|
||||||
|
val source = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
||||||
|
).readText()
|
||||||
|
val pinSource = sourceSection(
|
||||||
|
source = source,
|
||||||
|
startMarker = "private fun updateCreatorChannelCommunityPostFixed(item: CreatorChannelCommunityMenuItem)",
|
||||||
|
endMarker = "private fun showDeleteCreatorChannelCommunityPostDialog"
|
||||||
|
)
|
||||||
|
val deleteSource = sourceSection(
|
||||||
|
source = source,
|
||||||
|
startMarker = "private fun deleteCreatorChannelCommunityPost(item: CreatorChannelCommunityMenuItem)",
|
||||||
|
endMarker = "private fun reportCreatorChannelCommunityPost"
|
||||||
|
)
|
||||||
|
val deleteDialogSource = sourceSection(
|
||||||
|
source = source,
|
||||||
|
startMarker = "private fun showDeleteCreatorChannelCommunityPostDialog",
|
||||||
|
endMarker = "private fun deleteCreatorChannelCommunityPost"
|
||||||
|
)
|
||||||
|
|
||||||
|
listOf(pinSource, deleteSource).forEach { mutationSource ->
|
||||||
|
assertTrue(
|
||||||
|
mutationSource.contains(
|
||||||
|
"if (!item.isOwner || isCommunityPostMutating) return"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(mutationSource.contains("isCreatorChannelOwner()"))
|
||||||
|
assertTrue(mutationSource.contains("isCommunityPostMutating = true"))
|
||||||
|
assertEquals(2, "isCommunityPostMutating = false".toRegex().findAll(mutationSource).count())
|
||||||
|
assertTrue(mutationSource.contains("if (response.success)"))
|
||||||
|
assertTrue(mutationSource.contains("handleCommunityChange(CommunityChange.Updated)"))
|
||||||
|
assertTrue(mutationSource.contains("showCommunityMutationFailureToast"))
|
||||||
|
}
|
||||||
|
assertTrue(
|
||||||
|
pinSource.contains(
|
||||||
|
"creatorCommunityRepository.updateCommunityPostFixed(item.postId, !item.isPinned, authToken())"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(deleteDialogSource.contains("if (!item.isOwner) return"))
|
||||||
|
assertFalse(deleteDialogSource.contains("isCreatorChannelOwner()"))
|
||||||
|
assertTrue(deleteSource.contains("ModifyCommunityPostRequest("))
|
||||||
|
assertTrue(deleteSource.contains("creatorCommunityId = item.postId"))
|
||||||
|
assertTrue(deleteSource.contains("isActive = false"))
|
||||||
|
assertTrue(deleteSource.contains("Gson().toJson"))
|
||||||
|
assertTrue(deleteSource.contains("toRequestBody(\"text/plain\".toMediaType())"))
|
||||||
|
assertTrue(
|
||||||
|
deleteSource.contains(
|
||||||
|
"creatorCommunityRepository.modifyCommunityPost(postImage = null, request = request, token = authToken())"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `커뮤니티 신고 mutation은 방문자 역할과 중복을 재확인하고 성공 토스트만 표시한다`() {
|
||||||
|
val source = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
||||||
|
).readText()
|
||||||
|
val reportSource = sourceSection(
|
||||||
|
source = source,
|
||||||
|
startMarker = "private fun reportCreatorChannelCommunityPost(",
|
||||||
|
endMarker = "private fun showCommunityMutationFailureToast"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
reportSource.contains(
|
||||||
|
"if (item.isOwner || !item.showMore || reason.isBlank() || " +
|
||||||
|
"isCommunityPostMutating) return"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(reportSource.contains("isCreatorChannelOwner()"))
|
||||||
|
assertTrue(reportSource.contains("isCommunityPostMutating = true"))
|
||||||
|
assertEquals(2, "isCommunityPostMutating = false".toRegex().findAll(reportSource).count())
|
||||||
|
assertTrue(
|
||||||
|
reportSource.contains(
|
||||||
|
"creatorChannelRepository.reportCommunityPost(item.postId, reason, authToken())"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(reportSource.contains("response.message.isNullOrBlank()"))
|
||||||
|
assertTrue(reportSource.contains("R.string.character_comment_report_submitted"))
|
||||||
|
assertTrue(reportSource.contains("showCommunityMutationFailureToast"))
|
||||||
|
assertFalse(reportSource.contains("handleCommunityChange("))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `follow notify source는 Phase 11 직접 팔로우 알림 액션을 연결한다`() {
|
fun `follow notify source는 Phase 11 직접 팔로우 알림 액션을 연결한다`() {
|
||||||
val source = projectFile(
|
val source = projectFile(
|
||||||
@@ -653,80 +811,12 @@ class CreatorChannelActivitySourceTest {
|
|||||||
assertTrue(source.contains("iconResId = R.drawable.ic_new_upload_community_post"))
|
assertTrue(source.contains("iconResId = R.drawable.ic_new_upload_community_post"))
|
||||||
assertTrue(source.contains("textResId = R.string.creator_channel_owner_fab_community"))
|
assertTrue(source.contains("textResId = R.string.creator_channel_owner_fab_community"))
|
||||||
assertTrue(source.contains("CreatorChannelTab.Community.ordinal -> onOwnerFabCommunityClicked()"))
|
assertTrue(source.contains("CreatorChannelTab.Community.ordinal -> onOwnerFabCommunityClicked()"))
|
||||||
assertTrue(source.contains("private val communityPostModifyLauncher"))
|
assertFalse(source.contains("communityPostModifyLauncher"))
|
||||||
assertTrue(source.contains("CreatorCommunityModifyActivity::class.java"))
|
assertFalse(source.contains("onCreatorChannelCommunityOwnerMoreClicked"))
|
||||||
assertTrue(source.contains("putExtra(Constants.EXTRA_COMMUNITY_POST_ID, item.postId)"))
|
|
||||||
assertTrue(source.contains("creatorCommunityRepository.updateCommunityPostFixed("))
|
|
||||||
assertTrue(source.contains("isFixed = !item.isPinned"))
|
|
||||||
assertTrue(source.contains("creatorCommunityRepository.modifyCommunityPost("))
|
|
||||||
assertTrue(source.contains("isActive = false"))
|
|
||||||
assertTrue(source.contains("findCommunityFragment()?.onCreatorChannelCommunityRefreshRequested()"))
|
assertTrue(source.contains("findCommunityFragment()?.onCreatorChannelCommunityRefreshRequested()"))
|
||||||
assertFalse(source.contains("onCreatorChannelCommunityOwnerMoreClicked(postId: Long)"))
|
|
||||||
assertFalse(fragment.contains("onCreatorChannelCommunityOwnerMoreClicked(item.postId)"))
|
|
||||||
assertFalse(source.contains("onClickPin = {},"))
|
|
||||||
assertFalse(source.contains("onClickModify = {},"))
|
|
||||||
assertFalse(source.contains("onClickDelete = {},"))
|
|
||||||
assertTrue(fragment.contains("fun onCreatorChannelCommunityRefreshRequested()"))
|
assertTrue(fragment.contains("fun onCreatorChannelCommunityRefreshRequested()"))
|
||||||
assertTrue(
|
assertFalse(fragment.contains("CreatorCommunityMediaPlayerManager"))
|
||||||
|
assertFalse(fragment.contains("toggleCommunityAudio"))
|
||||||
fragment.contains(
|
|
||||||
"CreatorCommunityMediaPlayerManager(requireContext()) { listAdapter.notifyDataSetChanged() }"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertTrue(fragment.contains("mediaPlayerManager?.toggleContent(CreatorCommunityContentItem(item.postId, audioUrl))"))
|
|
||||||
assertTrue(fragment.contains("mediaPlayerManager?.stopContent()"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `커뮤니티 고정 성공은 실제 post id로 단일 변경 handler를 호출한다`() {
|
|
||||||
val source = projectFile(
|
|
||||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
|
||||||
).readText()
|
|
||||||
val fixedSource = sourceSection(
|
|
||||||
source = source,
|
|
||||||
startMarker = "private fun updateCreatorChannelCommunityPostFixed",
|
|
||||||
endMarker = "private fun deleteCreatorChannelCommunityPost"
|
|
||||||
)
|
|
||||||
val successMarker = "if (response.success)"
|
|
||||||
val beforeSuccessSource = fixedSource.substring(0, fixedSource.indexOf(successMarker))
|
|
||||||
val successSource = fixedSource.substring(
|
|
||||||
fixedSource.indexOf(successMarker),
|
|
||||||
fixedSource.indexOf("} else", fixedSource.indexOf(successMarker))
|
|
||||||
)
|
|
||||||
|
|
||||||
assertTrue(fixedSource.contains("postId = item.postId"))
|
|
||||||
assertTrue(fixedSource.contains("isFixed = !item.isPinned"))
|
|
||||||
assertTrue(successSource.contains("handleCommunityChange(CommunityChange.PinChanged(item.postId))"))
|
|
||||||
assertFalse(beforeSuccessSource.contains("CommunityChange.PinChanged"))
|
|
||||||
assertEquals(1, fixedSource.split("CommunityChange.PinChanged").size - 1)
|
|
||||||
assertFalse(fixedSource.contains("CommunityChange.PinChanged(creatorId)"))
|
|
||||||
assertFalse(fixedSource.contains("refreshCreatorChannelCommunity()"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `커뮤니티 삭제 성공은 실제 post id로 단일 변경 handler를 호출한다`() {
|
|
||||||
val source = projectFile(
|
|
||||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
|
||||||
).readText()
|
|
||||||
val deleteSource = sourceSection(
|
|
||||||
source = source,
|
|
||||||
startMarker = "private fun deleteCreatorChannelCommunityPost",
|
|
||||||
endMarker = "private fun authToken()"
|
|
||||||
)
|
|
||||||
val successMarker = "if (response.success)"
|
|
||||||
val beforeSuccessSource = deleteSource.substring(0, deleteSource.indexOf(successMarker))
|
|
||||||
val successSource = deleteSource.substring(
|
|
||||||
deleteSource.indexOf(successMarker),
|
|
||||||
deleteSource.indexOf("} else", deleteSource.indexOf(successMarker))
|
|
||||||
)
|
|
||||||
|
|
||||||
assertTrue(deleteSource.contains("creatorCommunityId = item.postId"))
|
|
||||||
assertTrue(deleteSource.contains("isActive = false"))
|
|
||||||
assertTrue(successSource.contains("handleCommunityChange(CommunityChange.Deleted(item.postId))"))
|
|
||||||
assertFalse(beforeSuccessSource.contains("CommunityChange.Deleted"))
|
|
||||||
assertEquals(1, deleteSource.split("CommunityChange.Deleted").size - 1)
|
|
||||||
assertFalse(deleteSource.contains("CommunityChange.Deleted(creatorId)"))
|
|
||||||
assertFalse(deleteSource.contains("refreshCreatorChannelCommunity()"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -743,18 +833,14 @@ class CreatorChannelActivitySourceTest {
|
|||||||
handlerSource.indexOf("CommunityChange.Created"),
|
handlerSource.indexOf("CommunityChange.Created"),
|
||||||
handlerSource.indexOf("CommunityChange.Updated")
|
handlerSource.indexOf("CommunityChange.Updated")
|
||||||
)
|
)
|
||||||
val mutationSource = handlerSource.substring(handlerSource.indexOf("CommunityChange.Updated"))
|
val updatedSource = handlerSource.substring(handlerSource.indexOf("CommunityChange.Updated"))
|
||||||
|
|
||||||
assertTrue(createdSource.contains("homeActionDelegate?.refreshHome()"))
|
assertTrue(createdSource.contains("homeActionDelegate?.refreshHome()"))
|
||||||
assertTrue(createdSource.contains("refreshCreatorChannelCommunity()"))
|
assertTrue(createdSource.contains("refreshCreatorChannelCommunity()"))
|
||||||
assertTrue(
|
assertTrue(updatedSource.contains("homeActionDelegate?.refreshHome()"))
|
||||||
mutationSource.contains(
|
assertTrue(updatedSource.contains("refreshCreatorChannelCommunity()"))
|
||||||
"CommunityChange.Updated,\n" +
|
assertFalse(updatedSource.contains("CommunityChange.Deleted"))
|
||||||
" is CommunityChange.Deleted,\n" +
|
assertFalse(updatedSource.contains("CommunityChange.PinChanged"))
|
||||||
" is CommunityChange.PinChanged -> refreshCreatorChannelCommunity()"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertFalse(mutationSource.contains("homeActionDelegate?.refreshHome()"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -2003,7 +2089,7 @@ class CreatorChannelActivitySourceTest {
|
|||||||
assertTrue(adapter.contains("val communityWidthDp = calculateCreatorChannelCommunityCardWidthDp("))
|
assertTrue(adapter.contains("val communityWidthDp = calculateCreatorChannelCommunityCardWidthDp("))
|
||||||
assertTrue(adapter.contains("rootWidthDp = communityWidthDp"))
|
assertTrue(adapter.contains("rootWidthDp = communityWidthDp"))
|
||||||
assertTrue(adapter.contains("setHideEmptyTextRows(true)"))
|
assertTrue(adapter.contains("setHideEmptyTextRows(true)"))
|
||||||
assertTrue(adapter.contains("bind(community.toFeedCommunityItem())"))
|
assertTrue(adapter.contains("bind(community.toFeedCommunityItem(showMore = menuItem.showMore))"))
|
||||||
assertTrue(adapter.contains("bindCommunityImages(row, community)"))
|
assertTrue(adapter.contains("bindCommunityImages(row, community)"))
|
||||||
assertTrue(adapter.contains("BlurTransformation(itemView.context, 25f, 2.5f)"))
|
assertTrue(adapter.contains("BlurTransformation(itemView.context, 25f, 2.5f)"))
|
||||||
assertTrue(adapter.contains("row.layoutParams = LinearLayout.LayoutParams("))
|
assertTrue(adapter.contains("row.layoutParams = LinearLayout.LayoutParams("))
|
||||||
|
|||||||
@@ -61,6 +61,16 @@ class CreatorChannelHomeMapperTest {
|
|||||||
assertFalse(visitorContent.sections.filterIsInstance<CreatorChannelHomeSection.Donations>().single().isOwner)
|
assertFalse(visitorContent.sections.filterIsInstance<CreatorChannelHomeSection.Donations>().single().isOwner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `현재 회원 id를 커뮤니티 section에 보존한다`() {
|
||||||
|
val communities = response().toUiContent(currentMemberId = 77L)
|
||||||
|
.sections
|
||||||
|
.filterIsInstance<CreatorChannelHomeSection.Communities>()
|
||||||
|
.single()
|
||||||
|
|
||||||
|
assertEquals(77L, communities.currentMemberId)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `null 단건 콘텐츠와 빈 리스트와 blank SNS는 후원 empty와 팬Talk empty section을 생성한다`() {
|
fun `null 단건 콘텐츠와 빈 리스트와 blank SNS는 후원 empty와 팬Talk empty section을 생성한다`() {
|
||||||
val content = response(
|
val content = response(
|
||||||
@@ -267,7 +277,9 @@ class CreatorChannelHomeMapperTest {
|
|||||||
dateUtc = "2026-06-11T12:00:00Z",
|
dateUtc = "2026-06-11T12:00:00Z",
|
||||||
existOrdered = false,
|
existOrdered = false,
|
||||||
likeCount = 1,
|
likeCount = 1,
|
||||||
commentCount = 2
|
commentCount = 2,
|
||||||
|
isPinned = false,
|
||||||
|
isCommentAvailable = true
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun fanTalk() = CreatorChannelFanTalkSummaryResponse(
|
private fun fanTalk() = CreatorChannelFanTalkSummaryResponse(
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import com.google.gson.Gson
|
|||||||
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelHomeResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelHomeResponse
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class CreatorChannelHomeModelsTest {
|
class CreatorChannelHomeModelsTest {
|
||||||
@@ -36,6 +38,15 @@ class CreatorChannelHomeModelsTest {
|
|||||||
assertEquals("https://x.example", response.sns.xUrl)
|
assertEquals("https://x.example", response.sns.xUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home response preserves community pinned and comment availability`() {
|
||||||
|
val response = gson.fromJson(homeJson(scheduleType = "AUDIO"), CreatorChannelHomeResponse::class.java)
|
||||||
|
|
||||||
|
val community = response.communities.single()
|
||||||
|
assertTrue(community.isPinned)
|
||||||
|
assertFalse(community.isCommentAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
private fun homeJson(scheduleType: String): String = """
|
private fun homeJson(scheduleType: String): String = """
|
||||||
{
|
{
|
||||||
"creator": {
|
"creator": {
|
||||||
@@ -63,7 +74,24 @@ class CreatorChannelHomeModelsTest {
|
|||||||
],
|
],
|
||||||
"audioContents": [],
|
"audioContents": [],
|
||||||
"series": [],
|
"series": [],
|
||||||
"communities": [],
|
"communities": [
|
||||||
|
{
|
||||||
|
"postId": 21,
|
||||||
|
"creatorId": 1,
|
||||||
|
"creatorNickname": "creator",
|
||||||
|
"creatorProfileUrl": "https://example.com/profile.png",
|
||||||
|
"imageUrl": null,
|
||||||
|
"audioUrl": null,
|
||||||
|
"content": "community",
|
||||||
|
"price": 0,
|
||||||
|
"dateUtc": "2026-06-12T00:00:00Z",
|
||||||
|
"existOrdered": false,
|
||||||
|
"likeCount": 6,
|
||||||
|
"commentCount": 5,
|
||||||
|
"isPinned": true,
|
||||||
|
"isCommentAvailable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
"fanTalk": {
|
"fanTalk": {
|
||||||
"totalCount": 0,
|
"totalCount": 0,
|
||||||
"latestFanTalk": null
|
"latestFanTalk": null
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -29,6 +30,7 @@ class CreatorChannelCommunityDetailReplyUiContractTest {
|
|||||||
val reply = inflateView(R.layout.activity_creator_channel_community_reply)
|
val reply = inflateView(R.layout.activity_creator_channel_community_reply)
|
||||||
|
|
||||||
assertNotNull(detail.findViewById<ImageButton>(R.id.btn_creator_channel_community_detail_back))
|
assertNotNull(detail.findViewById<ImageButton>(R.id.btn_creator_channel_community_detail_back))
|
||||||
|
assertNotNull(detail.findViewById<ImageButton>(R.id.btn_creator_channel_community_detail_more))
|
||||||
assertNotNull(detail.findViewById<TextView>(R.id.tv_creator_channel_community_detail_comment_header))
|
assertNotNull(detail.findViewById<TextView>(R.id.tv_creator_channel_community_detail_comment_header))
|
||||||
assertNotNull(detail.findViewById<RecyclerView>(R.id.rv_creator_channel_community_detail_comments))
|
assertNotNull(detail.findViewById<RecyclerView>(R.id.rv_creator_channel_community_detail_comments))
|
||||||
assertNotNull(detail.findViewById<View>(R.id.layout_creator_channel_community_detail_paywall))
|
assertNotNull(detail.findViewById<View>(R.id.layout_creator_channel_community_detail_paywall))
|
||||||
@@ -39,6 +41,9 @@ class CreatorChannelCommunityDetailReplyUiContractTest {
|
|||||||
assertNotNull(detail.findViewById<ImageButton>(R.id.btn_creator_channel_community_detail_send))
|
assertNotNull(detail.findViewById<ImageButton>(R.id.btn_creator_channel_community_detail_send))
|
||||||
assertNotNull(detail.findViewById<ImageButton>(R.id.btn_creator_channel_community_detail_edit_cancel))
|
assertNotNull(detail.findViewById<ImageButton>(R.id.btn_creator_channel_community_detail_edit_cancel))
|
||||||
assertRemovedId("tv_creator_channel_community_detail_title")
|
assertRemovedId("tv_creator_channel_community_detail_title")
|
||||||
|
assertRemovedId("layout_creator_channel_community_detail_price")
|
||||||
|
assertRemovedId("tv_creator_channel_community_detail_price")
|
||||||
|
assertRemovedId("tv_creator_channel_community_detail_purchased")
|
||||||
assertRemovedId("tv_creator_channel_community_detail_comment_unavailable")
|
assertRemovedId("tv_creator_channel_community_detail_comment_unavailable")
|
||||||
|
|
||||||
assertNotNull(reply.findViewById<ImageButton>(R.id.btn_creator_channel_community_reply_back))
|
assertNotNull(reply.findViewById<ImageButton>(R.id.btn_creator_channel_community_reply_back))
|
||||||
@@ -156,6 +161,23 @@ class CreatorChannelCommunityDetailReplyUiContractTest {
|
|||||||
assertTrue(detailActivity.contains("btn_audio_content_play"))
|
assertTrue(detailActivity.contains("btn_audio_content_play"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `상세 게시글 더보기는 기존 관리 신고 UI와 수정 launcher를 재사용한다`() {
|
||||||
|
val detailActivity = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/" +
|
||||||
|
"CreatorChannelCommunityDetailActivity.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertTrue(detailActivity.contains("CreatorCommunityPostMenuBottomSheetDialog"))
|
||||||
|
assertTrue(detailActivity.contains("CreatorCommunityReportDialog"))
|
||||||
|
assertTrue(detailActivity.contains("CreatorCommunityModifyActivity"))
|
||||||
|
assertTrue(detailActivity.contains("modifyLauncher"))
|
||||||
|
assertTrue(detailActivity.contains("Constants.EXTRA_COMMUNITY_POST_ID"))
|
||||||
|
assertTrue(detailActivity.contains("btnCreatorChannelCommunityDetailMore"))
|
||||||
|
assertFalse(detailActivity.contains("layoutCreatorChannelCommunityDetailPrice"))
|
||||||
|
assertFalse(detailActivity.contains("tvCreatorChannelCommunityDetailPurchased"))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `댓글 답글 Adapter는 PopupMenu 대신 regular font popup을 사용한다`() {
|
fun `댓글 답글 Adapter는 PopupMenu 대신 regular font popup을 사용한다`() {
|
||||||
val commentAdapter = projectFile(
|
val commentAdapter = projectFile(
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.kotlin.any
|
import org.mockito.kotlin.any
|
||||||
import org.mockito.kotlin.argThat
|
import org.mockito.kotlin.argThat
|
||||||
|
import org.mockito.kotlin.eq
|
||||||
|
import org.mockito.kotlin.isNull
|
||||||
import org.mockito.kotlin.never
|
import org.mockito.kotlin.never
|
||||||
import org.mockito.kotlin.times
|
import org.mockito.kotlin.times
|
||||||
import org.mockito.kotlin.verify
|
import org.mockito.kotlin.verify
|
||||||
@@ -469,6 +471,99 @@ class CreatorChannelCommunityDetailViewModelTest {
|
|||||||
verify(repository, times(2)).getCommunityPostDetail(POST_ID, AUTH_TOKEN)
|
verify(repository, times(2)).getCommunityPostDetail(POST_ID, AUTH_TOKEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `게시글 더보기는 작성자 또는 무료 또는 구매 완료일 때만 표시한다`() {
|
||||||
|
SharedPreferenceManager.userId = 10L
|
||||||
|
val cases = listOf(
|
||||||
|
detailResponse(isCommentAvailable = false, creatorId = 10L, price = 300, existOrdered = false),
|
||||||
|
detailResponse(isCommentAvailable = false, creatorId = 999L, price = 0, existOrdered = false),
|
||||||
|
detailResponse(isCommentAvailable = false, creatorId = 999L, price = 300, existOrdered = true),
|
||||||
|
detailResponse(isCommentAvailable = false, creatorId = 999L, price = 300, existOrdered = false)
|
||||||
|
)
|
||||||
|
|
||||||
|
val results = cases.map { response ->
|
||||||
|
stubDetail(response)
|
||||||
|
viewModel.loadDetail(POST_ID)
|
||||||
|
(viewModel.detailStateLiveData.requireValue() as CreatorChannelCommunityDetailUiState.Content).post
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(results[0].isOwner)
|
||||||
|
assertTrue(results[0].showMore)
|
||||||
|
assertFalse(results[1].isOwner)
|
||||||
|
assertTrue(results[1].showMore)
|
||||||
|
assertTrue(results[2].showMore)
|
||||||
|
assertFalse(results[3].showMore)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `게시글 고정 성공은 반전 값을 요청하고 상세 재조회와 변경 이벤트를 노출한다`() {
|
||||||
|
whenever(repository.getCommunityPostDetail(POST_ID, AUTH_TOKEN)).thenReturn(
|
||||||
|
Single.just(ApiResponse(true, detailResponse(isCommentAvailable = false, creatorId = 10L, isPinned = false), null)),
|
||||||
|
Single.just(ApiResponse(true, detailResponse(isCommentAvailable = false, creatorId = 10L, isPinned = true), null))
|
||||||
|
)
|
||||||
|
whenever(legacyRepository.updateCommunityPostFixed(POST_ID, true, AUTH_TOKEN))
|
||||||
|
.thenReturn(Single.just(ApiResponse(true, Any(), null)))
|
||||||
|
viewModel.loadDetail(POST_ID)
|
||||||
|
|
||||||
|
viewModel.updateCommunityPostFixed()
|
||||||
|
|
||||||
|
val state = viewModel.detailStateLiveData.requireValue() as CreatorChannelCommunityDetailUiState.Content
|
||||||
|
assertTrue(state.post.isPinned)
|
||||||
|
assertTrue(viewModel.postChangedEventLiveData.requireValue() == true)
|
||||||
|
verify(legacyRepository).updateCommunityPostFixed(POST_ID, true, AUTH_TOKEN)
|
||||||
|
verify(repository, times(2)).getCommunityPostDetail(POST_ID, AUTH_TOKEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `게시글 삭제 성공은 삭제 이벤트를 노출한다`() {
|
||||||
|
stubDetail(detailResponse(isCommentAvailable = false, creatorId = 10L))
|
||||||
|
whenever(legacyRepository.modifyCommunityPost(isNull(), any(), eq(AUTH_TOKEN)))
|
||||||
|
.thenReturn(Single.just(ApiResponse(true, Any(), null)))
|
||||||
|
viewModel.loadDetail(POST_ID)
|
||||||
|
|
||||||
|
viewModel.deleteCommunityPost()
|
||||||
|
|
||||||
|
assertTrue(viewModel.postDeletedEventLiveData.requireValue() == true)
|
||||||
|
verify(legacyRepository).modifyCommunityPost(
|
||||||
|
isNull(),
|
||||||
|
argThat { requestBody ->
|
||||||
|
okio.Buffer().also(requestBody::writeTo).readUtf8().contains("\"isActive\":false")
|
||||||
|
},
|
||||||
|
eq(AUTH_TOKEN)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `게시글 신고 성공은 repository 호출과 toast만 노출한다`() {
|
||||||
|
stubDetail(detailResponse(isCommentAvailable = false, creatorId = 999L))
|
||||||
|
whenever(repository.reportCommunityPost(POST_ID, "스팸", AUTH_TOKEN))
|
||||||
|
.thenReturn(Single.just(ApiResponse(true, Any(), "접수됨")))
|
||||||
|
viewModel.loadDetail(POST_ID)
|
||||||
|
|
||||||
|
viewModel.reportCommunityPost("스팸")
|
||||||
|
|
||||||
|
verify(repository).reportCommunityPost(POST_ID, "스팸", AUTH_TOKEN)
|
||||||
|
assertEquals("접수됨", viewModel.toastLiveData.requireValue()?.consume()?.message)
|
||||||
|
assertFalse(viewModel.postChangedEventLiveData.requireValue() == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `게시글 mutation 진행 중에는 중복 요청을 막고 실패 시 성공 이벤트를 노출하지 않는다`() {
|
||||||
|
stubDetail(detailResponse(isCommentAvailable = false, creatorId = 10L))
|
||||||
|
val subject = PublishSubject.create<ApiResponse<Any>>()
|
||||||
|
whenever(legacyRepository.updateCommunityPostFixed(POST_ID, true, AUTH_TOKEN))
|
||||||
|
.thenReturn(subject.firstOrError())
|
||||||
|
viewModel.loadDetail(POST_ID)
|
||||||
|
|
||||||
|
viewModel.updateCommunityPostFixed()
|
||||||
|
viewModel.updateCommunityPostFixed()
|
||||||
|
subject.onNext(ApiResponse(false, null, "고정 실패"))
|
||||||
|
|
||||||
|
verify(legacyRepository, times(1)).updateCommunityPostFixed(POST_ID, true, AUTH_TOKEN)
|
||||||
|
assertEquals("고정 실패", viewModel.toastLiveData.requireValue()?.consume()?.message)
|
||||||
|
assertFalse(viewModel.postChangedEventLiveData.requireValue() == true)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `댓글 UI 모델은 답글 화면 재포맷을 위해 원본 createdAtUtc를 보존한다`() {
|
fun `댓글 UI 모델은 답글 화면 재포맷을 위해 원본 createdAtUtc를 보존한다`() {
|
||||||
stubDetail(detailResponse(isCommentAvailable = true, commentIds = listOf(11L)))
|
stubDetail(detailResponse(isCommentAvailable = true, commentIds = listOf(11L)))
|
||||||
@@ -571,7 +666,8 @@ class CreatorChannelCommunityDetailViewModelTest {
|
|||||||
existOrdered: Boolean = true,
|
existOrdered: Boolean = true,
|
||||||
creatorId: Long = 100L,
|
creatorId: Long = 100L,
|
||||||
commentIds: List<Long> = emptyList(),
|
commentIds: List<Long> = emptyList(),
|
||||||
commentsHasNext: Boolean = false
|
commentsHasNext: Boolean = false,
|
||||||
|
isPinned: Boolean = false
|
||||||
) = CreatorChannelCommunityPostDetailResponse(
|
) = CreatorChannelCommunityPostDetailResponse(
|
||||||
postId = POST_ID,
|
postId = POST_ID,
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
@@ -587,7 +683,7 @@ class CreatorChannelCommunityDetailViewModelTest {
|
|||||||
likeCount = likeCount,
|
likeCount = likeCount,
|
||||||
commentCount = 2,
|
commentCount = 2,
|
||||||
isLiked = isLiked,
|
isLiked = isLiked,
|
||||||
isPinned = false,
|
isPinned = isPinned,
|
||||||
comments = commentsResponse(ids = commentIds, hasNext = commentsHasNext)
|
comments = commentsResponse(ids = commentIds, hasNext = commentsHasNext)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.test.core.app.ApplicationProvider
|
|||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.ui.calculateCreatorChannelCommunityGridItemSize
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.ui.calculateCreatorChannelCommunityGridItemSize
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertSame
|
import org.junit.Assert.assertSame
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
@@ -62,7 +63,7 @@ class CreatorChannelCommunityFragmentLayoutTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `커뮤니티 list item layout은 프로필 본문 이미지 잠금 재생 반응 owner 영역을 제공한다`() {
|
fun `커뮤니티 list item layout은 프로필 본문 이미지 잠금 반응과 작성자 금액을 제공한다`() {
|
||||||
val item = inflateView(R.layout.item_creator_channel_community_list)
|
val item = inflateView(R.layout.item_creator_channel_community_list)
|
||||||
val itemLayout = projectFile("app/src/main/res/layout/item_creator_channel_community_list.xml").readText()
|
val itemLayout = projectFile("app/src/main/res/layout/item_creator_channel_community_list.xml").readText()
|
||||||
val imageContainer = requireNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_image_container))
|
val imageContainer = requireNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_image_container))
|
||||||
@@ -77,24 +78,29 @@ class CreatorChannelCommunityFragmentLayoutTest {
|
|||||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_image))
|
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_image))
|
||||||
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_locked_overlay))
|
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_locked_overlay))
|
||||||
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_locked_price))
|
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_locked_price))
|
||||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_play))
|
|
||||||
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_comment_count))
|
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_comment_count))
|
||||||
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_like_count))
|
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_like_count))
|
||||||
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_top_actions))
|
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_top_actions))
|
||||||
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_top_price))
|
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_top_price))
|
||||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_owner_more))
|
|
||||||
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_top_price))
|
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_top_price))
|
||||||
|
val moreButton = requireNotNull(item.findViewById<View>(R.id.btn_creator_channel_community_list_more))
|
||||||
assertSame(imageContainer, (lockIcon.parent as View).parent)
|
assertSame(imageContainer, (lockIcon.parent as View).parent)
|
||||||
assertSame(imageContainer, (lockedPrice.parent as View).parent)
|
assertSame(imageContainer, ((lockedPrice.parent as View).parent as View).parent)
|
||||||
assertSame(
|
assertSame(
|
||||||
item.findViewById<View>(R.id.layout_creator_channel_community_list_top_actions),
|
item.findViewById<View>(R.id.layout_creator_channel_community_list_top_actions),
|
||||||
item.findViewById<View>(R.id.layout_creator_channel_community_list_top_price).parent
|
item.findViewById<View>(R.id.layout_creator_channel_community_list_top_price).parent
|
||||||
)
|
)
|
||||||
|
assertSame(
|
||||||
|
item.findViewById<View>(R.id.layout_creator_channel_community_list_top_actions),
|
||||||
|
moreButton.parent
|
||||||
|
)
|
||||||
assertTrue(itemLayout.contains("android:id=\"@+id/layout_creator_channel_community_list_locked_overlay\""))
|
assertTrue(itemLayout.contains("android:id=\"@+id/layout_creator_channel_community_list_locked_overlay\""))
|
||||||
assertTrue(itemLayout.contains("android:id=\"@+id/tv_creator_channel_community_list_locked_price\""))
|
assertTrue(itemLayout.contains("android:id=\"@+id/tv_creator_channel_community_list_locked_price\""))
|
||||||
assertTrue(itemLayout.contains("android:background=\"@drawable/bg_creator_channel_community_price\""))
|
assertTrue(itemLayout.contains("android:background=\"@drawable/bg_creator_channel_community_price\""))
|
||||||
assertTrue(itemLayout.contains("android:drawableStart=\"@drawable/ic_bar_cash\""))
|
assertFalse(itemLayout.contains("android:drawableStart=\"@drawable/ic_bar_cash\""))
|
||||||
assertTrue(itemLayout.contains("android:id=\"@+id/iv_creator_channel_community_list_play\""))
|
assertTrue(itemLayout.contains("android:src=\"@drawable/ic_bar_cash\""))
|
||||||
|
assertFalse(itemLayout.contains("iv_creator_channel_community_list_play"))
|
||||||
|
assertFalse(itemLayout.contains("iv_creator_channel_community_list_owner_more"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -138,7 +144,7 @@ class CreatorChannelCommunityFragmentLayoutTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `커뮤니티 fragment source는 pagination view mode owner padding stopContent 계약을 사용한다`() {
|
fun `커뮤니티 fragment source는 pagination view mode owner padding과 상세 이동 계약을 사용한다`() {
|
||||||
val fragment = projectFile(
|
val fragment = projectFile(
|
||||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt"
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt"
|
||||||
).readText()
|
).readText()
|
||||||
@@ -161,8 +167,10 @@ class CreatorChannelCommunityFragmentLayoutTest {
|
|||||||
assertTrue(fragment.contains("calculateCreatorChannelCommunityGridItemSize("))
|
assertTrue(fragment.contains("calculateCreatorChannelCommunityGridItemSize("))
|
||||||
assertTrue(fragment.contains("viewModel.consumePaginationErrorMessage()"))
|
assertTrue(fragment.contains("viewModel.consumePaginationErrorMessage()"))
|
||||||
assertTrue(fragment.contains("applyOwnerCtaPadding"))
|
assertTrue(fragment.contains("applyOwnerCtaPadding"))
|
||||||
assertTrue(fragment.contains("pauseContent"))
|
assertFalse(fragment.contains("CreatorCommunityMediaPlayerManager"))
|
||||||
assertTrue(fragment.contains("stopContent"))
|
assertFalse(fragment.contains("toggleCommunityAudio"))
|
||||||
|
assertFalse(fragment.contains("onPlayClick"))
|
||||||
|
assertFalse(fragment.contains("onCreatorChannelCommunityOwnerMoreClicked"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -181,19 +189,25 @@ class CreatorChannelCommunityFragmentLayoutTest {
|
|||||||
assertTrue(listAdapter.contains("showComment") && listAdapter.contains("isVisible = item.showComment"))
|
assertTrue(listAdapter.contains("showComment") && listAdapter.contains("isVisible = item.showComment"))
|
||||||
assertTrue(listAdapter.contains("isLocked"))
|
assertTrue(listAdapter.contains("isLocked"))
|
||||||
assertTrue(gridAdapter.contains("isLocked"))
|
assertTrue(gridAdapter.contains("isLocked"))
|
||||||
assertTrue(listAdapter.contains("showPlayButton"))
|
|
||||||
assertTrue(listAdapter.contains("showOwnerMore"))
|
|
||||||
assertTrue(listAdapter.contains("isPlayingContent"))
|
|
||||||
assertTrue(listAdapter.contains("R.drawable.ic_player_pause"))
|
|
||||||
assertTrue(listAdapter.contains("R.drawable.ic_new_player_play"))
|
|
||||||
assertTrue(listAdapter.contains("private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}"))
|
assertTrue(listAdapter.contains("private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}"))
|
||||||
|
assertTrue(listAdapter.contains("private val onMoreClick: (CreatorChannelCommunityMenuItem) -> Unit = {}"))
|
||||||
assertTrue(listAdapter.contains("root.setOnClickListener { onPostClick(item) }"))
|
assertTrue(listAdapter.contains("root.setOnClickListener { onPostClick(item) }"))
|
||||||
assertTrue(listAdapter.contains("ivCreatorChannelCommunityListPlay.setOnClickListener { onPlayClick(item) }"))
|
assertTrue(
|
||||||
assertTrue(listAdapter.contains("onOwnerMoreClick(item)"))
|
listAdapter.contains(
|
||||||
assertTrue(listAdapter.contains("ivCreatorChannelCommunityListOwnerMore.setOnClickListener { onOwnerMoreClick(item) }"))
|
"btnCreatorChannelCommunityListMore.setOnClickListener { onMoreClick(item.menuItem) }"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(listAdapter.contains("item.showOwnerTopPrice || item.menuItem.showMore"))
|
||||||
|
assertFalse(listAdapter.contains("onPlayClick"))
|
||||||
|
assertFalse(listAdapter.contains("onOwnerMoreClick"))
|
||||||
|
assertFalse(listAdapter.contains("isPlayingContent"))
|
||||||
|
assertFalse(listAdapter.contains("showPlayButton"))
|
||||||
|
assertFalse(listAdapter.contains("showOwnerMore"))
|
||||||
assertTrue(gridAdapter.contains("private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}"))
|
assertTrue(gridAdapter.contains("private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}"))
|
||||||
assertTrue(gridAdapter.contains("root.setOnClickListener { onPostClick(item) }"))
|
assertTrue(gridAdapter.contains("root.setOnClickListener { onPostClick(item) }"))
|
||||||
assertTrue(listAdapter.contains("tvCreatorChannelCommunityListLockedPrice.isVisible = item.isLocked"))
|
assertFalse(gridAdapter.contains("onMoreClick"))
|
||||||
|
assertFalse(gridAdapter.contains("CreatorChannelCommunityMenuItem"))
|
||||||
|
assertTrue(listAdapter.contains("layoutCreatorChannelCommunityListLockedPrice.isVisible = item.isLocked"))
|
||||||
assertTrue(listAdapter.contains("layoutCreatorChannelCommunityListTopPrice.isVisible = item.showOwnerTopPrice"))
|
assertTrue(listAdapter.contains("layoutCreatorChannelCommunityListTopPrice.isVisible = item.showOwnerTopPrice"))
|
||||||
assertTrue(!listAdapter.contains("item.isLocked || item.showOwnerTopPrice"))
|
assertTrue(!listAdapter.contains("item.isLocked || item.showOwnerTopPrice"))
|
||||||
assertTrue(gridAdapter.contains(".asBitmap()"))
|
assertTrue(gridAdapter.contains(".asBitmap()"))
|
||||||
@@ -211,8 +225,17 @@ class CreatorChannelCommunityFragmentLayoutTest {
|
|||||||
).readText()
|
).readText()
|
||||||
|
|
||||||
assertTrue(fragment.contains("onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) }"))
|
assertTrue(fragment.contains("onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) }"))
|
||||||
|
assertTrue(fragment.contains("onMoreClick = { item -> host.onCreatorChannelCommunityMoreClicked(item) }"))
|
||||||
assertTrue(fragment.contains("private val gridAdapter = CreatorChannelCommunityGridAdapter("))
|
assertTrue(fragment.contains("private val gridAdapter = CreatorChannelCommunityGridAdapter("))
|
||||||
assertTrue(fragment.contains("fun onCreatorChannelCommunityPostClicked(postId: Long)"))
|
assertTrue(fragment.contains("fun onCreatorChannelCommunityPostClicked(postId: Long)"))
|
||||||
|
assertTrue(fragment.contains("fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem)"))
|
||||||
|
assertFalse(fragment.contains("onPlayClick"))
|
||||||
|
assertFalse(fragment.contains("onCreatorChannelCommunityOwnerMoreClicked"))
|
||||||
|
assertFalse(
|
||||||
|
fragment.substringAfter("private val gridAdapter = CreatorChannelCommunityGridAdapter(")
|
||||||
|
.substringBefore(")\n private var currentContentState")
|
||||||
|
.contains("onMoreClick")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class CreatorChannelCommunityMapperTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `유료 미구매 타인 게시글은 잠금 상태이고 play button을 숨긴다`() {
|
fun `유료 미구매 타인 게시글은 잠금 상태이고 이미지를 숨긴다`() {
|
||||||
val item = listOf(
|
val item = listOf(
|
||||||
communityPost(price = 100, existOrdered = false, imageUrl = "image.png", audioUrl = "audio.mp3")
|
communityPost(price = 100, existOrdered = false, imageUrl = "image.png", audioUrl = "audio.mp3")
|
||||||
).toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L).single()
|
).toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L).single()
|
||||||
@@ -74,7 +74,6 @@ class CreatorChannelCommunityMapperTest {
|
|||||||
assertTrue(item.isLocked)
|
assertTrue(item.isLocked)
|
||||||
assertNull(item.imageUrl)
|
assertNull(item.imageUrl)
|
||||||
assertEquals(CreatorChannelCommunityImageMode.LockedGray, item.imageMode)
|
assertEquals(CreatorChannelCommunityImageMode.LockedGray, item.imageMode)
|
||||||
assertFalse(item.showPlayButton)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -93,7 +92,7 @@ class CreatorChannelCommunityMapperTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `고정 게시글 여부는 owner more 정책에서 사용할 수 있도록 UI 모델에 보존한다`() {
|
fun `고정 게시글 여부는 UI 모델에 보존한다`() {
|
||||||
val pinnedItem = listOf(communityPost(isPinned = true))
|
val pinnedItem = listOf(communityPost(isPinned = true))
|
||||||
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L)
|
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L)
|
||||||
.single()
|
.single()
|
||||||
@@ -108,33 +107,32 @@ class CreatorChannelCommunityMapperTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `본인 또는 구매한 사용자는 이미지와 오디오가 있으면 play button을 표시한다`() {
|
fun `메뉴 권한은 작성자 무료 구매 여부에 따라 계산하고 고정 상태를 보존한다`() {
|
||||||
val ownerItem = listOf(
|
val authorPaidNotOrdered = listOf(communityPost(price = 100, existOrdered = false, isPinned = true))
|
||||||
communityPost(price = 100, existOrdered = false, imageUrl = "image.png", audioUrl = "audio.mp3")
|
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L)
|
||||||
).toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L).single()
|
.single()
|
||||||
val orderedItem = listOf(
|
val visitorFree = listOf(communityPost(price = 0, existOrdered = false))
|
||||||
communityPost(price = 100, existOrdered = true, imageUrl = "image.png", audioUrl = "audio.mp3")
|
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L)
|
||||||
).toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L).single()
|
.single()
|
||||||
|
val visitorPaidOrdered = listOf(communityPost(price = 100, existOrdered = true))
|
||||||
|
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L)
|
||||||
|
.single()
|
||||||
|
val visitorPaidNotOrdered = listOf(communityPost(price = 100, existOrdered = false))
|
||||||
|
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L)
|
||||||
|
.single()
|
||||||
|
|
||||||
assertFalse(ownerItem.isLocked)
|
assertTrue(authorPaidNotOrdered.menuItem.isOwner)
|
||||||
assertTrue(ownerItem.showPlayButton)
|
assertTrue(authorPaidNotOrdered.menuItem.showMore)
|
||||||
assertFalse(orderedItem.isLocked)
|
assertTrue(authorPaidNotOrdered.menuItem.isPinned)
|
||||||
assertTrue(orderedItem.showPlayButton)
|
assertTrue(authorPaidNotOrdered.isPinned)
|
||||||
|
assertTrue(visitorFree.menuItem.showMore)
|
||||||
|
assertTrue(visitorPaidOrdered.menuItem.showMore)
|
||||||
|
assertFalse(visitorPaidNotOrdered.menuItem.isOwner)
|
||||||
|
assertFalse(visitorPaidNotOrdered.menuItem.showMore)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `오디오 또는 이미지가 없으면 play button을 숨긴다`() {
|
fun `본인 채널에 본인이 쓴 유료 게시글에서만 top price를 표시한다`() {
|
||||||
val items = listOf(
|
|
||||||
communityPost(postId = 1L, imageUrl = null, audioUrl = "audio.mp3"),
|
|
||||||
communityPost(postId = 2L, imageUrl = "image.png", audioUrl = null),
|
|
||||||
communityPost(postId = 3L, imageUrl = "image.png", audioUrl = " ")
|
|
||||||
).toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L)
|
|
||||||
|
|
||||||
assertEquals(listOf(false, false, false), items.map { it.showPlayButton })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `본인 채널에 본인이 쓴 게시글에서만 owner more와 유료 top price를 표시한다`() {
|
|
||||||
val ownerPaid = listOf(communityPost(price = 100, creatorId = 10L))
|
val ownerPaid = listOf(communityPost(price = 100, creatorId = 10L))
|
||||||
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L)
|
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L)
|
||||||
.single()
|
.single()
|
||||||
@@ -148,13 +146,9 @@ class CreatorChannelCommunityMapperTest {
|
|||||||
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L)
|
.toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L)
|
||||||
.single()
|
.single()
|
||||||
|
|
||||||
assertTrue(ownerPaid.showOwnerMore)
|
|
||||||
assertTrue(ownerPaid.showOwnerTopPrice)
|
assertTrue(ownerPaid.showOwnerTopPrice)
|
||||||
assertTrue(ownerFree.showOwnerMore)
|
|
||||||
assertFalse(ownerFree.showOwnerTopPrice)
|
assertFalse(ownerFree.showOwnerTopPrice)
|
||||||
assertFalse(otherCreator.showOwnerMore)
|
|
||||||
assertFalse(otherCreator.showOwnerTopPrice)
|
assertFalse(otherCreator.showOwnerTopPrice)
|
||||||
assertFalse(otherChannel.showOwnerMore)
|
|
||||||
assertFalse(otherChannel.showOwnerTopPrice)
|
assertFalse(otherChannel.showOwnerTopPrice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.v2.main.home
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.ColorDrawable
|
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
@@ -659,6 +658,19 @@ class HomeMainFragmentLayoutTest {
|
|||||||
assertEquals("https://example.com/audio.m4a", item.toUiModel().item.audioUrl)
|
assertEquals("https://example.com/audio.m4a", item.toUiModel().item.audioUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `popular community mapper preserves locked item reactions`() {
|
||||||
|
val item = popularCommunityData(
|
||||||
|
audioUrl = null,
|
||||||
|
price = 100,
|
||||||
|
existOrdered = false
|
||||||
|
).toUiModel().item
|
||||||
|
|
||||||
|
assertTrue(item.isLocked)
|
||||||
|
assertTrue(item.showReaction)
|
||||||
|
assertTrue(item.showComment)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `home relative time formatter converts active creator utc timestamp to relative time text`() {
|
fun `home relative time formatter converts active creator utc timestamp to relative time text`() {
|
||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
@@ -1509,7 +1521,9 @@ class HomeMainFragmentLayoutTest {
|
|||||||
|
|
||||||
private fun popularCommunityData(
|
private fun popularCommunityData(
|
||||||
audioUrl: String?,
|
audioUrl: String?,
|
||||||
createdAt: String = "2분 전"
|
createdAt: String = "2분 전",
|
||||||
|
price: Int = 0,
|
||||||
|
existOrdered: Boolean = false
|
||||||
): HomePopularCommunityPostItem {
|
): HomePopularCommunityPostItem {
|
||||||
return HomePopularCommunityPostItem(
|
return HomePopularCommunityPostItem(
|
||||||
postId = 1L,
|
postId = 1L,
|
||||||
@@ -1519,11 +1533,11 @@ class HomeMainFragmentLayoutTest {
|
|||||||
imageUrl = null,
|
imageUrl = null,
|
||||||
audioUrl = audioUrl,
|
audioUrl = audioUrl,
|
||||||
content = "본문",
|
content = "본문",
|
||||||
price = 0,
|
price = price,
|
||||||
createdAt = createdAt,
|
createdAt = createdAt,
|
||||||
likeCount = 6L,
|
likeCount = 6L,
|
||||||
commentCount = 5L,
|
commentCount = 5L,
|
||||||
existOrdered = false
|
existOrdered = existOrdered
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.widget.feed
|
package kr.co.vividnext.sodalive.v2.widget.feed
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -102,4 +104,71 @@ class FeedItemTest {
|
|||||||
assertEquals(5, item.commentCount)
|
assertEquals(5, item.commentCount)
|
||||||
assertEquals(6, item.likeCount)
|
assertEquals(6, item.likeCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `locked paid community item hides reactions and comments`() {
|
||||||
|
val item = communityItem(
|
||||||
|
price = 100,
|
||||||
|
existOrdered = false,
|
||||||
|
hideReactionWhenLocked = true
|
||||||
|
)
|
||||||
|
|
||||||
|
assertTrue(item.isLocked)
|
||||||
|
assertFalse(item.showReaction)
|
||||||
|
assertFalse(item.showComment)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `locked paid community item preserves reactions by default`() {
|
||||||
|
val item = communityItem(price = 100, existOrdered = false)
|
||||||
|
|
||||||
|
assertTrue(item.isLocked)
|
||||||
|
assertTrue(item.showReaction)
|
||||||
|
assertTrue(item.showComment)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `free community item with comments disabled keeps likes only`() {
|
||||||
|
val item = communityItem(price = 0, isCommentAvailable = false)
|
||||||
|
|
||||||
|
assertFalse(item.isLocked)
|
||||||
|
assertTrue(item.showReaction)
|
||||||
|
assertFalse(item.showComment)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `community item preserves pinned state`() {
|
||||||
|
val item = communityItem(isPinned = true)
|
||||||
|
|
||||||
|
assertTrue(item.isPinned)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `community item hides more action by default`() {
|
||||||
|
assertFalse(communityItem().showMore)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun communityItem(
|
||||||
|
price: Int = 0,
|
||||||
|
existOrdered: Boolean = false,
|
||||||
|
isPinned: Boolean = false,
|
||||||
|
isCommentAvailable: Boolean = true,
|
||||||
|
hideReactionWhenLocked: Boolean = false
|
||||||
|
) = FeedItem.Community(
|
||||||
|
feedId = "feed-community-policy",
|
||||||
|
creatorId = "creator-1",
|
||||||
|
creatorName = "크리에이터 이름",
|
||||||
|
creatorImageUrl = "https://example.com/profile.png",
|
||||||
|
postId = "post-policy",
|
||||||
|
bodyText = "커뮤니티 본문",
|
||||||
|
keywordText = "",
|
||||||
|
createdAtText = "2분 전",
|
||||||
|
commentCount = 5,
|
||||||
|
likeCount = 6,
|
||||||
|
price = price,
|
||||||
|
existOrdered = existOrdered,
|
||||||
|
isPinned = isPinned,
|
||||||
|
isCommentAvailable = isCommentAvailable,
|
||||||
|
hideReactionWhenLocked = hideReactionWhenLocked
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,28 @@ class FeedViewTest {
|
|||||||
assertEquals(View.GONE, view.findViewById<TextView>(R.id.tv_feed_community_body).visibility)
|
assertEquals(View.GONE, view.findViewById<TextView>(R.id.tv_feed_community_body).visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `community more click은 현재 bind된 item만 전달한다`() {
|
||||||
|
val view = inflateView<FeedCommunityView>(R.layout.view_feed_community)
|
||||||
|
val first = sampleCommunityItem(bodyText = "첫 번째", keywordText = "", showMore = true)
|
||||||
|
val second = sampleCommunityItem(bodyText = "두 번째", keywordText = "", showMore = true)
|
||||||
|
var clickedItem: FeedItem.Community? = null
|
||||||
|
var rootClickedItem: FeedItem? = null
|
||||||
|
|
||||||
|
view.setOnMoreClick { clickedItem = it }
|
||||||
|
view.setOnFeedClick { rootClickedItem = it }
|
||||||
|
view.bind(first)
|
||||||
|
view.bind(second)
|
||||||
|
view.findViewById<View>(R.id.btn_feed_community_more).performClick()
|
||||||
|
|
||||||
|
assertSame(second, clickedItem)
|
||||||
|
assertEquals(null, rootClickedItem)
|
||||||
|
|
||||||
|
view.performClick()
|
||||||
|
|
||||||
|
assertSame(second, rootClickedItem)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `community layout uses runtime image height instead of fixed crop height`() {
|
fun `community layout uses runtime image height instead of fixed crop height`() {
|
||||||
val view = inflateView<FeedCommunityView>(R.layout.view_feed_community)
|
val view = inflateView<FeedCommunityView>(R.layout.view_feed_community)
|
||||||
@@ -201,7 +223,8 @@ class FeedViewTest {
|
|||||||
audioUrl: String? = null,
|
audioUrl: String? = null,
|
||||||
price: Int = 0,
|
price: Int = 0,
|
||||||
existOrdered: Boolean = false,
|
existOrdered: Boolean = false,
|
||||||
showKeyword: Boolean = true
|
showKeyword: Boolean = true,
|
||||||
|
showMore: Boolean = false
|
||||||
) = FeedItem.Community(
|
) = FeedItem.Community(
|
||||||
feedId = "feed-community-1",
|
feedId = "feed-community-1",
|
||||||
creatorId = "creator-1",
|
creatorId = "creator-1",
|
||||||
@@ -217,7 +240,8 @@ class FeedViewTest {
|
|||||||
audioUrl = audioUrl,
|
audioUrl = audioUrl,
|
||||||
price = price,
|
price = price,
|
||||||
existOrdered = existOrdered,
|
existOrdered = existOrdered,
|
||||||
showKeyword = showKeyword
|
showKeyword = showKeyword,
|
||||||
|
showMore = showMore
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun Int.dpToPx(): Int {
|
private fun Int.dpToPx(): Int {
|
||||||
|
|||||||
Reference in New Issue
Block a user