diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8fd0b098..7bcba105 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -145,6 +145,7 @@
+
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListFragment.kt
index cdde680c..03dd201b 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListFragment.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListFragment.kt
@@ -1,10 +1,13 @@
package kr.co.vividnext.sodalive.audio_content.playlist
import android.annotation.SuppressLint
+import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
+import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistDetailActivity
import kr.co.vividnext.sodalive.base.BaseFragment
+import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentPlaylistListBinding
import org.koin.android.ext.android.inject
@@ -29,7 +32,16 @@ class AudioContentPlaylistListFragment : BaseFragment
+ startActivity(
+ Intent(requireContext(), AudioContentPlaylistDetailActivity::class.java).apply {
+ putExtra(
+ Constants.EXTRA_AUDIO_CONTENT_PLAYLIST_ID,
+ playlistId
+ )
+ }
+ )
+ }
val recyclerView = binding.rvPlaylistList
recyclerView.setHasFixedSize(true)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListRepository.kt
deleted file mode 100644
index 9093868a..00000000
--- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListRepository.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package kr.co.vividnext.sodalive.audio_content.playlist
-
-class AudioContentPlaylistListRepository(private val api: PlaylistApi) {
- fun getPlaylistList(token: String) = api.getPlaylistList(authHeader = token)
-}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListViewModel.kt
index 47a48c1a..0b088db3 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListViewModel.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListViewModel.kt
@@ -9,7 +9,7 @@ import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class AudioContentPlaylistListViewModel(
- private val repository: AudioContentPlaylistListRepository
+ private val repository: AudioContentPlaylistRepository
) : BaseViewModel() {
private val _toastLiveData = MutableLiveData()
val toastLiveData: LiveData
@@ -28,12 +28,14 @@ class AudioContentPlaylistListViewModel(
get() = _playlistLiveData
fun getPlaylistList() {
+ _isLoading.value = true
compositeDisposable.add(
repository.getPlaylistList(token = "Bearer ${SharedPreferenceManager.token}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
+ _isLoading.value = false
if (it.success && it.data != null) {
_totalCountLiveData.value = it.data.totalCount
_playlistLiveData.value = it.data.items
@@ -48,6 +50,7 @@ class AudioContentPlaylistListViewModel(
}
},
{
+ _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/playlist/AudioContentPlaylistRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistRepository.kt
new file mode 100644
index 00000000..2a2a6d21
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistRepository.kt
@@ -0,0 +1,12 @@
+package kr.co.vividnext.sodalive.audio_content.playlist
+
+class AudioContentPlaylistRepository(private val api: PlaylistApi) {
+ fun getPlaylistList(token: String) = api.getPlaylistList(authHeader = token)
+ fun getPlaylistDetail(
+ playlistId: Long,
+ token: String
+ ) = api.getPlaylistDetail(
+ id = playlistId,
+ authHeader = token
+ )
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/PlaylistApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/PlaylistApi.kt
index 94ccc558..e9e3e4ac 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/PlaylistApi.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/PlaylistApi.kt
@@ -1,13 +1,21 @@
package kr.co.vividnext.sodalive.audio_content.playlist
import io.reactivex.rxjava3.core.Single
+import kr.co.vividnext.sodalive.audio_content.playlist.detail.GetPlaylistDetailResponse
import kr.co.vividnext.sodalive.common.ApiResponse
import retrofit2.http.GET
import retrofit2.http.Header
+import retrofit2.http.Path
interface PlaylistApi {
@GET("/audio-content/playlist")
fun getPlaylistList(
@Header("Authorization") authHeader: String
): Single>
+
+ @GET("/audio-content/playlist/{id}")
+ fun getPlaylistDetail(
+ @Path("id") id: Long,
+ @Header("Authorization") authHeader: String
+ ): Single>
}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailActivity.kt
new file mode 100644
index 00000000..b6356d74
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailActivity.kt
@@ -0,0 +1,118 @@
+package kr.co.vividnext.sodalive.audio_content.playlist.detail
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageView
+import android.widget.Toast
+import androidx.recyclerview.widget.LinearLayoutManager
+import coil.load
+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.ActivityAudioContentPlaylistDetailBinding
+import org.koin.android.ext.android.inject
+
+class AudioContentPlaylistDetailActivity : BaseActivity(
+ ActivityAudioContentPlaylistDetailBinding::inflate
+) {
+
+ private val viewModel: AudioContentPlaylistDetailViewModel by inject()
+
+ private lateinit var loadingDialog: LoadingDialog
+ private lateinit var adapter: AudioContentPlaylistDetailAdapter
+
+ private var playlistId: Long = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ playlistId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_PLAYLIST_ID, 0)
+ super.onCreate(savedInstanceState)
+
+ if (playlistId <= 0) {
+ showToast("잘못된 요청입니다.")
+ finish()
+ }
+
+ bindData()
+ viewModel.getPlaylistDetail(playlistId)
+ }
+
+ override fun setupView() {
+ loadingDialog = LoadingDialog(this, layoutInflater)
+ adapter = AudioContentPlaylistDetailAdapter()
+
+ val recyclerView = binding.rvPlaylistDetail
+ recyclerView.setHasFixedSize(true)
+ recyclerView.layoutManager = LinearLayoutManager(
+ applicationContext,
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ recyclerView.adapter = adapter
+
+ binding.llPlay.setOnClickListener { }
+ binding.llShuffle.setOnClickListener { }
+ }
+
+ @SuppressLint("SetTextI18n")
+ private 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.detailResponseLiveData.observe(this) {
+ binding.tvDesc.text = it.desc
+ binding.tvTitle.text = it.title
+ binding.tvContentCount.text = " ${it.contentCount}개"
+ binding.tvCreateDate.text = "만든 날짜 ${it.createdDate} "
+ updateCoverImageLayout(imageUrlList = it.playlistCoverImageList)
+ }
+ }
+
+ private fun updateCoverImageLayout(imageUrlList: List) {
+ val imageViews = listOf(
+ binding.ivCover1,
+ binding.ivCover2,
+ binding.ivCover3,
+ binding.ivCover4,
+ binding.ivCover5,
+ binding.ivCover6
+ )
+
+ imageViews.forEach { it.visibility = View.GONE }
+
+ when (imageUrlList.size) {
+ 1, 2 -> {
+ setImage(imageViews[0], imageUrlList[0])
+ }
+
+ 3 -> {
+ setImage(imageViews[1], imageUrlList[0])
+ setImage(imageViews[2], imageUrlList[1])
+ setImage(imageViews[3], imageUrlList[2])
+ }
+
+ 4 -> {
+ setImage(imageViews[1], imageUrlList[0])
+ setImage(imageViews[2], imageUrlList[1])
+ setImage(imageViews[4], imageUrlList[2])
+ setImage(imageViews[5], imageUrlList[3])
+ }
+ }
+ }
+
+ private fun setImage(imageView: ImageView, imageUrl: String) {
+ imageView.apply {
+ visibility = View.VISIBLE
+ this.load(imageUrl)
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailAdapter.kt
new file mode 100644
index 00000000..923f9fe5
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailAdapter.kt
@@ -0,0 +1,46 @@
+package kr.co.vividnext.sodalive.audio_content.playlist.detail
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import coil.load
+import coil.transform.RoundedCornersTransformation
+import kr.co.vividnext.sodalive.R
+import kr.co.vividnext.sodalive.databinding.ItemPlaylistContentBinding
+import kr.co.vividnext.sodalive.extensions.dpToPx
+
+class AudioContentPlaylistDetailAdapter :
+ RecyclerView.Adapter() {
+
+ val items = mutableListOf()
+
+ inner class ViewHolder(
+ private val binding: ItemPlaylistContentBinding
+ ) : RecyclerView.ViewHolder(binding.root) {
+ fun bind(item: AudioContentPlaylistContent) {
+ binding.ivCover.load(item.coverUrl) {
+ crossfade(true)
+ placeholder(R.drawable.bg_placeholder)
+ transformations(RoundedCornersTransformation(5.3f.dpToPx()))
+ }
+
+ binding.tvTitle.text = item.title
+ binding.tvTheme.text = item.category
+ binding.tvDuration.text = item.duration
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
+ ItemPlaylistContentBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+ )
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bind(items[position])
+ }
+
+ override fun getItemCount() = items.count()
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailViewModel.kt
new file mode 100644
index 00000000..01348269
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailViewModel.kt
@@ -0,0 +1,59 @@
+package kr.co.vividnext.sodalive.audio_content.playlist.detail
+
+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.playlist.AudioContentPlaylistRepository
+import kr.co.vividnext.sodalive.base.BaseViewModel
+import kr.co.vividnext.sodalive.common.SharedPreferenceManager
+
+class AudioContentPlaylistDetailViewModel(
+ private val repository: AudioContentPlaylistRepository
+) : BaseViewModel() {
+ private val _toastLiveData = MutableLiveData()
+ val toastLiveData: LiveData
+ get() = _toastLiveData
+
+ private var _isLoading = MutableLiveData(false)
+ val isLoading: LiveData
+ get() = _isLoading
+
+ private val _detailResponseLiveData = MutableLiveData()
+ val detailResponseLiveData: LiveData
+ get() = _detailResponseLiveData
+
+ fun getPlaylistDetail(playlistId: Long) {
+ _isLoading.value = true
+ compositeDisposable.add(
+ repository.getPlaylistDetail(
+ playlistId = playlistId,
+ token = "Bearer ${SharedPreferenceManager.token}"
+ )
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ if (it.success && it.data != null) {
+ _detailResponseLiveData.value = it.data!!
+ } else {
+ if (it.message != null) {
+ _toastLiveData.postValue(it.message)
+ } else {
+ _toastLiveData.postValue(
+ "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
+ )
+ }
+ }
+ _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/playlist/detail/GetPlaylistDetailResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/GetPlaylistDetailResponse.kt
new file mode 100644
index 00000000..726b6727
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/GetPlaylistDetailResponse.kt
@@ -0,0 +1,21 @@
+package kr.co.vividnext.sodalive.audio_content.playlist.detail
+
+import com.google.gson.annotations.SerializedName
+
+data class GetPlaylistDetailResponse(
+ @SerializedName("playlistId") val playlistId: Long,
+ @SerializedName("title") val title: String,
+ @SerializedName("desc") val desc: String,
+ @SerializedName("createdDate") val createdDate: String,
+ @SerializedName("contentCount") val contentCount: Int,
+ @SerializedName("playlistCoverImageList") val playlistCoverImageList: List,
+ @SerializedName("contentList") val contentList: List
+)
+
+data class AudioContentPlaylistContent(
+ @SerializedName("id") val id: Long,
+ @SerializedName("title") val title: String,
+ @SerializedName("category") val category: String,
+ @SerializedName("coverUrl") val coverUrl: String,
+ @SerializedName("duration") val duration: String
+)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt b/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt
index 40cff7b5..bff09a34 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt
@@ -41,6 +41,7 @@ object Constants {
const val EXTRA_SELECT_RECIPIENT = "extra_select_recipient"
const val EXTRA_ROOM_CHANNEL_NAME = "extra_room_channel_name"
const val EXTRA_LIVE_RESERVATION_RESPONSE = "extra_live_reservation_response"
+ const val EXTRA_AUDIO_CONTENT_PLAYLIST_ID = "extra_audio_content_playlist_id"
const val EXTRA_AUDIO_CONTENT_ID = "audio_content_id"
const val EXTRA_AUDIO_CONTENT_URL = "audio_content_url"
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 6a048106..f2907780 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
@@ -24,9 +24,10 @@ import kr.co.vividnext.sodalive.audio_content.main.ranking.AudioContentMainRanki
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.order.AudioContentOrderListViewModel
-import kr.co.vividnext.sodalive.audio_content.playlist.AudioContentPlaylistListRepository
+import kr.co.vividnext.sodalive.audio_content.playlist.AudioContentPlaylistRepository
import kr.co.vividnext.sodalive.audio_content.playlist.AudioContentPlaylistListViewModel
import kr.co.vividnext.sodalive.audio_content.playlist.PlaylistApi
+import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistDetailViewModel
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
@@ -270,6 +271,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { UserViewModel(get(), get()) }
viewModel { MenuConfigViewModel(get()) }
viewModel { AudioContentPlaylistListViewModel(get()) }
+ viewModel { AudioContentPlaylistDetailViewModel(get()) }
}
private val repositoryModule = module {
@@ -298,7 +300,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
factory { CreatorCommunityRepository(get()) }
factory { AlarmListRepository(get()) }
factory { MenuConfigRepository(get()) }
- factory { AudioContentPlaylistListRepository(get()) }
+ factory { AudioContentPlaylistRepository(get()) }
}
private val moduleList = listOf(
diff --git a/app/src/main/res/drawable-xxhdpi/ic_playlist_play.png b/app/src/main/res/drawable-xxhdpi/ic_playlist_play.png
new file mode 100644
index 00000000..a5c642d3
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_playlist_play.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_playlist_shuffle.png b/app/src/main/res/drawable-xxhdpi/ic_playlist_shuffle.png
new file mode 100644
index 00000000..56fa8d45
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_playlist_shuffle.png differ
diff --git a/app/src/main/res/layout/activity_audio_content_playlist_detail.xml b/app/src/main/res/layout/activity_audio_content_playlist_detail.xml
new file mode 100644
index 00000000..d6b56e4d
--- /dev/null
+++ b/app/src/main/res/layout/activity_audio_content_playlist_detail.xml
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_playlist_content.xml b/app/src/main/res/layout/item_playlist_content.xml
new file mode 100644
index 00000000..6aa01fec
--- /dev/null
+++ b/app/src/main/res/layout/item_playlist_content.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+