diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityAdapter.kt index 6833c503..418a7fa7 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityAdapter.kt @@ -12,7 +12,6 @@ import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.loadUrl -import kr.co.vividnext.sodalive.explorer.profile.creator_community.relativeTimeText class CreatorCommunityAdapter( private val width: Int, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt index 3948d98f..2be3b852 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt @@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PostCommunityPostLikeRequest import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PurchasePostRequest import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreateCommunityPostCommentRequest +import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.UpdateCommunityPostFixedRequest import okhttp3.MultipartBody import okhttp3.RequestBody import retrofit2.http.Body @@ -99,4 +100,10 @@ interface CreatorCommunityApi { @Body request: PurchasePostRequest, @Header("Authorization") authHeader: String ): Single> + + @PUT("/creator-community/fixed") + fun updateCommunityPostFixed( + @Body request: UpdateCommunityPostFixedRequest, + @Header("Authorization") authHeader: String + ): Single> } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt index 3a098531..dfddf751 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt @@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PostCommunityPostLikeRequest import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PurchasePostRequest import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreateCommunityPostCommentRequest +import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.UpdateCommunityPostFixedRequest import okhttp3.MultipartBody import okhttp3.RequestBody import java.util.TimeZone @@ -109,4 +110,10 @@ class CreatorCommunityRepository(private val api: CreatorCommunityApi) { request = PurchasePostRequest(postId = postId, timezone = TimeZone.getDefault().id), authHeader = token ) + + fun updateCommunityPostFixed(postId: Long, isFixed: Boolean, token: String) = + api.updateCommunityPostFixed( + request = UpdateCommunityPostFixedRequest(postId = postId, isFixed = isFixed), + authHeader = token + ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostListResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostListResponse.kt index d969594d..3c9b088a 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostListResponse.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostListResponse.kt @@ -30,6 +30,7 @@ data class GetCommunityPostListResponse( @SerializedName("likeCount") val likeCount: Int, @SerializedName("commentCount") val commentCount: Int, @SerializedName("firstComment") val firstComment: GetCommunityPostCommentListItem?, + @SerializedName("isFixed") val isFixed: Boolean, @SerializedName("isExpand") var isExpand: Boolean = false ) 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 c2f9467d..2a897659 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 @@ -19,6 +19,7 @@ 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.SharedPreferenceManager import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityAllBinding import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse @@ -166,11 +167,14 @@ class CreatorCommunityAllActivity : BaseActivity + viewModel.updateCommunityPostFixed(postId, isFixed) + }, onClickAudioContentPlayOrPause = { mediaPlayerManager.toggleContent(it) }, isAudioContentPlaying = { mediaPlayerManager.isPlayingContent(it) }, onClickPurchaseContent = { postId, can, onSuccess -> @@ -242,6 +249,10 @@ class CreatorCommunityAllActivity : BaseActivity + val item = gridAdapter.items.getOrNull(position) ?: return@CreatorCommunityAllGridAdapter + showCommunityOptionBottomSheet(item) } ) @@ -319,6 +330,52 @@ class CreatorCommunityAllActivity : BaseActivity Unit, private val onClickDelete: (Long) -> Unit, private val onClickReport: (Long) -> Unit, + private val onClickToggleFixed: (postId: Long, isFixed: Boolean) -> Unit, private val onClickAudioContentPlayOrPause: (CreatorCommunityContentItem) -> Unit, private val isAudioContentPlaying: (Long) -> Boolean, private val onClickPurchaseContent: @@ -301,7 +301,7 @@ class CreatorCommunityAllAdapter( textView.setOnClickListener { items[index] = items[index].copy( - isExpand = !isExpand, + isExpand = !isExpand ) notifyDataSetChanged() } @@ -329,34 +329,21 @@ class CreatorCommunityAllAdapter( postId: Long, creatorId: Long ) { - val popup = PopupMenu(context, v) - val inflater = popup.menuInflater + val item = items.find { it.postId == postId } ?: return + val isCreator = creatorId == SharedPreferenceManager.userId + val isFixed = item.isFixed - if (creatorId == SharedPreferenceManager.userId) { - inflater.inflate(R.menu.community_post_creator_option_menu, popup.menu) - } else { - inflater.inflate(R.menu.community_post_option_menu, popup.menu) - } - - popup.setOnMenuItemClickListener { - when (it.itemId) { - R.id.menu_modify -> { - onClickModify(postId) - } - - R.id.menu_delete -> { - onClickDelete(postId) - } - - R.id.menu_report -> { - onClickReport(postId) - } - } - - true - } - - popup.show() + val dialog = CreatorCommunityPostMenuBottomSheetDialog( + isFixed = isFixed, + isCreator = isCreator, + onClickPin = { + onClickToggleFixed(postId, !isFixed) + }, + onClickModify = { onClickModify(postId) }, + onClickDelete = { onClickDelete(postId) }, + onClickReport = { onClickReport(postId) } + ) + dialog.show((v.context as androidx.fragment.app.FragmentActivity).supportFragmentManager, dialog.tag) } @SuppressLint("NotifyDataSetChanged") 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 index 59d4eb01..a5aca4c0 100644 --- 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 @@ -11,7 +11,8 @@ import kr.co.vividnext.sodalive.extensions.loadUrl class CreatorCommunityAllGridAdapter( private val itemSize: Int, - private val onClickItem: (Int) -> Unit + private val onClickItem: (Int) -> Unit, + private val onLongClickItem: (Int) -> Unit ) : RecyclerView.Adapter() { companion object { @@ -30,6 +31,8 @@ class CreatorCommunityAllGridAdapter( lp.height = itemSize binding.root.layoutParams = lp + binding.ivPin.visibility = if (item.isFixed) View.VISIBLE else View.GONE + val isPaidLocked = item.price > 0 && !item.existOrdered val hasImage = !item.imageUrl.isNullOrBlank() @@ -67,6 +70,14 @@ class CreatorCommunityAllGridAdapter( onClickItem(position) } } + + binding.root.setOnLongClickListener { + val position = bindingAdapterPosition + if (position != RecyclerView.NO_POSITION && !isPaidLocked) { + onLongClickItem(position) + } + true + } } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllViewModel.kt index 0fabe32f..f5c32d01 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllViewModel.kt @@ -282,5 +282,49 @@ class CreatorCommunityAllViewModel( ) ) } + } + + fun updateCommunityPostFixed(postId: Long, isFixed: Boolean) { + if (_isLoading.value == true) return + _isLoading.value = true + + compositeDisposable.add( + repository.updateCommunityPostFixed( + postId = postId, + isFixed = isFixed, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success) { + // 목록을 초기화하고 재조회하여 최신 고정 상태를 반영한다. + page = 1 + isLast = false + getCommunityPostList() + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + SodaLiveApplicationHolder.get() + .getString(R.string.common_error_unknown) + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue( + SodaLiveApplicationHolder.get() + .getString(R.string.common_error_unknown) + ) + } + ) + ) + } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityPostMenuBottomSheetDialog.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityPostMenuBottomSheetDialog.kt new file mode 100644 index 00000000..a931c1b8 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityPostMenuBottomSheetDialog.kt @@ -0,0 +1,66 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.DialogCreatorCommunityPostMenuBinding + +class CreatorCommunityPostMenuBottomSheetDialog( + private val isFixed: Boolean, + private val isCreator: Boolean, + private val onClickPin: () -> Unit, + private val onClickModify: () -> Unit, + private val onClickDelete: () -> Unit, + private val onClickReport: () -> Unit +) : BottomSheetDialogFragment() { + private lateinit var dialog: DialogCreatorCommunityPostMenuBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + dialog = DialogCreatorCommunityPostMenuBinding.inflate(inflater, container, false) + return dialog.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (isCreator) { + dialog.tvReport.visibility = View.GONE + dialog.llMenuCreator.visibility = View.VISIBLE + + if (isFixed) { + dialog.ivPin.setImageResource(R.drawable.ic_pin_cancel) + dialog.tvPin.text = getString(R.string.screen_creator_community_unpin) + } else { + dialog.ivPin.setImageResource(R.drawable.ic_pin) + dialog.tvPin.text = getString(R.string.screen_creator_community_pin) + } + + dialog.llPin.setOnClickListener { + dismiss() + onClickPin() + } + dialog.llModify.setOnClickListener { + dismiss() + onClickModify() + } + dialog.llDelete.setOnClickListener { + dismiss() + onClickDelete() + } + } else { + dialog.llMenuCreator.visibility = View.GONE + dialog.tvReport.visibility = View.VISIBLE + dialog.tvReport.setOnClickListener { + dismiss() + onClickReport() + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/UpdateCommunityPostFixedRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/UpdateCommunityPostFixedRequest.kt new file mode 100644 index 00000000..70af8fac --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/UpdateCommunityPostFixedRequest.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class UpdateCommunityPostFixedRequest( + @SerializedName("postId") val postId: Long, + @SerializedName("isFixed") val isFixed: Boolean +) diff --git a/app/src/main/res/layout/dialog_creator_community_post_menu.xml b/app/src/main/res/layout/dialog_creator_community_post_menu.xml new file mode 100644 index 00000000..5026bca3 --- /dev/null +++ b/app/src/main/res/layout/dialog_creator_community_post_menu.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_creator_community.xml b/app/src/main/res/layout/item_creator_community.xml index bef5070c..4c2f1c6a 100644 --- a/app/src/main/res/layout/item_creator_community.xml +++ b/app/src/main/res/layout/item_creator_community.xml @@ -48,6 +48,7 @@ android:textSize="14sp" tools:text="3일전" /> + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 0f421ca2..03d2b28a 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -87,6 +87,10 @@ Confirm Cancel + + Pin to top + Unpin + Language Korean diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 26b9f7c9..425e3b70 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -87,6 +87,10 @@ 確認 キャンセル + + 最上部に固定 + 固定を解除 + 言語設定 韓国語 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9ad6bc22..a69f202e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -86,6 +86,10 @@ 확인 취소 + + 최상단에 고정 + 고정 해제 + 언어 설정 한국어 diff --git a/docs/20260316_그리드_유료게시물_보조메뉴_제한.md b/docs/20260316_그리드_유료게시물_보조메뉴_제한.md new file mode 100644 index 00000000..b94f37f4 --- /dev/null +++ b/docs/20260316_그리드_유료게시물_보조메뉴_제한.md @@ -0,0 +1,14 @@ +# 20260316_그리드_유료게시물_보조메뉴_제한.md + +## 개요 +그리드 모드에서 유료 게시물 중 구매하지 않은 게시물에 대해 롱클릭 시 보조 메뉴가 표시되지 않도록 수정한다. + +## 작업 내용 +- [x] CreatorCommunityAllGridAdapter.kt 수정: `isPaidLocked`가 `true`일 때 롱클릭 리스너를 무시하도록 처리. + +## 검증 기록 +- 무엇을: 그리드 모드 유료/미구매 게시물 롱클릭 시 보조 메뉴 노출 여부 확인 +- 왜: 유료 게시물을 구매하기 전에는 보조 메뉴(고정/해제, 수정, 삭제 등)가 노출되지 않아야 함 +- 어떻게: `CreatorCommunityAllGridAdapter`의 `isPaidLocked` 조건 확인 및 롱클릭 리스너 수정 +- 실행 명령: `./gradlew :app:assembleDebug` +- 결과: `./gradlew :app:assembleDebug` 성공. `isPaidLocked`일 때 롱클릭 리스너 내 조건 처리가 정상적으로 추가됨. diff --git a/docs/20260316_커뮤니티_고정게시물_핀표시_그리드전용_수정.md b/docs/20260316_커뮤니티_고정게시물_핀표시_그리드전용_수정.md new file mode 100644 index 00000000..74342d25 --- /dev/null +++ b/docs/20260316_커뮤니티_고정게시물_핀표시_그리드전용_수정.md @@ -0,0 +1,22 @@ +# 20260316_커뮤니티_고정게시물_핀표시_그리드전용_수정.md + +## 개요 +- 커뮤니티 게시물 고정 기능을 리스트 형태와 그리드 형태 모두에 적용했으나, 요구사항 변경에 따라 리스트 형태에서는 핀 아이콘을 제거하고 그리드 형태에서만 표시하도록 수정한다. + +## 작업 내용 +- [x] `item_creator_community_all.xml` (리스트 아이템)에서 `iv_pin` 제거 +- [x] `item_creator_community.xml` (리스트 아이템)에서 `iv_pin` 제거 +- [x] `CreatorCommunityAllAdapter.kt` (리스트 어댑터)에서 `iv_pin` 표시 로직 제거 +- [x] `CreatorCommunityAdapter.kt` (리스트 어댑터)에서 `iv_pin` 표시 로직 제거 +- [x] 빌드 및 린트 체크 (`./gradlew :app:assembleDebug`, `./gradlew :app:ktlintCheck`) + +## 검증 기록 +- 무엇을: 리스트 형태에서 고정 핀 아이콘 노출 여부 확인 +- 왜: 요구사항에 따라 그리드 형태에서만 핀을 노출하기 위함 +- 어떻게: 코드 수정 후 빌드 성공 여부 및 린트 확인 +- 결과: + - 리스트 형태 아이템 레이아웃에서 `iv_pin` 뷰를 삭제함. + - 리스트 어댑터들에서 `iv_pin`을 참조하거나 가시성을 변경하는 코드를 삭제함. + - 그리드 형태(`item_creator_community_all_grid.xml`, `CreatorCommunityAllGridAdapter.kt`)는 기존대로 유지하여 핀 아이콘이 노출되도록 함. + - `./gradlew :app:assembleDebug` 성공. + - `./gradlew :app:ktlintCheck` 결과, 패키지명 규칙 외의 다른 스타일 위반 사항(빈 줄, 후행 쉼표 등)을 수정 완료함.