feat(home): 추천 상태 observe를 연결한다
This commit is contained in:
@@ -1,32 +1,35 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.main
|
package kr.co.vividnext.sodalive.v2.main
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
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.base.BaseFragment
|
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.FragmentV2MainHomeBinding
|
||||||
import kr.co.vividnext.sodalive.databinding.ViewSectionTitleBinding
|
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.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.HomeRecommendationBannerSection
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerUiModel
|
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.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.HomeRecommendationFirstAudioContentSection
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationFirstAudioContentUiModel
|
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.HomeRecommendationGenreCreatorGroupUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorSection
|
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.HomeRecommendationLiveSection
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveUiModel
|
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.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.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.HomeRecommendationRecentDebutCreatorSection
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationUiState
|
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.model.visibleHomeGenreCreatorGroups
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeAiCharacterAdapter
|
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeAiCharacterAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeBannerBinder
|
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.HomePopularCommunityAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentActivityCreatorAdapter
|
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.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 kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
||||||
FragmentV2MainHomeBinding::inflate
|
FragmentV2MainHomeBinding::inflate
|
||||||
) {
|
) {
|
||||||
|
private val homeRecommendationViewModel: HomeRecommendationViewModel by viewModel()
|
||||||
|
private val loadingDialog: LoadingDialog by lazy { LoadingDialog(requireActivity(), layoutInflater) }
|
||||||
private val liveAdapter = HomeLiveAdapter()
|
private val liveAdapter = HomeLiveAdapter()
|
||||||
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter()
|
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { openCreatorProfile(it.creatorId) }
|
||||||
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter()
|
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
|
||||||
private val firstAudioAdapter = HomeFirstAudioAdapter()
|
private val firstAudioAdapter = HomeFirstAudioAdapter { openAudioContentDetail(it) }
|
||||||
private val aiCharacterAdapter = HomeAiCharacterAdapter()
|
private val aiCharacterAdapter = HomeAiCharacterAdapter { openCharacterDetail(it.characterId) }
|
||||||
private val genreCreatorAdapter = HomeGenreCreatorAdapter { creatorIds -> onGenreFollowAllClick(creatorIds) }
|
private val genreCreatorAdapter = HomeGenreCreatorAdapter(
|
||||||
private val cheerCreatorAdapter = HomeCheerCreatorAdapter { creatorIds -> onCheerFollowAllClick(creatorIds) }
|
onFollowAllClick = { creatorIds -> onGenreFollowAllClick(creatorIds) },
|
||||||
private val popularCommunityAdapter = HomePopularCommunityAdapter { }
|
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 bannerBinder: HomeBannerBinder? = null
|
||||||
private var onGenreFollowAllClick: (List<Long>) -> Unit = {}
|
private var onGenreFollowAllClick: (List<Long>) -> Unit = {}
|
||||||
private var onCheerFollowAllClick: (List<Long>) -> Unit = {}
|
private var onCheerFollowAllClick: (List<Long>) -> Unit = {}
|
||||||
@@ -71,7 +81,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
setUpSectionTitles()
|
setUpSectionTitles()
|
||||||
setUpRecommendationAdapters()
|
setUpRecommendationAdapters()
|
||||||
setUpBusinessInfo()
|
setUpBusinessInfo()
|
||||||
bindHomeRecommendationContent(phase6SampleContent())
|
bindHomeRecommendationObservers()
|
||||||
|
homeRecommendationViewModel.loadRecommendations()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpBusinessInfo() {
|
private fun setUpBusinessInfo() {
|
||||||
@@ -94,7 +105,10 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpRecommendationAdapters() {
|
private fun setUpRecommendationAdapters() {
|
||||||
bannerBinder = HomeBannerBinder(binding.rvHomeBanners)
|
bannerBinder = HomeBannerBinder(binding.rvHomeBanners).apply {
|
||||||
|
setOnBannerClick { onBannerClick(it) }
|
||||||
|
}
|
||||||
|
liveAdapter.setOnLiveClick { onLiveClick(it) }
|
||||||
binding.rvHomeLives.adapter = liveAdapter
|
binding.rvHomeLives.adapter = liveAdapter
|
||||||
binding.rvHomeRecentActivityCreators.adapter = recentActivityCreatorAdapter
|
binding.rvHomeRecentActivityCreators.adapter = recentActivityCreatorAdapter
|
||||||
binding.rvHomeRecentDebutCreators.adapter = recentDebutCreatorAdapter
|
binding.rvHomeRecentDebutCreators.adapter = recentDebutCreatorAdapter
|
||||||
@@ -112,6 +126,33 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
||||||
adapter = popularCommunityAdapter
|
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) {
|
private fun bindHomeRecommendationContent(content: HomeRecommendationUiState.Content) {
|
||||||
@@ -204,143 +245,82 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
ivSectionTitleChevron.visibility = if (showMore) View.VISIBLE else View.GONE
|
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 List<*>.toSectionVisibility(): Int = if (isEmpty()) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
private fun phase6SampleContent(): HomeRecommendationUiState.Content = HomeRecommendationUiState.Content(
|
private companion object {
|
||||||
lives = HomeRecommendationLiveSection(
|
const val SECTION_KEY_CHEER_CREATORS = "cheerCreators"
|
||||||
listOf(
|
const val SECTION_KEY_GENRE_CREATORS = "genreCreators"
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import org.junit.Assert.assertEquals
|
|||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertSame
|
import org.junit.Assert.assertSame
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
@@ -927,6 +928,27 @@ class HomeMainFragmentLayoutTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home main fragment phase9 replaces sample content with viewmodel state binding`() {
|
||||||
|
val source = homeMainFragmentSource()
|
||||||
|
|
||||||
|
assertFalse(source.contains("phase6SampleContent"))
|
||||||
|
assertTrue(source.contains("HomeRecommendationViewModel"))
|
||||||
|
assertTrue(source.contains("recommendationStateLiveData.observe(viewLifecycleOwner)"))
|
||||||
|
assertTrue(source.contains("homeRecommendationViewModel.loadRecommendations()"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home main fragment phase9 connects loading toast and follow all callbacks`() {
|
||||||
|
val source = homeMainFragmentSource()
|
||||||
|
|
||||||
|
assertTrue(source.contains("isLoading.observe(viewLifecycleOwner)"))
|
||||||
|
assertTrue(source.contains("toastLiveData.observe(viewLifecycleOwner)"))
|
||||||
|
assertTrue(source.contains("showToast"))
|
||||||
|
assertTrue(source.contains("followCreators(SECTION_KEY_CHEER_CREATORS"))
|
||||||
|
assertTrue(source.contains("followCreators(genreSectionKey"))
|
||||||
|
}
|
||||||
|
|
||||||
private fun TextView.clickableSpanCount(): Int {
|
private fun TextView.clickableSpanCount(): Int {
|
||||||
val spanned = text as? Spanned ?: return 0
|
val spanned = text as? Spanned ?: return 0
|
||||||
return spanned.getSpans(0, spanned.length, ClickableSpan::class.java).size
|
return spanned.getSpans(0, spanned.length, ClickableSpan::class.java).size
|
||||||
@@ -937,6 +959,14 @@ class HomeMainFragmentLayoutTest {
|
|||||||
spanned.getSpans(0, spanned.length, ClickableSpan::class.java).last().onClick(this)
|
spanned.getSpans(0, spanned.length, ClickableSpan::class.java).last().onClick(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun homeMainFragmentSource(): String {
|
||||||
|
val projectRoot = java.io.File("..").canonicalFile
|
||||||
|
return java.io.File(
|
||||||
|
projectRoot,
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt"
|
||||||
|
).readText()
|
||||||
|
}
|
||||||
|
|
||||||
private fun inflateView(layoutResId: Int): View {
|
private fun inflateView(layoutResId: Int): View {
|
||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
return LayoutInflater.from(context).inflate(layoutResId, null, false)
|
return LayoutInflater.from(context).inflate(layoutResId, null, false)
|
||||||
|
|||||||
Reference in New Issue
Block a user