콘텐츠 메인

- 인기 크리에이터 추가
This commit is contained in:
klaus 2025-01-05 17:21:11 +09:00
parent 2bec9d4595
commit 9cafb13b50
6 changed files with 227 additions and 4 deletions

View File

@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationReque
import kr.co.vividnext.sodalive.audio_content.order.OrderRequest import kr.co.vividnext.sodalive.audio_content.order.OrderRequest
import kr.co.vividnext.sodalive.audio_content.order.OrderType import kr.co.vividnext.sodalive.audio_content.order.OrderType
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.explorer.ExplorerApi
import kr.co.vividnext.sodalive.settings.ContentType import kr.co.vividnext.sodalive.settings.ContentType
import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest
import kr.co.vividnext.sodalive.user.UserApi import kr.co.vividnext.sodalive.user.UserApi
@ -16,7 +17,8 @@ import java.util.TimeZone
class AudioContentRepository( class AudioContentRepository(
private val api: AudioContentApi, private val api: AudioContentApi,
private val userApi: UserApi, private val userApi: UserApi,
private val categoryApi: CategoryApi private val categoryApi: CategoryApi,
private val explorerApi: ExplorerApi
) { ) {
fun getAudioContentListByCurationId( fun getAudioContentListByCurationId(
curationId: Long, curationId: Long,
@ -232,4 +234,6 @@ class AudioContentRepository(
sort = sort, sort = sort,
authHeader = token authHeader = token
) )
fun getCreatorRank(token: String) = explorerApi.getCreatorRank(authHeader = token)
} }

View File

@ -2,16 +2,23 @@ package kr.co.vividnext.sodalive.audio_content.main
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.graphics.Rect import android.graphics.Rect
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.annotation.OptIn
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.orhanobut.logger.Logger
import com.zhpan.bannerview.BaseBannerAdapter import com.zhpan.bannerview.BaseBannerAdapter
import com.zhpan.indicator.enums.IndicatorSlideMode import com.zhpan.indicator.enums.IndicatorSlideMode
import com.zhpan.indicator.enums.IndicatorStyle import com.zhpan.indicator.enums.IndicatorStyle
@ -29,16 +36,17 @@ import kr.co.vividnext.sodalive.audio_content.main.curation.AudioContentMainCura
import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentThemeAdapter import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentThemeAdapter
import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentViewModel import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentViewModel
import kr.co.vividnext.sodalive.audio_content.main.order.AudioContentMainOrderListViewModel import kr.co.vividnext.sodalive.audio_content.main.order.AudioContentMainOrderListViewModel
import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainCreatorRankingViewModel
import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingAdapter import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingAdapter
import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingViewModel import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingViewModel
import kr.co.vividnext.sodalive.audio_content.main.recommend_series.AudioContentMainRecommendSeriesViewModel import kr.co.vividnext.sodalive.audio_content.main.recommend_series.AudioContentMainRecommendSeriesViewModel
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListActivity
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentMainBinding import kr.co.vividnext.sodalive.databinding.FragmentAudioContentMainBinding
import kr.co.vividnext.sodalive.explorer.ExplorerSectionAdapter
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
import kr.co.vividnext.sodalive.explorer.profile.series.UserProfileSeriesListAdapter import kr.co.vividnext.sodalive.explorer.profile.series.UserProfileSeriesListAdapter
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
@ -48,9 +56,13 @@ import kr.co.vividnext.sodalive.settings.notification.MemberRole
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import kotlin.math.roundToInt import kotlin.math.roundToInt
@OptIn(UnstableApi::class)
class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>( class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
FragmentAudioContentMainBinding::inflate FragmentAudioContentMainBinding::inflate
) { ) {
private val creatorRankViewModel: AudioContentMainCreatorRankingViewModel by inject()
private lateinit var creatorRankAdaptor: ExplorerSectionAdapter
private val recommendSeriesViewModel: AudioContentMainRecommendSeriesViewModel by inject() private val recommendSeriesViewModel: AudioContentMainRecommendSeriesViewModel by inject()
private lateinit var seriesAdapter: UserProfileSeriesListAdapter private lateinit var seriesAdapter: UserProfileSeriesListAdapter
@ -78,6 +90,7 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
curationViewModel.getCurationList() curationViewModel.getCurationList()
bannerViewModel.getMainBannerList() bannerViewModel.getMainBannerList()
newContentViewModel.getThemeList() newContentViewModel.getThemeList()
creatorRankViewModel.getCreatorRank()
newContentViewModel.getNewContentOfTheme("전체") newContentViewModel.getNewContentOfTheme("전체")
contentRankingViewModel.getContentRanking() contentRankingViewModel.getContentRanking()
contentRankingViewModel.getContentRankingSortType() contentRankingViewModel.getContentRankingSortType()
@ -99,6 +112,7 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
binding.llUploadContent.visibility = View.GONE binding.llUploadContent.visibility = View.GONE
} }
setupCreatorRank()
setupRecommendSeries() setupRecommendSeries()
setupBanner() setupBanner()
setupOrderList() setupOrderList()
@ -143,6 +157,95 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
} }
} }
private fun setupCreatorRank() {
creatorRankAdaptor = ExplorerSectionAdapter(
onClickItem = {
startActivity(
Intent(requireContext(), UserProfileActivity::class.java).apply {
putExtra(Constants.EXTRA_USER_ID, it)
}
)
},
isVisibleRanking = true
)
binding.rvCreatorRank.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
binding.rvCreatorRank.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 6.7f.dpToPx().toInt()
}
creatorRankAdaptor.itemCount - 1 -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 6.7f.dpToPx().toInt()
}
}
}
})
binding.rvCreatorRank.adapter = creatorRankAdaptor
creatorRankViewModel.creatorRankLiveData.observe(viewLifecycleOwner) {
binding.tvDesc.text = it.desc
binding.tvCreatorRankTitle.text = if (
!it.coloredTitle.isNullOrBlank() &&
!it.color.isNullOrBlank()
) {
val spStr = SpannableString(it.title)
try {
spStr.setSpan(
ForegroundColorSpan(
Color.parseColor("#${it.color}")
),
it.title.indexOf(it.coloredTitle),
it.title.indexOf(it.coloredTitle) + it.coloredTitle.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
spStr
} catch (e: IllegalArgumentException) {
it.title
}
} else {
it.title
}
creatorRankAdaptor.addItems(it.creators)
if (creatorRankAdaptor.itemCount <= 0 && it.creators.isEmpty()) {
binding.llCreatorRank.visibility = View.GONE
binding.rvCreatorRank.visibility = View.GONE
} else {
binding.llCreatorRank.visibility = View.VISIBLE
binding.rvCreatorRank.visibility = View.VISIBLE
}
}
creatorRankViewModel.toastLiveData.observe(viewLifecycleOwner) {
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
}
}
private fun setupRecommendSeries() { private fun setupRecommendSeries() {
seriesAdapter = UserProfileSeriesListAdapter( seriesAdapter = UserProfileSeriesListAdapter(
onClickItem = { onClickItem = {

View File

@ -0,0 +1,55 @@
package kr.co.vividnext.sodalive.audio_content.main.ranking
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse
class AudioContentMainCreatorRankingViewModel(
private val repository: AudioContentRepository
) : BaseViewModel() {
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private val _creatorRankLiveData = MutableLiveData<GetExplorerSectionResponse>()
val creatorRankLiveData: LiveData<GetExplorerSectionResponse>
get() = _creatorRankLiveData
fun getCreatorRank() {
compositeDisposable.add(
repository
.getCreatorRank(token = "Bearer ${SharedPreferenceManager.token}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
_creatorRankLiveData.value = it.data!!
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"인기 크리에이터를 불러오지 못했습니다. 다시 시도해 주세요.\n" +
"계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
)
}
}
},
{
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue(
"인기 크리에이터를 불러오지 못했습니다. 다시 시도해 주세요.\n" +
"계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
)
}
)
)
}
}

View File

@ -20,6 +20,7 @@ import kr.co.vividnext.sodalive.audio_content.main.banner.AudioContentMainBanner
import kr.co.vividnext.sodalive.audio_content.main.curation.AudioContentMainCurationViewModel import kr.co.vividnext.sodalive.audio_content.main.curation.AudioContentMainCurationViewModel
import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentViewModel import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentViewModel
import kr.co.vividnext.sodalive.audio_content.main.order.AudioContentMainOrderListViewModel import kr.co.vividnext.sodalive.audio_content.main.order.AudioContentMainOrderListViewModel
import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainCreatorRankingViewModel
import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingViewModel import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRankingViewModel
import kr.co.vividnext.sodalive.audio_content.main.recommend_series.AudioContentMainRecommendSeriesViewModel import kr.co.vividnext.sodalive.audio_content.main.recommend_series.AudioContentMainRecommendSeriesViewModel
import kr.co.vividnext.sodalive.audio_content.modify.AudioContentModifyViewModel import kr.co.vividnext.sodalive.audio_content.modify.AudioContentModifyViewModel
@ -288,6 +289,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { AuditionViewModel(get()) } viewModel { AuditionViewModel(get()) }
viewModel { AuditionDetailViewModel(get()) } viewModel { AuditionDetailViewModel(get()) }
viewModel { AuditionRoleDetailViewModel(get()) } viewModel { AuditionRoleDetailViewModel(get()) }
viewModel { AudioContentMainCreatorRankingViewModel(get()) }
} }
private val repositoryModule = module { private val repositoryModule = module {
@ -305,7 +307,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
factory { ExplorerRepository(get()) } factory { ExplorerRepository(get()) }
factory { MessageRepository(get()) } factory { MessageRepository(get()) }
factory { NoticeRepository(get()) } factory { NoticeRepository(get()) }
factory { AudioContentRepository(get(), get(), get()) } factory { AudioContentRepository(get(), get(), get(), get()) }
factory { AudioContentCommentRepository(get()) } factory { AudioContentCommentRepository(get()) }
factory { PlaybackTrackingRepository(get()) } factory { PlaybackTrackingRepository(get()) }
factory { FollowingCreatorRepository(get()) } factory { FollowingCreatorRepository(get()) }

View File

@ -25,6 +25,11 @@ interface ExplorerApi {
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<GetExplorerResponse>> ): Single<ApiResponse<GetExplorerResponse>>
@GET("/explorer/creator-rank")
fun getCreatorRank(
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetExplorerSectionResponse>>
@GET("/explorer/search/channel") @GET("/explorer/search/channel")
fun searchChannel( fun searchChannel(
@Query("channel") channel: String, @Query("channel") channel: String,

View File

@ -64,6 +64,60 @@
android:layout_marginTop="6.7dp" android:layout_marginTop="6.7dp"
android:layout_marginBottom="26.7dp" /> android:layout_marginBottom="26.7dp" />
<LinearLayout
android:id="@+id/ll_creator_rank"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:id="@+id/tv_creator_rank_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:paddingHorizontal="13.3dp"
android:text="인기 급상승"
android:textColor="@color/color_eeeeee"
android:textSize="18.3sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:background="@color/color_222222"
android:gravity="center"
android:orientation="vertical"
android:paddingVertical="8dp">
<TextView
android:id="@+id/tv_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:textColor="@color/color_eeeeee"
android:textSize="14.7sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:fontFamily="@font/gmarket_sans_light"
android:text="※ 인기 크리에이터의 순위는 매주 업데이트됩니다."
android:textColor="@color/color_bbbbbb"
android:textSize="13.3sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_creator_rank"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:clipToPadding="false"
android:paddingHorizontal="13.3dp" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/ll_recommend_series" android:id="@+id/ll_recommend_series"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -136,8 +190,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawablePadding="2.7dp" android:drawablePadding="2.7dp"
android:fontFamily="@font/gmarket_sans_bold" android:fontFamily="@font/gmarket_sans_bold"
android:text="보이스 모닝콜"
android:gravity="center" android:gravity="center"
android:text="보이스 모닝콜"
android:textColor="@color/color_0057ff" android:textColor="@color/color_0057ff"
android:textSize="16.7sp" android:textSize="16.7sp"
app:drawableStartCompat="@drawable/ic_alarm_clock_blue" /> app:drawableStartCompat="@drawable/ic_alarm_clock_blue" />