feat(creator): 채널 홈 탭 전환을 연결한다
This commit is contained in:
@@ -3,11 +3,9 @@ package kr.co.vividnext.sodalive.v2.creator.channel
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.View.MeasureSpec
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
@@ -15,7 +13,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
@@ -31,25 +31,22 @@ import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelScrollState
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarState
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelHomeSectionAdapter
|
||||
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class CreatorChannelActivity : BaseActivity<ActivityCreatorChannelBinding>(
|
||||
ActivityCreatorChannelBinding::inflate
|
||||
) {
|
||||
class CreatorChannelActivity :
|
||||
BaseActivity<ActivityCreatorChannelBinding>(ActivityCreatorChannelBinding::inflate),
|
||||
CreatorChannelHomeFragment.Host {
|
||||
|
||||
private val viewModel: CreatorChannelHomeViewModel by viewModel()
|
||||
private val sectionAdapter = CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)
|
||||
private var creatorId: Long = 0L
|
||||
private var currentHeader: CreatorChannelHeaderUiModel? = null
|
||||
private var selectedTab: CreatorChannelTab = CreatorChannelTab.Home
|
||||
private var homeActionDelegate: CreatorChannelHomeFragment.HomeActionDelegate? = null
|
||||
private var isFollowInProgress: Boolean = false
|
||||
private var statusBarHeight: Int = 0
|
||||
private var tabLayoutMediator: TabLayoutMediator? = null
|
||||
private var pageChangeCallback: ViewPager2.OnPageChangeCallback? = null
|
||||
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
|
||||
|
||||
override val shouldApplySystemBarTopInset: Boolean = false
|
||||
@@ -61,18 +58,11 @@ class CreatorChannelActivity : BaseActivity<ActivityCreatorChannelBinding>(
|
||||
return
|
||||
}
|
||||
|
||||
setupRecyclerView()
|
||||
setupTabsAndPager()
|
||||
setStatusBarIconAppearance()
|
||||
setTitleBarTopInset()
|
||||
setupScrollListener()
|
||||
setupClickListeners()
|
||||
observeViewModel()
|
||||
viewModel.loadHome(creatorId)
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
binding.rvHomeSections.layoutManager = LinearLayoutManager(this)
|
||||
binding.rvHomeSections.adapter = sectionAdapter
|
||||
}
|
||||
|
||||
private fun setupClickListeners() {
|
||||
@@ -81,47 +71,13 @@ class CreatorChannelActivity : BaseActivity<ActivityCreatorChannelBinding>(
|
||||
binding.layoutFollowCapsule.setOnClickListener { onFollowActionClicked() }
|
||||
binding.ivBell.setOnClickListener { onFollowActionClicked() }
|
||||
binding.tvChatButton.setOnClickListener {
|
||||
currentHeader?.characterId?.let { characterId -> viewModel.createChatRoom(characterId) }
|
||||
currentHeader?.characterId?.let { characterId -> homeActionDelegate?.createChatRoom(characterId) }
|
||||
}
|
||||
binding.tvDmButton.setOnClickListener {
|
||||
startActivity(DmChatRoomActivity.newIntentByCreatorId(this, creatorId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeViewModel() {
|
||||
viewModel.homeStateLiveData.observe(this) { state ->
|
||||
when (state) {
|
||||
is CreatorChannelHomeUiState.Content -> bindContent(state)
|
||||
is CreatorChannelHomeUiState.Error -> Unit
|
||||
CreatorChannelHomeUiState.Empty -> Unit
|
||||
CreatorChannelHomeUiState.Loading -> Unit
|
||||
}
|
||||
}
|
||||
viewModel.chatRoomIdLiveData.observe(this) { event ->
|
||||
event.consume()?.let { chatRoomId ->
|
||||
startActivity(ChatRoomActivity.newIntent(this, chatRoomId))
|
||||
}
|
||||
}
|
||||
viewModel.toastLiveData.observe(this) { event ->
|
||||
event.consume()?.let {
|
||||
val message = it.message ?: it.resId?.let(::getString)
|
||||
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) {
|
||||
currentHeader = content.header
|
||||
bindHeader(content.header)
|
||||
bindTitleBar(content.header)
|
||||
bindTabs(content.tabs)
|
||||
sectionAdapter.submitItems(content.sections)
|
||||
}
|
||||
|
||||
private fun bindHeader(header: CreatorChannelHeaderUiModel) {
|
||||
binding.tvNickname.text = header.nickname
|
||||
binding.tvFollowerCount.text = getString(
|
||||
@@ -166,54 +122,11 @@ class CreatorChannelActivity : BaseActivity<ActivityCreatorChannelBinding>(
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindTabs(tabs: List<CreatorChannelTab>) {
|
||||
binding.tabContainer.removeAllViews()
|
||||
tabs.forEach { tab ->
|
||||
binding.tabContainer.addView(createTabView(tab, isSelected = tab == selectedTab))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTabView(tab: CreatorChannelTab, isSelected: Boolean): LinearLayout {
|
||||
val tabText = TextView(this).apply {
|
||||
text = getString(tab.labelResId)
|
||||
gravity = Gravity.CENTER
|
||||
setTextColor(getColor(if (isSelected) R.color.white else R.color.gray_500))
|
||||
setTypeface(null, Typeface.NORMAL)
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
0,
|
||||
1f
|
||||
)
|
||||
}
|
||||
tabText.textSize = 16f
|
||||
val indicator = View(this).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
3.dpToPx().toInt()
|
||||
)
|
||||
}
|
||||
indicator.setBackgroundColor(getColor(R.color.soda_400))
|
||||
indicator.isVisible = isSelected
|
||||
return LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT
|
||||
).apply {
|
||||
width = 110.dpToPx().toInt()
|
||||
}
|
||||
addView(tabText)
|
||||
addView(indicator)
|
||||
setOnClickListener { onTabClicked(tab) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFollowActionClicked() {
|
||||
if (isFollowInProgress) return
|
||||
val header = currentHeader ?: return
|
||||
if (!header.isFollow) {
|
||||
viewModel.follow(follow = true, notify = true)
|
||||
homeActionDelegate?.follow(follow = true, notify = true)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -222,20 +135,13 @@ class CreatorChannelActivity : BaseActivity<ActivityCreatorChannelBinding>(
|
||||
|
||||
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) }
|
||||
onClickNotifyAll = { homeActionDelegate?.follow(follow = true, notify = true) },
|
||||
onClickNotifyNone = { homeActionDelegate?.follow(follow = true, notify = false) },
|
||||
onClickUnFollow = { homeActionDelegate?.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() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.titleBarContainer) { view, insets ->
|
||||
val topInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
|
||||
@@ -262,7 +168,7 @@ class CreatorChannelActivity : BaseActivity<ActivityCreatorChannelBinding>(
|
||||
if (headerHeight <= 0) return
|
||||
|
||||
val tabTranslationY = (scrollY - (headerHeight - stickyTop)).coerceAtLeast(0)
|
||||
binding.horizontalTabScrollView.translationY = tabTranslationY.toFloat()
|
||||
binding.tabLayout.translationY = tabTranslationY.toFloat()
|
||||
|
||||
val tabBarTop = headerHeight - scrollY + tabTranslationY
|
||||
val profileVisibleHeight = (headerHeight - scrollY).coerceIn(0, headerHeight)
|
||||
@@ -293,6 +199,73 @@ class CreatorChannelActivity : BaseActivity<ActivityCreatorChannelBinding>(
|
||||
Toast.makeText(applicationContext, getString(R.string.creator_channel_more_ready), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun setupTabsAndPager() {
|
||||
binding.viewPager.adapter = CreatorChannelPagerAdapter(this, creatorId)
|
||||
binding.viewPager.isUserInputEnabled = true
|
||||
binding.viewPager.offscreenPageLimit = CreatorChannelTab.entries.size - 1
|
||||
tabLayoutMediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
|
||||
tab.text = getString(CreatorChannelTab.entries[position].labelResId)
|
||||
}.also {
|
||||
it.attach()
|
||||
}
|
||||
val callback = object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
updateViewPagerHeight()
|
||||
}
|
||||
}
|
||||
pageChangeCallback = callback
|
||||
binding.viewPager.registerOnPageChangeCallback(callback)
|
||||
}
|
||||
|
||||
override fun onCreatorChannelHeaderChanged(header: CreatorChannelHeaderUiModel) {
|
||||
currentHeader = header
|
||||
bindHeader(header)
|
||||
bindTitleBar(header)
|
||||
}
|
||||
|
||||
override fun onCreatorChannelFollowProgressChanged(inProgress: Boolean) {
|
||||
isFollowInProgress = inProgress
|
||||
currentHeader?.let(::bindTitleBar)
|
||||
}
|
||||
|
||||
override fun onCreatorChannelChatRoomCreated(chatRoomId: Long) {
|
||||
startActivity(ChatRoomActivity.newIntent(this, chatRoomId))
|
||||
}
|
||||
|
||||
override fun onCreatorChannelScheduleClicked(schedule: CreatorChannelScheduleResponse) {
|
||||
onScheduleClicked(schedule)
|
||||
}
|
||||
|
||||
override fun onCreatorChannelAudioContentClicked(audioContent: CreatorChannelAudioContentResponse) {
|
||||
onAudioContentClicked(audioContent)
|
||||
}
|
||||
|
||||
override fun onCreatorChannelHomeActionDelegateReady(
|
||||
delegate: CreatorChannelHomeFragment.HomeActionDelegate?
|
||||
) {
|
||||
homeActionDelegate = delegate
|
||||
}
|
||||
|
||||
override fun onCreatorChannelHomeContentChanged() {
|
||||
updateViewPagerHeight()
|
||||
}
|
||||
|
||||
private fun updateViewPagerHeight() {
|
||||
binding.viewPager.post {
|
||||
val recyclerView = binding.viewPager.getChildAt(0) as? RecyclerView ?: return@post
|
||||
val currentPage = recyclerView.layoutManager?.findViewByPosition(binding.viewPager.currentItem) ?: return@post
|
||||
val widthSpec = MeasureSpec.makeMeasureSpec(binding.viewPager.width, MeasureSpec.EXACTLY)
|
||||
val heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
||||
currentPage.measure(widthSpec, heightSpec)
|
||||
val measuredHeight = currentPage.measuredHeight
|
||||
if (measuredHeight <= 0 || binding.viewPager.layoutParams.height == measuredHeight) return@post
|
||||
|
||||
binding.viewPager.updateLayoutParams {
|
||||
height = measuredHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse) {
|
||||
when (schedule.type) {
|
||||
CreatorActivityType.Audio,
|
||||
@@ -330,6 +303,15 @@ class CreatorChannelActivity : BaseActivity<ActivityCreatorChannelBinding>(
|
||||
detailFragment.show(supportFragmentManager, detailFragment.tag)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
tabLayoutMediator?.detach()
|
||||
pageChangeCallback?.let { callback ->
|
||||
binding.viewPager.unregisterOnPageChangeCallback(callback)
|
||||
}
|
||||
pageChangeCallback = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_CREATOR_ID: String = "extra_creator_id"
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding.rvHomeSections.adapter = null
|
||||
host.onCreatorChannelHomeActionDelegateReady(null)
|
||||
super.onDestroyView()
|
||||
}
|
||||
@@ -55,6 +56,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
||||
is CreatorChannelHomeUiState.Content -> {
|
||||
host.onCreatorChannelHeaderChanged(state.header)
|
||||
sectionAdapter.submitItems(state.sections)
|
||||
host.onCreatorChannelHomeContentChanged()
|
||||
}
|
||||
is CreatorChannelHomeUiState.Error -> Unit
|
||||
CreatorChannelHomeUiState.Empty -> Unit
|
||||
@@ -90,6 +92,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
||||
fun onCreatorChannelScheduleClicked(schedule: CreatorChannelScheduleResponse)
|
||||
fun onCreatorChannelAudioContentClicked(audioContent: CreatorChannelAudioContentResponse)
|
||||
fun onCreatorChannelHomeActionDelegateReady(delegate: HomeActionDelegate?)
|
||||
fun onCreatorChannelHomeContentChanged()
|
||||
}
|
||||
|
||||
interface HomeActionDelegate {
|
||||
|
||||
Reference in New Issue
Block a user