feat(creator-channel): 커뮤니티 답글 화면을 구현한다
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.InputType
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
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.databinding.ActivityCreatorChannelCommunityReplyBinding
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityCommentUiModel
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.ui.CreatorChannelCommunityReplyAdapter
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui.CreatorChannelCommunityCommentAdapter
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class CreatorChannelCommunityReplyActivity : BaseActivity<ActivityCreatorChannelCommunityReplyBinding>(
|
||||
ActivityCreatorChannelCommunityReplyBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: CreatorChannelCommunityReplyViewModel by viewModel()
|
||||
private val replyAdapter = CreatorChannelCommunityReplyAdapter(
|
||||
onModifyClick = { reply -> showModifyReplyDialog(reply.replyId, reply.comment) },
|
||||
onDeleteClick = { reply -> showDeleteReplyDialog(reply.replyId) }
|
||||
)
|
||||
private var postId: Long = 0L
|
||||
|
||||
override fun setupView() {
|
||||
postId = intent.getLongExtra(EXTRA_POST_ID, 0L)
|
||||
val parentComment = parentCommentFromIntent()
|
||||
if (postId <= 0L || parentComment.commentId <= 0L) {
|
||||
Toast.makeText(this, R.string.creator_channel_community_invalid_post, Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
binding.btnCreatorChannelCommunityReplyBack.setOnClickListener { finish() }
|
||||
binding.rvCreatorChannelCommunityReply.layoutManager = LinearLayoutManager(this)
|
||||
binding.rvCreatorChannelCommunityReply.adapter = replyAdapter
|
||||
binding.rvCreatorChannelCommunityReply.addOnScrollListener(
|
||||
object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
if (dy > 0 && !recyclerView.canScrollVertically(1)) {
|
||||
viewModel.loadMoreReplies()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
binding.etCreatorChannelCommunityReply.movementMethod = ScrollingMovementMethod()
|
||||
binding.etCreatorChannelCommunityReply.doAfterTextChanged { editable ->
|
||||
viewModel.updateReplyInput(editable?.toString().orEmpty())
|
||||
}
|
||||
binding.btnCreatorChannelCommunityReplySend.setOnClickListener { viewModel.submitReply() }
|
||||
|
||||
viewModel.replyStateLiveData.observe(this) { state -> render(state) }
|
||||
viewModel.replyChangedEventLiveData.observe(this) { changed ->
|
||||
if (changed == true) {
|
||||
setResult(Activity.RESULT_OK)
|
||||
viewModel.consumeReplyChangedEvent()
|
||||
}
|
||||
}
|
||||
viewModel.parentCommentChangedEventLiveData.observe(this) { changed ->
|
||||
if (changed == true) {
|
||||
setResult(Activity.RESULT_OK)
|
||||
viewModel.consumeParentCommentChangedEvent()
|
||||
}
|
||||
}
|
||||
viewModel.parentCommentDeletedEventLiveData.observe(this) { deleted ->
|
||||
if (deleted == true) {
|
||||
setResult(Activity.RESULT_OK)
|
||||
viewModel.consumeParentCommentDeletedEvent()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
viewModel.toastLiveData.observe(this) { event ->
|
||||
event.consume()?.let { toast ->
|
||||
val message = toast.message ?: toast.resId?.let(::getString)
|
||||
message?.let(::showToast)
|
||||
}
|
||||
}
|
||||
viewModel.loadReplies(postId, parentComment)
|
||||
}
|
||||
|
||||
private fun render(state: CreatorChannelCommunityReplyUiState) {
|
||||
when (state) {
|
||||
is CreatorChannelCommunityReplyUiState.Content -> renderContent(state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderContent(content: CreatorChannelCommunityReplyUiState.Content) = with(binding) {
|
||||
CreatorChannelCommunityCommentAdapter.bindComment(
|
||||
layoutCreatorChannelCommunityReplyParent,
|
||||
content.parentComment,
|
||||
onReplyClick = {},
|
||||
onModifyClick = { comment -> showModifyParentCommentDialog(comment.comment) },
|
||||
onDeleteClick = { showDeleteParentCommentDialog() }
|
||||
)
|
||||
layoutCreatorChannelCommunityReplyParent.layoutCreatorChannelCommunityCommentLatestReply.isVisible = false
|
||||
replyAdapter.submitItems(content.replies)
|
||||
if (etCreatorChannelCommunityReply.text.toString() != content.replyInput) {
|
||||
etCreatorChannelCommunityReply.setText(content.replyInput)
|
||||
etCreatorChannelCommunityReply.setSelection(content.replyInput.length)
|
||||
}
|
||||
updateSendButton(content.replyInput.trim().isNotEmpty() && !content.isSendingReply)
|
||||
}
|
||||
|
||||
private fun showModifyReplyDialog(replyId: Long, comment: String) {
|
||||
val editText = EditText(this).apply {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
minLines = 3
|
||||
setText(comment)
|
||||
setSelection(comment.length)
|
||||
}
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.menu_modify_action)
|
||||
.setView(editText)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.confirm) { _, _ ->
|
||||
val modifiedComment = editText.text?.toString().orEmpty()
|
||||
if (modifiedComment.trim() == comment.trim()) {
|
||||
Toast.makeText(this, R.string.audio_content_comment_no_change, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
viewModel.modifyReply(replyId, modifiedComment)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showModifyParentCommentDialog(comment: String) {
|
||||
val editText = EditText(this).apply {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
minLines = 3
|
||||
setText(comment)
|
||||
setSelection(comment.length)
|
||||
}
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.menu_modify_action)
|
||||
.setView(editText)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.confirm) { _, _ ->
|
||||
val modifiedComment = editText.text?.toString().orEmpty()
|
||||
if (modifiedComment.trim() == comment.trim()) {
|
||||
Toast.makeText(this, R.string.audio_content_comment_no_change, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
viewModel.modifyParentComment(modifiedComment)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showDeleteParentCommentDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.confirm_delete_title)
|
||||
.setMessage(R.string.confirm_delete_message)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.confirm) { _, _ -> viewModel.deleteParentComment() }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showDeleteReplyDialog(replyId: Long) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.confirm_delete_title)
|
||||
.setMessage(R.string.confirm_delete_message)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.confirm) { _, _ -> viewModel.deleteReply(replyId) }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun updateSendButton(enabled: Boolean) = with(binding.btnCreatorChannelCommunityReplySend) {
|
||||
isEnabled = enabled
|
||||
setBackgroundResource(
|
||||
if (enabled) {
|
||||
R.drawable.bg_creator_channel_community_send_enabled
|
||||
} else {
|
||||
R.drawable.bg_creator_channel_community_send_disabled
|
||||
}
|
||||
)
|
||||
setImageResource(if (enabled) R.drawable.ic_new_arrow_up_white else R.drawable.ic_new_arrow_up_gray)
|
||||
}
|
||||
|
||||
private fun parentCommentFromIntent(): CreatorChannelCommunityCommentUiModel {
|
||||
return CreatorChannelCommunityCommentUiModel(
|
||||
commentId = intent.getLongExtra(EXTRA_COMMENT_ID, 0L),
|
||||
memberId = intent.getLongExtra(EXTRA_MEMBER_ID, 0L),
|
||||
memberNickname = intent.getStringExtra(EXTRA_MEMBER_NICKNAME).orEmpty(),
|
||||
memberProfileUrl = intent.getStringExtra(EXTRA_MEMBER_PROFILE_URL),
|
||||
createdAtUtc = intent.getStringExtra(EXTRA_CREATED_AT_UTC).orEmpty(),
|
||||
createdAtText = intent.getStringExtra(EXTRA_CREATED_AT_TEXT).orEmpty(),
|
||||
comment = intent.getStringExtra(EXTRA_COMMENT).orEmpty(),
|
||||
likeCount = intent.getIntExtra(EXTRA_LIKE_COUNT, 0),
|
||||
replyCount = intent.getIntExtra(EXTRA_REPLY_COUNT, 0),
|
||||
isLiked = intent.getBooleanExtra(EXTRA_IS_LIKED, false),
|
||||
isMine = intent.getBooleanExtra(EXTRA_IS_MINE, false),
|
||||
isCreatorOwner = intent.getBooleanExtra(EXTRA_IS_CREATOR_OWNER, false),
|
||||
latestReply = null
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_POST_ID = "extra_post_id"
|
||||
private const val EXTRA_COMMENT_ID = "extra_comment_id"
|
||||
private const val EXTRA_MEMBER_ID = "extra_member_id"
|
||||
private const val EXTRA_MEMBER_NICKNAME = "extra_member_nickname"
|
||||
private const val EXTRA_MEMBER_PROFILE_URL = "extra_member_profile_url"
|
||||
private const val EXTRA_CREATED_AT_UTC = "extra_created_at_utc"
|
||||
private const val EXTRA_CREATED_AT_TEXT = "extra_created_at_text"
|
||||
private const val EXTRA_COMMENT = "extra_comment"
|
||||
private const val EXTRA_LIKE_COUNT = "extra_like_count"
|
||||
private const val EXTRA_REPLY_COUNT = "extra_reply_count"
|
||||
private const val EXTRA_IS_LIKED = "extra_is_liked"
|
||||
private const val EXTRA_IS_MINE = "extra_is_mine"
|
||||
private const val EXTRA_IS_CREATOR_OWNER = "extra_is_creator_owner"
|
||||
|
||||
fun newIntent(context: Context, postId: Long, parentComment: CreatorChannelCommunityCommentUiModel): Intent {
|
||||
return Intent(context, CreatorChannelCommunityReplyActivity::class.java)
|
||||
.putExtra(EXTRA_POST_ID, postId)
|
||||
.putExtra(EXTRA_COMMENT_ID, parentComment.commentId)
|
||||
.putExtra(EXTRA_MEMBER_ID, parentComment.memberId)
|
||||
.putExtra(EXTRA_MEMBER_NICKNAME, parentComment.memberNickname)
|
||||
.putExtra(EXTRA_MEMBER_PROFILE_URL, parentComment.memberProfileUrl)
|
||||
.putExtra(EXTRA_CREATED_AT_UTC, parentComment.createdAtUtc)
|
||||
.putExtra(EXTRA_CREATED_AT_TEXT, parentComment.createdAtText)
|
||||
.putExtra(EXTRA_COMMENT, parentComment.comment)
|
||||
.putExtra(EXTRA_LIKE_COUNT, parentComment.likeCount)
|
||||
.putExtra(EXTRA_REPLY_COUNT, parentComment.replyCount)
|
||||
.putExtra(EXTRA_IS_LIKED, parentComment.isLiked)
|
||||
.putExtra(EXTRA_IS_MINE, parentComment.isMine)
|
||||
.putExtra(EXTRA_IS_CREATOR_OWNER, parentComment.isCreatorOwner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelCommunityReplyBinding
|
||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityReplyUiModel
|
||||
|
||||
class CreatorChannelCommunityReplyAdapter(
|
||||
private val onModifyClick: (CreatorChannelCommunityReplyUiModel) -> Unit,
|
||||
private val onDeleteClick: (CreatorChannelCommunityReplyUiModel) -> Unit
|
||||
) : RecyclerView.Adapter<CreatorChannelCommunityReplyAdapter.ViewHolder>() {
|
||||
|
||||
private var items: List<CreatorChannelCommunityReplyUiModel> = emptyList()
|
||||
|
||||
fun submitItems(items: List<CreatorChannelCommunityReplyUiModel>) {
|
||||
this.items = items
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(
|
||||
ItemCreatorChannelCommunityReplyBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
onModifyClick,
|
||||
onDeleteClick
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
class ViewHolder(
|
||||
private val binding: ItemCreatorChannelCommunityReplyBinding,
|
||||
private val onModifyClick: (CreatorChannelCommunityReplyUiModel) -> Unit,
|
||||
private val onDeleteClick: (CreatorChannelCommunityReplyUiModel) -> Unit
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: CreatorChannelCommunityReplyUiModel) = with(binding) {
|
||||
ivCreatorChannelCommunityReplyItemProfile.loadUrl(item.memberProfileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
tvCreatorChannelCommunityReplyItemNickname.text = item.memberNickname
|
||||
tvCreatorChannelCommunityReplyItemTime.text = item.createdAtText
|
||||
tvCreatorChannelCommunityReplyItemBody.text = item.comment
|
||||
ivCreatorChannelCommunityReplyItemMore.isVisible = item.canShowMore
|
||||
ivCreatorChannelCommunityReplyItemMore.setOnClickListener { anchor ->
|
||||
showOptionMenu(anchor, item, onModifyClick, onDeleteClick)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOptionMenu(
|
||||
anchor: View,
|
||||
item: CreatorChannelCommunityReplyUiModel,
|
||||
onModifyClick: (CreatorChannelCommunityReplyUiModel) -> Unit,
|
||||
onDeleteClick: (CreatorChannelCommunityReplyUiModel) -> Unit
|
||||
) {
|
||||
if (!item.canShowMore) return
|
||||
val popup = PopupMenu(anchor.context, anchor)
|
||||
val menuRes = if (item.isMine) R.menu.content_comment_option_menu else R.menu.content_comment_option_menu2
|
||||
popup.menuInflater.inflate(menuRes, popup.menu)
|
||||
popup.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.menu_review_modify -> onModifyClick(item)
|
||||
R.id.menu_review_delete -> onDeleteClick(item)
|
||||
}
|
||||
true
|
||||
}
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private val CreatorChannelCommunityReplyUiModel.canShowMore: Boolean
|
||||
get() = isMine || isCreatorOwner
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user