feat(creator-channel): FanTalk 글 입력 화면을 연결한다
This commit is contained in:
@@ -114,6 +114,9 @@
|
||||
<activity android:name=".v2.main.MainV2Activity" />
|
||||
<activity android:name=".v2.main.content.overview.ContentOverviewActivity" />
|
||||
<activity android:name=".v2.creator.channel.CreatorChannelActivity" />
|
||||
<activity
|
||||
android:name=".v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
<activity android:name=".v2.live.onair.HomeOnAirLiveActivity" />
|
||||
<activity
|
||||
android:name=".v2.main.chat.dm.DmChatRoomActivity"
|
||||
|
||||
@@ -185,6 +185,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.write.CreatorChannelFanTalkWriteViewModel
|
||||
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.live.onair.HomeOnAirLiveViewModel
|
||||
@@ -453,6 +454,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
||||
viewModel { CreatorChannelSeriesViewModel(get()) }
|
||||
viewModel { CreatorChannelCommunityViewModel(get(), get()) }
|
||||
viewModel { CreatorChannelFanTalkViewModel(get(), get()) }
|
||||
viewModel { CreatorChannelFanTalkWriteViewModel(get()) }
|
||||
viewModel { CreatorChannelDonationViewModel(get(), get()) }
|
||||
viewModel { PushNotificationListViewModel(get()) }
|
||||
viewModel { CharacterTabViewModel(get()) }
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.InputFilter
|
||||
import android.view.Gravity
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.addCallback
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.base.SodaDialog
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelFantalkWriteBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class CreatorChannelFanTalkWriteActivity : BaseActivity<ActivityCreatorChannelFantalkWriteBinding>(
|
||||
ActivityCreatorChannelFantalkWriteBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: CreatorChannelFanTalkWriteViewModel by viewModel()
|
||||
private var creatorId: Long = 0L
|
||||
|
||||
override fun setupView() {
|
||||
creatorId = intent.getLongExtra(EXTRA_CREATOR_ID, 0L)
|
||||
if (creatorId <= 0L) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
setupContentInput()
|
||||
setupClickListeners()
|
||||
setupBackHandler()
|
||||
observeViewModel()
|
||||
focusContentInput()
|
||||
}
|
||||
|
||||
private fun setupContentInput() = with(binding.etCreatorChannelFantalkWriteContent) {
|
||||
filters = arrayOf(InputFilter.LengthFilter(CreatorChannelFanTalkWriteViewModel.MAX_CONTENT_LENGTH))
|
||||
}
|
||||
|
||||
private fun setupClickListeners() = with(binding) {
|
||||
tvCreatorChannelFantalkWriteCancel.setOnClickListener { handleCancel() }
|
||||
tvCreatorChannelFantalkWriteSend.setOnClickListener { viewModel.submit(creatorId) }
|
||||
etCreatorChannelFantalkWriteContent.addTextChangedListener { text ->
|
||||
viewModel.onContentChanged(text?.toString().orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBackHandler() {
|
||||
onBackPressedDispatcher.addCallback(this) {
|
||||
handleCancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeViewModel() {
|
||||
viewModel.isSendEnabledLiveData.observe(this) { isEnabled ->
|
||||
bindSendEnabled(isEnabled && viewModel.isLoadingLiveData.value != true)
|
||||
}
|
||||
viewModel.contentLengthLiveData.observe(this) { length ->
|
||||
binding.tvCreatorChannelFantalkWriteCount.text = length.toString()
|
||||
}
|
||||
viewModel.isLoadingLiveData.observe(this) { isLoading ->
|
||||
bindLoading(isLoading)
|
||||
}
|
||||
viewModel.finishEventLiveData.observe(this) { shouldFinish ->
|
||||
if (shouldFinish == true) {
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
viewModel.toastEventLiveData.observe(this) { message ->
|
||||
message?.let { Toast.makeText(this, it, Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindSendEnabled(isEnabled: Boolean) = with(binding.tvCreatorChannelFantalkWriteSend) {
|
||||
this.isEnabled = isEnabled
|
||||
setTextColor(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
if (isEnabled) R.color.white else R.color.gray_500
|
||||
)
|
||||
)
|
||||
setBackgroundResource(
|
||||
if (isEnabled) {
|
||||
R.drawable.bg_creator_channel_fantalk_write_send_enabled
|
||||
} else {
|
||||
R.drawable.bg_creator_channel_fantalk_write_send_disabled
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun bindLoading(isLoading: Boolean) = with(binding) {
|
||||
etCreatorChannelFantalkWriteContent.isEnabled = !isLoading
|
||||
tvCreatorChannelFantalkWriteSend.text = getString(
|
||||
if (isLoading) R.string.creator_channel_fantalk_write_sending else R.string.creator_channel_fantalk_write_send
|
||||
)
|
||||
bindSendEnabled(!isLoading && viewModel.isSendEnabledLiveData.value == true)
|
||||
}
|
||||
|
||||
private fun focusContentInput() {
|
||||
binding.etCreatorChannelFantalkWriteContent.post {
|
||||
binding.etCreatorChannelFantalkWriteContent.requestFocus()
|
||||
WindowCompat.getInsetsController(window, binding.etCreatorChannelFantalkWriteContent)
|
||||
.show(WindowInsetsCompat.Type.ime())
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(binding.etCreatorChannelFantalkWriteContent, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCancel() {
|
||||
if (binding.etCreatorChannelFantalkWriteContent.text?.toString().orEmpty().trim().isEmpty()) {
|
||||
finish()
|
||||
} else {
|
||||
showCancelConfirmDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCancelConfirmDialog() {
|
||||
SodaDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = getString(R.string.creator_channel_fantalk_write_exit_title),
|
||||
desc = getString(R.string.creator_channel_fantalk_write_exit_desc),
|
||||
confirmButtonTitle = getString(R.string.creator_channel_fantalk_write_exit_confirm),
|
||||
confirmButtonClick = { finish() },
|
||||
cancelButtonTitle = getString(R.string.creator_channel_fantalk_write_exit_cancel),
|
||||
cancelButtonClick = {},
|
||||
descGravity = Gravity.CENTER
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_CREATOR_ID: String = "extra_creator_id"
|
||||
|
||||
fun newIntent(context: Context, creatorId: Long): Intent {
|
||||
return Intent(context, CreatorChannelFanTalkWriteActivity::class.java).apply {
|
||||
putExtra(EXTRA_CREATOR_ID, creatorId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class CreatorChannelFanTalkWriteSourceTest {
|
||||
|
||||
@Test
|
||||
fun `글 입력 Activity source는 intent focus keyboard result cancel back 계약을 포함한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt"
|
||||
).readText()
|
||||
val manifest = projectFile("app/src/main/AndroidManifest.xml").readText()
|
||||
|
||||
assertTrue(source.contains("fun newIntent(context: Context, creatorId: Long): Intent"))
|
||||
assertTrue(source.contains("putExtra(EXTRA_CREATOR_ID, creatorId)"))
|
||||
assertTrue(source.contains("if (creatorId <= 0L)"))
|
||||
assertTrue(source.contains("finish()"))
|
||||
assertTrue(source.contains("requestFocus()"))
|
||||
assertTrue(source.contains("WindowCompat.getInsetsController(window, binding.etCreatorChannelFantalkWriteContent)"))
|
||||
assertTrue(source.contains("WindowInsetsCompat.Type.ime()"))
|
||||
assertTrue(source.contains("onBackPressedDispatcher.addCallback"))
|
||||
assertTrue(source.contains("showCancelConfirmDialog()"))
|
||||
assertTrue(source.contains("setResult(RESULT_OK)"))
|
||||
assertTrue(source.contains("viewModel.submit(creatorId)"))
|
||||
assertTrue(manifest.contains(".v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `글 입력 layout과 문자열은 Figma 확정 계약을 포함한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt"
|
||||
).readText()
|
||||
val layout = projectFile("app/src/main/res/layout/activity_creator_channel_fantalk_write.xml").readText()
|
||||
val strings = projectFile("app/src/main/res/values/strings.xml").readText()
|
||||
val stringsEn = projectFile("app/src/main/res/values-en/strings.xml").readText()
|
||||
val stringsJa = projectFile("app/src/main/res/values-ja/strings.xml").readText()
|
||||
|
||||
assertTrue(layout.contains("@+id/tv_creator_channel_fantalk_write_cancel"))
|
||||
assertTrue(layout.contains("@+id/et_creator_channel_fantalk_write_content"))
|
||||
assertTrue(layout.contains("@+id/tv_creator_channel_fantalk_write_send"))
|
||||
assertTrue(layout.contains("@+id/layout_creator_channel_fantalk_write_info_bar"))
|
||||
assertTrue(layout.contains("@+id/tv_creator_channel_fantalk_write_count"))
|
||||
assertTrue(layout.contains("@+id/tv_creator_channel_fantalk_write_count_limit"))
|
||||
assertTrue(layout.contains("android:maxLength=\"500\""))
|
||||
assertTrue(layout.contains("android:text=\"/500자\""))
|
||||
assertFalse(layout.contains("tv_creator_channel_fantalk_write_title"))
|
||||
assertTrue(layout.contains("@string/creator_channel_fantalk_write_cancel"))
|
||||
assertTrue(layout.contains("@string/creator_channel_fantalk_write_hint"))
|
||||
assertTrue(layout.contains("@string/creator_channel_fantalk_write_send"))
|
||||
assertTrue(
|
||||
projectFile("app/src/main/res/drawable/bg_creator_channel_fantalk_write_send_enabled.xml")
|
||||
.readText()
|
||||
.contains("@color/soda_400")
|
||||
)
|
||||
assertTrue(source.contains("if (isEnabled) R.color.white else R.color.gray_500"))
|
||||
assertTrue(strings.contains("<string name=\"creator_channel_fantalk_write_exit_title\">입력 종료</string>"))
|
||||
assertTrue(
|
||||
strings.contains(
|
||||
"<string name=\"creator_channel_fantalk_write_exit_desc\">입력을 종료할까요?\\n지금 나가면 입력한 내용이 저장되지 않아요.</string>"
|
||||
)
|
||||
)
|
||||
assertTrue(strings.contains("<string name=\"creator_channel_fantalk_write_exit_cancel\">계속 작성</string>"))
|
||||
assertTrue(strings.contains("<string name=\"creator_channel_fantalk_write_exit_confirm\">종료</string>"))
|
||||
assertTrue(stringsEn.contains("creator_channel_fantalk_write_exit_title"))
|
||||
assertTrue(stringsJa.contains("creator_channel_fantalk_write_exit_title"))
|
||||
}
|
||||
|
||||
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