feat(creator-channel): FanTalk 상세 화면을 추가한다
This commit is contained in:
@@ -123,6 +123,9 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity"
|
android:name=".v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
<activity
|
||||||
|
android:name=".v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailActivity"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
|
||||||
<activity android:name=".v2.live.onair.HomeOnAirLiveActivity" />
|
<activity android:name=".v2.live.onair.HomeOnAirLiveActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".v2.main.chat.dm.DmChatRoomActivity"
|
android:name=".v2.main.chat.dm.DmChatRoomActivity"
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelApi
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesViewModel
|
||||||
@@ -458,6 +459,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||||||
viewModel { CreatorChannelCommunityDetailViewModel(get(), get(), get()) }
|
viewModel { CreatorChannelCommunityDetailViewModel(get(), get(), get()) }
|
||||||
viewModel { CreatorChannelCommunityReplyViewModel(get(), get(), get()) }
|
viewModel { CreatorChannelCommunityReplyViewModel(get(), get(), get()) }
|
||||||
viewModel { CreatorChannelFanTalkViewModel(get(), get()) }
|
viewModel { CreatorChannelFanTalkViewModel(get(), get()) }
|
||||||
|
viewModel { CreatorChannelFanTalkDetailViewModel(get(), get()) }
|
||||||
viewModel { CreatorChannelFanTalkWriteViewModel(get()) }
|
viewModel { CreatorChannelFanTalkWriteViewModel(get()) }
|
||||||
viewModel { CreatorChannelDonationViewModel(get(), get()) }
|
viewModel { CreatorChannelDonationViewModel(get(), get()) }
|
||||||
viewModel { PushNotificationListViewModel(get()) }
|
viewModel { PushNotificationListViewModel(get()) }
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.text.method.ScrollingMovementMethod
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.PopupWindow
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.IntentCompat
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.doAfterTextChanged
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelFantalkDetailBinding
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ViewCreatorChannelFantalkMorePopupBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkReplyUiModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkUiModel
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
|
class CreatorChannelFanTalkDetailActivity : BaseActivity<ActivityCreatorChannelFantalkDetailBinding>(
|
||||||
|
ActivityCreatorChannelFantalkDetailBinding::inflate
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val viewModel: CreatorChannelFanTalkDetailViewModel by viewModel()
|
||||||
|
private var creatorId: Long = 0L
|
||||||
|
private var replyMorePopup: PopupWindow? = null
|
||||||
|
private var renderedEditingTarget: CreatorChannelFanTalkDetailEditTarget? = null
|
||||||
|
|
||||||
|
override fun setupView() {
|
||||||
|
creatorId = intent.getLongExtra(EXTRA_CREATOR_ID, 0L)
|
||||||
|
val payload = payloadFromIntent() ?: run {
|
||||||
|
Toast.makeText(this, R.string.creator_channel_community_invalid_post, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (creatorId <= 0L || payload.fanTalkId <= 0L) {
|
||||||
|
Toast.makeText(this, R.string.creator_channel_community_invalid_post, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnCreatorChannelFantalkDetailBack.setOnClickListener { finish() }
|
||||||
|
binding.etCreatorChannelFantalkDetailReply.movementMethod = ScrollingMovementMethod()
|
||||||
|
binding.etCreatorChannelFantalkDetailReply.doAfterTextChanged { editable ->
|
||||||
|
viewModel.updateReplyInput(editable?.toString().orEmpty())
|
||||||
|
}
|
||||||
|
binding.btnCreatorChannelFantalkDetailSend.setOnClickListener { viewModel.submitReply() }
|
||||||
|
binding.btnCreatorChannelFantalkDetailEditCancel.setOnClickListener { viewModel.cancelReplyEdit() }
|
||||||
|
|
||||||
|
viewModel.detailStateLiveData.observe(this) { state -> render(state) }
|
||||||
|
viewModel.replacementModalEventLiveData.observe(this) { event ->
|
||||||
|
event.consume()?.let(::showReplacementDialog)
|
||||||
|
}
|
||||||
|
viewModel.replyChangedEventLiveData.observe(this) { changed ->
|
||||||
|
if (changed == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
hideReplyKeyboard()
|
||||||
|
viewModel.consumeReplyChangedEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.toastLiveData.observe(this) { event ->
|
||||||
|
event.consume()?.let { toast ->
|
||||||
|
val message = toast.message ?: toast.resId?.let(::getString)
|
||||||
|
message?.let(::showToast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.load(creatorId, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
replyMorePopup = null
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun render(state: CreatorChannelFanTalkDetailUiState) {
|
||||||
|
when (state) {
|
||||||
|
is CreatorChannelFanTalkDetailUiState.Content -> renderContent(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderContent(content: CreatorChannelFanTalkDetailUiState.Content) = with(binding) {
|
||||||
|
bindParentFanTalk(content.parentFanTalk)
|
||||||
|
bindReply(content.reply)
|
||||||
|
if (etCreatorChannelFantalkDetailReply.text.toString() != content.replyInput) {
|
||||||
|
etCreatorChannelFantalkDetailReply.setText(content.replyInput)
|
||||||
|
etCreatorChannelFantalkDetailReply.setSelection(content.replyInput.length)
|
||||||
|
}
|
||||||
|
btnCreatorChannelFantalkDetailEditCancel.isVisible = content.editingTarget != null
|
||||||
|
if (content.editingTarget != null && content.editingTarget != renderedEditingTarget) {
|
||||||
|
showReplyKeyboard()
|
||||||
|
}
|
||||||
|
renderedEditingTarget = content.editingTarget
|
||||||
|
updateSendButton(content.isSendEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindParentFanTalk(item: CreatorChannelFanTalkUiModel) = with(binding) {
|
||||||
|
ivCreatorChannelFantalkDetailProfile.loadProfile(item.writerProfileImageUrl)
|
||||||
|
tvCreatorChannelFantalkDetailNickname.text = item.writerNickname
|
||||||
|
tvCreatorChannelFantalkDetailTime.text = item.createdAtText
|
||||||
|
tvCreatorChannelFantalkDetailContent.text = item.content
|
||||||
|
ivCreatorChannelFantalkDetailMore.isVisible = false
|
||||||
|
ivCreatorChannelFantalkDetailMore.setOnClickListener(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindReply(reply: CreatorChannelFanTalkReplyUiModel?) = with(binding) {
|
||||||
|
viewCreatorChannelFantalkDetailReplyConnector.isVisible = reply != null
|
||||||
|
layoutCreatorChannelFantalkDetailReply.isVisible = reply != null
|
||||||
|
if (reply == null) {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
return@with
|
||||||
|
}
|
||||||
|
|
||||||
|
ivCreatorChannelFantalkDetailReplyProfile.loadProfile(reply.writerProfileImageUrl)
|
||||||
|
tvCreatorChannelFantalkDetailReplyNickname.text = reply.writerNickname
|
||||||
|
tvCreatorChannelFantalkDetailReplyTime.text = reply.createdAtText
|
||||||
|
tvCreatorChannelFantalkDetailReplyContent.text = reply.content
|
||||||
|
ivCreatorChannelFantalkDetailReplyMore.isVisible = reply.fanTalkId > 0L
|
||||||
|
ivCreatorChannelFantalkDetailReplyMore.setOnClickListener(
|
||||||
|
if (reply.fanTalkId > 0L) {
|
||||||
|
View.OnClickListener { anchor -> showReplyMorePopup(anchor) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showReplyMorePopup(anchor: View) {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
val popupBinding = ViewCreatorChannelFantalkMorePopupBinding.inflate(LayoutInflater.from(anchor.context))
|
||||||
|
replyMorePopup = PopupWindow(anchor.context).apply {
|
||||||
|
contentView = popupBinding.root
|
||||||
|
width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
isOutsideTouchable = true
|
||||||
|
isFocusable = true
|
||||||
|
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
|
||||||
|
}
|
||||||
|
popupBinding.tvCreatorChannelFantalkMoreEdit.isVisible = true
|
||||||
|
popupBinding.tvCreatorChannelFantalkMoreDelete.isVisible = true
|
||||||
|
popupBinding.tvCreatorChannelFantalkMoreEdit.setOnClickListener {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
viewModel.startReplyEdit()
|
||||||
|
}
|
||||||
|
popupBinding.tvCreatorChannelFantalkMoreDelete.setOnClickListener {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
showDeleteReplyDialog()
|
||||||
|
}
|
||||||
|
replyMorePopup?.showAsDropDown(anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showReplacementDialog(modal: CreatorChannelFanTalkReplacementModalUiModel) {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(modal.titleResId),
|
||||||
|
desc = getString(modal.descriptionResId),
|
||||||
|
confirmButtonTitle = getString(modal.confirmResId),
|
||||||
|
confirmButtonClick = { viewModel.confirmReplacement() },
|
||||||
|
cancelButtonTitle = getString(modal.cancelResId),
|
||||||
|
cancelButtonClick = { viewModel.cancelReplacement() }
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDeleteReplyDialog() {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.confirm_delete_title),
|
||||||
|
desc = getString(R.string.confirm_delete_message),
|
||||||
|
confirmButtonTitle = getString(R.string.confirm),
|
||||||
|
confirmButtonClick = { viewModel.deleteReply() },
|
||||||
|
cancelButtonTitle = getString(R.string.cancel)
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSendButton(enabled: Boolean) = with(binding.btnCreatorChannelFantalkDetailSend) {
|
||||||
|
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 showReplyKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelFantalkDetailReply
|
||||||
|
editText.requestFocus()
|
||||||
|
editText.post {
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideReplyKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelFantalkDetailReply
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(editText.windowToken, 0)
|
||||||
|
editText.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun payloadFromIntent(): CreatorChannelFanTalkDetailPayload? {
|
||||||
|
return IntentCompat.getParcelableExtra(intent, EXTRA_PAYLOAD, CreatorChannelFanTalkDetailPayload::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ImageView.loadProfile(url: String) {
|
||||||
|
loadUrl(url) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.ic_placeholder_profile)
|
||||||
|
error(R.drawable.ic_placeholder_profile)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_CREATOR_ID = "extra_creator_id"
|
||||||
|
private const val EXTRA_PAYLOAD = "extra_payload"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, creatorId: Long, payload: CreatorChannelFanTalkDetailPayload): Intent {
|
||||||
|
return Intent(context, CreatorChannelFanTalkDetailActivity::class.java)
|
||||||
|
.putExtra(EXTRA_CREATOR_ID, creatorId)
|
||||||
|
.putExtra(EXTRA_PAYLOAD, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/layout_creator_channel_fantalk_detail_title_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_fantalk_detail_back"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/a11y_back"
|
||||||
|
android:src="@drawable/ic_new_bar_back"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scroll_creator_channel_fantalk_detail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/layout_creator_channel_fantalk_detail_input_bar"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/layout_creator_channel_fantalk_detail_title_bar">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="@dimen/spacing_14"
|
||||||
|
android:paddingBottom="@dimen/spacing_14">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_fantalk_detail_profile"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:contentDescription="@string/a11y_feed_profile_image"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/ic_placeholder_profile" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_14"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/iv_creator_channel_fantalk_detail_more"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/iv_creator_channel_fantalk_detail_profile"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_fantalk_detail_profile"
|
||||||
|
tools:text="닉네임" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_time"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_4"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/gray_500"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
tools:text="2분 전" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_fantalk_detail_more"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_new_more"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_content"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/iv_creator_channel_fantalk_detail_profile"
|
||||||
|
tools:text="팬톡 응원 메시지 내용입니다." />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view_creator_channel_fantalk_detail_reply_connector"
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="@dimen/spacing_14"
|
||||||
|
android:layout_marginTop="@dimen/spacing_14"
|
||||||
|
android:background="@color/gray_800"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_fantalk_detail_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_fantalk_detail_content"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_fantalk_detail_reply"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:background="@drawable/bg_creator_channel_fantalk_reply"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/spacing_14"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_fantalk_detail_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/view_creator_channel_fantalk_detail_reply_connector"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_fantalk_detail_reply_profile"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/a11y_feed_profile_image"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@drawable/ic_placeholder_profile" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_reply_nickname"
|
||||||
|
style="@style/Typography.Caption3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="크리에이터" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_reply_time"
|
||||||
|
style="@style/Typography.Caption3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_4"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/gray_400"
|
||||||
|
tools:text="방금 전" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view_creator_channel_fantalk_detail_reply_header_spacer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_fantalk_detail_reply_more"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_new_more" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_reply_content"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="응원 고마워요!" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_fantalk_detail_input_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_creator_channel_fantalk_detail_reply"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_input_field"
|
||||||
|
android:hint="@string/creator_channel_community_reply_placeholder"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="textMultiLine|textCapSentences"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:minLines="1"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textColorHint="@color/gray_500"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_fantalk_detail_send"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_send_disabled"
|
||||||
|
android:contentDescription="@string/action_send"
|
||||||
|
android:enabled="false"
|
||||||
|
android:src="@drawable/ic_new_arrow_up_gray" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_fantalk_detail_edit_cancel"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/cancel"
|
||||||
|
android:src="@drawable/ic_x_white"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class CreatorChannelFanTalkDetailUiContractTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `상세 Activity source는 payload intent와 ViewModel 이벤트를 연결한다`() {
|
||||||
|
val source = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailActivity.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertTrue(source.contains("BaseActivity<ActivityCreatorChannelFantalkDetailBinding>"))
|
||||||
|
assertTrue(source.contains("private val viewModel: CreatorChannelFanTalkDetailViewModel by viewModel()"))
|
||||||
|
assertTrue(
|
||||||
|
source.contains(
|
||||||
|
"fun newIntent(context: Context, creatorId: Long, " +
|
||||||
|
"payload: CreatorChannelFanTalkDetailPayload): Intent"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(source.contains("putExtra(EXTRA_PAYLOAD, payload)"))
|
||||||
|
assertTrue(source.contains("IntentCompat.getParcelableExtra"))
|
||||||
|
assertTrue(source.contains("if (creatorId <= 0L || payload.fanTalkId <= 0L)"))
|
||||||
|
assertTrue(source.contains("R.string.creator_channel_community_invalid_post"))
|
||||||
|
assertTrue(source.contains("viewModel.load(creatorId, payload)"))
|
||||||
|
assertTrue(source.contains("doAfterTextChanged"))
|
||||||
|
assertTrue(source.contains("viewModel.updateReplyInput"))
|
||||||
|
assertTrue(source.contains("viewModel.submitReply()"))
|
||||||
|
assertTrue(source.contains("viewModel.cancelReplyEdit()"))
|
||||||
|
assertTrue(source.contains("viewModel.replacementModalEventLiveData.observe"))
|
||||||
|
assertTrue(source.contains("viewModel.replyChangedEventLiveData.observe"))
|
||||||
|
assertTrue(source.contains("setResult(Activity.RESULT_OK)"))
|
||||||
|
assertTrue(source.contains("viewModel.consumeReplyChangedEvent()"))
|
||||||
|
assertTrue(source.contains("viewModel.startReplyEdit()"))
|
||||||
|
assertTrue(source.contains("showDeleteReplyDialog()"))
|
||||||
|
assertTrue(source.contains("ivCreatorChannelFantalkDetailReplyMore.isVisible = reply.fanTalkId > 0L"))
|
||||||
|
assertTrue(source.contains("V2ModalDialog("))
|
||||||
|
assertTrue(source.contains("getString(modal.titleResId)"))
|
||||||
|
assertTrue(source.contains("getString(modal.descriptionResId)"))
|
||||||
|
assertTrue(source.contains("getString(modal.confirmResId)"))
|
||||||
|
assertTrue(source.contains("getString(modal.cancelResId)"))
|
||||||
|
assertTrue(source.contains("R.drawable.bg_creator_channel_community_send_enabled"))
|
||||||
|
assertTrue(source.contains("R.drawable.bg_creator_channel_community_send_disabled"))
|
||||||
|
assertTrue(source.contains("R.drawable.ic_new_arrow_up_white"))
|
||||||
|
assertTrue(source.contains("R.drawable.ic_new_arrow_up_gray"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `상세 layout은 커뮤니티 답글 구조와 FanTalk 답글 card 토큰을 사용한다`() {
|
||||||
|
val layout = projectFile("app/src/main/res/layout/activity_creator_channel_fantalk_detail.xml").readText()
|
||||||
|
|
||||||
|
assertTrue(layout.contains("""android:background="@color/black""""))
|
||||||
|
assertTrue(layout.contains("""android:id="@+id/btn_creator_channel_fantalk_detail_back""""))
|
||||||
|
assertTrue(layout.contains("""android:layout_height="60dp""""))
|
||||||
|
assertTrue(layout.contains("""android:id="@+id/iv_creator_channel_fantalk_detail_profile""""))
|
||||||
|
assertTrue(layout.contains("""android:layout_width="42dp""""))
|
||||||
|
assertTrue(layout.contains("""android:id="@+id/tv_creator_channel_fantalk_detail_nickname""""))
|
||||||
|
assertTrue(layout.contains("""android:id="@+id/tv_creator_channel_fantalk_detail_content""""))
|
||||||
|
assertTrue(layout.contains("""android:id="@+id/layout_creator_channel_fantalk_detail_reply""""))
|
||||||
|
assertTrue(layout.contains("@drawable/bg_creator_channel_fantalk_reply"))
|
||||||
|
assertTrue(layout.contains("""android:id="@+id/iv_creator_channel_fantalk_detail_reply_profile""""))
|
||||||
|
assertTrue(layout.contains("""android:layout_width="20dp""""))
|
||||||
|
assertTextViewUsesStyle(
|
||||||
|
layout,
|
||||||
|
"tv_creator_channel_fantalk_detail_reply_nickname",
|
||||||
|
"@style/Typography.Caption3"
|
||||||
|
)
|
||||||
|
assertTextViewUsesStyle(
|
||||||
|
layout,
|
||||||
|
"tv_creator_channel_fantalk_detail_reply_time",
|
||||||
|
"@style/Typography.Caption3"
|
||||||
|
)
|
||||||
|
assertTextViewContains(
|
||||||
|
layout,
|
||||||
|
"tv_creator_channel_fantalk_detail_reply_nickname",
|
||||||
|
"""android:layout_width="wrap_content"""
|
||||||
|
)
|
||||||
|
assertTextViewContains(
|
||||||
|
layout,
|
||||||
|
"tv_creator_channel_fantalk_detail_reply_time",
|
||||||
|
"""android:layout_marginStart="@dimen/spacing_4"""
|
||||||
|
)
|
||||||
|
assertTrue(layout.contains("""android:id="@+id/iv_creator_channel_fantalk_detail_reply_more""""))
|
||||||
|
assertTrue(layout.contains("""android:id="@+id/layout_creator_channel_fantalk_detail_input_bar""""))
|
||||||
|
assertTrue(layout.contains("view_creator_channel_fantalk_detail_reply_header_spacer"))
|
||||||
|
assertTrue(layout.contains("android:layout_width=\"0dp\""))
|
||||||
|
assertTrue(layout.contains("android:layout_weight=\"1\""))
|
||||||
|
assertTrue(layout.contains("@drawable/bg_creator_channel_community_input_field"))
|
||||||
|
assertTrue(layout.contains("@string/creator_channel_community_reply_placeholder"))
|
||||||
|
assertTrue(layout.contains("""android:maxLines="3""""))
|
||||||
|
assertTrue(layout.contains("@drawable/bg_creator_channel_community_send_disabled"))
|
||||||
|
assertTrue(layout.contains("@drawable/ic_new_arrow_up_gray"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `상세 Manifest DI ViewModel source 계약을 등록한다`() {
|
||||||
|
val manifest = projectFile("app/src/main/AndroidManifest.xml").readText()
|
||||||
|
val appDi = projectFile("app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt").readText()
|
||||||
|
val viewModel = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModel.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertTrue(manifest.contains(".v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailActivity"))
|
||||||
|
assertTrue(manifest.contains("""android:windowSoftInputMode="stateAlwaysHidden|adjustResize""""))
|
||||||
|
assertTrue(
|
||||||
|
appDi.contains(
|
||||||
|
"import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail." +
|
||||||
|
"CreatorChannelFanTalkDetailViewModel"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(appDi.contains("viewModel { CreatorChannelFanTalkDetailViewModel(get(), get()) }"))
|
||||||
|
assertTrue(viewModel.contains("fun cancelReplyEdit()"))
|
||||||
|
assertTrue(viewModel.contains("fun consumeReplyChangedEvent()"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `답글 대체 모달 문자열은 리소스 id와 다국어 strings로 제공한다`() {
|
||||||
|
val uiModels = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiModels.kt"
|
||||||
|
).readText()
|
||||||
|
val koStrings = projectFile("app/src/main/res/values/strings.xml").readText()
|
||||||
|
val enStrings = projectFile("app/src/main/res/values-en/strings.xml").readText()
|
||||||
|
val jaStrings = projectFile("app/src/main/res/values-ja/strings.xml").readText()
|
||||||
|
|
||||||
|
assertTrue(uiModels.contains("R.string.screen_user_profile_reply_edit"))
|
||||||
|
assertTrue(uiModels.contains("R.string.creator_channel_fantalk_reply_replace_description"))
|
||||||
|
assertTrue(uiModels.contains("R.string.cancel"))
|
||||||
|
assertTrue(uiModels.contains("R.string.confirm"))
|
||||||
|
assertFalse(uiModels.contains("기존 답글이 신규 답글로 대체됩니다"))
|
||||||
|
assertTrue(koStrings.contains("creator_channel_fantalk_reply_replace_description"))
|
||||||
|
assertTrue(enStrings.contains("creator_channel_fantalk_reply_replace_description"))
|
||||||
|
assertTrue(jaStrings.contains("creator_channel_fantalk_reply_replace_description"))
|
||||||
|
assertEquals(R.string.screen_user_profile_reply_edit, CreatorChannelFanTalkReplacementModalUiModel().titleResId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertTextViewUsesStyle(layout: String, id: String, style: String) {
|
||||||
|
val textViewPattern = Regex("<TextView[\\s\\S]*?android:id=\"@\\+id/$id\"[\\s\\S]*?style=\"$style\"")
|
||||||
|
|
||||||
|
assertTrue("Expected $id to use $style", textViewPattern.containsMatchIn(layout))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertTextViewContains(layout: String, id: String, expected: String) {
|
||||||
|
val textViewPattern = Regex("<TextView[\\s\\S]*?android:id=\"@\\+id/$id\"[\\s\\S]*?/>")
|
||||||
|
val textView = textViewPattern.find(layout)?.value.orEmpty()
|
||||||
|
|
||||||
|
assertTrue("Expected $id to contain $expected", textView.contains(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun projectFile(path: String): File = File(projectRoot(), path)
|
||||||
|
|
||||||
|
private fun projectRoot(): File {
|
||||||
|
return generateSequence(File(System.getProperty("user.dir") ?: ".").absoluteFile) { it.parentFile }
|
||||||
|
.first { File(it, "settings.gradle").exists() }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user