From 62ecf3dd516031c8d8c5fd5695aea13cea883d6b Mon Sep 17 00:00:00 2001 From: klaus Date: Mon, 25 Dec 2023 02:13:57 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 2 +- .../co/vividnext/sodalive/common/Constants.kt | 4 + .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 4 + .../explorer/profile/UserProfileActivity.kt | 17 +- .../creator_community/CreatorCommunityApi.kt | 43 +++ .../CreatorCommunityRepository.kt | 48 ++++ .../GetCommunityPostCommentListResponse.kt | 5 +- .../GetCommunityPostListResponse.kt | 2 +- .../all/CreatorCommunityAllActivity.kt | 132 ++++++++++ .../all/CreatorCommunityAllAdapter.kt | 120 +++++++++ .../all/CreatorCommunityAllViewModel.kt | 131 +++++++++ .../all/PostCommunityPostLikeRequest.kt | 7 + .../CreateCommunityPostCommentRequest.kt | 9 + .../comment/CreatorCommunityCommentAdapter.kt | 128 +++++++++ .../CreatorCommunityCommentFragment.kt | 78 ++++++ .../CreatorCommunityCommentListFragment.kt | 215 +++++++++++++++ .../CreatorCommunityCommentListViewModel.kt | 248 ++++++++++++++++++ .../CreatorCommunityCommentReplyAdapter.kt | 173 ++++++++++++ .../CreatorCommunityCommentReplyFragment.kt | 239 +++++++++++++++++ .../drawable/bg_round_corner_5_3_222222.xml | 8 + .../layout/activity_creator_community_all.xml | 15 ++ .../layout/item_community_post_comment.xml | 131 +++++++++ .../item_community_post_comment_reply.xml | 111 ++++++++ .../res/layout/item_creator_community_all.xml | 209 +++++++++++++++ 24 files changed, 2074 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllViewModel.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/PostCommunityPostLikeRequest.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreateCommunityPostCommentRequest.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentFragment.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListFragment.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListViewModel.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyFragment.kt create mode 100644 app/src/main/res/drawable/bg_round_corner_5_3_222222.xml create mode 100644 app/src/main/res/layout/activity_creator_community_all.xml create mode 100644 app/src/main/res/layout/item_community_post_comment.xml create mode 100644 app/src/main/res/layout/item_community_post_comment_reply.xml create mode 100644 app/src/main/res/layout/item_creator_community_all.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 75bbdd2..042874c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -95,8 +95,8 @@ - + diff --git a/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt b/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt index bdf701a..1eab262 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt @@ -57,4 +57,8 @@ object Constants { const val LIVE_SERVICE_NOTIFICATION_ID: Int = 2 const val ACTION_AUDIO_CONTENT_RECEIVER = "soda_live_action_content_receiver" const val ACTION_MAIN_AUDIO_CONTENT_RECEIVER = "soda_live_action_main_content_receiver" + + const val EXTRA_COMMUNITY_POST_ID = "community_post_id" + const val EXTRA_COMMUNITY_CREATOR_ID = "community_creator_id" + const val EXTRA_COMMUNITY_POST_COMMENT = "community_post_comment_id" } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt index 8839b2f..cd468ac 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt @@ -32,6 +32,8 @@ import kr.co.vividnext.sodalive.explorer.ExplorerViewModel import kr.co.vividnext.sodalive.explorer.profile.UserProfileViewModel import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityApi import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository +import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllViewModel +import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreatorCommunityCommentListViewModel import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewModel import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewModel import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListViewModel @@ -215,6 +217,8 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { viewModel { AudioContentNewAllViewModel(get()) } viewModel { AudioContentRankingAllViewModel(get()) } viewModel { RouletteSettingsViewModel(get()) } + viewModel { CreatorCommunityAllViewModel(get()) } + viewModel { CreatorCommunityCommentListViewModel(get()) } } private val repositoryModule = module { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt index 8771025..959b29e 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt @@ -36,6 +36,7 @@ import kr.co.vividnext.sodalive.databinding.ActivityUserProfileBinding import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse +import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAdapter import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewActivity import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewActivity @@ -474,7 +475,13 @@ class UserProfileActivity : BaseActivity( private fun setupCreatorCommunityView() { binding.layoutCreatorCommunityPost.ivWrite.setOnClickListener { } - binding.layoutCreatorCommunityPost.llAll.setOnClickListener { } + binding.layoutCreatorCommunityPost.llAll.setOnClickListener { + startActivity( + Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply { + putExtra(Constants.EXTRA_USER_ID, userId) + } + ) + } } private fun bindData() { @@ -734,7 +741,13 @@ class UserProfileActivity : BaseActivity( layout.tvLikeCount.text = "${item.likeCount}" layout.tvCommentCount.text = "${item.commentCount}" - layout.root.setOnClickListener { } + layout.root.setOnClickListener { + startActivity( + Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply { + putExtra(Constants.EXTRA_USER_ID, userId) + } + ) + } if (index > 0) { val lp = layout.root.layoutParams as LinearLayout.LayoutParams 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 b4fd776..4c0043a 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 @@ -1,9 +1,16 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest 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.comment.CreateCommunityPostCommentRequest +import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path import retrofit2.http.Query interface CreatorCommunityApi { @@ -21,4 +28,40 @@ interface CreatorCommunityApi { @Query("timezone") timezone: String, @Header("Authorization") authHeader: String ): Single>> + + @POST("/creator-community/like") + fun communityPostLike( + @Body request: PostCommunityPostLikeRequest, + @Header("Authorization") authHeader: String + ): Single> + + @GET("/creator-community/{id}/comment") + fun getCommunityPostCommentList( + @Path("id") postId: Long, + @Query("page") page: Int, + @Query("size") size: Int, + @Query("timezone") timezone: String, + @Header("Authorization") authHeader: String + ): Single> + + @POST("/creator-community/comment") + fun createCommunityPostComment( + @Body request: CreateCommunityPostCommentRequest, + @Header("Authorization") authHeader: String + ): Single> + + @PUT("/creator-community/comment") + fun modifyComment( + @Body request: ModifyCommentRequest, + @Header("Authorization") authHeader: String + ): Single> + + @GET("/creator-community/comment/{id}") + fun getCommentReplyList( + @Path("id") commentId: Long, + @Query("page") page: Int, + @Query("size") size: Int, + @Query("timezone") timezone: String, + @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 6b9e3bd..27db775 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 @@ -1,5 +1,8 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community +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.comment.CreateCommunityPostCommentRequest import java.util.TimeZone class CreatorCommunityRepository(private val api: CreatorCommunityApi) { @@ -22,4 +25,49 @@ class CreatorCommunityRepository(private val api: CreatorCommunityApi) { timezone = TimeZone.getDefault().id, authHeader = token ) + + fun communityPostLike(postId: Long, token: String) = api.communityPostLike( + request = PostCommunityPostLikeRequest(postId = postId), + authHeader = token + ) + + fun getCommunityPostCommentList( + postId: Long, + page: Int, + size: Int, + token: String + ) = api.getCommunityPostCommentList( + postId = postId, + page = page, + size = size, + timezone = TimeZone.getDefault().id, + authHeader = token + ) + + fun registerComment( + postId: Long, + comment: String, + parentId: Long? = null, + token: String + ) = api.createCommunityPostComment( + request = CreateCommunityPostCommentRequest( + comment = comment, + postId = postId, + parentId = parentId + ), + authHeader = token + ) + + fun modifyComment(request: ModifyCommentRequest, token: String) = api.modifyComment( + request = request, + authHeader = token + ) + + fun getCommentReplyList(commentId: Long, page: Int, size: Int, token: String) = api.getCommentReplyList( + commentId = commentId, + page = page, + size = size, + timezone = TimeZone.getDefault().id, + authHeader = token + ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostCommentListResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostCommentListResponse.kt index 9e36e35..c8a3297 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostCommentListResponse.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostCommentListResponse.kt @@ -1,12 +1,15 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community +import android.os.Parcelable import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize data class GetCommunityPostCommentListResponse( @SerializedName("totalCount") val totalCount: Int, @SerializedName("items") val items: List ) +@Parcelize data class GetCommunityPostCommentListItem( @SerializedName("id") val id: Long, @SerializedName("writerId") val writerId: Long, @@ -15,4 +18,4 @@ data class GetCommunityPostCommentListItem( @SerializedName("comment") val comment: String, @SerializedName("date") val date: String, @SerializedName("replyCount") val replyCount: Int, -) +) : Parcelable 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 e8d05c0..501d78a 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 @@ -12,7 +12,7 @@ data class GetCommunityPostListResponse( @SerializedName("date") val date: String, @SerializedName("isCommentAvailable") val isCommentAvailable: Boolean, @SerializedName("isAdult") val isAdult: Boolean, - @SerializedName("isLike") val isLike: Boolean, + @SerializedName("isLike") var isLike: Boolean, @SerializedName("likeCount") val likeCount: Int, @SerializedName("commentCount") val commentCount: Int, @SerializedName("firstComment") val firstComment: GetCommunityPostCommentListItem? 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 new file mode 100644 index 0000000..d0bcb62 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllActivity.kt @@ -0,0 +1,132 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all + +import android.annotation.SuppressLint +import android.graphics.Rect +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityAllBinding +import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreatorCommunityCommentFragment +import kr.co.vividnext.sodalive.extensions.dpToPx +import org.koin.android.ext.android.inject + +class CreatorCommunityAllActivity : BaseActivity( + ActivityCreatorCommunityAllBinding::inflate +) { + + private val viewModel: CreatorCommunityAllViewModel by inject() + + private lateinit var loadingDialog: LoadingDialog + private lateinit var adapter: CreatorCommunityAllAdapter + + private var creatorId: Long = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + creatorId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0) + if (creatorId <= 0) { + Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show() + finish() + } + + bindData() + viewModel.creatorId = creatorId + viewModel.getCommunityPostList() + } + + override fun setupView() { + loadingDialog = LoadingDialog(this, layoutInflater) + binding.toolbar.tvBack.text = "커뮤니티" + binding.toolbar.tvBack.setOnClickListener { finish() } + + adapter = CreatorCommunityAllAdapter( + onClickLike = { viewModel.communityPostLike(it) }, + writeComment = { postId, parentId, comment -> + viewModel.registerComment( + comment, + postId, + parentId + ) + }, + showCommentBottomSheetDialog = { + val dialog = CreatorCommunityCommentFragment( + creatorId = creatorId, + postId = it + ) + dialog.show( + supportFragmentManager, + dialog.tag + ) + } + ) + + val recyclerView = binding.rvCreatorCommunity + recyclerView.layoutManager = LinearLayoutManager( + applicationContext, + LinearLayoutManager.VERTICAL, + false + ) + + recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + outRect.left = 6.7f.dpToPx().toInt() + 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.adapter = adapter + } + + @SuppressLint("NotifyDataSetChanged") + private fun bindData() { + viewModel.toastLiveData.observe(this) { + it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } + } + + viewModel.isLoading.observe(this) { + if (it) { + loadingDialog.show(screenWidth, "") + } else { + loadingDialog.dismiss() + } + } + + viewModel.communityPostListLiveData.observe(this) { + if (viewModel.page == 2) { + adapter.items.clear() + } + + adapter.items.addAll(it) + adapter.notifyDataSetChanged() + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllAdapter.kt new file mode 100644 index 0000000..2e3d4c8 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllAdapter.kt @@ -0,0 +1,120 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.CircleCropTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityAllBinding +import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse +import kr.co.vividnext.sodalive.extensions.loadUrl + +class CreatorCommunityAllAdapter( + private val onClickLike: (Long) -> Unit, + private val writeComment: (Long, Long?, String) -> Unit, + private val showCommentBottomSheetDialog: (Long) -> Unit +) : RecyclerView.Adapter() { + + val items = mutableListOf() + + inner class ViewHolder( + private val binding: ItemCreatorCommunityAllBinding + ) : RecyclerView.ViewHolder(binding.root) { + @SuppressLint("NotifyDataSetChanged") + fun bind(item: GetCommunityPostListResponse, index: Int) { + binding.tvNickname.text = item.creatorNickname + binding.ivCreatorProfile.loadUrl(item.creatorProfileUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(CircleCropTransformation()) + } + + binding.tvContent.text = item.content + + binding.ivLike.setImageResource( + if (item.isLike) { + R.drawable.ic_audio_content_heart_pressed + } else { + R.drawable.ic_audio_content_heart_normal + } + ) + + binding.tvLike.text = "${item.likeCount}" + binding.llLike.setOnClickListener { + val isLike = !item.isLike + + items[index] = items[index].copy( + isLike = !item.isLike, + likeCount = if (isLike) item.likeCount + 1 else item.likeCount - 1 + ) + notifyDataSetChanged() + + onClickLike(item.postId) + } + + if (item.imageUrl != null) { + binding.ivContent.visibility = View.VISIBLE + binding.ivContent.loadUrl(item.imageUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + } + } else { + binding.ivContent.visibility = View.GONE + } + + if (item.isCommentAvailable) { + binding.llComment.visibility = View.VISIBLE + binding.tvCommentCount.text = "${item.commentCount}" + } else { + binding.llComment.visibility = View.GONE + } + + if (item.commentCount > 0) { + binding.ivCommentProfile.load(item.firstComment!!.profileUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(CircleCropTransformation()) + } + binding.tvCommentText.text = item.firstComment.comment + binding.tvCommentText.visibility = View.VISIBLE + binding.rlInputComment.visibility = View.GONE + + binding.llComment.setOnClickListener { showCommentBottomSheetDialog(item.postId) } + } else { + binding.tvCommentText.visibility = View.GONE + binding.rlInputComment.visibility = View.VISIBLE + binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(CircleCropTransformation()) + } + + binding.ivCommentSend.setOnClickListener { + val comment = binding.etComment.text.toString() + binding.etComment.setText("") + writeComment(item.postId, null, comment) + } + + binding.llComment.setOnClickListener {} + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemCreatorCommunityAllBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun getItemCount() = items.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(items[position], position) + } +} 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 new file mode 100644 index 0000000..9732da6 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllViewModel.kt @@ -0,0 +1,131 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.orhanobut.logger.Logger +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository +import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse + +class CreatorCommunityAllViewModel( + private val repository: CreatorCommunityRepository +) : BaseViewModel() { + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private var _communityPostListLiveData = MutableLiveData>() + val communityPostListLiveData: LiveData> + get() = _communityPostListLiveData + + var page = 1 + var isLast = false + private val pageSize = 10 + + var creatorId = 0L + + fun getCommunityPostList() { + if (!_isLoading.value!! && !isLast) { + _isLoading.value = true + compositeDisposable.add( + repository + .getCommunityPostList( + creatorId = creatorId, + page = page, + size = pageSize, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + if (it.data.isNotEmpty()) { + page += 1 + _communityPostListLiveData.postValue(it.data!!) + } else { + isLast = true + } + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + } + + fun communityPostLike(postId: Long) { + compositeDisposable.add( + repository.communityPostLike( + postId = postId, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({}, {}) + ) + + } + + fun registerComment(comment: String, postId: Long, parentId: Long? = null) { + if (!_isLoading.value!!) { + _isLoading.value = true + } + + compositeDisposable.add( + repository.registerComment( + postId = postId, + comment = comment, + parentId = parentId, + 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( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/PostCommunityPostLikeRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/PostCommunityPostLikeRequest.kt new file mode 100644 index 0000000..4d03c63 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/PostCommunityPostLikeRequest.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all + +import com.google.gson.annotations.SerializedName + +data class PostCommunityPostLikeRequest( + @SerializedName("postId") val postId: Long +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreateCommunityPostCommentRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreateCommunityPostCommentRequest.kt new file mode 100644 index 0000000..f1acf95 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreateCommunityPostCommentRequest.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment + +import com.google.gson.annotations.SerializedName + +data class CreateCommunityPostCommentRequest( + @SerializedName("comment") val comment: String, + @SerializedName("postId") val postId: Long, + @SerializedName("parentId") val parentId: Long? +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentAdapter.kt new file mode 100644 index 0000000..3b0af50 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentAdapter.kt @@ -0,0 +1,128 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.PopupMenu +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.CircleCropTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.ItemCommunityPostCommentBinding +import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostCommentListItem + +class CreatorCommunityCommentAdapter( + private val creatorId: Long, + private val modifyComment: (Long, String) -> Unit, + private val onClickDelete: (Long) -> Unit, + private val onItemClick: (GetCommunityPostCommentListItem) -> Unit +) : RecyclerView.Adapter() { + var items = mutableSetOf() + + inner class ViewHolder( + private val context: Context, + private val binding: ItemCommunityPostCommentBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: GetCommunityPostCommentListItem) { + binding.ivCommentProfile.load(item.profileUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(CircleCropTransformation()) + } + + binding.tvComment.text = item.comment + binding.tvCommentDate.text = item.date + binding.tvCommentNickname.text = item.nickname + + binding.tvWriteReply.text = if (item.replyCount > 0) { + "답글 ${item.replyCount}개" + } else { + "답글 쓰기" + } + + if ( + item.writerId == SharedPreferenceManager.userId || + creatorId == SharedPreferenceManager.userId + ) { + binding.etCommentModify.setText(item.comment) + binding.ivMenu.visibility = View.VISIBLE + binding.ivMenu.setOnClickListener { + showOptionMenu( + context, + binding.ivMenu, + commentId = item.id, + writerId = item.writerId, + creatorId = creatorId, + onClickModify = { + binding.rlCommentModify.visibility = View.VISIBLE + binding.tvComment.visibility = View.GONE + } + ) + } + + binding.tvModify.setOnClickListener { + binding.rlCommentModify.visibility = View.GONE + binding.tvComment.visibility = View.VISIBLE + modifyComment(item.id, binding.etCommentModify.text.toString()) + } + } else { + binding.ivMenu.visibility = View.GONE + } + + binding.tvWriteReply.setOnClickListener { onItemClick(item) } + binding.root.setOnClickListener { onItemClick(item) } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + parent.context, + ItemCommunityPostCommentBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(items.toList()[position]) + } + + override fun getItemCount() = items.size + + private fun showOptionMenu( + context: Context, + v: View, + commentId: Long, + writerId: Long, + creatorId: Long, + onClickModify: () -> Unit + ) { + val popup = PopupMenu(context, v) + val inflater = popup.menuInflater + + if (writerId == SharedPreferenceManager.userId) { + inflater.inflate(R.menu.content_comment_option_menu, popup.menu) + } else if (creatorId == SharedPreferenceManager.userId) { + inflater.inflate(R.menu.content_comment_option_menu2, popup.menu) + } + + popup.setOnMenuItemClickListener { + when (it.itemId) { + R.id.menu_review_modify -> { + onClickModify() + } + + R.id.menu_review_delete -> { + onClickDelete(commentId) + } + } + + true + } + + popup.show() + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentFragment.kt new file mode 100644 index 0000000..471cf40 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentFragment.kt @@ -0,0 +1,78 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.DialogAudioContentCommentBinding +import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostCommentListItem + +class CreatorCommunityCommentFragment( + private val creatorId: Long, + private val postId: Long +) : BottomSheetDialogFragment() { + + private lateinit var binding: DialogAudioContentCommentBinding + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + dialog.setOnShowListener { + val d = it as BottomSheetDialog + val bottomSheet = d.findViewById( + com.google.android.material.R.id.design_bottom_sheet + ) + if (bottomSheet != null) { + BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED + } + } + + return dialog + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = DialogAudioContentCommentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val commentListFragmentTag = "COMMENT_LIST_FRAGMENT" + val commentListFragment = CreatorCommunityCommentListFragment.newInstance( + creatorId = creatorId, + postId = postId + ) + val fragmentTransaction = childFragmentManager.beginTransaction() + fragmentTransaction.add(R.id.fl_container, commentListFragment, commentListFragmentTag) + fragmentTransaction.addToBackStack(commentListFragmentTag) + fragmentTransaction.commit() + } + + fun hideCommentDialog() { + dialog?.dismiss() + } + + fun onClickComment(comment: GetCommunityPostCommentListItem) { + val commentReplyFragmentTag = "COMMENT_REPLY_FRAGMENT" + val commentReplyFragment = CreatorCommunityCommentReplyFragment.newInstance( + creatorId = creatorId, + postId = postId, + comment = comment + ) + val fragmentTransaction = childFragmentManager.beginTransaction() + fragmentTransaction.add(R.id.fl_container, commentReplyFragment, commentReplyFragmentTag) + fragmentTransaction.addToBackStack(commentReplyFragmentTag) + fragmentTransaction.commit() + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListFragment.kt new file mode 100644 index 0000000..3e09c41 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListFragment.kt @@ -0,0 +1,215 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment + +import android.annotation.SuppressLint +import android.app.Service +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.Toast +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.CircleCropTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentFragment +import kr.co.vividnext.sodalive.base.BaseFragment +import kr.co.vividnext.sodalive.base.SodaDialog +import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentListBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import org.koin.android.ext.android.inject + +class CreatorCommunityCommentListFragment : BaseFragment( + FragmentAudioContentCommentListBinding::inflate +) { + private val viewModel: CreatorCommunityCommentListViewModel by inject() + + private lateinit var imm: InputMethodManager + private lateinit var loadingDialog: LoadingDialog + private lateinit var adapter: CreatorCommunityCommentAdapter + + private var creatorId: Long = 0 + private var postId: Long = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + creatorId = arguments?.getLong(Constants.EXTRA_COMMUNITY_CREATOR_ID) ?: 0 + postId = arguments?.getLong(Constants.EXTRA_COMMUNITY_POST_ID) ?: 0 + return super.onCreateView(inflater, container, savedInstanceState) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + loadingDialog = LoadingDialog(requireActivity(), layoutInflater) + imm = requireContext().getSystemService( + Service.INPUT_METHOD_SERVICE + ) as InputMethodManager + + setupView() + bindData() + viewModel.getCommentList(postId) { hideDialog() } + } + + private fun hideDialog() { + (parentFragment as CreatorCommunityCommentFragment).hideCommentDialog() + } + + private fun setupView() { + binding.ivClose.setOnClickListener { hideDialog() } + + binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) { + crossfade(true) + placeholder(R.drawable.ic_place_holder) + transformations(CircleCropTransformation()) + } + + binding.ivCommentSend.setOnClickListener { + hideKeyboard() + val comment = binding.etComment.text.toString() + binding.etComment.setText("") + viewModel.registerComment(postId, comment) + } + + adapter = CreatorCommunityCommentAdapter( + creatorId = creatorId, + modifyComment = { commentId, comment -> + hideKeyboard() + viewModel.modifyComment( + commentId = commentId, + postId = postId, + comment = comment + ) + }, + onClickDelete = { + SodaDialog( + activity = requireActivity(), + layoutInflater = layoutInflater, + title = "댓글 삭제", + desc = "삭제하시겠습니까?", + confirmButtonTitle = "삭제", + confirmButtonClick = { + viewModel.modifyComment( + commentId = it, + postId = postId, + isActive = false + ) + }, + cancelButtonTitle = "취소", + cancelButtonClick = {} + ).show(screenWidth) + }, + onItemClick = { + (parentFragment as CreatorCommunityCommentFragment).onClickComment(it) + } + ) + + val recyclerView = binding.rvComment + recyclerView.setHasFixedSize(true) + recyclerView.layoutManager = LinearLayoutManager( + activity, + LinearLayoutManager.VERTICAL, + false + ) + recyclerView.addItemDecoration(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)) { + 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 lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!! + .findLastCompletelyVisibleItemPosition() + val itemTotalCount = recyclerView.adapter!!.itemCount - 1 + + // 스크롤이 끝에 도달했는지 확인 + if (!recyclerView.canScrollVertically(1) && + lastVisibleItemPosition == itemTotalCount + ) { + viewModel.getCommentList(postId = postId) + } + } + }) + + recyclerView.adapter = adapter + } + + @SuppressLint("NotifyDataSetChanged") + private fun bindData() { + viewModel.isLoading.observe(viewLifecycleOwner) { + if (it) { + loadingDialog.show(screenWidth) + } else { + loadingDialog.dismiss() + } + } + + viewModel.toastLiveData.observe(viewLifecycleOwner) { + it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() } + } + + viewModel.totalCommentCount.observe(viewLifecycleOwner) { + binding.tvCommentCount.text = "$it" + } + + viewModel.commentList.observe(viewLifecycleOwner) { + if (viewModel.page - 1 == 1) { + adapter.items.clear() + binding.rvComment.scrollToPosition(0) + } + + adapter.items.addAll(it) + adapter.notifyDataSetChanged() + } + } + + private fun hideKeyboard() { + imm.hideSoftInputFromWindow(view?.windowToken, 0) + } + + companion object { + fun newInstance(creatorId: Long, postId: Long): CreatorCommunityCommentListFragment { + val args = Bundle() + args.putLong(Constants.EXTRA_COMMUNITY_CREATOR_ID, creatorId) + args.putLong(Constants.EXTRA_COMMUNITY_POST_ID, postId) + + val fragment = CreatorCommunityCommentListFragment() + fragment.arguments = args + return fragment + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListViewModel.kt new file mode 100644 index 0000000..f1cb2b6 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListViewModel.kt @@ -0,0 +1,248 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.orhanobut.logger.Logger +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository +import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostCommentListItem + +class CreatorCommunityCommentListViewModel( + private val repository: CreatorCommunityRepository +) : BaseViewModel() { + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private var _commentList = MutableLiveData>() + val commentList: LiveData> + get() = _commentList + + private var _totalCommentCount = MutableLiveData(0) + val totalCommentCount: LiveData + get() = _totalCommentCount + + var page = 1 + private var isLast = false + private val size = 10 + + fun getCommentList(postId: Long, onFailure: (() -> Unit)? = null) { + if (!_isLoading.value!! && !isLast) { + _isLoading.value = true + compositeDisposable.add( + repository.getCommunityPostCommentList( + postId = postId, + page = page - 1, + size = size, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + _totalCommentCount.postValue(it.data.totalCount) + + page += 1 + if (it.data.items.isNotEmpty()) { + _commentList.postValue(it.data.items) + } else { + isLast = true + _commentList.postValue(listOf()) + } + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + + if (onFailure != null) { + onFailure() + } + } + + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + if (onFailure != null) { + onFailure() + } + } + ) + ) + } + } + + fun registerComment(postId: Long, comment: String, commentId: Long? = null) { + if (!_isLoading.value!!) { + _isLoading.value = true + } + + compositeDisposable.add( + repository.registerComment( + postId = postId, + comment = comment, + parentId = commentId, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + + if (it.success) { + page = 1 + isLast = false + if (commentId != null) { + getCommentReplyList(commentId = commentId) + } else { + getCommentList(postId) + } + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun modifyComment( + commentId: Long, + postId: Long, + parentCommentId: Long? = null, + comment: String? = null, + isActive: Boolean? = null + ) { + if (comment == null && isActive == null) { + _toastLiveData.postValue("변경사항이 없습니다.") + return + } + + if (comment != null && comment.isBlank()) { + _toastLiveData.postValue("내용을 입력하세요") + return + } + + _isLoading.value = true + + val request = ModifyCommentRequest(commentId = commentId) + + if (comment != null) { + request.comment = comment + } + + if (isActive != null) { + request.isActive = isActive + } + + compositeDisposable.add( + repository.modifyComment( + request = request, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + + if (it.success) { + page = 1 + isLast = false + + if (parentCommentId != null) { + getCommentReplyList(parentCommentId) + } else { + getCommentList(postId) + } + } else { + val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + _toastLiveData.postValue(message) + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun getCommentReplyList(commentId: Long, onFailure: (() -> Unit)? = null) { + if (!_isLoading.value!! && !isLast) { + _isLoading.value = true + compositeDisposable.add( + repository.getCommentReplyList( + commentId = commentId, + page = page - 1, + size = size, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + page += 1 + if (it.data.items.isNotEmpty()) { + _commentList.postValue(it.data.items) + } else { + isLast = true + _commentList.postValue(listOf()) + } + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + + if (onFailure != null) { + onFailure() + } + } + + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + if (onFailure != null) { + onFailure() + } + } + ) + ) + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyAdapter.kt new file mode 100644 index 0000000..f1fd79b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyAdapter.kt @@ -0,0 +1,173 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.PopupMenu +import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding +import coil.load +import coil.transform.CircleCropTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.ItemCommunityPostCommentBinding +import kr.co.vividnext.sodalive.databinding.ItemCommunityPostCommentReplyBinding +import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostCommentListItem +import kr.co.vividnext.sodalive.extensions.loadUrl + +class CreatorCommunityCommentReplyAdapter( + private val creatorId: Long, + private val modifyComment: (Long, String) -> Unit, + private val onClickDelete: (Long) -> Unit +) : RecyclerView.Adapter() { + var items = mutableSetOf() + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): CreatorCommunityCommentReplyViewHolder { + return if (viewType == 0) { + CreatorCommunityCommentReplyHeaderViewHolder( + binding = ItemCommunityPostCommentBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } else { + CreatorCommunityCommentReplyItemViewHolder( + context = parent.context, + creatorId = creatorId, + binding = ItemCommunityPostCommentReplyBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ), + showOptionMenu = { context, view, commentId, writerId, creatorId, onClickModify -> + showOptionMenu(context, view, commentId, writerId, creatorId, onClickModify) + }, + modifyComment = modifyComment + ) + } + } + + override fun onBindViewHolder(holder: CreatorCommunityCommentReplyViewHolder, position: Int) { + holder.bind(items.toList()[position]) + } + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int): Int { + return position + } + + private fun showOptionMenu( + context: Context, + v: View, + commentId: Long, + writerId: Long, + creatorId: Long, + onClickModify: () -> Unit + ) { + val popup = PopupMenu(context, v) + val inflater = popup.menuInflater + + if (writerId == SharedPreferenceManager.userId) { + inflater.inflate(R.menu.content_comment_option_menu, popup.menu) + } else if (creatorId == SharedPreferenceManager.userId) { + inflater.inflate(R.menu.content_comment_option_menu2, popup.menu) + } + + popup.setOnMenuItemClickListener { + when (it.itemId) { + R.id.menu_review_modify -> { + onClickModify() + } + + R.id.menu_review_delete -> { + onClickDelete(commentId) + } + } + + true + } + + popup.show() + } +} + +abstract class CreatorCommunityCommentReplyViewHolder( + binding: ViewBinding +) : RecyclerView.ViewHolder(binding.root) { + abstract fun bind(item: GetCommunityPostCommentListItem) +} + +class CreatorCommunityCommentReplyHeaderViewHolder( + private val binding: ItemCommunityPostCommentBinding +) : CreatorCommunityCommentReplyViewHolder(binding) { + override fun bind(item: GetCommunityPostCommentListItem) { + binding.ivCommentProfile.loadUrl(item.profileUrl) { + crossfade(true) + placeholder(R.drawable.ic_place_holder) + transformations(CircleCropTransformation()) + } + + binding.tvComment.text = item.comment + binding.tvCommentDate.text = item.date + binding.tvCommentNickname.text = item.nickname + + binding.tvWriteReply.visibility = View.GONE + binding.ivMenu.visibility = View.GONE + } +} + +class CreatorCommunityCommentReplyItemViewHolder( + private val context: Context, + private val creatorId: Long, + private val binding: ItemCommunityPostCommentReplyBinding, + private val showOptionMenu: ( + Context, View, Long, Long, Long, onClickModify: () -> Unit + ) -> Unit, + private val modifyComment: (Long, String) -> Unit +) : CreatorCommunityCommentReplyViewHolder(binding) { + override fun bind(item: GetCommunityPostCommentListItem) { + binding.ivCommentProfile.load(item.profileUrl) { + crossfade(true) + placeholder(R.drawable.ic_place_holder) + transformations(CircleCropTransformation()) + } + + binding.tvComment.text = item.comment + binding.tvCommentDate.text = item.date + binding.tvCommentNickname.text = item.nickname + + if ( + item.writerId == SharedPreferenceManager.userId || + creatorId == SharedPreferenceManager.userId + ) { + binding.etCommentModify.setText(item.comment) + binding.ivMenu.visibility = View.VISIBLE + binding.ivMenu.setOnClickListener { + showOptionMenu( + context, + binding.ivMenu, + item.id, + item.writerId, + creatorId + ) { + binding.rlCommentModify.visibility = View.VISIBLE + binding.tvComment.visibility = View.GONE + } + } + + binding.tvModify.setOnClickListener { + binding.rlCommentModify.visibility = View.GONE + binding.tvComment.visibility = View.VISIBLE + modifyComment(item.id, binding.etCommentModify.text.toString()) + } + } else { + binding.ivMenu.visibility = View.GONE + } + } + +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyFragment.kt new file mode 100644 index 0000000..51fb407 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyFragment.kt @@ -0,0 +1,239 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment + +import android.annotation.SuppressLint +import android.app.Service +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.Toast +import androidx.core.os.BundleCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.CircleCropTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.base.BaseFragment +import kr.co.vividnext.sodalive.base.SodaDialog +import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentReplyBinding +import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostCommentListItem +import kr.co.vividnext.sodalive.extensions.dpToPx +import org.koin.android.ext.android.inject + +class CreatorCommunityCommentReplyFragment : BaseFragment( + FragmentAudioContentCommentReplyBinding::inflate +) { + private val viewModel: CreatorCommunityCommentListViewModel by inject() + + private lateinit var imm: InputMethodManager + private lateinit var loadingDialog: LoadingDialog + private lateinit var adapter: CreatorCommunityCommentReplyAdapter + + private var originalComment: GetCommunityPostCommentListItem? = null + private var creatorId: Long = 0 + private var postId: Long = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + creatorId = arguments?.getLong(Constants.EXTRA_COMMUNITY_CREATOR_ID) ?: 0 + postId = arguments?.getLong(Constants.EXTRA_COMMUNITY_POST_ID) ?: 0 + originalComment = BundleCompat.getParcelable( + requireArguments(), + Constants.EXTRA_COMMUNITY_POST_COMMENT, + GetCommunityPostCommentListItem::class.java + ) + return super.onCreateView(inflater, container, savedInstanceState) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (originalComment == null) { + parentFragmentManager.popBackStack() + } + + loadingDialog = LoadingDialog(requireActivity(), layoutInflater) + imm = requireContext().getSystemService( + Service.INPUT_METHOD_SERVICE + ) as InputMethodManager + + setupView() + bindData() + viewModel.getCommentReplyList(commentId = originalComment!!.id) { + parentFragmentManager.popBackStack() + } + } + + private fun hideDialog() { + (parentFragment as CreatorCommunityCommentFragment).hideCommentDialog() + } + + private fun setupView() { + binding.root.setOnClickListener { } + + binding.tvBack.setOnClickListener { + parentFragmentManager.popBackStack() + } + + binding.ivClose.setOnClickListener { hideDialog() } + + binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) { + crossfade(true) + placeholder(R.drawable.ic_place_holder) + transformations(CircleCropTransformation()) + } + + binding.ivCommentSend.setOnClickListener { + hideKeyboard() + val comment = binding.etComment.text.toString() + binding.etComment.setText("") + viewModel.registerComment(postId, comment, originalComment!!.id) + } + + adapter = CreatorCommunityCommentReplyAdapter( + creatorId = creatorId, + modifyComment = { commentId, comment -> + hideKeyboard() + viewModel.modifyComment( + commentId = commentId, + postId = postId, + parentCommentId = originalComment!!.id, + comment = comment + ) + }, + onClickDelete = { + SodaDialog( + activity = requireActivity(), + layoutInflater = layoutInflater, + title = "댓글 삭제", + desc = "삭제하시겠습니까?", + confirmButtonTitle = "삭제", + confirmButtonClick = { + viewModel.modifyComment( + commentId = it, + postId = postId, + parentCommentId = originalComment!!.id, + isActive = false + ) + }, + cancelButtonTitle = "취소", + cancelButtonClick = {} + ).show(screenWidth) + }, + ).apply { + items.add(originalComment!!) + } + + val recyclerView = binding.rvCommentReply + recyclerView.setHasFixedSize(true) + recyclerView.layoutManager = LinearLayoutManager( + activity, + LinearLayoutManager.VERTICAL, + false + ) + + recyclerView.addItemDecoration(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)) { + 0 -> { + outRect.top = 13.3f.dpToPx().toInt() + outRect.bottom = 12f.dpToPx().toInt() + } + + adapter.itemCount - 1 -> { + outRect.top = 12f.dpToPx().toInt() + outRect.bottom = 13.3f.dpToPx().toInt() + } + + else -> { + outRect.top = 12f.dpToPx().toInt() + outRect.bottom = 12f.dpToPx().toInt() + } + } + } + }) + + recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!! + .findLastCompletelyVisibleItemPosition() + val itemTotalCount = recyclerView.adapter!!.itemCount - 1 + + // 스크롤이 끝에 도달했는지 확인 + if (!recyclerView.canScrollVertically(1) && + lastVisibleItemPosition == itemTotalCount + ) { + viewModel.getCommentReplyList(originalComment!!.id) + } + } + }) + + recyclerView.adapter = adapter + } + + @SuppressLint("NotifyDataSetChanged") + private fun bindData() { + viewModel.isLoading.observe(viewLifecycleOwner) { + if (it) { + loadingDialog.show(screenWidth) + } else { + loadingDialog.dismiss() + } + } + + viewModel.toastLiveData.observe(viewLifecycleOwner) { + it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() } + } + + viewModel.commentList.observe(viewLifecycleOwner) { + if (viewModel.page - 1 == 1) { + adapter.items.clear() + binding.rvCommentReply.scrollToPosition(0) + adapter.items.add(originalComment!!) + } + + adapter.items.addAll(it) + adapter.notifyDataSetChanged() + } + } + + private fun hideKeyboard() { + imm.hideSoftInputFromWindow(view?.windowToken, 0) + } + + companion object { + fun newInstance( + creatorId: Long, + postId: Long, + comment: GetCommunityPostCommentListItem + ): CreatorCommunityCommentReplyFragment { + val args = Bundle() + args.putLong(Constants.EXTRA_COMMUNITY_POST_ID, postId) + args.putLong(Constants.EXTRA_COMMUNITY_CREATOR_ID, creatorId) + args.putParcelable(Constants.EXTRA_COMMUNITY_POST_COMMENT, comment) + + val fragment = CreatorCommunityCommentReplyFragment() + fragment.arguments = args + return fragment + } + } +} diff --git a/app/src/main/res/drawable/bg_round_corner_5_3_222222.xml b/app/src/main/res/drawable/bg_round_corner_5_3_222222.xml new file mode 100644 index 0000000..2989d8f --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_5_3_222222.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_creator_community_all.xml b/app/src/main/res/layout/activity_creator_community_all.xml new file mode 100644 index 0000000..7f4605d --- /dev/null +++ b/app/src/main/res/layout/activity_creator_community_all.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_community_post_comment.xml b/app/src/main/res/layout/item_community_post_comment.xml new file mode 100644 index 0000000..bbb3f4a --- /dev/null +++ b/app/src/main/res/layout/item_community_post_comment.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_community_post_comment_reply.xml b/app/src/main/res/layout/item_community_post_comment_reply.xml new file mode 100644 index 0000000..91af3fd --- /dev/null +++ b/app/src/main/res/layout/item_community_post_comment_reply.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_creator_community_all.xml b/app/src/main/res/layout/item_creator_community_all.xml new file mode 100644 index 0000000..6b91ba3 --- /dev/null +++ b/app/src/main/res/layout/item_creator_community_all.xml @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +