feat(character list): 캐릭터 탭

- 배너 리스트 추가
- 배너, 캐릭터 클릭시 캐릭터 상세 페이지로 이동
This commit is contained in:
2025-08-13 00:05:39 +09:00
parent d8b48fe362
commit ff1e134fe4
7 changed files with 96 additions and 52 deletions

View File

@@ -0,0 +1,54 @@
package kr.co.vividnext.sodalive.chat.character
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.widget.FrameLayout
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.zhpan.bannerview.BaseBannerAdapter
import com.zhpan.bannerview.BaseViewHolder
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentBannerResponse
class CharacterBannerAdapter(
private val context: Context,
private val itemWidth: Int,
private val itemHeight: Int,
private val onClick: (CharacterBannerResponse) -> Unit
) : BaseBannerAdapter<CharacterBannerResponse>() {
override fun bindData(
holder: BaseViewHolder<CharacterBannerResponse>,
data: CharacterBannerResponse,
position: Int,
pageSize: Int
) {
val ivBanner = holder.findViewById<ImageView>(R.id.iv_recommend_live)
val layoutParams = ivBanner.layoutParams as FrameLayout.LayoutParams
layoutParams.width = itemWidth
layoutParams.height = itemHeight
Glide
.with(context)
.asBitmap()
.load(data.imageUrl)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
ivBanner.setImageBitmap(resource)
ivBanner.layoutParams = layoutParams
}
override fun onLoadCleared(placeholder: Drawable?) {
}
})
ivBanner.setOnClickListener { onClick(data) }
}
override fun getLayoutId(viewType: Int): Int {
return R.layout.item_recommend_live
}
}

View File

@@ -2,15 +2,20 @@ package kr.co.vividnext.sodalive.chat.character
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentBannerResponse
import kr.co.vividnext.sodalive.chat.character.curation.CurationSection
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacter
@Keep
data class CharacterHomeResponse(
@SerializedName("banners") val banners: List<GetAudioContentBannerResponse>,
@SerializedName("banners") val banners: List<CharacterBannerResponse>,
@SerializedName("recentCharacters") val recentCharacters: List<RecentCharacter>,
@SerializedName("popularCharacters") val popularCharacters: List<Character>,
@SerializedName("newCharacters") val newCharacters: List<Character>,
@SerializedName("curationSections") val curationSections: List<CurationSection>
)
@Keep
data class CharacterBannerResponse(
@SerializedName("characterId") val characterId: Long,
@SerializedName("imageUrl") val imageUrl: String
)

View File

@@ -2,7 +2,6 @@ package kr.co.vividnext.sodalive.chat.character
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
@@ -16,21 +15,17 @@ import com.zhpan.bannerview.BaseBannerAdapter
import com.zhpan.indicator.enums.IndicatorSlideMode
import com.zhpan.indicator.enums.IndicatorStyle
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.main.AudioContentBannerType
import kr.co.vividnext.sodalive.audio_content.main.banner.AudioContentMainBannerAdapter
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.chat.character.curation.CurationSectionAdapter
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Companion.EXTRA_CHARACTER_ID
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacter
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacterAdapter
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentCharacterTabBinding
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.main.MainActivity
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import org.koin.android.ext.android.inject
// 캐릭터 탭 프래그먼트
@@ -40,7 +35,7 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
) {
private val viewModel: CharacterTabViewModel by inject()
private lateinit var contentBannerAdapter: AudioContentMainBannerAdapter
private lateinit var contentBannerAdapter: CharacterBannerAdapter
private lateinit var recentCharacterAdapter: RecentCharacterAdapter
private lateinit var popularCharacterAdapter: CharacterAdapter
private lateinit var newCharacterAdapter: CharacterAdapter
@@ -75,41 +70,17 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
layoutParams.width = pagerWidth
layoutParams.height = pagerHeight
contentBannerAdapter = AudioContentMainBannerAdapter(
contentBannerAdapter = CharacterBannerAdapter(
requireContext(),
pagerWidth,
pagerHeight
) {
if (SharedPreferenceManager.token.isNotBlank()) {
when (it.type) {
AudioContentBannerType.EVENT -> {
startActivity(
Intent(requireContext(), EventDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_EVENT, it.eventItem!!)
}
)
startActivity(
Intent(requireContext(), CharacterDetailActivity::class.java).apply {
putExtra(EXTRA_CHARACTER_ID, it.characterId)
}
AudioContentBannerType.CREATOR -> {
startActivity(
Intent(requireContext(), UserProfileActivity::class.java).apply {
putExtra(Constants.EXTRA_USER_ID, it.creatorId!!)
}
)
}
AudioContentBannerType.SERIES -> {
startActivity(
Intent(requireContext(), SeriesDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, it.seriesId!!)
}
)
}
AudioContentBannerType.LINK -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.link!!)))
}
}
)
} else {
(requireActivity() as MainActivity).showLoginActivity()
}
@@ -401,6 +372,14 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
}
private fun onCharacterClick(character: Character) {
// TODO: 캐릭터 클릭 처리
if (SharedPreferenceManager.token.isNotBlank()) {
startActivity(
Intent(requireContext(), CharacterDetailActivity::class.java).apply {
putExtra(EXTRA_CHARACTER_ID, character.id)
}
)
} else {
(requireActivity() as MainActivity).showLoginActivity()
}
}
}

