fix(content): 상세 오디오와 구매 흐름을 보정한다
This commit is contained in:
@@ -16,6 +16,7 @@ import com.bumptech.glide.Glide
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelCommunityDetailBinding
|
||||
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.CreatorCommunityMediaPlayerManager
|
||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||
@@ -46,7 +47,7 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
||||
private var renderedEditingCommentId: Long? = null
|
||||
|
||||
override fun setupView() {
|
||||
mediaPlayerManager = CreatorCommunityMediaPlayerManager(this) { }
|
||||
mediaPlayerManager = CreatorCommunityMediaPlayerManager(this) { renderCurrentAudioButton() }
|
||||
postId = intent.getLongExtra(EXTRA_POST_ID, 0L)
|
||||
if (postId <= 0L) {
|
||||
Toast.makeText(this, R.string.creator_channel_community_invalid_post, Toast.LENGTH_SHORT).show()
|
||||
@@ -154,6 +155,12 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
||||
layoutCreatorChannelCommunityDetailImageContainer.isVisible = post.imageUrl != null || post.showPaywall
|
||||
layoutCreatorChannelCommunityDetailPaywall.isVisible = post.showPaywall
|
||||
tvCreatorChannelCommunityDetailPaywallPrice.text = post.price.moneyFormat()
|
||||
layoutCreatorChannelCommunityDetailPaywall.setOnClickListener {
|
||||
showPurchaseDialog(post)
|
||||
}
|
||||
tvCreatorChannelCommunityDetailPaywallPrice.setOnClickListener {
|
||||
showPurchaseDialog(post)
|
||||
}
|
||||
if (post.imageUrl != null) {
|
||||
Glide.with(ivCreatorChannelCommunityDetailImage)
|
||||
.load(post.imageUrl)
|
||||
@@ -162,11 +169,14 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
||||
} else {
|
||||
Glide.with(ivCreatorChannelCommunityDetailImage).clear(ivCreatorChannelCommunityDetailImage)
|
||||
}
|
||||
tvCreatorChannelCommunityDetailAudio.isVisible = post.audioUrl != null
|
||||
ivCreatorChannelCommunityDetailPlayOrPause.isVisible = post.audioUrl != null && post.imageUrl != null
|
||||
ivCreatorChannelCommunityDetailPlayOrPause.setOnClickListener { toggleAudio(post) }
|
||||
tvCreatorChannelCommunityDetailAudio.isVisible = post.audioUrl != null && post.imageUrl == null
|
||||
tvCreatorChannelCommunityDetailAudio.text = getString(R.string.creator_channel_tab_audio)
|
||||
tvCreatorChannelCommunityDetailAudio.setOnClickListener {
|
||||
toggleAudio(post)
|
||||
}
|
||||
renderAudioButton(post)
|
||||
rvCreatorChannelCommunityDetailComments.isVisible = content.isCommentAvailable
|
||||
tvCreatorChannelCommunityDetailCommentHeader.isVisible = content.isCommentAvailable
|
||||
viewCreatorChannelCommunityDetailCommentDivider.isVisible = content.isCommentAvailable
|
||||
@@ -201,6 +211,32 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
||||
private fun toggleAudio(post: CreatorChannelCommunityPostDetailUiModel) {
|
||||
val audioUrl = post.audioUrl ?: return
|
||||
mediaPlayerManager.toggleContent(CreatorCommunityContentItem(post.postId, audioUrl))
|
||||
renderAudioButton(post)
|
||||
}
|
||||
|
||||
private fun renderCurrentAudioButton() {
|
||||
val content = viewModel.detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||
renderAudioButton(content.post)
|
||||
}
|
||||
|
||||
private fun renderAudioButton(post: CreatorChannelCommunityPostDetailUiModel) {
|
||||
binding.ivCreatorChannelCommunityDetailPlayOrPause.setImageResource(
|
||||
if (mediaPlayerManager.isPlayingContent(post.postId)) {
|
||||
R.drawable.btn_audio_content_pause
|
||||
} else {
|
||||
R.drawable.btn_audio_content_play
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun showPurchaseDialog(post: CreatorChannelCommunityPostDetailUiModel) {
|
||||
if (!post.isLocked) return
|
||||
PurchaseCommunityPostDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
can = post.price,
|
||||
confirmButtonClick = { viewModel.purchaseCommunityPost() }
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
private fun showDeleteCommentDialog(commentId: Long) {
|
||||
|
||||
@@ -48,6 +48,7 @@ class CreatorChannelCommunityDetailViewModel(
|
||||
private var isTogglingLike = false
|
||||
private var isSendingComment = false
|
||||
private var isModifyingComment = false
|
||||
private var isPurchasingPost = false
|
||||
|
||||
fun loadDetail(postId: Long) {
|
||||
if (postId <= 0) return
|
||||
@@ -217,6 +218,33 @@ class CreatorChannelCommunityDetailViewModel(
|
||||
modifyComment(ModifyCommentRequest(commentId = commentId, isActive = false))
|
||||
}
|
||||
|
||||
fun purchaseCommunityPost() {
|
||||
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||
if (!content.post.isLocked || isPurchasingPost) return
|
||||
|
||||
isPurchasingPost = true
|
||||
compositeDisposable.add(
|
||||
legacyRepository.purchaseCommunityPost(postId, authToken())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ response ->
|
||||
isPurchasingPost = false
|
||||
if (response.success) {
|
||||
_postChangedEventLiveData.value = true
|
||||
loadDetail(postId)
|
||||
} else {
|
||||
showMutationFailureToast(response.message)
|
||||
}
|
||||
},
|
||||
{
|
||||
isPurchasingPost = false
|
||||
showMutationFailureToast()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun submitCommentEdit(
|
||||
content: CreatorChannelCommunityDetailUiState.Content,
|
||||
commentId: Long,
|
||||
|
||||
@@ -149,6 +149,16 @@
|
||||
android:contentDescription="@string/a11y_feed_content_image"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_creator_channel_community_detail_play_or_pause"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/btn_audio_content_play"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_creator_channel_community_detail_paywall"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -33,6 +33,7 @@ class CreatorChannelCommunityDetailReplyUiContractTest {
|
||||
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<TextView>(R.id.tv_creator_channel_community_detail_paywall_price))
|
||||
assertNotNull(detail.findViewById<ImageView>(R.id.iv_creator_channel_community_detail_play_or_pause))
|
||||
assertNotNull(detail.findViewById<View>(R.id.layout_creator_channel_community_detail_input_bar))
|
||||
assertNotNull(detail.findViewById<EditText>(R.id.et_creator_channel_community_detail_comment))
|
||||
assertNotNull(detail.findViewById<ImageButton>(R.id.btn_creator_channel_community_detail_send))
|
||||
@@ -138,6 +139,23 @@ class CreatorChannelCommunityDetailReplyUiContractTest {
|
||||
assertTrue(!replyActivity.contains("AlertDialog"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `상세 Activity는 레거시 구매 다이얼로그와 이미지 중앙 오디오 버튼을 사용한다`() {
|
||||
val detailLayout = projectFile("app/src/main/res/layout/activity_creator_channel_community_detail.xml").readText()
|
||||
val detailActivity = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/" +
|
||||
"CreatorChannelCommunityDetailActivity.kt"
|
||||
).readText()
|
||||
|
||||
assertTrue(detailLayout.contains("@+id/iv_creator_channel_community_detail_play_or_pause"))
|
||||
assertTrue(detailLayout.contains("android:layout_gravity=\"center\""))
|
||||
assertTrue(detailLayout.contains("@drawable/btn_audio_content_play"))
|
||||
assertTrue(detailActivity.contains("PurchaseCommunityPostDialog"))
|
||||
assertTrue(detailActivity.contains("viewModel.purchaseCommunityPost()"))
|
||||
assertTrue(detailActivity.contains("btn_audio_content_pause"))
|
||||
assertTrue(detailActivity.contains("btn_audio_content_play"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `댓글 답글 Adapter는 PopupMenu 대신 regular font popup을 사용한다`() {
|
||||
val commentAdapter = projectFile(
|
||||
|
||||
@@ -394,6 +394,51 @@ class CreatorChannelCommunityDetailViewModelTest {
|
||||
assertFalse(state.post.showPaywall)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `게시물 구매 성공은 상세를 다시 조회해 잠금 상태를 해제한다`() {
|
||||
whenever(repository.getCommunityPostDetail(POST_ID, AUTH_TOKEN))
|
||||
.thenReturn(
|
||||
Single.just(
|
||||
ApiResponse(
|
||||
true,
|
||||
detailResponse(
|
||||
isCommentAvailable = false,
|
||||
price = 300,
|
||||
existOrdered = false,
|
||||
creatorId = 999L
|
||||
),
|
||||
null
|
||||
)
|
||||
),
|
||||
Single.just(
|
||||
ApiResponse(
|
||||
true,
|
||||
detailResponse(
|
||||
isCommentAvailable = false,
|
||||
price = 300,
|
||||
existOrdered = true,
|
||||
creatorId = 999L
|
||||
),
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
whenever(legacyRepository.purchaseCommunityPost(POST_ID, AUTH_TOKEN))
|
||||
.thenReturn(Single.just(ApiResponse(true, org.mockito.kotlin.mock(), null)))
|
||||
viewModel.loadDetail(POST_ID)
|
||||
|
||||
viewModel.purchaseCommunityPost()
|
||||
|
||||
val state = viewModel.detailStateLiveData.requireValue() as CreatorChannelCommunityDetailUiState.Content
|
||||
assertFalse(state.post.isLocked)
|
||||
assertEquals("image.png", state.post.imageUrl)
|
||||
assertEquals("audio.mp3", state.post.audioUrl)
|
||||
assertTrue(state.post.existOrdered)
|
||||
assertTrue(viewModel.postChangedEventLiveData.requireValue() == true)
|
||||
verify(legacyRepository).purchaseCommunityPost(POST_ID, AUTH_TOKEN)
|
||||
verify(repository, times(2)).getCommunityPostDetail(POST_ID, AUTH_TOKEN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `댓글 UI 모델은 답글 화면 재포맷을 위해 원본 createdAtUtc를 보존한다`() {
|
||||
stubDetail(detailResponse(isCommentAvailable = true, commentIds = listOf(11L)))
|
||||
|
||||
Reference in New Issue
Block a user