feat(home): AI 캐릭터 크리에이터 이동을 연결한다
This commit is contained in:
@@ -7,7 +7,6 @@ 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.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.Constants
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
import kr.co.vividnext.sodalive.common.ToastMessage
|
import kr.co.vividnext.sodalive.common.ToastMessage
|
||||||
@@ -18,6 +17,7 @@ import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCo
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
|
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeCreatorRankingUiState
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeCreatorRankingUiState
|
||||||
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
|
||||||
@@ -35,6 +35,8 @@ import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentlyAct
|
|||||||
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.toHomeRecommendationBannerIntent
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerIntent
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerRoute
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerRoute
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterIntent
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterRoute
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorIntent
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorIntent
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorRoute
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorRoute
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.visibleHomeGenreCreatorGroups
|
import kr.co.vividnext.sodalive.v2.main.home.model.visibleHomeGenreCreatorGroups
|
||||||
@@ -63,7 +65,7 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) }
|
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) }
|
||||||
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
|
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
|
||||||
private val firstAudioAdapter = HomeFirstAudioAdapter { openAudioContentDetail(it) }
|
private val firstAudioAdapter = HomeFirstAudioAdapter { openAudioContentDetail(it) }
|
||||||
private val aiCharacterAdapter = HomeAiCharacterAdapter { openCharacterDetail(it.characterId) }
|
private val aiCharacterAdapter = HomeAiCharacterAdapter { onAiCharacterClick(it) }
|
||||||
private val genreCreatorAdapter = HomeGenreCreatorAdapter(
|
private val genreCreatorAdapter = HomeGenreCreatorAdapter(
|
||||||
onFollowAllClick = { creatorIds -> onGenreFollowAllClick(creatorIds) },
|
onFollowAllClick = { creatorIds -> onGenreFollowAllClick(creatorIds) },
|
||||||
onCreatorClick = { creator -> openCreatorProfile(creator.creatorId) }
|
onCreatorClick = { creator -> openCreatorProfile(creator.creatorId) }
|
||||||
@@ -339,6 +341,11 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
startActivity(route.toHomeRecommendationRecentlyActiveCreatorIntent(requireContext()))
|
startActivity(route.toHomeRecommendationRecentlyActiveCreatorIntent(requireContext()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onAiCharacterClick(item: HomeRecommendationAiCharacterUiModel) {
|
||||||
|
val route = item.toHomeRecommendationAiCharacterRoute() ?: return
|
||||||
|
startActivity(route.toHomeRecommendationAiCharacterIntent(requireContext()))
|
||||||
|
}
|
||||||
|
|
||||||
private fun openCreatorRankingProfile(item: CreatorRankingItem) {
|
private fun openCreatorRankingProfile(item: CreatorRankingItem) {
|
||||||
if (item.creatorId <= 0L) return
|
if (item.creatorId <= 0L) return
|
||||||
openCreatorProfile(item.creatorId)
|
openCreatorProfile(item.creatorId)
|
||||||
@@ -358,14 +365,6 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openCharacterDetail(characterId: Long) {
|
|
||||||
startActivity(
|
|
||||||
Intent(requireContext(), CharacterDetailActivity::class.java).apply {
|
|
||||||
putExtra(CharacterDetailActivity.EXTRA_CHARACTER_ID, characterId)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openPopularCommunityPost(item: FeedItem.Community) {
|
private fun openPopularCommunityPost(item: FeedItem.Community) {
|
||||||
val creatorId = item.creatorId.toLongOrNull() ?: return
|
val creatorId = item.creatorId.toLongOrNull() ?: return
|
||||||
val postId = item.postId.toLongOrNull() ?: return
|
val postId = item.postId.toLongOrNull() ?: return
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ data class HomeFirstAudioContentItem(
|
|||||||
@Keep
|
@Keep
|
||||||
data class HomeAiCharacterItem(
|
data class HomeAiCharacterItem(
|
||||||
@SerializedName("characterId") val characterId: Long,
|
@SerializedName("characterId") val characterId: Long,
|
||||||
|
@SerializedName("creatorId") val creatorId: Long,
|
||||||
@SerializedName("name") val name: String,
|
@SerializedName("name") val name: String,
|
||||||
@SerializedName("description") val description: String,
|
@SerializedName("description") val description: String,
|
||||||
@SerializedName("profileImage") val profileImage: String?,
|
@SerializedName("profileImage") val profileImage: String?,
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ fun HomeFirstAudioContentItem.toUiModel(): HomeRecommendationFirstAudioContentUi
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun HomeAiCharacterItem.toUiModel(): HomeRecommendationAiCharacterUiModel = HomeRecommendationAiCharacterUiModel(
|
fun HomeAiCharacterItem.toUiModel(): HomeRecommendationAiCharacterUiModel = HomeRecommendationAiCharacterUiModel(
|
||||||
|
creatorId = creatorId,
|
||||||
item = CharacterChatThumbnailItem(
|
item = CharacterChatThumbnailItem(
|
||||||
characterId = characterId,
|
characterId = characterId,
|
||||||
imageUrl = profileImage.orEmpty(),
|
imageUrl = profileImage.orEmpty(),
|
||||||
|
|||||||
@@ -191,9 +191,24 @@ data class HomeRecommendationFirstAudioContentUiModel(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class HomeRecommendationAiCharacterUiModel(
|
data class HomeRecommendationAiCharacterUiModel(
|
||||||
|
val creatorId: Long,
|
||||||
val item: CharacterChatThumbnailItem
|
val item: CharacterChatThumbnailItem
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sealed interface HomeRecommendationAiCharacterRoute {
|
||||||
|
data class Creator(val creatorId: Long) : HomeRecommendationAiCharacterRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HomeRecommendationAiCharacterUiModel.toHomeRecommendationAiCharacterRoute(): HomeRecommendationAiCharacterRoute? {
|
||||||
|
return creatorId.takeIf { it > 0L }?.let(HomeRecommendationAiCharacterRoute::Creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HomeRecommendationAiCharacterRoute.toHomeRecommendationAiCharacterIntent(context: Context): Intent {
|
||||||
|
return when (this) {
|
||||||
|
is HomeRecommendationAiCharacterRoute.Creator -> CreatorChannelActivity.newIntent(context, creatorId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class HomeRecommendationGenreCreatorGroupUiModel(
|
data class HomeRecommendationGenreCreatorGroupUiModel(
|
||||||
val genre: String,
|
val genre: String,
|
||||||
val creators: List<HomeRecommendationCreatorUiModel>,
|
val creators: List<HomeRecommendationCreatorUiModel>,
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterUiModel
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.widget.characterchatthumbnail.CharacterChatThumbnailItem
|
|
||||||
import kr.co.vividnext.sodalive.v2.widget.characterchatthumbnail.CharacterChatThumbnailView
|
import kr.co.vividnext.sodalive.v2.widget.characterchatthumbnail.CharacterChatThumbnailView
|
||||||
|
|
||||||
class HomeAiCharacterAdapter(
|
class HomeAiCharacterAdapter(
|
||||||
private val onClickItem: (CharacterChatThumbnailItem) -> Unit = {}
|
private val onClickItem: (HomeRecommendationAiCharacterUiModel) -> Unit = {}
|
||||||
) : RecyclerView.Adapter<HomeAiCharacterAdapter.CharacterViewHolder>() {
|
) : RecyclerView.Adapter<HomeAiCharacterAdapter.CharacterViewHolder>() {
|
||||||
private var items: List<HomeRecommendationAiCharacterUiModel> = emptyList()
|
private var items: List<HomeRecommendationAiCharacterUiModel> = emptyList()
|
||||||
|
|
||||||
@@ -31,19 +30,19 @@ class HomeAiCharacterAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
||||||
holder.bind(items[position].item)
|
holder.bind(items[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
class CharacterViewHolder(
|
class CharacterViewHolder(
|
||||||
private val view: CharacterChatThumbnailView,
|
private val view: CharacterChatThumbnailView,
|
||||||
private val onClickItem: (CharacterChatThumbnailItem) -> Unit
|
private val onClickItem: (HomeRecommendationAiCharacterUiModel) -> Unit
|
||||||
) : RecyclerView.ViewHolder(view) {
|
) : RecyclerView.ViewHolder(view) {
|
||||||
fun bind(item: CharacterChatThumbnailItem) {
|
fun bind(item: HomeRecommendationAiCharacterUiModel) {
|
||||||
view.bind(item)
|
view.bind(item.item)
|
||||||
view.imageView().loadUrl(item.imageUrl)
|
view.imageView().loadUrl(item.item.imageUrl)
|
||||||
view.setOnCharacterClick(onClickItem)
|
view.setOnCharacterClick { onClickItem(item) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
|
|||||||
import kr.co.vividnext.sodalive.settings.event.EventItem
|
import kr.co.vividnext.sodalive.settings.event.EventItem
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
|
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.HomeActiveCreatorItem
|
import kr.co.vividnext.sodalive.v2.main.home.data.HomeActiveCreatorItem
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.data.HomeAiCharacterItem
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.HomeBannerItem
|
import kr.co.vividnext.sodalive.v2.main.home.data.HomeBannerItem
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.HomeCreatorItem
|
import kr.co.vividnext.sodalive.v2.main.home.data.HomeCreatorItem
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.HomeFirstAudioContentItem
|
import kr.co.vividnext.sodalive.v2.main.home.data.HomeFirstAudioContentItem
|
||||||
@@ -40,6 +41,7 @@ import kr.co.vividnext.sodalive.v2.main.home.data.HomeLiveItem
|
|||||||
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.HomeRecommendationBannerRoute
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerRoute
|
||||||
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.HomeRecommendationAiCharacterRoute
|
||||||
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.HomeRecommendationCreatorUiModel
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCreatorUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorGroupUiModel
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorGroupUiModel
|
||||||
@@ -53,6 +55,8 @@ import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentlyAct
|
|||||||
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerIntent
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerIntent
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerRoute
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerRoute
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterIntent
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterRoute
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorIntent
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorIntent
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorRoute
|
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorRoute
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.visibleHomePopularCommunityPosts
|
import kr.co.vividnext.sodalive.v2.main.home.model.visibleHomePopularCommunityPosts
|
||||||
@@ -1087,6 +1091,41 @@ class HomeMainFragmentLayoutTest {
|
|||||||
assertEquals(13L, communityIntent.getLongExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, 0L))
|
assertEquals(13L, communityIntent.getLongExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, 0L))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home ai character mapper preserves creator id for routing and character id for display`() {
|
||||||
|
val item = HomeAiCharacterItem(
|
||||||
|
characterId = 11L,
|
||||||
|
creatorId = 22L,
|
||||||
|
name = "캐릭터",
|
||||||
|
description = "설명",
|
||||||
|
profileImage = "https://example.com/character.png",
|
||||||
|
totalChatCount = 33L,
|
||||||
|
originalWorkTitle = "원작"
|
||||||
|
).toUiModel()
|
||||||
|
|
||||||
|
assertEquals(22L, item.creatorId)
|
||||||
|
assertEquals(11L, item.item.characterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home ai character route uses creator id and ignores invalid ids`() {
|
||||||
|
assertEquals(
|
||||||
|
HomeRecommendationAiCharacterRoute.Creator(22L),
|
||||||
|
aiCharacter(creatorId = 22L).toHomeRecommendationAiCharacterRoute()
|
||||||
|
)
|
||||||
|
assertEquals(null, aiCharacter(creatorId = 0L).toHomeRecommendationAiCharacterRoute())
|
||||||
|
assertEquals(null, aiCharacter(creatorId = -1L).toHomeRecommendationAiCharacterRoute())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home ai character route creates creator channel intent`() {
|
||||||
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
|
val intent = HomeRecommendationAiCharacterRoute.Creator(22L).toHomeRecommendationAiCharacterIntent(context)
|
||||||
|
|
||||||
|
assertEquals(CreatorChannelActivity::class.java.name, intent.component?.className)
|
||||||
|
assertEquals(22L, intent.getLongExtra(CreatorChannelActivity.EXTRA_CREATOR_ID, 0L))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `home popular community adapter applies blur when locked paid post image is loaded`() {
|
fun `home popular community adapter applies blur when locked paid post image is loaded`() {
|
||||||
val source = projectFile(
|
val source = projectFile(
|
||||||
@@ -1517,6 +1556,18 @@ class HomeMainFragmentLayoutTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun aiCharacter(creatorId: Long): kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterUiModel {
|
||||||
|
return HomeAiCharacterItem(
|
||||||
|
characterId = 11L,
|
||||||
|
creatorId = creatorId,
|
||||||
|
name = "캐릭터",
|
||||||
|
description = "설명",
|
||||||
|
profileImage = null,
|
||||||
|
totalChatCount = 33L,
|
||||||
|
originalWorkTitle = null
|
||||||
|
).toUiModel()
|
||||||
|
}
|
||||||
|
|
||||||
private fun popularCommunityData(
|
private fun popularCommunityData(
|
||||||
audioUrl: String?,
|
audioUrl: String?,
|
||||||
createdAt: String = "2분 전"
|
createdAt: String = "2분 전"
|
||||||
|
|||||||
Reference in New Issue
Block a user