fix(community): 채널 이미지 원본비율 표시를 반영한다

This commit is contained in:
2026-07-12 20:51:54 +09:00
parent 2a499eaf41
commit 6cbc751f0c
6 changed files with 148 additions and 12 deletions

View File

@@ -3,8 +3,12 @@ package kr.co.vividnext.sodalive.v2.creator.channel.community.detail
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Outline
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.view.View
import android.view.ViewOutlineProvider
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
@@ -13,6 +17,10 @@ import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.LinearLayoutManager
import coil.transform.CircleCropTransformation
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelCommunityDetailBinding
@@ -24,6 +32,7 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyActivity
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui.CreatorChannelCommunityCommentAdapter
import kr.co.vividnext.sodalive.v2.widget.feed.calculateFeedCommunityImageHeight
import org.koin.androidx.viewmodel.ext.android.viewModel
class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChannelCommunityDetailBinding>(
@@ -56,6 +65,8 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
}
binding.btnCreatorChannelCommunityDetailBack.setOnClickListener { finish() }
binding.layoutCreatorChannelCommunityDetailImageContainer.clipToOutline = true
binding.layoutCreatorChannelCommunityDetailImageContainer.outlineProvider = roundedOutlineProvider()
binding.rvCreatorChannelCommunityDetailComments.layoutManager = LinearLayoutManager(this)
binding.rvCreatorChannelCommunityDetailComments.adapter = commentAdapter
binding.scrollCreatorChannelCommunityDetail.setOnScrollChangeListener { scrollView, _, scrollY, _, oldScrollY ->
@@ -156,6 +167,10 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
tvCreatorChannelCommunityDetailPrice.text = post.price.moneyFormat()
tvCreatorChannelCommunityDetailPurchased.isVisible = post.existOrdered && post.price > 0
layoutCreatorChannelCommunityDetailImageContainer.isVisible = post.imageUrl != null || post.showPaywall
layoutCreatorChannelCommunityDetailImageContainer.layoutParams =
layoutCreatorChannelCommunityDetailImageContainer.layoutParams.apply {
height = if (post.imageUrl != null) DEFAULT_IMAGE_HEIGHT_DP.dpToPx() else DEFAULT_PAYWALL_HEIGHT_DP.dpToPx()
}
layoutCreatorChannelCommunityDetailPaywall.isVisible = post.showPaywall
tvCreatorChannelCommunityDetailPaywallPrice.text = post.price.moneyFormat()
layoutCreatorChannelCommunityDetailPaywall.setOnClickListener {
@@ -165,6 +180,25 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
Glide.with(ivCreatorChannelCommunityDetailImage)
.load(post.imageUrl)
.placeholder(R.drawable.ic_place_holder)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean = false
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
applyDetailImageSize(resource.intrinsicWidth, resource.intrinsicHeight)
return false
}
})
.into(ivCreatorChannelCommunityDetailImage)
} else {
Glide.with(ivCreatorChannelCommunityDetailImage).clear(ivCreatorChannelCommunityDetailImage)
@@ -196,6 +230,29 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
updateSendButton(content.commentInput.trim().isNotEmpty() && !content.isSendingComment)
}
private fun applyDetailImageSize(intrinsicWidthPx: Int, intrinsicHeightPx: Int): Unit = with(binding) {
val container = layoutCreatorChannelCommunityDetailImageContainer
val containerWidth = container.width.takeIf { it > 0 }
?: container.measuredWidth.takeIf { it > 0 }
?: ivCreatorChannelCommunityDetailImage.width.takeIf { it > 0 }
?: run {
container.post { applyDetailImageSize(intrinsicWidthPx, intrinsicHeightPx) }
return@with
}
val imageHeight = calculateFeedCommunityImageHeight(
containerWidthPx = containerWidth,
intrinsicWidthPx = intrinsicWidthPx,
intrinsicHeightPx = intrinsicHeightPx
) ?: return@with
container.layoutParams = container.layoutParams.apply { height = imageHeight }
}
private fun roundedOutlineProvider() = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(0, 0, view.width, view.height, COMMUNITY_IMAGE_RADIUS_DP.dpToPx().toFloat())
}
}
private fun updateSendButton(enabled: Boolean) = with(binding.btnCreatorChannelCommunityDetailSend) {
isEnabled = enabled
setBackgroundResource(
@@ -269,10 +326,15 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
companion object {
private const val EXTRA_POST_ID = "extra_post_id"
private const val DEFAULT_IMAGE_HEIGHT_DP = 236
private const val DEFAULT_PAYWALL_HEIGHT_DP = 236
private const val COMMUNITY_IMAGE_RADIUS_DP = 14
fun newIntent(context: Context, postId: Long): Intent {
return Intent(context, CreatorChannelCommunityDetailActivity::class.java)
.putExtra(EXTRA_POST_ID, postId)
}
}
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).toInt()
}

View File

