feat(creator-channel): FanTalk 상세 화면을 추가한다
This commit is contained in:
@@ -123,6 +123,9 @@
|
||||
<activity
|
||||
android:name=".v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity"
|
||||
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.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.donation.CreatorChannelDonationViewModel
|
||||
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.live.CreatorChannelLiveViewModel
|
||||
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 { CreatorChannelCommunityReplyViewModel(get(), get(), get()) }
|
||||
viewModel { CreatorChannelFanTalkViewModel(get(), get()) }
|
||||
viewModel { CreatorChannelFanTalkDetailViewModel(get(), get()) }
|
||||
viewModel { CreatorChannelFanTalkWriteViewModel(get()) }
|
||||
viewModel { CreatorChannelDonationViewModel(get(), 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