feat(creator): 채널 홈 탭 고정 스크롤을 연결한다
This commit is contained in:
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -30,6 +31,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioConte
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
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.CreatorChannelHeaderUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState
|
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.CreatorChannelTab
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarState
|
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.creator.channel.ui.CreatorChannelHomeSectionAdapter
|
||||||
@@ -44,6 +46,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 statusBarHeight: Int = 0
|
||||||
|
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
|
||||||
|
|
||||||
override val shouldApplySystemBarTopInset: Boolean = false
|
override val shouldApplySystemBarTopInset: Boolean = false
|
||||||
|
|
||||||
@@ -57,6 +61,7 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setStatusBarIconAppearance()
|
setStatusBarIconAppearance()
|
||||||
setTitleBarTopInset()
|
setTitleBarTopInset()
|
||||||
|
setupScrollListener()
|
||||||
setupClickListeners()
|
setupClickListeners()
|
||||||
observeViewModel()
|
observeViewModel()
|
||||||
viewModel.loadHome(creatorId)
|
viewModel.loadHome(creatorId)
|
||||||
@@ -194,15 +199,44 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setTitleBarTopInset() {
|
private fun setTitleBarTopInset() {
|
||||||
val baseTitleBarHeight = 60.dpToPx().toInt()
|
|
||||||
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
|
||||||
|
statusBarHeight = topInset
|
||||||
view.updatePadding(top = topInset)
|
view.updatePadding(top = topInset)
|
||||||
view.updateLayoutParams {
|
view.updateLayoutParams {
|
||||||
height = baseTitleBarHeight + topInset
|
height = baseTitleBarHeight + topInset
|
||||||
}
|
}
|
||||||
|
updateScrollState(binding.nestedScrollView.scrollY)
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
|
ViewCompat.requestApplyInsets(binding.titleBarContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupScrollListener() {
|
||||||
|
binding.nestedScrollView.setOnScrollChangeListener { _, _, scrollY, _, _ ->
|
||||||
|
updateScrollState(scrollY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateScrollState(scrollY: Int) {
|
||||||
|
val stickyTop = CreatorChannelScrollState.calculateStickyTop(statusBarHeight, baseTitleBarHeight)
|
||||||
|
val headerHeight = binding.headerContainer.height
|
||||||
|
if (headerHeight <= 0) return
|
||||||
|
|
||||||
|
val tabTranslationY = (scrollY - (headerHeight - stickyTop)).coerceAtLeast(0)
|
||||||
|
binding.horizontalTabScrollView.translationY = tabTranslationY.toFloat()
|
||||||
|
|
||||||
|
val tabBarTop = headerHeight - scrollY + tabTranslationY
|
||||||
|
val profileVisibleHeight = (headerHeight - scrollY).coerceIn(0, headerHeight)
|
||||||
|
val shouldUseBlackTitleBar = CreatorChannelScrollState.shouldUseBlackTitleBar(
|
||||||
|
titleBarBottom = stickyTop,
|
||||||
|
tabBarTop = tabBarTop,
|
||||||
|
profileImageVisibleHeight = profileVisibleHeight,
|
||||||
|
profileImageTotalHeight = headerHeight
|
||||||
|
)
|
||||||
|
binding.titleBarContainer.setBackgroundColor(
|
||||||
|
if (shouldUseBlackTitleBar) Color.BLACK else Color.TRANSPARENT
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setStatusBarIconAppearance() {
|
private fun setStatusBarIconAppearance() {
|
||||||
|
|||||||
@@ -119,6 +119,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="52dp"
|
android:layout_height="52dp"
|
||||||
android:background="@color/black"
|
android:background="@color/black"
|
||||||
|
android:elevation="1dp"
|
||||||
android:fillViewport="false"
|
android:fillViewport="false"
|
||||||
android:overScrollMode="never"
|
android:overScrollMode="never"
|
||||||
android:scrollbars="none">
|
android:scrollbars="none">
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class CreatorChannelHomeActivitySourceTest {
|
|||||||
|
|
||||||
assertTrue(layout.contains("<HorizontalScrollView"))
|
assertTrue(layout.contains("<HorizontalScrollView"))
|
||||||
assertTrue(layout.contains("@+id/horizontal_tab_scroll_view"))
|
assertTrue(layout.contains("@+id/horizontal_tab_scroll_view"))
|
||||||
|
assertTrue(layout.contains("android:elevation=\"1dp\""))
|
||||||
assertTrue(layout.contains("@+id/tab_container"))
|
assertTrue(layout.contains("@+id/tab_container"))
|
||||||
assertTrue(layout.contains("@+id/rv_home_sections"))
|
assertTrue(layout.contains("@+id/rv_home_sections"))
|
||||||
assertTrue(layout.contains("android:drawableStart=\"@drawable/ic_new_talk\""))
|
assertTrue(layout.contains("android:drawableStart=\"@drawable/ic_new_talk\""))
|
||||||
@@ -96,6 +97,23 @@ class CreatorChannelHomeActivitySourceTest {
|
|||||||
assertTrue(baseActivity.contains("if (shouldApplySystemBarTopInset) systemBars.top else 0"))
|
assertTrue(baseActivity.contains("if (shouldApplySystemBarTopInset) systemBars.top else 0"))
|
||||||
assertTrue(source.contains("override val shouldApplySystemBarTopInset: Boolean = false"))
|
assertTrue(source.contains("override val shouldApplySystemBarTopInset: Boolean = false"))
|
||||||
assertTrue(source.contains("setTitleBarTopInset"))
|
assertTrue(source.contains("setTitleBarTopInset"))
|
||||||
|
assertTrue(source.contains("ViewCompat.requestApplyInsets(binding.titleBarContainer)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `scroll source는 tab sticky와 title bar black 전환을 연결한다`() {
|
||||||
|
val source = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertTrue(source.contains("setupScrollListener"))
|
||||||
|
assertTrue(source.contains("binding.nestedScrollView.setOnScrollChangeListener"))
|
||||||
|
assertTrue(source.contains("CreatorChannelScrollState.calculateStickyTop"))
|
||||||
|
assertTrue(source.contains("binding.horizontalTabScrollView.translationY"))
|
||||||
|
assertTrue(source.contains("CreatorChannelScrollState.shouldUseBlackTitleBar"))
|
||||||
|
assertTrue(source.contains("binding.titleBarContainer.setBackgroundColor"))
|
||||||
|
assertTrue(source.contains("Color.BLACK"))
|
||||||
|
assertTrue(source.contains("Color.TRANSPARENT"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user