From 2f9bace3dee66ebb4a81d30a7a079da6058a4e8e Mon Sep 17 00:00:00 2001 From: klaus Date: Fri, 18 Jul 2025 20:43:30 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=20-=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EB=93=A3=EA=B8=B0=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/audio_content/AudioContentApi.kt | 8 ++ .../audio_content/AudioContentRepository.kt | 6 ++ .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 2 +- .../vividnext/sodalive/live/LiveFragment.kt | 82 +++++++++++++++++++ .../co/vividnext/sodalive/live/LiveSummary.kt | 5 +- .../vividnext/sodalive/live/LiveViewModel.kt | 32 +++++++- app/src/main/res/layout/fragment_live.xml | 32 ++++++++ 7 files changed, 163 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt index 345d253c..332f2467 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt @@ -1,5 +1,6 @@ package kr.co.vividnext.sodalive.audio_content +import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single import kr.co.vividnext.sodalive.audio_content.all.GetNewContentAllResponse import kr.co.vividnext.sodalive.audio_content.all.by_theme.GetContentByThemeResponse @@ -56,6 +57,13 @@ interface AudioContentApi { @Header("Authorization") authHeader: String ): Single> + @GET("/audio-content/replay-live") + fun getAudioContentReplayLiveList( + @Query("isAdultContentVisible") isAdultContentVisible: Boolean, + @Query("contentType") contentType: ContentType, + @Header("Authorization") authHeader: String + ): Flowable>> + @GET("/audio-content/theme") fun getAudioContentThemeList( @Header("Authorization") authHeader: String 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 02d6e443..57b572c1 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 @@ -50,6 +50,12 @@ class AudioContentRepository( authHeader = token ) + fun getAudioContentReplayLiveList(token: String) = api.getAudioContentReplayLiveList( + isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, + contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + authHeader = token + ) + fun getAudioContentThemeList(token: String) = api.getAudioContentThemeList(token) fun uploadAudioContent( 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 0e9eca4a..c3eff15a 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 @@ -252,7 +252,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { viewModel { TermsViewModel(get()) } viewModel { FindPasswordViewModel(get()) } viewModel { MainViewModel(get(), get(), get(), get(), get()) } - viewModel { LiveViewModel(get(), get(), get(), get()) } + viewModel { LiveViewModel(get(), get(), get(), get(), get()) } viewModel { MyPageViewModel(get(), get()) } viewModel { CanStatusViewModel(get()) } viewModel { CanChargePgViewModel(get()) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt index 888a8060..8350afba 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt @@ -15,6 +15,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts 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.zhpan.bannerview.BaseBannerAdapter @@ -22,6 +23,7 @@ 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.AudioContentPlayService +import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.common.Constants @@ -34,6 +36,8 @@ import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCo import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.following.FollowingCreatorActivity +import kr.co.vividnext.sodalive.home.AudioContentMainItem +import kr.co.vividnext.sodalive.home.HomeContentAdapter import kr.co.vividnext.sodalive.live.now.LiveNowAdapter import kr.co.vividnext.sodalive.live.now.all.LiveNowAllActivity import kr.co.vividnext.sodalive.live.recommend.RecommendLiveAdapter @@ -162,6 +166,7 @@ class LiveFragment : BaseFragment(FragmentLiveBinding::infl setupRecommendLive() setupRecommendChannel() setupLatestFinishedLiveChannel() + setupLiveReplay() setupLiveReservation() } @@ -403,6 +408,83 @@ class LiveFragment : BaseFragment(FragmentLiveBinding::infl } } + private fun setupLiveReplay() { + val adapter = HomeContentAdapter { + if (SharedPreferenceManager.token.isNotBlank()) { + startActivity( + Intent(requireContext(), AudioContentDetailActivity::class.java).apply { + putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it) + } + ) + } else { + (requireActivity() as MainActivity).showLoginActivity() + } + } + + val rvContent = binding.rvReplayLive + rvContent.layoutManager = GridLayoutManager(context, 2, RecyclerView.HORIZONTAL, false) + rvContent.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.top = 8f.dpToPx().toInt() + outRect.bottom = 8f.dpToPx().toInt() + + val position = parent.getChildAdapterPosition(view) + + if (position == 0 || position == 1) { + outRect.left = 0f.dpToPx().toInt() + } else { + outRect.left = 8f.dpToPx().toInt() + } + + outRect.right = 8f.dpToPx().toInt() + } + }) + rvContent.adapter = adapter + + adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + if (adapter.itemCount < 2) { + // 1개일 땐 단일 행 + rvContent.layoutManager = + LinearLayoutManager(rvContent.context, RecyclerView.HORIZONTAL, false) + } else { + // 2개 이상일 땐 2행 바둑판 + rvContent.layoutManager = + GridLayoutManager(rvContent.context, 2, RecyclerView.HORIZONTAL, false) + } + } + }) + + viewModel.replayContentListLiveData.observe(viewLifecycleOwner) { contentList -> + if (contentList.isNotEmpty()) { + adapter.addItems( + contentList.map { + AudioContentMainItem( + contentId = it.contentId, + creatorId = it.creatorId, + title = it.title, + coverImageUrl = it.coverImageUrl, + creatorNickname = it.creatorNickname, + isPointAvailable = it.isPointAvailable + ) + } + ) + + binding.llReplayLive.visibility = View.VISIBLE + binding.rvReplayLive.visibility = View.VISIBLE + } else { + binding.llReplayLive.visibility = View.GONE + binding.rvReplayLive.visibility = View.GONE + } + } + } + @SuppressLint("NotifyDataSetChanged") private fun setupLiveNow() { binding diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveSummary.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveSummary.kt index 78a171c0..8ca414a0 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveSummary.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveSummary.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.live import androidx.annotation.Keep import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.live.recommend.GetRecommendLiveResponse import kr.co.vividnext.sodalive.settings.event.GetEventResponse @@ -17,5 +18,7 @@ data class LiveSummary( @SerializedName("recommendLive") val recommendLive: ApiResponse>, @SerializedName("latestFinishedLive") - val latestFinishedLive: ApiResponse> + val latestFinishedLive: ApiResponse>, + @SerializedName("replayLive") + val replayLive: ApiResponse> ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt index 6f96cd6c..4a396909 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt @@ -6,10 +6,13 @@ import com.orhanobut.logger.Logger import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.audio_content.AudioContentRepository +import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse +import kr.co.vividnext.sodalive.home.AudioContentMainItem import kr.co.vividnext.sodalive.live.recommend.GetRecommendLiveResponse import kr.co.vividnext.sodalive.live.recommend.LiveRecommendRepository import kr.co.vividnext.sodalive.live.recommend_channel.GetRecommendChannelResponse @@ -26,6 +29,7 @@ import kr.co.vividnext.sodalive.settings.event.EventRepository class LiveViewModel( private val repository: LiveRepository, private val eventRepository: EventRepository, + private val contentRepository: AudioContentRepository, private val liveRecommendRepository: LiveRecommendRepository, private val creatorCommunityRepository: CreatorCommunityRepository ) : BaseViewModel() { @@ -61,6 +65,10 @@ class LiveViewModel( val communityPostItemLiveData: LiveData> get() = _communityPostItemLiveData + private var _replayContentListLiveData = MutableLiveData>() + val replayContentListLiveData: LiveData> + get() = _replayContentListLiveData + private val _latestFinishedLiveListLiveData = MutableLiveData>() val latestFinishedLiveListLiveData: LiveData> @@ -166,6 +174,10 @@ class LiveViewModel( token = "Bearer ${SharedPreferenceManager.token}" ) + val replayLive = contentRepository.getAudioContentReplayLiveList( + token = "Bearer ${SharedPreferenceManager.token}" + ) + _isLoading.postValue(true) compositeDisposable.add( @@ -174,8 +186,18 @@ class LiveViewModel( liveReservation, event, recommendLive, - latestFinishedLive - ) { t1, t2, t3, t4, t5 -> LiveSummary(t1, t2, t3, t4, t5) } + latestFinishedLive, + replayLive + ) { t1, t2, t3, t4, t5, t6 -> + LiveSummary( + liveNow = t1, + liveReservation = t2, + event = t3, + recommendLive = t4, + latestFinishedLive = t5, + replayLive = t6 + ) + } .subscribeOn(Schedulers.io()) .subscribe( { @@ -245,6 +267,12 @@ class LiveViewModel( _latestFinishedLiveListLiveData.postValue(emptyList()) } + val replayLive = it.replayLive + if (replayLive.success && replayLive.data != null) { + val data = replayLive.data!! + _replayContentListLiveData.postValue(data) + } + _isLoading.postValue(false) }, { diff --git a/app/src/main/res/layout/fragment_live.xml b/app/src/main/res/layout/fragment_live.xml index 4fc1ea2d..56c7e765 100644 --- a/app/src/main/res/layout/fragment_live.xml +++ b/app/src/main/res/layout/fragment_live.xml @@ -134,6 +134,38 @@ android:clipToPadding="false" android:paddingHorizontal="24dp" /> + + + + + + + +