From 9cafb13b50b675ba04f2b32821bd2d14b264e3ed Mon Sep 17 00:00:00 2001 From: klaus Date: Sun, 5 Jan 2025 17:21:11 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=EC=9D=B8=EA=B8=B0=20=ED=81=AC=EB=A6=AC?= =?UTF-8?q?=EC=97=90=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../audio_content/AudioContentRepository.kt | 6 +- .../main/AudioContentMainFragment.kt | 105 +++++++++++++++++- ...AudioContentMainCreatorRankingViewModel.kt | 55 +++++++++ .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 4 +- .../sodalive/explorer/ExplorerApi.kt | 5 + .../layout/fragment_audio_content_main.xml | 56 +++++++++- 6 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/ranking/AudioContentMainCreatorRankingViewModel.kt diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt index c3949f9..5c8f0e6 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt @@ -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.OrderType 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.user.CreatorFollowRequestRequest import kr.co.vividnext.sodalive.user.UserApi @@ -16,7 +17,8 @@ import java.util.TimeZone class AudioContentRepository( private val api: AudioContentApi, private val userApi: UserApi, - private val categoryApi: CategoryApi + private val categoryApi: CategoryApi, + private val explorerApi: ExplorerApi ) { fun getAudioContentListByCurationId( curationId: Long, @@ -232,4 +234,6 @@ class AudioContentRepository( sort = sort, authHeader = token ) + + fun getCreatorRank(token: String) = explorerApi.getCreatorRank(authHeader = token) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainFragment.kt index d6dd23e..02672ea 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainFragment.kt @@ -2,16 +2,23 @@ package kr.co.vividnext.sodalive.audio_content.main import android.annotation.SuppressLint import android.content.Intent +import android.graphics.Color import android.graphics.Rect import android.net.Uri import android.os.Bundle +import android.text.SpannableString +import android.text.Spanned +import android.text.style.ForegroundColorSpan import android.view.View import android.widget.LinearLayout import android.widget.Toast +import androidx.annotation.OptIn import androidx.core.content.ContextCompat +import androidx.media3.common.util.UnstableApi import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.orhanobut.logger.Logger import com.zhpan.bannerview.BaseBannerAdapter import com.zhpan.indicator.enums.IndicatorSlideMode 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.AudioContentMainNewContentViewModel 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.AudioContentMainRankingViewModel 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.upload.AudioContentUploadActivity import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.SharedPreferenceManager 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.series.UserProfileSeriesListAdapter 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 kotlin.math.roundToInt +@OptIn(UnstableApi::class) class AudioContentMainFragment : BaseFragment( FragmentAudioContentMainBinding::inflate ) { + private val creatorRankViewModel: AudioContentMainCreatorRankingViewModel by inject() + private lateinit var creatorRankAdaptor: ExplorerSectionAdapter + private val recommendSeriesViewModel: AudioContentMainRecommendSeriesViewModel by inject() private lateinit var seriesAdapter: UserProfileSeriesListAdapter @@ -78,6 +90,7 @@ class AudioContentMainFragment : BaseFragment( curationViewModel.getCurationList() bannerViewModel.getMainBannerList() newContentViewModel.getThemeList() + creatorRankViewModel.getCreatorRank() newContentViewModel.getNewContentOfTheme("전체") contentRankingViewModel.getContentRanking() contentRankingViewModel.getContentRankingSortType() @@ -99,6 +112,7 @@ class AudioContentMainFragment : BaseFragment( binding.llUploadContent.visibility = View.GONE } + setupCreatorRank() setupRecommendSeries() setupBanner() setupOrderList() @@ -143,6 +157,95 @@ class AudioContentMainFragment : BaseFragment( } } + 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() { seriesAdapter = UserProfileSeriesListAdapter( onClickItem = { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/ranking/AudioContentMainCreatorRankingViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/ranking/AudioContentMainCreatorRankingViewModel.kt new file mode 100644 index 0000000..7fdcab1 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/ranking/AudioContentMainCreatorRankingViewModel.kt @@ -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() + val toastLiveData: LiveData + get() = _toastLiveData + + private val _creatorRankLiveData = MutableLiveData() + val creatorRankLiveData: LiveData + 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" + + "계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + ) + } + ) + ) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt index 6d2dc12..531344e 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt @@ -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.new_content.AudioContentMainNewContentViewModel 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.recommend_series.AudioContentMainRecommendSeriesViewModel 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 { AuditionDetailViewModel(get()) } viewModel { AuditionRoleDetailViewModel(get()) } + viewModel { AudioContentMainCreatorRankingViewModel(get()) } } private val repositoryModule = module { @@ -305,7 +307,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { factory { ExplorerRepository(get()) } factory { MessageRepository(get()) } factory { NoticeRepository(get()) } - factory { AudioContentRepository(get(), get(), get()) } + factory { AudioContentRepository(get(), get(), get(), get()) } factory { AudioContentCommentRepository(get()) } factory { PlaybackTrackingRepository(get()) } factory { FollowingCreatorRepository(get()) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt index 6fa0b87..734705d 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt @@ -25,6 +25,11 @@ interface ExplorerApi { @Header("Authorization") authHeader: String ): Single> + @GET("/explorer/creator-rank") + fun getCreatorRank( + @Header("Authorization") authHeader: String + ): Single> + @GET("/explorer/search/channel") fun searchChannel( @Query("channel") channel: String, diff --git a/app/src/main/res/layout/fragment_audio_content_main.xml b/app/src/main/res/layout/fragment_audio_content_main.xml index 1f785dc..cd82336 100644 --- a/app/src/main/res/layout/fragment_audio_content_main.xml +++ b/app/src/main/res/layout/fragment_audio_content_main.xml @@ -64,6 +64,60 @@ android:layout_marginTop="6.7dp" android:layout_marginBottom="26.7dp" /> + + + + + + + + + + + + + +