From b38ada0b736aec790248c1b7087e950dca49dc91 Mon Sep 17 00:00:00 2001 From: klaus Date: Fri, 29 Nov 2024 12:15:32 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=AC=EC=83=9D=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../AudioContentPlaylistListFragment.kt | 14 +- .../AudioContentPlaylistListRepository.kt | 5 - .../AudioContentPlaylistListViewModel.kt | 5 +- .../AudioContentPlaylistRepository.kt | 12 + .../audio_content/playlist/PlaylistApi.kt | 8 + .../AudioContentPlaylistDetailActivity.kt | 118 +++++++++ .../AudioContentPlaylistDetailAdapter.kt | 46 ++++ .../AudioContentPlaylistDetailViewModel.kt | 59 +++++ .../detail/GetPlaylistDetailResponse.kt | 21 ++ .../co/vividnext/sodalive/common/Constants.kt | 1 + .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 6 +- .../res/drawable-xxhdpi/ic_playlist_play.png | Bin 0 -> 491 bytes .../drawable-xxhdpi/ic_playlist_shuffle.png | Bin 0 -> 563 bytes ...activity_audio_content_playlist_detail.xml | 235 ++++++++++++++++++ .../main/res/layout/item_playlist_content.xml | 64 +++++ 16 files changed, 586 insertions(+), 9 deletions(-) delete mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistListRepository.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/AudioContentPlaylistRepository.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailViewModel.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/GetPlaylistDetailResponse.kt create mode 100644 app/src/main/res/drawable-xxhdpi/ic_playlist_play.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_playlist_shuffle.png create mode 100644 app/src/main/res/layout/activity_audio_content_playlist_detail.xml create mode 100644 app/src/main/res/layout/item_playlist_content.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fd0b09..7bcba10 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 cdde680..03dd201 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 9093868..0000000 --- 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 47a48c1..0b088db 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 0000000..2a2a6d2 --- /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 94ccc55..e9e3e4a 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 0000000..b6356d7 --- /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 0000000..923f9fe --- /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 0000000..0134826 --- /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 0000000..726b672 --- /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 40cff7b..bff09a3 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 6a04810..f290778 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 0000000000000000000000000000000000000000..a5c642d312bfe59a64352ac144cae556172305bd GIT binary patch literal 491 zcmV0a|84$w4B(=_Q3(H+l*=Saaf&yJ^qbsVDq4Rpx{R{0J66D~TK+$JA-PxBpl23SHe zHY!V@o>C)KsO$~O8FkvZftn9*yR;g_14W1iNpt3asdL1-XHgQF#K66yI z_6E=O2-|!_LYe}49nT?UWwiy2wy7$sHK3QIvYG>W9YSRX2x$)(?I2_&U?<(8m4IFk z0ZRczECu|6fR%u5wOV~Zz)HX^gtP}d@x1W%-XU}gy&TUIhZFtxBCQ+r8@pc`&eIk! zR&vdALIRp9>X4w4Q(89ohD4QIB*QBUf5-8RjA~ZY@jUR>95Sj^(O5|bO%hZzdnKnz zR@9+DB^RM^Z7uQYe+vsJ+$pH7o#ngRkjYDtQToC+eR5evmMdwLAGa h%vP~!nx<*4+z%3#h!Ww7h}HlA002ovPDHLkV1n>X#a93T literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..56fa8d459080d40a2519e7e128b28087da6ad9ad GIT binary patch literal 563 zcmV-30?hr1P))_OswkAToiRaJkO8zk83a8?+}3nH+0-E?N|>My3_4I_yX zL^q_i<^##rB)LI6pL8^IyfS?(kOPuhJ45Fi2{HLRLsVfZOlOaf9FUt<&=!+alG=Yc z`OE~g%_eAzN+L<^R*c-j$*+)udnFmUlkR3}6d}kI5-jPFt37!Yaxzwut}ln=n4Ai^ zVI&(*oRK>Z$uap9O28?qWv&Ow+1Ofl6VA~WlLaej3j1)r50;x9_SZOx+}vQ~&d)Bl z#4{jrbA^#Jteu#peYX8zPU!9pU{KD+9edYz1Nmwj+h(7btXz9b&c-%Q+|CnM%-aMf z*W75MZb=p)=#f)_Z3iVw5j@1m_1b$SvkRJH?vd-|VI{K)rjqOByOPW%m|Cuv*Ofd^ zE~u{;eiahJNJjS)yL8`QQs2lk`gvRYdXCLgwAsVR1gF~5ClQ+@el7d(FG66*sTBn002ovPDHLkV1o4( B<}CmK literal 0 HcmV?d00001 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 0000000..d6b56e4 --- /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 0000000..6aa01fe --- /dev/null +++ b/app/src/main/res/layout/item_playlist_content.xml @@ -0,0 +1,64 @@ + + + + + + + + + + +