feat(home): 추천 상태 observe를 연결한다
This commit is contained in:
@@ -1,32 +1,35 @@
|
||||
package kr.co.vividnext.sodalive.v2.main
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
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.BaseFragment
|
||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.ToastMessage
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentV2MainHomeBinding
|
||||
import kr.co.vividnext.sodalive.databinding.ViewSectionTitleBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
|
||||
import kr.co.vividnext.sodalive.v2.main.home.HomeRecommendationViewModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterUiModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerUiModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCheerCreatorSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCreatorUiModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationFirstAudioContentSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationFirstAudioContentUiModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorGroupUiModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveUiModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPaidStatus
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPopularCommunityPostSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPopularCommunityPostUiModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentlyActiveCreatorSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentlyActiveCreatorUiModel
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentDebutCreatorSection
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationUiState
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.RecommendedActivityType
|
||||
import kr.co.vividnext.sodalive.v2.main.home.model.visibleHomeGenreCreatorGroups
|
||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeAiCharacterAdapter
|
||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeBannerBinder
|
||||
@@ -38,21 +41,28 @@ import kr.co.vividnext.sodalive.v2.main.home.ui.HomeLiveAdapter
|
||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomePopularCommunityAdapter
|
||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentActivityCreatorAdapter
|
||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentDebutCreatorAdapter
|
||||
import kr.co.vividnext.sodalive.v2.widget.AudioContentTag
|
||||
import kr.co.vividnext.sodalive.v2.widget.characterchatthumbnail.CharacterChatThumbnailItem
|
||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
||||
FragmentV2MainHomeBinding::inflate
|
||||
) {
|
||||
private val homeRecommendationViewModel: HomeRecommendationViewModel by viewModel()
|
||||
private val loadingDialog: LoadingDialog by lazy { LoadingDialog(requireActivity(), layoutInflater) }
|
||||
private val liveAdapter = HomeLiveAdapter()
|
||||
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter()
|
||||
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter()
|
||||
private val firstAudioAdapter = HomeFirstAudioAdapter()
|
||||
private val aiCharacterAdapter = HomeAiCharacterAdapter()
|
||||
private val genreCreatorAdapter = HomeGenreCreatorAdapter { creatorIds -> onGenreFollowAllClick(creatorIds) }
|
||||
private val cheerCreatorAdapter = HomeCheerCreatorAdapter { creatorIds -> onCheerFollowAllClick(creatorIds) }
|
||||
private val popularCommunityAdapter = HomePopularCommunityAdapter { }
|
||||
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { openCreatorProfile(it.creatorId) }
|
||||
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
|
||||
private val firstAudioAdapter = HomeFirstAudioAdapter { openAudioContentDetail(it) }
|
||||
private val aiCharacterAdapter = HomeAiCharacterAdapter { openCharacterDetail(it.characterId) }
|
||||
private val genreCreatorAdapter = HomeGenreCreatorAdapter(
|
||||
onFollowAllClick = { creatorIds -> onGenreFollowAllClick(creatorIds) },
|
||||
onCreatorClick = { creator -> openCreatorProfile(creator.creatorId) }
|
||||
)
|
||||
private val cheerCreatorAdapter = HomeCheerCreatorAdapter(
|
||||
onFollowAllClick = { creatorIds -> onCheerFollowAllClick(creatorIds) },
|
||||
onCreatorClick = { creator -> openCreatorProfile(creator.creatorId) }
|
||||
)
|
||||
private val popularCommunityAdapter = HomePopularCommunityAdapter { openPopularCommunityPost(it) }
|
||||
private var bannerBinder: HomeBannerBinder? = null
|
||||
private var onGenreFollowAllClick: (List<Long>) -> Unit = {}
|
||||
private var onCheerFollowAllClick: (List<Long>) -> Unit = {}
|
||||
@@ -71,7 +81,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
||||
setUpSectionTitles()
|
||||
setUpRecommendationAdapters()
|
||||
setUpBusinessInfo()
|
||||
bindHomeRecommendationContent(phase6SampleContent())
|
||||
bindHomeRecommendationObservers()
|
||||
homeRecommendationViewModel.loadRecommendations()
|
||||
}
|
||||
|
||||
private fun setUpBusinessInfo() {
|
||||
@@ -94,7 +105,10 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
||||
}
|
||||
|
||||
private fun setUpRecommendationAdapters() {
|
||||
bannerBinder = HomeBannerBinder(binding.rvHomeBanners)
|
||||
bannerBinder = HomeBannerBinder(binding.rvHomeBanners).apply {
|
||||
setOnBannerClick { onBannerClick(it) }
|
||||
}
|
||||
liveAdapter.setOnLiveClick { onLiveClick(it) }
|
||||
binding.rvHomeLives.adapter = liveAdapter
|
||||
binding.rvHomeRecentActivityCreators.adapter = recentActivityCreatorAdapter
|
||||
binding.rvHomeRecentDebutCreators.adapter = recentDebutCreatorAdapter
|
||||
@@ -112,6 +126,33 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
||||
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
||||
adapter = popularCommunityAdapter
|
||||
}
|
||||
onGenreFollowAllClick = { creatorIds ->
|
||||
homeRecommendationViewModel.followCreators(genreSectionKey(creatorIds), creatorIds)
|
||||
}
|
||||
onCheerFollowAllClick = { creatorIds ->
|
||||
homeRecommendationViewModel.followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindHomeRecommendationObservers() {
|
||||
homeRecommendationViewModel.recommendationStateLiveData.observe(viewLifecycleOwner) { state ->
|
||||
when (state) {
|
||||
is HomeRecommendationUiState.Content -> bindHomeRecommendationContent(state)
|
||||
HomeRecommendationUiState.Empty,
|
||||
is HomeRecommendationUiState.Error -> bindHomeRecommendationContent(emptyHomeRecommendationContent())
|
||||
HomeRecommendationUiState.Loading -> Unit
|
||||
}
|
||||
}
|
||||
homeRecommendationViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||
if (isLoading) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
homeRecommendationViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
||||
toastMessage?.let(::showToast)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindHomeRecommendationContent(content: HomeRecommendationUiState.Content) {
|
||||
@@ -204,143 +245,82 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
||||
ivSectionTitleChevron.visibility = if (showMore) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun onLiveClick(item: HomeRecommendationLiveUiModel) = Unit
|
||||
|
||||
private fun onBannerClick(item: HomeRecommendationBannerUiModel) = Unit
|
||||
|
||||
private fun openCreatorProfile(creatorId: Long) {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, creatorId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun openAudioContentDetail(item: HomeRecommendationFirstAudioContentUiModel) {
|
||||
startActivity(
|
||||
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, item.contentId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun openCharacterDetail(characterId: Long) {
|
||||
startActivity(
|
||||
Intent(requireContext(), CharacterDetailActivity::class.java).apply {
|
||||
putExtra(CharacterDetailActivity.EXTRA_CHARACTER_ID, characterId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun openPopularCommunityPost(item: FeedItem.Community) {
|
||||
val creatorId = item.creatorId.toLongOrNull() ?: return
|
||||
val postId = item.postId.toLongOrNull() ?: return
|
||||
startActivity(
|
||||
Intent(requireContext(), CreatorCommunityAllActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, creatorId)
|
||||
putExtra(Constants.EXTRA_COMMUNITY_POST_ID, postId)
|
||||
putExtra(Constants.EXTRA_COMMUNITY_EXIST_ORDERED, item.existOrdered)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun showToast(toastMessage: ToastMessage) {
|
||||
toastMessage.message?.let { message -> showToast(message) }
|
||||
?: toastMessage.resId?.let { resId -> showToast(getString(resId)) }
|
||||
}
|
||||
|
||||
private fun genreSectionKey(creatorIds: List<Long>): String {
|
||||
val group = currentVisibleGenreGroups().firstOrNull { group ->
|
||||
group.creators.map { it.creatorId } == creatorIds
|
||||
}
|
||||
return "$SECTION_KEY_GENRE_CREATORS:${group?.genre.orEmpty()}"
|
||||
}
|
||||
|
||||
private fun currentVisibleGenreGroups(): List<HomeRecommendationGenreCreatorGroupUiModel> {
|
||||
val content = homeRecommendationViewModel.recommendationStateLiveData.value as? HomeRecommendationUiState.Content
|
||||
?: return emptyList()
|
||||
return content.genreCreators.visibleHomeGenreCreatorGroups()
|
||||
}
|
||||
|
||||
private fun emptyHomeRecommendationContent(): HomeRecommendationUiState.Content {
|
||||
return HomeRecommendationUiState.Content(
|
||||
lives = HomeRecommendationLiveSection(emptyList()),
|
||||
banners = HomeRecommendationBannerSection(emptyList()),
|
||||
recentlyActiveCreators = HomeRecommendationRecentlyActiveCreatorSection(emptyList()),
|
||||
recentDebutCreators = HomeRecommendationRecentDebutCreatorSection(emptyList()),
|
||||
firstAudioContents = HomeRecommendationFirstAudioContentSection(emptyList()),
|
||||
aiCharacters = HomeRecommendationAiCharacterSection(emptyList()),
|
||||
genreCreators = HomeRecommendationGenreCreatorSection(emptyList()),
|
||||
cheerCreators = HomeRecommendationCheerCreatorSection(emptyList()),
|
||||
popularCommunityPosts = HomeRecommendationPopularCommunityPostSection(emptyList())
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<*>.toSectionVisibility(): Int = if (isEmpty()) View.GONE else View.VISIBLE
|
||||
|
||||
private fun phase6SampleContent(): HomeRecommendationUiState.Content = HomeRecommendationUiState.Content(
|
||||
lives = HomeRecommendationLiveSection(
|
||||
listOf(
|
||||
HomeRecommendationLiveUiModel(1L, 101L, null, "오늘 밤 라이브", "소다", "20:00"),
|
||||
HomeRecommendationLiveUiModel(2L, 102L, null, "감성 낭독", "라임", "21:30"),
|
||||
HomeRecommendationLiveUiModel(3L, 103L, null, "신작 토크", "하루", "22:00")
|
||||
)
|
||||
),
|
||||
banners = HomeRecommendationBannerSection(
|
||||
listOf(
|
||||
HomeRecommendationBannerUiModel("sample-banner-1", null, null, null, null),
|
||||
HomeRecommendationBannerUiModel("sample-banner-2", null, null, null, null)
|
||||
)
|
||||
),
|
||||
recentlyActiveCreators = HomeRecommendationRecentlyActiveCreatorSection(
|
||||
listOf(
|
||||
HomeRecommendationRecentlyActiveCreatorUiModel(
|
||||
201L,
|
||||
"베리",
|
||||
null,
|
||||
"LIVE_REPLAY도 라이브 라벨로 표시",
|
||||
RecommendedActivityType.LiveReplay,
|
||||
RecommendedActivityType.LiveReplay.labelResId,
|
||||
"방금"
|
||||
),
|
||||
HomeRecommendationRecentlyActiveCreatorUiModel(202L, "모카", null, "알 수 없는 활동", null, null, "오늘")
|
||||
)
|
||||
),
|
||||
recentDebutCreators = HomeRecommendationRecentDebutCreatorSection(sampleCreators(301L)),
|
||||
firstAudioContents = HomeRecommendationFirstAudioContentSection(
|
||||
listOf(
|
||||
HomeRecommendationFirstAudioContentUiModel(
|
||||
contentId = 401L,
|
||||
creatorId = 301L,
|
||||
creatorNickname = "아린",
|
||||
creatorProfileImage = null,
|
||||
title = "처음 공개하는 오디오",
|
||||
price = 0,
|
||||
coverImage = null,
|
||||
releaseDate = "2026.06.02",
|
||||
tags = setOf(AudioContentTag.First, AudioContentTag.Point, AudioContentTag.Free)
|
||||
)
|
||||
)
|
||||
),
|
||||
aiCharacters = HomeRecommendationAiCharacterSection(
|
||||
listOf(
|
||||
HomeRecommendationAiCharacterUiModel(
|
||||
CharacterChatThumbnailItem(
|
||||
characterId = 501L,
|
||||
imageUrl = "",
|
||||
characterName = "레아",
|
||||
characterDescription = "밤마다 이야기를 들려주는 AI 캐릭터",
|
||||
chatMessageCount = 14000L,
|
||||
hasOriginal = true,
|
||||
originalTitle = "달빛 아래"
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
genreCreators = HomeRecommendationGenreCreatorSection(
|
||||
listOf(
|
||||
HomeRecommendationGenreCreatorGroupUiModel("로맨스", sampleCreators(601L, count = 8)),
|
||||
HomeRecommendationGenreCreatorGroupUiModel("판타지", sampleCreators(701L, count = 8))
|
||||
)
|
||||
),
|
||||
cheerCreators = HomeRecommendationCheerCreatorSection(sampleCreators(701L, count = 8)),
|
||||
popularCommunityPosts = HomeRecommendationPopularCommunityPostSection(samplePopularCommunityPosts())
|
||||
)
|
||||
|
||||
private fun samplePopularCommunityPosts(): List<HomeRecommendationPopularCommunityPostUiModel> {
|
||||
return listOf(
|
||||
samplePopularCommunityPost(
|
||||
id = 801L,
|
||||
creatorName = "소다",
|
||||
bodyText = "무료 커뮤니티 게시글 샘플입니다. 이미지 영역과 본문, 댓글/좋아요 영역을 함께 확인합니다.",
|
||||
imageUrl = "https://picsum.photos/seed/sodalive-community-free/692/472",
|
||||
price = 0,
|
||||
existOrdered = false,
|
||||
paidStatus = HomeRecommendationPaidStatus.Free
|
||||
),
|
||||
samplePopularCommunityPost(
|
||||
id = 802L,
|
||||
creatorName = "라임",
|
||||
bodyText = "유료 미구매 커뮤니티 게시글 샘플입니다. 원본 이미지는 로드하지 않고 lock overlay와 가격만 표시합니다.",
|
||||
imageUrl = "https://picsum.photos/seed/sodalive-community-locked/692/472",
|
||||
price = 30,
|
||||
existOrdered = false,
|
||||
paidStatus = HomeRecommendationPaidStatus.Paid(30)
|
||||
),
|
||||
samplePopularCommunityPost(
|
||||
id = 803L,
|
||||
creatorName = "하루",
|
||||
bodyText = "구매 완료 커뮤니티 게시글 샘플입니다. 유료지만 overlay 없이 이미지를 표시합니다.",
|
||||
imageUrl = "https://picsum.photos/seed/sodalive-community-purchased/692/472",
|
||||
price = 50,
|
||||
existOrdered = true,
|
||||
paidStatus = HomeRecommendationPaidStatus.Purchased
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun samplePopularCommunityPost(
|
||||
id: Long,
|
||||
creatorName: String,
|
||||
bodyText: String,
|
||||
imageUrl: String,
|
||||
price: Int,
|
||||
existOrdered: Boolean,
|
||||
paidStatus: HomeRecommendationPaidStatus
|
||||
): HomeRecommendationPopularCommunityPostUiModel {
|
||||
return HomeRecommendationPopularCommunityPostUiModel(
|
||||
item = FeedItem.Community(
|
||||
feedId = "sample-community-$id",
|
||||
creatorId = "sample-creator-$id",
|
||||
creatorName = creatorName,
|
||||
creatorImageUrl = "",
|
||||
postId = id.toString(),
|
||||
bodyText = bodyText,
|
||||
keywordText = "",
|
||||
createdAtText = "방금",
|
||||
commentCount = 5,
|
||||
likeCount = 12,
|
||||
imageUrl = imageUrl,
|
||||
audioUrl = "https://example.com/sample-community-$id.m4a",
|
||||
price = price,
|
||||
existOrdered = existOrdered,
|
||||
showKeyword = false
|
||||
),
|
||||
paidStatus = paidStatus
|
||||
)
|
||||
}
|
||||
|
||||
private fun sampleCreators(startId: Long, count: Int = 3): List<HomeRecommendationCreatorUiModel> {
|
||||
return List(count) { index ->
|
||||
HomeRecommendationCreatorUiModel(startId + index, "크리에이터${index + 1}", null)
|
||||
}
|
||||
private companion object {
|
||||
const val SECTION_KEY_CHEER_CREATORS = "cheerCreators"
|
||||
const val SECTION_KEY_GENRE_CREATORS = "genreCreators"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user