시리즈 콘텐츠 전체보기 페이지 추가

This commit is contained in:
klaus 2024-04-27 00:34:42 +09:00
parent 2778638dc9
commit 0b999a874c
11 changed files with 257 additions and 24 deletions

View File

@ -135,6 +135,7 @@
<activity android:name=".live.roulette.config.RouletteConfigActivity" /> <activity android:name=".live.roulette.config.RouletteConfigActivity" />
<activity android:name=".audio_content.series.SeriesListAllActivity" /> <activity android:name=".audio_content.series.SeriesListAllActivity" />
<activity android:name=".audio_content.series.detail.SeriesDetailActivity" /> <activity android:name=".audio_content.series.detail.SeriesDetailActivity" />
<activity android:name=".audio_content.series.content.SeriesContentAllActivity" />
<activity <activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity" android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"

View File

@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.audio_content.series package kr.co.vividnext.sodalive.audio_content.series
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesContentListResponse
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesDetailResponse import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesDetailResponse
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import retrofit2.http.GET import retrofit2.http.GET
@ -23,4 +24,12 @@ interface SeriesApi {
@Path("id") seriesId: Long, @Path("id") seriesId: Long,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<GetSeriesDetailResponse>> ): Single<ApiResponse<GetSeriesDetailResponse>>
@GET("/audio-content/series/{id}/content")
fun getSeriesContentList(
@Path("id") seriesId: Long,
@Query("page") page: Int,
@Query("size") size: Int,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetSeriesContentListResponse>>
} }

View File

@ -19,4 +19,16 @@ class SeriesRepository(private val api: SeriesApi) {
seriesId = seriesId, seriesId = seriesId,
authHeader = token authHeader = token
) )
fun getSeriesContentList(
seriesId: Long,
page: Int,
size: Int,
token: String
) = api.getSeriesContentList(
seriesId = seriesId,
page = page - 1,
size = size,
authHeader = token
)
} }

View File

@ -1,4 +1,4 @@
package kr.co.vividnext.sodalive.audio_content.series.detail package kr.co.vividnext.sodalive.audio_content.series.content
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
@ -8,12 +8,13 @@ import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
import coil.transform.RoundedCornersTransformation import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesContentListItem
import kr.co.vividnext.sodalive.databinding.ItemSeriesContentBinding import kr.co.vividnext.sodalive.databinding.ItemSeriesContentBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
class SeriesDetailHomeContentAdapter( class SeriesContentAdapter(
private val onClickItem: (Long) -> Unit private val onClickItem: (Long) -> Unit
) : RecyclerView.Adapter<SeriesDetailHomeContentAdapter.ViewHolder>() { ) : RecyclerView.Adapter<SeriesContentAdapter.ViewHolder>() {
val items = mutableListOf<GetSeriesContentListItem>() val items = mutableListOf<GetSeriesContentListItem>()

View File

@ -0,0 +1,103 @@
package kr.co.vividnext.sodalive.audio_content.series.content
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.ActivitySeriesContentAllBinding
import org.koin.android.ext.android.inject
class SeriesContentAllActivity : BaseActivity<ActivitySeriesContentAllBinding>(
ActivitySeriesContentAllBinding::inflate
) {
private val viewModel: SeriesContentAllViewModel by inject()
private lateinit var loadingDialog: LoadingDialog
private lateinit var adapter: SeriesContentAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val seriesId = intent.getLongExtra(Constants.EXTRA_SERIES_ID, 0)
if (seriesId <= 0) {
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
finish()
}
bindData()
viewModel.seriesId = seriesId
viewModel.getSeriesContentList()
}
override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater)
binding.toolbar.tvBack.text = "콘텐츠 전체보기"
binding.toolbar.tvBack.setOnClickListener { finish() }
adapter = SeriesContentAdapter {
startActivity(
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
}
)
}
binding.rvSeriesContentAll.layoutManager = LinearLayoutManager(
applicationContext,
LinearLayoutManager.VERTICAL,
false
)
binding.rvSeriesContentAll.addItemDecoration(
DividerItemDecoration(
applicationContext,
DividerItemDecoration.VERTICAL
)
)
binding.rvSeriesContentAll.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
.findLastCompletelyVisibleItemPosition()
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
// 스크롤이 끝에 도달했는지 확인
if (!recyclerView.canScrollVertically(1) &&
lastVisibleItemPosition == itemTotalCount
) {
viewModel.getSeriesContentList()
}
}
})
binding.rvSeriesContentAll.adapter = adapter
}
fun bindData() {
viewModel.toastLiveData.observe(this) {
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
}
viewModel.isLoading.observe(this) {
if (it) {
loadingDialog.show(screenWidth, "")
} else {
loadingDialog.dismiss()
}
}
viewModel.seriesContentListLiveData.observe(this) {
adapter.addItems(it)
}
}
}

View File

