feat(creator): 채널 홈 팔로우와 탭 동작을 연결한다
This commit is contained in:
@@ -10,11 +10,11 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||||
@@ -22,6 +22,7 @@ import kr.co.vividnext.sodalive.base.BaseActivity
|
|||||||
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.common.Constants
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelHomeBinding
|
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelHomeBinding
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.CreatorFollowNotifyFragment
|
||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
@@ -46,6 +47,8 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
private val sectionAdapter = CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)
|
private val sectionAdapter = CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)
|
||||||
private var creatorId: Long = 0L
|
private var creatorId: Long = 0L
|
||||||
private var currentHeader: CreatorChannelHeaderUiModel? = null
|
private var currentHeader: CreatorChannelHeaderUiModel? = null
|
||||||
|
private var selectedTab: CreatorChannelTab = CreatorChannelTab.Home
|
||||||
|
private var isFollowInProgress: Boolean = false
|
||||||
private var statusBarHeight: Int = 0
|
private var statusBarHeight: Int = 0
|
||||||
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
|
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
|
||||||
|
|
||||||
@@ -75,6 +78,8 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
private fun setupClickListeners() {
|
private fun setupClickListeners() {
|
||||||
binding.ivBack.setOnClickListener { finish() }
|
binding.ivBack.setOnClickListener { finish() }
|
||||||
binding.ivMore.setOnClickListener { onMoreClicked() }
|
binding.ivMore.setOnClickListener { onMoreClicked() }
|
||||||
|
binding.layoutFollowCapsule.setOnClickListener { onFollowActionClicked() }
|
||||||
|
binding.ivBell.setOnClickListener { onFollowActionClicked() }
|
||||||
binding.tvChatButton.setOnClickListener {
|
binding.tvChatButton.setOnClickListener {
|
||||||
currentHeader?.characterId?.let { characterId -> viewModel.createChatRoom(characterId) }
|
currentHeader?.characterId?.let { characterId -> viewModel.createChatRoom(characterId) }
|
||||||
}
|
}
|
||||||
@@ -103,6 +108,10 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
message?.let { text -> Toast.makeText(applicationContext, text, Toast.LENGTH_LONG).show() }
|
message?.let { text -> Toast.makeText(applicationContext, text, Toast.LENGTH_LONG).show() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModel.isFollowInProgressLiveData.observe(this) { inProgress ->
|
||||||
|
isFollowInProgress = inProgress
|
||||||
|
currentHeader?.let(::bindTitleBar)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindContent(content: CreatorChannelHomeUiState.Content) {
|
private fun bindContent(content: CreatorChannelHomeUiState.Content) {
|
||||||
@@ -133,7 +142,7 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
val titleBarState = CreatorChannelTitleBarState.from(
|
val titleBarState = CreatorChannelTitleBarState.from(
|
||||||
isFollow = header.isFollow,
|
isFollow = header.isFollow,
|
||||||
isNotify = header.isNotify,
|
isNotify = header.isNotify,
|
||||||
isInProgress = false
|
isInProgress = isFollowInProgress
|
||||||
)
|
)
|
||||||
binding.ivFollow.setImageResource(titleBarState.followIconResId)
|
binding.ivFollow.setImageResource(titleBarState.followIconResId)
|
||||||
binding.layoutFollowCapsule.isEnabled = titleBarState.isActionEnabled
|
binding.layoutFollowCapsule.isEnabled = titleBarState.isActionEnabled
|
||||||
@@ -150,6 +159,7 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
binding.tvFollowLabel.isVisible = !header.isFollow
|
binding.tvFollowLabel.isVisible = !header.isFollow
|
||||||
titleBarState.bellIconResId?.let {
|
titleBarState.bellIconResId?.let {
|
||||||
binding.ivBell.setImageResource(it)
|
binding.ivBell.setImageResource(it)
|
||||||
|
binding.ivBell.isEnabled = titleBarState.isActionEnabled
|
||||||
binding.ivBell.visibility = View.VISIBLE
|
binding.ivBell.visibility = View.VISIBLE
|
||||||
} ?: run {
|
} ?: run {
|
||||||
binding.ivBell.visibility = View.GONE
|
binding.ivBell.visibility = View.GONE
|
||||||
@@ -158,8 +168,8 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
|
|
||||||
private fun bindTabs(tabs: List<CreatorChannelTab>) {
|
private fun bindTabs(tabs: List<CreatorChannelTab>) {
|
||||||
binding.tabContainer.removeAllViews()
|
binding.tabContainer.removeAllViews()
|
||||||
tabs.forEachIndexed { index, tab ->
|
tabs.forEach { tab ->
|
||||||
binding.tabContainer.addView(createTabView(tab, isSelected = index == 0))
|
binding.tabContainer.addView(createTabView(tab, isSelected = tab == selectedTab))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,9 +205,37 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
}
|
}
|
||||||
addView(tabText)
|
addView(tabText)
|
||||||
addView(indicator)
|
addView(indicator)
|
||||||
|
setOnClickListener { onTabClicked(tab) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onFollowActionClicked() {
|
||||||
|
if (isFollowInProgress) return
|
||||||
|
val header = currentHeader ?: return
|
||||||
|
if (!header.isFollow) {
|
||||||
|
viewModel.follow(follow = true, notify = true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showFollowNotifyFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showFollowNotifyFragment() {
|
||||||
|
val notifyFragment = CreatorFollowNotifyFragment(
|
||||||
|
onClickNotifyAll = { viewModel.follow(follow = true, notify = true) },
|
||||||
|
onClickNotifyNone = { viewModel.follow(follow = true, notify = false) },
|
||||||
|
onClickUnFollow = { viewModel.follow(follow = false, notify = false) }
|
||||||
|
)
|
||||||
|
notifyFragment.show(supportFragmentManager, CreatorFollowNotifyFragment::class.java.simpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onTabClicked(tab: CreatorChannelTab) {
|
||||||
|
if (tab != CreatorChannelTab.Home) return
|
||||||
|
selectedTab = CreatorChannelTab.Home
|
||||||
|
val content = viewModel.homeStateLiveData.value as? CreatorChannelHomeUiState.Content ?: return
|
||||||
|
bindTabs(content.tabs)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setTitleBarTopInset() {
|
private fun setTitleBarTopInset() {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.titleBarContainer) { view, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(binding.titleBarContainer) { view, insets ->
|
||||||
val topInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
|
val topInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ class CreatorChannelHomeViewModel(
|
|||||||
val chatRoomIdLiveData: LiveData<CreatorChannelEvent<Long>>
|
val chatRoomIdLiveData: LiveData<CreatorChannelEvent<Long>>
|
||||||
get() = _chatRoomIdLiveData
|
get() = _chatRoomIdLiveData
|
||||||
|
|
||||||
|
private val _isFollowInProgressLiveData = MutableLiveData(false)
|
||||||
|
val isFollowInProgressLiveData: LiveData<Boolean>
|
||||||
|
get() = _isFollowInProgressLiveData
|
||||||
|
|
||||||
private var isFollowInProgress = false
|
private var isFollowInProgress = false
|
||||||
private var isCreateChatRoomInProgress = false
|
private var isCreateChatRoomInProgress = false
|
||||||
|
|
||||||
@@ -62,6 +66,7 @@ class CreatorChannelHomeViewModel(
|
|||||||
if (isFollowInProgress) return
|
if (isFollowInProgress) return
|
||||||
|
|
||||||
isFollowInProgress = true
|
isFollowInProgress = true
|
||||||
|
_isFollowInProgressLiveData.value = true
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
repository.followCreator(
|
repository.followCreator(
|
||||||
creatorId = content.header.creatorId,
|
creatorId = content.header.creatorId,
|
||||||
@@ -74,6 +79,7 @@ class CreatorChannelHomeViewModel(
|
|||||||
.subscribe(
|
.subscribe(
|
||||||
{
|
{
|
||||||
isFollowInProgress = false
|
isFollowInProgress = false
|
||||||
|
_isFollowInProgressLiveData.value = false
|
||||||
if (it.success) {
|
if (it.success) {
|
||||||
_homeStateLiveData.value = content.copy(
|
_homeStateLiveData.value = content.copy(
|
||||||
header = content.header.copy(isFollow = follow, isNotify = notify)
|
header = content.header.copy(isFollow = follow, isNotify = notify)
|
||||||
@@ -84,6 +90,7 @@ class CreatorChannelHomeViewModel(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
isFollowInProgress = false
|
isFollowInProgress = false
|
||||||
|
_isFollowInProgressLiveData.value = false
|
||||||
it.message?.let { message -> Logger.e(message) }
|
it.message?.let { message -> Logger.e(message) }
|
||||||
showUnknownErrorToast()
|
showUnknownErrorToast()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,27 @@ class CreatorChannelHomeActivitySourceTest {
|
|||||||
assertTrue(source.contains("viewModel.createChatRoom(characterId)"))
|
assertTrue(source.contains("viewModel.createChatRoom(characterId)"))
|
||||||
assertTrue(source.contains("updateActionButtonLayout"))
|
assertTrue(source.contains("updateActionButtonLayout"))
|
||||||
assertTrue(source.contains("marginStart = if (isChatVisible && isDmVisible)"))
|
assertTrue(source.contains("marginStart = if (isChatVisible && isDmVisible)"))
|
||||||
assertFalse(source.contains("CreatorFollowNotifyFragment"))
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `follow notify source는 미팔로우 직접 팔로우와 팔로우 중 알림 sheet를 연결한다`() {
|
||||||
|
val source = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertTrue(source.contains("CreatorFollowNotifyFragment"))
|
||||||
|
assertTrue(source.contains("binding.layoutFollowCapsule.setOnClickListener"))
|
||||||
|
assertTrue(source.contains("binding.ivBell.setOnClickListener"))
|
||||||
|
assertTrue(source.contains("private fun onFollowActionClicked"))
|
||||||
|
assertTrue(source.contains("if (!header.isFollow)"))
|
||||||
|
assertTrue(source.contains("viewModel.follow(follow = true, notify = true)"))
|
||||||
|
assertTrue(source.contains("showFollowNotifyFragment"))
|
||||||
|
assertTrue(source.contains("onClickNotifyAll = { viewModel.follow(follow = true, notify = true) }"))
|
||||||
|
assertTrue(source.contains("onClickNotifyNone = { viewModel.follow(follow = true, notify = false) }"))
|
||||||
|
assertTrue(source.contains("onClickUnFollow = { viewModel.follow(follow = false, notify = false) }"))
|
||||||
|
assertTrue(source.contains("viewModel.isFollowInProgressLiveData.observe(this)"))
|
||||||
|
assertTrue(source.contains("binding.layoutFollowCapsule.isEnabled = titleBarState.isActionEnabled"))
|
||||||
|
assertTrue(source.contains("binding.ivBell.isEnabled = titleBarState.isActionEnabled"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -132,13 +152,32 @@ class CreatorChannelHomeActivitySourceTest {
|
|||||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt"
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt"
|
||||||
).readText()
|
).readText()
|
||||||
|
|
||||||
assertTrue(source.contains("createTabView(tab, isSelected = index == 0)"))
|
assertTrue(source.contains("createTabView(tab, isSelected = tab == selectedTab)"))
|
||||||
assertTrue(source.contains("tabText.textSize = 16f"))
|
assertTrue(source.contains("tabText.textSize = 16f"))
|
||||||
assertTrue(source.contains("width = 110.dpToPx().toInt()"))
|
assertTrue(source.contains("width = 110.dpToPx().toInt()"))
|
||||||
assertTrue(source.contains("indicator.setBackgroundColor(getColor(R.color.soda_400))"))
|
assertTrue(source.contains("indicator.setBackgroundColor(getColor(R.color.soda_400))"))
|
||||||
assertTrue(source.contains("indicator.isVisible = isSelected"))
|
assertTrue(source.contains("indicator.isVisible = isSelected"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `tab source는 홈 기본 선택과 홈 외 탭 no op 정책을 명시한다`() {
|
||||||
|
val source = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertTrue(source.contains("private var selectedTab: CreatorChannelTab = CreatorChannelTab.Home"))
|
||||||
|
assertTrue(source.contains("createTabView(tab, isSelected = tab == selectedTab)"))
|
||||||
|
assertTrue(source.contains("setOnClickListener { onTabClicked(tab) }"))
|
||||||
|
assertTrue(source.contains("private fun onTabClicked(tab: CreatorChannelTab)"))
|
||||||
|
assertTrue(source.contains("if (tab != CreatorChannelTab.Home) return"))
|
||||||
|
assertFalse(source.contains("CreatorChannelTab.Live ->"))
|
||||||
|
assertFalse(source.contains("CreatorChannelTab.Audio ->"))
|
||||||
|
assertFalse(source.contains("CreatorChannelTab.Series ->"))
|
||||||
|
assertFalse(source.contains("CreatorChannelTab.Community ->"))
|
||||||
|
assertFalse(source.contains("CreatorChannelTab.FanTalk ->"))
|
||||||
|
assertFalse(source.contains("CreatorChannelTab.Donation ->"))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `section adapter source는 활동 지표를 행 단위 resource label로 표시한다`() {
|
fun `section adapter source는 활동 지표를 행 단위 resource label로 표시한다`() {
|
||||||
val adapter = projectFile(
|
val adapter = projectFile(
|
||||||
@@ -940,6 +979,27 @@ class CreatorChannelHomeActivitySourceTest {
|
|||||||
assertTrue(manifest.contains(".v2.creator.channel.CreatorChannelHomeActivity"))
|
assertTrue(manifest.contains(".v2.creator.channel.CreatorChannelHomeActivity"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `기존 크리에이터 채널 진입점은 UserProfileActivity 대신 CreatorChannelHomeActivity로 이동한다`() {
|
||||||
|
val sourceRoot = projectFile("app/src/main/java")
|
||||||
|
val directUserProfileRoutes = sourceRoot
|
||||||
|
.walkTopDown()
|
||||||
|
.filter { it.isFile && it.extension in setOf("kt", "java") }
|
||||||
|
.filterNot { it.name == "UserProfileActivity.kt" }
|
||||||
|
.filter { file ->
|
||||||
|
val source = file.readText()
|
||||||
|
source.contains("UserProfileActivity::class.java") ||
|
||||||
|
source.contains("import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity")
|
||||||
|
}
|
||||||
|
.map { it.relativeTo(sourceRoot).path }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
"UserProfileActivity direct routes remain: $directUserProfileRoutes",
|
||||||
|
directUserProfileRoutes.isEmpty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `채팅과 DM Activity intent helper 계약을 참조한다`() {
|
fun `채팅과 DM Activity intent helper 계약을 참조한다`() {
|
||||||
val chatRoom = projectFile("app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt").readText()
|
val chatRoom = projectFile("app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt").readText()
|
||||||
|
|||||||
@@ -149,8 +149,10 @@ class CreatorChannelHomeViewModelTest {
|
|||||||
viewModel.follow(follow = false, notify = false)
|
viewModel.follow(follow = false, notify = false)
|
||||||
viewModel.follow(follow = false, notify = false)
|
viewModel.follow(follow = false, notify = false)
|
||||||
|
|
||||||
|
assertEquals(true, viewModel.isFollowInProgressLiveData.requireValue())
|
||||||
verify(repository, times(1)).followCreator(100L, false, false, "Bearer test-token")
|
verify(repository, times(1)).followCreator(100L, false, false, "Bearer test-token")
|
||||||
pending.onSuccess(ApiResponse(true, Any(), null))
|
pending.onSuccess(ApiResponse(true, Any(), null))
|
||||||
|
assertEquals(false, viewModel.isFollowInProgressLiveData.requireValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user