diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d73d05a7..1c44d6f3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -112,6 +112,7 @@
+
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/base/BaseActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/base/BaseActivity.kt
index 5874a849..0372fa28 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/base/BaseActivity.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/base/BaseActivity.kt
@@ -31,6 +31,8 @@ abstract class BaseActivity(
lateinit var binding: T
private set
+ protected open val shouldApplySystemBarTopInset: Boolean = true
+
val screenWidth: Int by lazy {
resources.displayMetrics.widthPixels
}
@@ -81,7 +83,7 @@ abstract class BaseActivity(
// 루트는 좌/우/하만 처리(상단은 Toolbar에 위임). IME가 등장하면 하단 패딩을 IME 높이까지 확장
val left = max(systemBars.left, ime.left)
- val top = systemBars.top
+ val top = if (shouldApplySystemBarTopInset) systemBars.top else 0
val right = max(systemBars.right, ime.right)
val bottom = max(systemBars.bottom, ime.bottom)
v.setPadding(left, top, right, bottom)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt
new file mode 100644
index 00000000..3342340d
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt
@@ -0,0 +1,261 @@
+package kr.co.vividnext.sodalive.v2.creator.channel
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.Typeface
+import android.view.Gravity
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.TextView
+import android.widget.Toast
+import androidx.core.view.ViewCompat
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.core.view.updatePadding
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowCompat
+import androidx.recyclerview.widget.LinearLayoutManager
+import kr.co.vividnext.sodalive.R
+import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
+import kr.co.vividnext.sodalive.base.BaseActivity
+import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
+import kr.co.vividnext.sodalive.common.Constants
+import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelHomeBinding
+import kr.co.vividnext.sodalive.extensions.dpToPx
+import kr.co.vividnext.sodalive.extensions.loadUrl
+import kr.co.vividnext.sodalive.extensions.moneyFormat
+import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment
+import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
+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.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 CreatorChannelHomeActivity : BaseActivity(
+ ActivityCreatorChannelHomeBinding::inflate
+) {
+
+ private val viewModel: CreatorChannelHomeViewModel by viewModel()
+ private val sectionAdapter = CreatorChannelHomeSectionAdapter(::onScheduleClicked)
+ private var creatorId: Long = 0L
+ private var currentHeader: CreatorChannelHeaderUiModel? = null
+
+ override val shouldApplySystemBarTopInset: Boolean = false
+
+ override fun setupView() {
+ creatorId = intent.getLongExtra(EXTRA_CREATOR_ID, 0L)
+ if (creatorId <= 0L) {
+ finish()
+ return
+ }
+
+ setupRecyclerView()
+ setStatusBarIconAppearance()
+ setTitleBarTopInset()
+ setupClickListeners()
+ observeViewModel()
+ viewModel.loadHome(creatorId)
+ }
+
+ private fun setupRecyclerView() {
+ binding.rvHomeSections.layoutManager = LinearLayoutManager(this)
+ binding.rvHomeSections.adapter = sectionAdapter
+ }
+
+ private fun setupClickListeners() {
+ binding.ivBack.setOnClickListener { finish() }
+ binding.ivMore.setOnClickListener { onMoreClicked() }
+ binding.tvChatButton.setOnClickListener {
+ currentHeader?.characterId?.let { characterId -> viewModel.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() }
+ }
+ }
+ }
+
+ 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(
+ R.string.creator_channel_follower_count,
+ header.followerCount.moneyFormat()
+ )
+ binding.ivHeaderImage.loadUrl(header.profileImageUrl) {
+ placeholder(R.drawable.ic_placeholder_profile)
+ error(R.drawable.ic_placeholder_profile)
+ }
+ updateActionButtonLayout(
+ isChatVisible = header.isAiChatAvailable && header.characterId != null,
+ isDmVisible = header.isDmAvailable
+ )
+ }
+
+ private fun bindTitleBar(header: CreatorChannelHeaderUiModel) {
+ val titleBarState = CreatorChannelTitleBarState.from(
+ isFollow = header.isFollow,
+ isNotify = header.isNotify,
+ isInProgress = false
+ )
+ binding.ivFollow.setImageResource(titleBarState.followIconResId)
+ binding.layoutFollowCapsule.isEnabled = titleBarState.isActionEnabled
+ binding.layoutFollowCapsule.setBackgroundResource(
+ if (header.isFollow) {
+ R.drawable.bg_creator_channel_following_capsule
+ } else {
+ R.drawable.bg_creator_channel_follow_capsule
+ }
+ )
+ binding.layoutFollowCapsule.updateLayoutParams {
+ width = if (header.isFollow) 36.dpToPx().toInt() else LinearLayout.LayoutParams.WRAP_CONTENT
+ }
+ binding.tvFollowLabel.isVisible = !header.isFollow
+ titleBarState.bellIconResId?.let {
+ binding.ivBell.setImageResource(it)
+ binding.ivBell.visibility = View.VISIBLE
+ } ?: run {
+ binding.ivBell.visibility = View.GONE
+ }
+ }
+
+ private fun bindTabs(tabs: List) {
+ binding.tabContainer.removeAllViews()
+ tabs.forEachIndexed { index, tab ->
+ binding.tabContainer.addView(createTabView(tab, isSelected = index == 0))
+ }
+ }
+
+ 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)
+ }
+ }
+
+ private fun setTitleBarTopInset() {
+ val baseTitleBarHeight = 60.dpToPx().toInt()
+ ViewCompat.setOnApplyWindowInsetsListener(binding.titleBarContainer) { view, insets ->
+ val topInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
+ view.updatePadding(top = topInset)
+ view.updateLayoutParams {
+ height = baseTitleBarHeight + topInset
+ }
+ insets
+ }
+ }
+
+ private fun setStatusBarIconAppearance() {
+ WindowCompat.getInsetsController(window, binding.root).isAppearanceLightStatusBars = false
+ }
+
+ private fun updateActionButtonLayout(isChatVisible: Boolean, isDmVisible: Boolean) {
+ binding.tvChatButton.isVisible = isChatVisible
+ binding.tvDmButton.isVisible = isDmVisible
+ (binding.tvDmButton.layoutParams as LinearLayout.LayoutParams).apply {
+ marginStart = if (isChatVisible && isDmVisible) 6.dpToPx().toInt() else 0
+ }.also(binding.tvDmButton::setLayoutParams)
+ }
+
+ private fun onMoreClicked() {
+ Toast.makeText(applicationContext, getString(R.string.creator_channel_more_ready), Toast.LENGTH_SHORT).show()
+ }
+
+ private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse) {
+ when (schedule.type) {
+ CreatorActivityType.Audio,
+ CreatorActivityType.LiveReplay -> startActivity(
+ Intent(this, AudioContentDetailActivity::class.java).apply {
+ putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, schedule.targetId)
+ }
+ )
+
+ CreatorActivityType.Live -> showLiveRoomDetail(schedule.targetId)
+
+ CreatorActivityType.Community -> Unit
+ }
+ }
+
+ private fun showLiveRoomDetail(roomId: Long) {
+ val detailFragment = LiveRoomDetailFragment(
+ roomId,
+ onClickParticipant = {},
+ onClickReservation = {},
+ onClickModify = {},
+ onClickStart = {},
+ onClickCancel = {}
+ )
+ if (detailFragment.isAdded) return
+
+ detailFragment.show(supportFragmentManager, detailFragment.tag)
+ }
+
+ companion object {
+ const val EXTRA_CREATOR_ID: String = "extra_creator_id"
+
+ fun newIntent(context: Context, creatorId: Long): Intent {
+ return Intent(context, CreatorChannelHomeActivity::class.java).apply {
+ putExtra(EXTRA_CREATOR_ID, creatorId)
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_creator_channel_home.xml b/app/src/main/res/layout/activity_creator_channel_home.xml
new file mode 100644
index 00000000..d88438c0
--- /dev/null
+++ b/app/src/main/res/layout/activity_creator_channel_home.xml
@@ -0,0 +1,225 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+