feat(community): 커뮤니티 전체보기 리스트 그리드 전환 탭을 추가한다
This commit is contained in:
@@ -9,15 +9,19 @@ import android.os.Looper
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
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.base.SodaDialog
|
import kr.co.vividnext.sodalive.base.SodaDialog
|
||||||
import kr.co.vividnext.sodalive.common.Constants
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
|
import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityAllBinding
|
import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityAllBinding
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreatorCommunityCommentFragment
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreatorCommunityCommentFragment
|
||||||
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.explorer.profile.creator_community.modify.CreatorCommunityModifyActivity
|
||||||
@@ -28,15 +32,64 @@ class CreatorCommunityAllActivity : BaseActivity<ActivityCreatorCommunityAllBind
|
|||||||
ActivityCreatorCommunityAllBinding::inflate
|
ActivityCreatorCommunityAllBinding::inflate
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val GRID_SPAN_COUNT = 3
|
||||||
|
}
|
||||||
|
|
||||||
private val viewModel: CreatorCommunityAllViewModel by inject()
|
private val viewModel: CreatorCommunityAllViewModel by inject()
|
||||||
|
|
||||||
private lateinit var loadingDialog: LoadingDialog
|
private lateinit var loadingDialog: LoadingDialog
|
||||||
private lateinit var adapter: CreatorCommunityAllAdapter
|
private lateinit var listAdapter: CreatorCommunityAllAdapter
|
||||||
|
private lateinit var gridAdapter: CreatorCommunityAllGridAdapter
|
||||||
private lateinit var mediaPlayerManager: CreatorCommunityMediaPlayerManager
|
private lateinit var mediaPlayerManager: CreatorCommunityMediaPlayerManager
|
||||||
private lateinit var imm: InputMethodManager
|
private lateinit var imm: InputMethodManager
|
||||||
|
|
||||||
private var creatorId: Long = 0
|
private var creatorId: Long = 0
|
||||||
|
private var isListMode = false
|
||||||
|
private var listAnchorPosition = 0
|
||||||
|
private var gridAnchorPosition = 0
|
||||||
|
private var isListEnteredFromGridClick = false
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private val gridSpacingPx by lazy { 1.3f.dpToPx().toInt() }
|
||||||
|
private val gridItemDecoration by lazy {
|
||||||
|
GridSpacingItemDecoration(
|
||||||
|
spanCount = GRID_SPAN_COUNT,
|
||||||
|
spacing = gridSpacingPx,
|
||||||
|
includeEdge = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val listItemDecoration = object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
|
||||||
|
outRect.left = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.right = 13.3f.dpToPx().toInt()
|
||||||
|
|
||||||
|
when (parent.getChildAdapterPosition(view)) {
|
||||||
|
RecyclerView.NO_POSITION -> Unit
|
||||||
|
|
||||||
|
0 -> {
|
||||||
|
outRect.top = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
listAdapter.itemCount - 1 -> {
|
||||||
|
outRect.top = 6.7f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
outRect.top = 6.7f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val modifyResult = registerForActivityResult(
|
private val modifyResult = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
@@ -54,7 +107,9 @@ class CreatorCommunityAllActivity : BaseActivity<ActivityCreatorCommunityAllBind
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
mediaPlayerManager = CreatorCommunityMediaPlayerManager(this) {
|
mediaPlayerManager = CreatorCommunityMediaPlayerManager(this) {
|
||||||
adapter.updateUI()
|
if (::listAdapter.isInitialized) {
|
||||||
|
listAdapter.updateUI()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
creatorId = intent.getLongExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, 0)
|
creatorId = intent.getLongExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, 0)
|
||||||
@@ -87,9 +142,29 @@ class CreatorCommunityAllActivity : BaseActivity<ActivityCreatorCommunityAllBind
|
|||||||
imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
||||||
binding.toolbar.tvBack.setText(R.string.screen_creator_community_all_title)
|
binding.toolbar.tvBack.setText(R.string.screen_creator_community_all_title)
|
||||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
binding.toolbar.tvBack.setOnClickListener {
|
||||||
|
handleBackNavigation()
|
||||||
|
}
|
||||||
|
binding.llTabList.setOnClickListener {
|
||||||
|
if (isListMode) {
|
||||||
|
isListEnteredFromGridClick = false
|
||||||
|
updateTabUi()
|
||||||
|
} else {
|
||||||
|
switchToListMode(gridAnchorPosition, fromGridItemClick = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.llTabGrid.setOnClickListener {
|
||||||
|
if (isListMode) {
|
||||||
|
switchToGridMode(anchorPosition = listAnchorPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
handleBackNavigation()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
adapter = CreatorCommunityAllAdapter(
|
listAdapter = CreatorCommunityAllAdapter(
|
||||||
screenWidth = screenWidth,
|
screenWidth = screenWidth,
|
||||||
onClickLike = { viewModel.communityPostLike(it) },
|
onClickLike = { viewModel.communityPostLike(it) },
|
||||||
writeComment = { postId, parentId, comment, isSecret ->
|
writeComment = { postId, parentId, comment, isSecret ->
|
||||||
@@ -154,65 +229,25 @@ class CreatorCommunityAllActivity : BaseActivity<ActivityCreatorCommunityAllBind
|
|||||||
confirmButtonClick = {
|
confirmButtonClick = {
|
||||||
viewModel.purchaseCommunityPost(postId) {
|
viewModel.purchaseCommunityPost(postId) {
|
||||||
onSuccess(it)
|
onSuccess(it)
|
||||||
|
updateGridItem(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).show(screenWidth)
|
).show(screenWidth)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val recyclerView = binding.rvCreatorCommunity
|
val gridItemSize = ((screenWidth - (gridSpacingPx * (GRID_SPAN_COUNT + 1))) / GRID_SPAN_COUNT)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(
|
.coerceAtLeast(1)
|
||||||
applicationContext,
|
gridAdapter = CreatorCommunityAllGridAdapter(
|
||||||
LinearLayoutManager.VERTICAL,
|
itemSize = gridItemSize,
|
||||||
false
|
onClickItem = {
|
||||||
|
switchToListMode(it, fromGridItemClick = true)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
setupRecyclerViews()
|
||||||
override fun getItemOffsets(
|
|
||||||
outRect: Rect,
|
|
||||||
view: View,
|
|
||||||
parent: RecyclerView,
|
|
||||||
state: RecyclerView.State
|
|
||||||
) {
|
|
||||||
super.getItemOffsets(outRect, view, parent, state)
|
|
||||||
|
|
||||||
outRect.left = 6.7f.dpToPx().toInt()
|
switchToListMode(0, fromGridItemClick = false)
|
||||||
outRect.right = 6.7f.dpToPx().toInt()
|
|
||||||
|
|
||||||
when (parent.getChildAdapterPosition(view)) {
|
|
||||||
0 -> {
|
|
||||||
outRect.top = 13.3f.dpToPx().toInt()
|
|
||||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter.itemCount - 1 -> {
|
|
||||||
outRect.top = 6.7f.dpToPx().toInt()
|
|
||||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
outRect.top = 6.7f.dpToPx().toInt()
|
|
||||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
|
||||||
|
|
||||||
val lastVisiblePosition = (recyclerView.layoutManager as LinearLayoutManager)
|
|
||||||
.findLastVisibleItemPosition()
|
|
||||||
val itemTotalCount = adapter.itemCount - 1
|
|
||||||
|
|
||||||
if (itemTotalCount > 0 && lastVisiblePosition == itemTotalCount) {
|
|
||||||
viewModel.getCommunityPostList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
recyclerView.adapter = adapter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@@ -231,11 +266,146 @@ class CreatorCommunityAllActivity : BaseActivity<ActivityCreatorCommunityAllBind
|
|||||||
|
|
||||||
viewModel.communityPostListLiveData.observe(this) {
|
viewModel.communityPostListLiveData.observe(this) {
|
||||||
if (viewModel.page == 2) {
|
if (viewModel.page == 2) {
|
||||||
adapter.items.clear()
|
listAdapter.items.clear()
|
||||||
|
gridAdapter.items.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.items.addAll(it)
|
listAdapter.items.addAll(it)
|
||||||
adapter.notifyDataSetChanged()
|
gridAdapter.items.addAll(it)
|
||||||
|
listAdapter.notifyDataSetChanged()
|
||||||
|
gridAdapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerViews() {
|
||||||
|
val listRecyclerView = binding.rvCreatorCommunity
|
||||||
|
listRecyclerView.layoutManager = LinearLayoutManager(
|
||||||
|
applicationContext,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
listRecyclerView.adapter = listAdapter
|
||||||
|
listRecyclerView.setHasFixedSize(true)
|
||||||
|
listRecyclerView.itemAnimator = null
|
||||||
|
if (listRecyclerView.itemDecorationCount == 0) {
|
||||||
|
listRecyclerView.addItemDecoration(listItemDecoration)
|
||||||
|
}
|
||||||
|
listRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val layoutManager = recyclerView.layoutManager as? LinearLayoutManager ?: return
|
||||||
|
val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()
|
||||||
|
if (firstVisiblePosition != RecyclerView.NO_POSITION) {
|
||||||
|
listAnchorPosition = firstVisiblePosition
|
||||||
|
}
|
||||||
|
|
||||||
|
val lastVisiblePosition = layoutManager.findLastVisibleItemPosition()
|
||||||
|
val itemTotalCount = listAdapter.itemCount - 1
|
||||||
|
if (itemTotalCount > 0 && lastVisiblePosition == itemTotalCount) {
|
||||||
|
viewModel.getCommunityPostList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val gridRecyclerView = binding.rvCreatorCommunityGrid
|
||||||
|
gridRecyclerView.layoutManager = GridLayoutManager(applicationContext, GRID_SPAN_COUNT)
|
||||||
|
gridRecyclerView.adapter = gridAdapter
|
||||||
|
gridRecyclerView.setHasFixedSize(true)
|
||||||
|
gridRecyclerView.itemAnimator = null
|
||||||
|
if (gridRecyclerView.itemDecorationCount == 0) {
|
||||||
|
gridRecyclerView.addItemDecoration(gridItemDecoration)
|
||||||
|
}
|
||||||
|
gridRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val layoutManager = recyclerView.layoutManager as? GridLayoutManager ?: return
|
||||||
|
val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()
|
||||||
|
if (firstVisiblePosition != RecyclerView.NO_POSITION) {
|
||||||
|
gridAnchorPosition = firstVisiblePosition
|
||||||
|
}
|
||||||
|
|
||||||
|
val lastVisiblePosition = layoutManager.findLastVisibleItemPosition()
|
||||||
|
val itemTotalCount = gridAdapter.itemCount - 1
|
||||||
|
if (itemTotalCount > 0 && lastVisiblePosition == itemTotalCount) {
|
||||||
|
viewModel.getCommunityPostList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleBackNavigation() {
|
||||||
|
if (isListMode && isListEnteredFromGridClick) {
|
||||||
|
switchToGridMode(anchorPosition = listAnchorPosition)
|
||||||
|
} else {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun switchToListMode(position: Int, fromGridItemClick: Boolean) {
|
||||||
|
val requestedPosition = position.coerceAtLeast(0)
|
||||||
|
isListMode = true
|
||||||
|
isListEnteredFromGridClick = fromGridItemClick
|
||||||
|
listAnchorPosition = requestedPosition
|
||||||
|
updateTabUi()
|
||||||
|
|
||||||
|
binding.rvCreatorCommunity.visibility = View.VISIBLE
|
||||||
|
binding.rvCreatorCommunityGrid.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
val recyclerView = binding.rvCreatorCommunity
|
||||||
|
|
||||||
|
recyclerView.post {
|
||||||
|
if (listAdapter.itemCount > 0) {
|
||||||
|
val targetPosition = requestedPosition.coerceIn(0, listAdapter.itemCount - 1)
|
||||||
|
(recyclerView.layoutManager as? LinearLayoutManager)
|
||||||
|
?.scrollToPositionWithOffset(targetPosition, 0)
|
||||||
|
listAnchorPosition = targetPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun switchToGridMode(anchorPosition: Int = 0) {
|
||||||
|
isListMode = false
|
||||||
|
isListEnteredFromGridClick = false
|
||||||
|
updateTabUi()
|
||||||
|
if (::mediaPlayerManager.isInitialized) {
|
||||||
|
mediaPlayerManager.pauseContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.rvCreatorCommunity.visibility = View.INVISIBLE
|
||||||
|
binding.rvCreatorCommunityGrid.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
val recyclerView = binding.rvCreatorCommunityGrid
|
||||||
|
|
||||||
|
recyclerView.post {
|
||||||
|
if (gridAdapter.itemCount > 0) {
|
||||||
|
val targetPosition = anchorPosition.coerceIn(0, gridAdapter.itemCount - 1)
|
||||||
|
recyclerView.scrollToPosition(targetPosition)
|
||||||
|
gridAnchorPosition = targetPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTabUi() {
|
||||||
|
if (isListMode) {
|
||||||
|
binding.ivTabList.setImageResource(R.drawable.ic_community_list_selected)
|
||||||
|
binding.ivTabGrid.setImageResource(R.drawable.ic_community_grid)
|
||||||
|
binding.vTabListIndicator.visibility = View.VISIBLE
|
||||||
|
binding.vTabGridIndicator.visibility = View.INVISIBLE
|
||||||
|
} else {
|
||||||
|
binding.ivTabList.setImageResource(R.drawable.ic_community_list)
|
||||||
|
binding.ivTabGrid.setImageResource(R.drawable.ic_community_grid_selected)
|
||||||
|
binding.vTabListIndicator.visibility = View.INVISIBLE
|
||||||
|
binding.vTabGridIndicator.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateGridItem(updatedPost: GetCommunityPostListResponse) {
|
||||||
|
val index = gridAdapter.items.indexOfFirst { it.postId == updatedPost.postId }
|
||||||
|
if (index >= 0) {
|
||||||
|
gridAdapter.items[index] = updatedPost
|
||||||
|
gridAdapter.notifyItemChanged(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package kr.co.vividnext.sodalive.explorer.profile.creator_community.all
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityAllGridBinding
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
|
||||||
|
class CreatorCommunityAllGridAdapter(
|
||||||
|
private val itemSize: Int,
|
||||||
|
private val onClickItem: (Int) -> Unit
|
||||||
|
) : RecyclerView.Adapter<CreatorCommunityAllGridAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CONTENT_PREVIEW_MAX_LENGTH = 24
|
||||||
|
}
|
||||||
|
|
||||||
|
val items = mutableListOf<GetCommunityPostListResponse>()
|
||||||
|
|
||||||
|
inner class ViewHolder(
|
||||||
|
private val binding: ItemCreatorCommunityAllGridBinding
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(item: GetCommunityPostListResponse) {
|
||||||
|
val lp = binding.root.layoutParams
|
||||||
|
lp.width = itemSize
|
||||||
|
lp.height = itemSize
|
||||||
|
binding.root.layoutParams = lp
|
||||||
|
|
||||||
|
val isPaidLocked = item.price > 0 && !item.existOrdered
|
||||||
|
val hasImage = !item.imageUrl.isNullOrBlank()
|
||||||
|
|
||||||
|
when {
|
||||||
|
isPaidLocked -> {
|
||||||
|
binding.ivGridImage.visibility = View.GONE
|
||||||
|
binding.tvGridText.visibility = View.GONE
|
||||||
|
binding.ivGridLock.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
hasImage -> {
|
||||||
|
binding.ivGridImage.visibility = View.VISIBLE
|
||||||
|
binding.tvGridText.visibility = View.GONE
|
||||||
|
binding.ivGridLock.visibility = View.GONE
|
||||||
|
binding.ivGridImage.loadUrl(item.imageUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.ic_place_holder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
binding.ivGridImage.visibility = View.GONE
|
||||||
|
binding.tvGridText.visibility = View.VISIBLE
|
||||||
|
binding.ivGridLock.visibility = View.GONE
|
||||||
|
binding.tvGridText.text = item.content
|
||||||
|
.replace("\n", " ")
|
||||||
|
.trim()
|
||||||
|
.take(CONTENT_PREVIEW_MAX_LENGTH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.root.setOnClickListener {
|
||||||
|
val position = bindingAdapterPosition
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
onClickItem(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||||
|
ItemCreatorCommunityAllGridBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.bind(items[position])
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-xxhdpi/ic_community_grid.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_community_grid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 702 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_community_grid_selected.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_community_grid_selected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 594 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_community_list.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_community_list.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 600 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_community_list_selected.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_community_list_selected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 517 B |
@@ -9,8 +9,91 @@
|
|||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
layout="@layout/detail_toolbar" />
|
layout="@layout/detail_toolbar" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<LinearLayout
|
||||||
android:id="@+id/rv_creator_community"
|
android:id="@+id/ll_mode_tab"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="52dp"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_tab_list"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_tab_list"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_community_list_selected" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/v_tab_list_indicator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:background="#FFFFFF" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_tab_grid"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_tab_grid"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_community_grid" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/v_tab_grid_indicator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:background="#FFFFFF"
|
||||||
|
android:visibility="invisible" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#909090" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_creator_community"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_creator_community_grid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="invisible" />
|
||||||
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
41
app/src/main/res/layout/item_creator_community_all_grid.xml
Normal file
41
app/src/main/res/layout/item_creator_community_all_grid.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_round_corner_5_3_333333"
|
||||||
|
android:clipToOutline="true"
|
||||||
|
android:outlineProvider="background">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_grid_image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:src="@drawable/ic_place_holder" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_grid_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:textColor="@color/color_bbbbbb"
|
||||||
|
android:textSize="13.3sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="텍스트만 있는 게시물" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_grid_lock"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_lock_bb"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</FrameLayout>
|
||||||
48
docs/20260305_크리에이터커뮤니티전체보기그리드리스트구현.md
Normal file
48
docs/20260305_크리에이터커뮤니티전체보기그리드리스트구현.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# 크리에이터 커뮤니티 전체보기 그리드/리스트 전환 구현
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [x] Toolbar 하단에 좌/우 2탭(왼쪽 List, 오른쪽 Grid) 가로 꽉찬 UI 추가
|
||||||
|
- [x] 탭 아이콘 선택/비선택 상태(`ic_community_list*`, `ic_community_grid*`) 반영
|
||||||
|
- [x] 기본 초기 화면을 List 모드로 표시
|
||||||
|
- [x] Grid 아이템 클릭 시 List 모드로 전환하고 클릭 아이템을 리스트 상단에 표시
|
||||||
|
- [x] Grid 클릭으로 진입한 List 상태에서 뒤로가기 시 Grid로 복귀
|
||||||
|
- [x] 초기 기본 List 상태에서 뒤로가기 시 Activity 기본 종료 동작 수행
|
||||||
|
- [x] Grid 셀 규칙 적용 (이미지 우선, 이미지 없음 시 텍스트 일부, 유료 미구매 시 자물쇠 이미지)
|
||||||
|
- [x] Grid 레이아웃 한 줄 3개 고정(`GridLayoutManager` span 3)
|
||||||
|
- [x] List/Grid 전환 버벅임 완화를 위해 데이터 재로딩 없이 화면 전환 비용 최소화
|
||||||
|
- [x] 탭 배경/하단 라인/선택 인디케이터 디테일 적용
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 무엇/왜/어떻게: `CreatorCommunityAllActivity`에 Grid/List 모드 전환 상태(`isListMode`)를 추가하고, 초기 Grid 표시 -> 아이템 클릭 시 List 전환 -> List 뒤로가기 시 Grid 복귀 흐름을 구현했다. List 스크롤 시 첫 가시 아이템을 앵커로 저장해 Grid 복귀 시 해당 아이템이 보이도록 처리했다.
|
||||||
|
- 무엇/왜/어떻게: Grid 전용 `CreatorCommunityAllGridAdapter`와 `item_creator_community_all_grid.xml`을 추가했다. 규칙은 `price > 0 && !existOrdered`면 `ic_lock_bb` 잠금 아이콘, 이미지 URL이 있으면 이미지, 없으면 본문 텍스트 일부(24자) 표시로 분기했다.
|
||||||
|
- 무엇/왜/어떻게: 사용자 추가 요청("그리드 리스트 한 줄에 3개 표시")을 반영해 `GridLayoutManager(applicationContext, 3)`로 고정했다.
|
||||||
|
- 무엇/왜/어떻게: `activity_creator_community_all.xml`의 RecyclerView 높이를 `0dp + weight 1`로 조정해 Grid/List 모두 화면 높이를 점유하며 스크롤 동작이 안정적으로 유지되도록 보강했다.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug` (최종 수정 후 재검증)
|
||||||
|
- 결과: BUILD SUCCESSFUL
|
||||||
|
- 진단: `.kt`, `.xml` 확장자에 대한 LSP 서버가 현재 환경에 미구성되어 `lsp_diagnostics`는 실행 불가(도구 오류 메시지 확인).
|
||||||
|
- 무엇/왜/어떻게: `switchToGridMode()`에서 `mediaPlayerManager.pauseContent()` 호출 전에 `::mediaPlayerManager.isInitialized` 가드를 추가해 `lateinit property mediaPlayerManager has not been initialized` 크래시를 방지했다.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL
|
||||||
|
- 무엇/왜/어떻게: Grid 아이템 클릭 직후 `onScrolled` 초기 이벤트가 앵커를 0으로 덮어쓰던 문제를 막기 위해, `switchToListMode()`에서 클릭 위치를 `requestedPosition`으로 고정해 해당 위치로 스크롤하도록 수정했다.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL
|
||||||
|
- 무엇/왜/어떻게: Grid 셀 자물쇠 아이콘(`iv_grid_lock`)의 크기를 요청사항에 맞춰 `24dp x 24dp`로 조정했다.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL
|
||||||
|
- 진단: `.xml` 확장자 LSP 서버가 현재 환경에 없어 `lsp_diagnostics`는 실행 불가(도구 오류 메시지 확인).
|
||||||
|
- 무엇/왜/어떻게: `activity_creator_community_all.xml`에 툴바 하단 2탭(좌 List/우 Grid)을 추가하고 각 탭 아이콘을 선택/비선택 리소스로 토글하도록 반영했다.
|
||||||
|
- 무엇/왜/어떻게: `CreatorCommunityAllActivity`의 초기 진입 모드를 List로 변경하고, Grid 아이템 클릭으로 List에 들어온 경우에만 뒤로가기를 Grid 복귀로 처리하도록 `isListEnteredFromGridClick` 분기 로직을 추가했다.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL
|
||||||
|
- 진단: `.kt`, `.xml` 확장자 LSP 서버가 현재 환경에 미구성되어 `lsp_diagnostics`는 실행 불가(도구 오류 메시지 확인).
|
||||||
|
- 무엇/왜/어떻게: 전환 버벅임 완화를 위해 `RecyclerView`를 List/Grid 각각 유지하는 구조로 변경하고, 전환 시 `LayoutManager`/`Adapter` 재할당 대신 `visibility`만 전환하도록 수정했다. 데이터는 기존처럼 `listAdapter`/`gridAdapter`에 동일 소스를 공유해 재요청 없이 유지된다.
|
||||||
|
- 무엇/왜/어떻게: `activity_creator_community_all.xml`을 `FrameLayout + rv_creator_community + rv_creator_community_grid` 구조로 바꾸고, `setupRecyclerViews()`에서 두 RecyclerView를 초기 1회만 구성해 전환 비용을 줄였다.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL
|
||||||
|
- 진단: `.kt`, `.xml` 확장자 LSP 서버 미구성으로 `lsp_diagnostics` 실행 불가(도구 오류 메시지 확인).
|
||||||
|
- 무엇/왜/어떻게: 탭 영역 배경을 `#777777`로 변경하고 탭 하단 전체 라인(`1dp`, `#909090`)을 추가했다. 선택 탭 하단에는 인디케이터(`2dp`, `#FFFFFF`)가 표시되도록 `v_tab_list_indicator`, `v_tab_grid_indicator`를 레이아웃과 `updateTabUi()`에 연결했다.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL
|
||||||
|
- 진단: `.kt`, `.xml` 확장자 LSP 서버 미구성으로 `lsp_diagnostics` 실행 불가(도구 오류 메시지 확인).
|
||||||
Reference in New Issue
Block a user