View File

@@ -22,8 +22,8 @@ class CharacterTabViewModel(
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _bannerListLiveData = MutableLiveData<List<GetAudioContentBannerResponse>>()
val bannerListLiveData: LiveData<List<GetAudioContentBannerResponse>>
private var _bannerListLiveData = MutableLiveData<List<CharacterBannerResponse>>()
val bannerListLiveData: LiveData<List<CharacterBannerResponse>>
get() = _bannerListLiveData
// 최근 대화한 캐릭터 LiveData

View File

@@ -40,10 +40,10 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
// 더미 데이터 로드 (추후 Intent/Repository 연동)
val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0)
// if (characterId <= 0) {
// showToast("잘못된 접근 입니다.")
// finish()
// }
if (characterId <= 0) {
showToast("잘못된 접근 입니다.")
finish()
}
viewModel.loadMock(characterId)
bindObservers()

View File

@@ -17,7 +17,7 @@
android:id="@+id/ll_banner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"
android:layout_marginBottom="24dp"
android:orientation="vertical">
<com.zhpan.bannerview.BannerViewPager
@@ -40,7 +40,7 @@
android:id="@+id/ll_latest_characters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"
android:layout_marginBottom="24dp"
android:orientation="vertical">
<!-- 제목 -->
@@ -87,7 +87,7 @@
android:id="@+id/ll_popular_characters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"
android:layout_marginBottom="24dp"
android:orientation="vertical">
<!-- 제목과 전체보기 -->
@@ -114,7 +114,8 @@
android:fontFamily="@font/pretendard_regular"
android:text="전체보기"
android:textColor="#90A4AE"
android:textSize="14sp" />
android:textSize="14sp"
android:visibility="gone" />
</LinearLayout>
<!-- 캐릭터 카드 리스트 -->
@@ -132,7 +133,7 @@
android:id="@+id/ll_new_characters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"
android:layout_marginBottom="24dp"
android:orientation="vertical">
<!-- 제목과 전체보기 -->
@@ -159,7 +160,8 @@
android:fontFamily="@font/pretendard_regular"
android:text="전체보기"
android:textColor="#90A4AE"
android:textSize="14sp" />
android:textSize="14sp"
android:visibility="gone" />
</LinearLayout>

View File

@@ -47,7 +47,9 @@
android:id="@+id/tv_character_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:fontFamily="@font/pretendard_regular"
android:maxLines="1"
android:textColor="@color/color_b0bec5"
android:textSize="18sp" />
@@ -56,7 +58,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:fontFamily="@font/pretendard_regular"
android:maxLines="1"
android:textColor="#78909C"
android:textSize="14sp" />