@ -0,0 +1,73 @@
package kr.co.vividnext.sodalive.audio_content.series.content
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.series.SeriesRepository
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesContentListItem
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class SeriesContentAllViewModel(private val repository: SeriesRepository) : BaseViewModel() {
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
private var _seriesContentListLiveData = MutableLiveData<List<GetSeriesContentListItem>>()
val seriesContentListLiveData: LiveData<List<GetSeriesContentListItem>>
get() = _seriesContentListLiveData
var seriesId = 0L
var page = 1
private var pageSize = 10
private var isLast = false
fun getSeriesContentList() {
if (!_isLoading.value!! && !isLast) {
_isLoading.value = true
compositeDisposable.add(
repository.getSeriesContentList(
seriesId = seriesId,
page = page,
size = pageSize,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
page += 1
if (it.data.items.isNotEmpty()) {
_seriesContentListLiveData.value = it.data.items
} else {
isLast = true
}
} else {
if (it.message != null) {
_toastLiveData.value = it.message
} else {
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
}
}
_isLoading.value = false
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}
}

View File

@ -8,6 +8,8 @@ import android.view.View
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.series.content.SeriesContentAdapter
import kr.co.vividnext.sodalive.audio_content.series.content.SeriesContentAllActivity
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.databinding.FragmentSeriesDetailHomeBinding import kr.co.vividnext.sodalive.databinding.FragmentSeriesDetailHomeBinding
@ -17,7 +19,7 @@ class SeriesDetailHomeFragment : BaseFragment<FragmentSeriesDetailHomeBinding>(
) { ) {
private var seriesDetailResponse: GetSeriesDetailResponse? = null private var seriesDetailResponse: GetSeriesDetailResponse? = null
private lateinit var adapter: SeriesDetailHomeContentAdapter private lateinit var adapter: SeriesContentAdapter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -45,9 +47,15 @@ class SeriesDetailHomeFragment : BaseFragment<FragmentSeriesDetailHomeBinding>(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun setContent() { private fun setContent() {
binding.tvTotalCount.text = "(${seriesDetailResponse!!.contentCount})" binding.tvTotalCount.text = "(${seriesDetailResponse!!.contentCount})"
binding.llContentAll.setOnClickListener {} binding.llContentAll.setOnClickListener {
startActivity(
Intent(requireActivity(), SeriesContentAllActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, seriesDetailResponse!!.seriesId)
}
)
}
adapter = SeriesDetailHomeContentAdapter { adapter = SeriesContentAdapter {
startActivity( startActivity(
Intent(requireActivity(), AudioContentDetailActivity::class.java).apply { Intent(requireActivity(), AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it) putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)

View File

@ -27,6 +27,8 @@ class SeriesDetailViewModel(private val repository: SeriesRepository) : BaseView
lateinit var seriesDetailResponse: GetSeriesDetailResponse lateinit var seriesDetailResponse: GetSeriesDetailResponse
fun getSeriesDetail() { fun getSeriesDetail() {
_isLoading.value = true
compositeDisposable.add( compositeDisposable.add(
repository.getSeriesDetail( repository.getSeriesDetail(
seriesId = seriesId, seriesId = seriesId,

View File

@ -27,6 +27,7 @@ import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListViewMod
import kr.co.vividnext.sodalive.audio_content.series.SeriesApi import kr.co.vividnext.sodalive.audio_content.series.SeriesApi
import kr.co.vividnext.sodalive.audio_content.series.SeriesListAllViewModel import kr.co.vividnext.sodalive.audio_content.series.SeriesListAllViewModel
import kr.co.vividnext.sodalive.audio_content.series.SeriesRepository import kr.co.vividnext.sodalive.audio_content.series.SeriesRepository
import kr.co.vividnext.sodalive.audio_content.series.content.SeriesContentAllViewModel
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailViewModel import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailViewModel
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadViewModel import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadViewModel
import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeViewModel import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeViewModel
@ -204,13 +205,14 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { VoiceMessageViewModel(get()) } viewModel { VoiceMessageViewModel(get()) }
viewModel { VoiceMessageWriteViewModel(get()) } viewModel { VoiceMessageWriteViewModel(get()) }
viewModel { SelectMessageRecipientViewModel(get(), get()) } viewModel { SelectMessageRecipientViewModel(get(), get()) }
viewModel { SignOutViewModel(get()) }
viewModel { NoticeViewModel(get()) } viewModel { NoticeViewModel(get()) }
viewModel { EventViewModel(get()) } viewModel { EventViewModel(get()) }
viewModel { NotificationSettingsViewModel(get()) } viewModel { NotificationSettingsViewModel(get()) }
viewModel { SettingsViewModel(get()) } viewModel { SettingsViewModel(get()) }
viewModel { SeriesDetailViewModel(get()) } viewModel { SeriesDetailViewModel(get()) }
viewModel { SeriesListAllViewModel(get()) } viewModel { SeriesListAllViewModel(get()) }
viewModel { SeriesContentAllViewModel(get()) }
viewModel { SignOutViewModel(get()) }
viewModel { TextMessageDetailViewModel(get()) } viewModel { TextMessageDetailViewModel(get()) }
viewModel { LiveReservationStatusViewModel(get()) } viewModel { LiveReservationStatusViewModel(get()) }
viewModel { AudioContentMainBannerViewModel(get()) } viewModel { AudioContentMainBannerViewModel(get()) }

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar"
layout="@layout/detail_toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_series_content_all"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="true"
android:padding="13.3dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -15,23 +15,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout
android:id="@+id/rl_toolbar"
android:layout_width="0dp"
android:layout_height="51.7dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:contentDescription="@null"
android:paddingHorizontal="13.3dp"
android:src="@drawable/ic_back" />
</RelativeLayout>
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
@ -219,4 +202,20 @@
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<RelativeLayout
android:id="@+id/rl_toolbar"
android:layout_width="0dp"
android:layout_height="51.7dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:contentDescription="@null"
android:paddingHorizontal="13.3dp"
android:src="@drawable/ic_back" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>