From 93b620f4a8ed41a1abf150c697c3bd9595565f39 Mon Sep 17 00:00:00 2001 From: klaus Date: Fri, 6 Mar 2026 14:25:26 +0900 Subject: [PATCH] =?UTF-8?q?feat(community):=20=EC=BB=A4=EB=AE=A4=EB=8B=88?= =?UTF-8?q?=ED=8B=B0=20=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20=ED=83=AD=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../all/CreatorCommunityAllActivity.kt | 284 ++++++++++++++---- .../all/CreatorCommunityAllGridAdapter.kt | 86 ++++++ .../res/drawable-xxhdpi/ic_community_grid.png | Bin 0 -> 702 bytes .../ic_community_grid_selected.png | Bin 0 -> 594 bytes .../res/drawable-xxhdpi/ic_community_list.png | Bin 0 -> 600 bytes .../ic_community_list_selected.png | Bin 0 -> 517 bytes .../layout/activity_creator_community_all.xml | 89 +++++- .../item_creator_community_all_grid.xml | 41 +++ ...리에이터커뮤니티전체보기그리드리스트구현.md | 48 +++ 9 files changed, 488 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllGridAdapter.kt create mode 100644 app/src/main/res/drawable-xxhdpi/ic_community_grid.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_community_grid_selected.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_community_list.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_community_list_selected.png create mode 100644 app/src/main/res/layout/item_creator_community_all_grid.xml create mode 100644 docs/20260305_크리에이터커뮤니티전체보기그리드리스트구현.md diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllActivity.kt index 46903064..81a53d58 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllActivity.kt @@ -9,15 +9,19 @@ import android.os.Looper import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.SodaDialog 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.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.player.CreatorCommunityMediaPlayerManager import kr.co.vividnext.sodalive.explorer.profile.creator_community.modify.CreatorCommunityModifyActivity @@ -28,15 +32,64 @@ class CreatorCommunityAllActivity : BaseActivity 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( ActivityResultContracts.StartActivityForResult() @@ -54,7 +107,9 @@ class CreatorCommunityAllActivity : BaseActivity @@ -154,65 +229,25 @@ class CreatorCommunityAllActivity : BaseActivity { - 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 + switchToListMode(0, fromGridItemClick = false) } @SuppressLint("NotifyDataSetChanged") @@ -231,11 +266,146 @@ class CreatorCommunityAllActivity : BaseActivity 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) } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllGridAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllGridAdapter.kt new file mode 100644 index 00000000..59d4eb01 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllGridAdapter.kt @@ -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() { + + companion object { + private const val CONTENT_PREVIEW_MAX_LENGTH = 24 + } + + val items = mutableListOf() + + 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]) + } +} diff --git a/app/src/main/res/drawable-xxhdpi/ic_community_grid.png b/app/src/main/res/drawable-xxhdpi/ic_community_grid.png new file mode 100644 index 0000000000000000000000000000000000000000..eb54f310ecd57dbb42c302bc2378c6f8d99ffc74 GIT binary patch literal 702 zcmV;v0zv(WP)6vjUw2*z@Q9D(jgrDHdGg0?3p3u1+V z0a)k>;0SPn(uJ9B-AcEPj3EA`?};p>b`paqBz`~10@?3D{MmW2qh~-61i@lbQ4Oor z>Qz3UuWU9O7Z*$_m6~#Cx7)Kw_F)F~dflauK9w@!#ehmPOo%`MYPDK1old{f*DJ)6 z*>=0V?{>RWoq<*Wf8@FIF5=Cc%C&ALq;+1N=QR-j&NDoF`?^Nu4Dd()6A5CuTo!u0 z-h98_Q5nrcRrcB#pn@*q3l~n0HUl~rTvrtvHz_^VgM{h1c2q32EouhIdiV}0U`cM zj0A|fCqL)=0G+=Mi2<-25dfAW0>E-)A!K4K+hN%bCl3(-mLpFAm?7I?*$yWR5dfAW z0>E-a09cNZ0Q7vl_?%(R6*RI}RnlVOkvgkOFCB!X%bjZ_=ns%8M-YzVR9dap=uf@M zc(qy$bGe*O02T)aa<=yY>-G9wRj=%cpHn+Vau^tAixW@SrJBc`8I`84Q7Zr|M)%yU z{{wMl&IcxfPK{)OyeE^%cd1kwd7f9G=vqX)7*o0HbUGhFMv)Iq@fiZnBIkk&CX>m` k7K_DDbv8f{1VPy22Wr|3%2z`~zyJUM07*qoM6N<$f@~isaR2}S literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_community_grid_selected.png b/app/src/main/res/drawable-xxhdpi/ic_community_grid_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..f527036d3d0888d561c497115f47a1a8fda6a9da GIT binary patch literal 594 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBK3|DzL`iUdT1k0gQ7VI5W_oVoyp7Y6fx6Cm zx;TbZFutArF!zvwKx@3fe{b^w^98~zW*WjJE$a?&yk)t-yNmH9OKS5n;VHbAHha`J z)cCDo=zRR$bGb*S{0V*W<9+8C6kAk$u5C*U4K=p3@s!}1y|-8Av{ww%%N-MMgnryO z(ZKibX(=oF>4*K2+AK6x!zS@ouWih7csuXy9>+T}Dso%hlO74K6Rdx(YgTQ3%Qc&QIk?(~oG&sLc?HTxUyHdkpC=4-c3+zQ;k zZ`rnM``ccqb3A50yj;`TpTjhv<5|7W*2$K-)FA*^(`jB_uljG4GOl$;fC<3J8>wl-y!p? zs3HVp^nE6d#SGIAU%y&xS(BUrF$74dUulrYYV-9-&Q*)hdLEzRRM`HkXy?9t>^G#B zh05i9p`Kg}>*aIq3)ZdFHIUmDwY4I6=N{kL5#PD~1bgW;pMIKsQ0P~M z0JGWT@O^KM?`?P3d->3;4}Cv03hroZ{BFv1(@Ze@PYsU literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_community_list.png b/app/src/main/res/drawable-xxhdpi/ic_community_list.png new file mode 100644 index 0000000000000000000000000000000000000000..7a559d741d9f7521168ac22803e57839856f9e08 GIT binary patch literal 600 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBK3|DzL`iUdT1k0gQ7VI5W_oVoyp7Y6fx0ex zx;TbZFut8_U3ADmpsoJ)!;7qW50od&Q_{RVQCP*2F;8OqLKdkaCJQD@#}ygQyGjao zP0>rNuvmT2SK{OYzohT8Z#GW6#{4fVXe$$oqkzQYxw9o)0~g1wFE862%hCPOCVyp; zfrLu>)A?QbljL>LUUgA`!}{ylKllCjigu*#-xsrRy1+h3@( zHiq8{*VJkGSM2d|#q8A~TCy^H$Lgnu8Z6x_axf|5>#9d3rojvK?pLtASa9n9fud9E z&IK{)2?{bi>tHZ&XKAohVmKqnn9#?;Q0&4mLz(G-3_+>oQyA`lU;AD%Y;RNQr}aFK zI}`UvAOE;@?c1Wt#x1>*u4a98HeV;Y;buZoxvr2wrcDQ5^r@`P~2j literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_community_list_selected.png b/app/src/main/res/drawable-xxhdpi/ic_community_list_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..9ff608251d06aac4d66ec7e5c33f6991f13c41cb GIT binary patch literal 517 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBK3|DzL`iUdT1k0gQ7VI5W_oVoyp7Y6fw~$z zT^vIy7~jsm=zG{epzXiHJx%Euyr($sHuxRjNM#92XlmoVvEXh)j>C+Gyo7gCJ_!93 z()oJu+~4^nT`ATR|NNY^=MH0wz@r(@_H!rqNbj`qWSQc-{Gy3(f3|`&ztHjsdH2o@ zTlS|VevaNbN5yQ#1q(HvCcAvt!{`_E)-Y{+{8hW}IO2 zSAR`+^~t8wB_FOu>}fqK-L^$*qQb|VGcV=W%;kH=m2vbDH<#O{aQ^2LW^k?iT717j z|is>6e)o(~YZkKp0drEiuW#&y> zs`kOnVJp|zyrXwMee23C?GNty8&B&^m(Ra(@Soh3 zhO|$0>uS@!=^K<`IjP;8mNxqri1rrUc~ SC0+!^5`(9!pUXO@geCwcI??F> literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_creator_community_all.xml b/app/src/main/res/layout/activity_creator_community_all.xml index 0946f378..9443ab84 100644 --- a/app/src/main/res/layout/activity_creator_community_all.xml +++ b/app/src/main/res/layout/activity_creator_community_all.xml @@ -9,8 +9,91 @@ android:id="@+id/toolbar" layout="@layout/detail_toolbar" /> - + android:layout_height="52dp" + android:background="@color/black" + android:baselineAligned="false" + android:orientation="horizontal"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_creator_community_all_grid.xml b/app/src/main/res/layout/item_creator_community_all_grid.xml new file mode 100644 index 00000000..5d7e0c99 --- /dev/null +++ b/app/src/main/res/layout/item_creator_community_all_grid.xml @@ -0,0 +1,41 @@ + + + + + + + + + diff --git a/docs/20260305_크리에이터커뮤니티전체보기그리드리스트구현.md b/docs/20260305_크리에이터커뮤니티전체보기그리드리스트구현.md new file mode 100644 index 00000000..51cb84c6 --- /dev/null +++ b/docs/20260305_크리에이터커뮤니티전체보기그리드리스트구현.md @@ -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` 실행 불가(도구 오류 메시지 확인).