diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 626b987..a3ecd18 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -135,6 +135,7 @@ <activity android:name=".live.roulette.config.RouletteConfigActivity" /> <activity android:name=".audio_content.series.SeriesListAllActivity" /> <activity android:name=".audio_content.series.detail.SeriesDetailActivity" /> + <activity android:name=".audio_content.series.content.SeriesContentAllActivity" /> <activity android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity" diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/SeriesApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/SeriesApi.kt index a3b1d1c..7f258e5 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/SeriesApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/SeriesApi.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.audio_content.series 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.common.ApiResponse import retrofit2.http.GET @@ -23,4 +24,12 @@ interface SeriesApi { @Path("id") seriesId: Long, @Header("Authorization") authHeader: String ): 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>> } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/SeriesRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/SeriesRepository.kt index 332678b..6b76289 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/SeriesRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/SeriesRepository.kt @@ -19,4 +19,16 @@ class SeriesRepository(private val api: SeriesApi) { seriesId = seriesId, authHeader = token ) + + fun getSeriesContentList( + seriesId: Long, + page: Int, + size: Int, + token: String + ) = api.getSeriesContentList( + seriesId = seriesId, + page = page - 1, + size = size, + authHeader = token + ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailHomeContentAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAdapter.kt similarity index 91% rename from app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailHomeContentAdapter.kt rename to app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAdapter.kt index c7eb974..69d3dac 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailHomeContentAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAdapter.kt @@ -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.view.LayoutInflater @@ -8,12 +8,13 @@ import androidx.recyclerview.widget.RecyclerView import coil.load import coil.transform.RoundedCornersTransformation 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.extensions.dpToPx -class SeriesDetailHomeContentAdapter( +class SeriesContentAdapter( private val onClickItem: (Long) -> Unit -) : RecyclerView.Adapter<SeriesDetailHomeContentAdapter.ViewHolder>() { +) : RecyclerView.Adapter<SeriesContentAdapter.ViewHolder>() { val items = mutableListOf<GetSeriesContentListItem>() diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAllActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAllActivity.kt new file mode 100644 index 0000000..0db3f2f --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAllActivity.kt @@ -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) + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAllViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAllViewModel.kt new file mode 100644 index 0000000..98ae18c --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/content/SeriesContentAllViewModel.kt @@ -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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailHomeFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailHomeFragment.kt index 912f846..a2d9fab 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailHomeFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailHomeFragment.kt @@ -8,6 +8,8 @@ import android.view.View import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager 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.common.Constants import kr.co.vividnext.sodalive.databinding.FragmentSeriesDetailHomeBinding @@ -17,7 +19,7 @@ class SeriesDetailHomeFragment : BaseFragment<FragmentSeriesDetailHomeBinding>( ) { private var seriesDetailResponse: GetSeriesDetailResponse? = null - private lateinit var adapter: SeriesDetailHomeContentAdapter + private lateinit var adapter: SeriesContentAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -45,9 +47,15 @@ class SeriesDetailHomeFragment : BaseFragment<FragmentSeriesDetailHomeBinding>( @SuppressLint("SetTextI18n") private fun setContent() { 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( Intent(requireActivity(), AudioContentDetailActivity::class.java).apply { putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailViewModel.kt index f448452..52543da 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailViewModel.kt @@ -27,6 +27,8 @@ class SeriesDetailViewModel(private val repository: SeriesRepository) : BaseView lateinit var seriesDetailResponse: GetSeriesDetailResponse fun getSeriesDetail() { + _isLoading.value = true + compositeDisposable.add( repository.getSeriesDetail( seriesId = seriesId, 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 257d645..d2b7471 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 @@ -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.SeriesListAllViewModel 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.upload.AudioContentUploadViewModel 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 { VoiceMessageWriteViewModel(get()) } viewModel { SelectMessageRecipientViewModel(get(), get()) } - viewModel { SignOutViewModel(get()) } viewModel { NoticeViewModel(get()) } viewModel { EventViewModel(get()) } viewModel { NotificationSettingsViewModel(get()) } viewModel { SettingsViewModel(get()) } viewModel { SeriesDetailViewModel(get()) } viewModel { SeriesListAllViewModel(get()) } + viewModel { SeriesContentAllViewModel(get()) } + viewModel { SignOutViewModel(get()) } viewModel { TextMessageDetailViewModel(get()) } viewModel { LiveReservationStatusViewModel(get()) } viewModel { AudioContentMainBannerViewModel(get()) } diff --git a/app/src/main/res/layout/activity_series_content_all.xml b/app/src/main/res/layout/activity_series_content_all.xml new file mode 100644 index 0000000..0e91873 --- /dev/null +++ b/app/src/main/res/layout/activity_series_content_all.xml @@ -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> diff --git a/app/src/main/res/layout/activity_series_detail.xml b/app/src/main/res/layout/activity_series_detail.xml index 6cceae7..63dc982 100644 --- a/app/src/main/res/layout/activity_series_detail.xml +++ b/app/src/main/res/layout/activity_series_detail.xml @@ -15,23 +15,6 @@ app:layout_constraintStart_toStartOf="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 android:layout_width="0dp" android:layout_height="0dp" @@ -219,4 +202,20 @@ </LinearLayout> </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>