@@ -1,20 +1,26 @@
package kr.co.vividnext.sodalive.v2.creator.channel.community.ui
import android.graphics.Outline
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import coil.transform.CircleCropTransformation
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelCommunityListBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.loadUrl
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
import kr.co.vividnext.sodalive.v2.widget.feed.calculateFeedCommunityImageHeight
class CreatorChannelCommunityListAdapter(
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
@@ -54,6 +60,11 @@ class CreatorChannelCommunityListAdapter(
private val isPlayingContent: (Long) -> Boolean
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.layoutCreatorChannelCommunityListImageContainer.clipToOutline = true
binding.layoutCreatorChannelCommunityListImageContainer.outlineProvider = roundedOutlineProvider()
}
fun bind(item: CreatorChannelCommunityPostUiModel) = with(binding) {
root.setOnClickListener { onPostClick(item) }
ivCreatorChannelCommunityListProfile.loadUrl(item.creatorProfileUrl) {
@@ -77,11 +88,30 @@ class CreatorChannelCommunityListAdapter(
layoutCreatorChannelCommunityListImageContainer.isVisible =
visibleImageUrl != null || item.isLocked || item.showPlayButton
ivCreatorChannelCommunityListImage.isVisible = visibleImageUrl != null
resetCommunityImageHeight()
if (visibleImageUrl != null) {
Glide.with(ivCreatorChannelCommunityListImage)
.load(visibleImageUrl)
.placeholder(R.drawable.ic_place_holder)
.apply(communityImageRequestOptions())
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean = false
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
applyCommunityImageSize(resource.intrinsicWidth, resource.intrinsicHeight)
return false
}
})
.into(ivCreatorChannelCommunityListImage)
} else {
Glide.with(ivCreatorChannelCommunityListImage).clear(ivCreatorChannelCommunityListImage)
@@ -104,11 +134,45 @@ class CreatorChannelCommunityListAdapter(
ivCreatorChannelCommunityListOwnerMore.setOnClickListener { onOwnerMoreClick(item) }
}
private fun communityImageRequestOptions(): RequestOptions {
return RequestOptions().transform(
CenterCrop(),
RoundedCorners(14f.dpToPx().toInt())
private fun resetCommunityImageHeight() {
binding.layoutCreatorChannelCommunityListImageContainer.layoutParams =
binding.layoutCreatorChannelCommunityListImageContainer.layoutParams.apply {
height = DEFAULT_IMAGE_HEIGHT_DP.dpToPx().toInt()
}
}
private fun applyCommunityImageSize(intrinsicWidthPx: Int, intrinsicHeightPx: Int): Unit = with(binding) {
val container = layoutCreatorChannelCommunityListImageContainer
val containerWidth = container.width.takeIf { it > 0 }
?: container.measuredWidth.takeIf { it > 0 }
?: root.width.takeIf { it > 0 }?.let { it - root.paddingLeft - root.paddingRight }
?: run {
container.post { applyCommunityImageSize(intrinsicWidthPx, intrinsicHeightPx) }
return@with
}
val imageHeight = calculateFeedCommunityImageHeight(
containerWidthPx = containerWidth,
intrinsicWidthPx = intrinsicWidthPx,
intrinsicHeightPx = intrinsicHeightPx
) ?: return@with
container.layoutParams = container.layoutParams.apply { height = imageHeight }
}
private fun roundedOutlineProvider() = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(
0,
0,
view.width,
view.height,
COMMUNITY_IMAGE_RADIUS_DP.dpToPx()
)
}
}
private companion object {
const val DEFAULT_IMAGE_HEIGHT_DP = 236f
const val COMMUNITY_IMAGE_RADIUS_DP = 14f
}
}
}

View File

@@ -435,6 +435,14 @@ class CreatorChannelHomeSectionAdapter(
val communityImageUrl = community.imageUrl.takeIf { !it.isNullOrBlank() }
if (communityImageUrl != null) {
communityRow.communityImageView().loadUrl(communityImageUrl) {
listener(
onSuccess = { _, result ->
communityRow.applyCommunityImageSize(
result.drawable.intrinsicWidth,
result.drawable.intrinsicHeight
)
}
)
if (isCommunityLocked) {
transformations(BlurTransformation(itemView.context, 25f, 2.5f))
}

View File

@@ -136,7 +136,7 @@
<FrameLayout
android:id="@+id/layout_creator_channel_community_detail_image_container"
android:layout_width="match_parent"
android:layout_height="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_14"
android:background="@drawable/bg_feed_community_image"
android:visibility="gone"
@@ -147,7 +147,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/a11y_feed_content_image"
android:scaleType="centerCrop" />
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
<ImageView
android:id="@+id/iv_creator_channel_community_detail_play_or_pause"

View File

@@ -160,7 +160,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/a11y_feed_content_image"
android:scaleType="centerCrop"
android:scaleType="fitCenter"
tools:src="@drawable/ic_launcher_background" />
<View

View File

@@ -1847,6 +1847,7 @@ class CreatorChannelActivitySourceTest {
assertTrue(adapter.contains("communityWidthDp.dp()"))
assertTrue(adapter.contains("LinearLayout.LayoutParams.WRAP_CONTENT"))
assertTrue(adapter.contains("communityImageUrl = community.imageUrl.takeIf { !it.isNullOrBlank() }"))
assertTrue(adapter.contains("communityRow.applyCommunityImageSize("))
assertTrue(adapter.contains("communityImageView().setImageDrawable(null)"))
assertTrue(adapter.contains("private const val MAX_COMMUNITY_ITEM_COUNT = 3"))
assertFalse(adapter.contains("CreatorChannelCommunityCardView"))