From a4b1ef0005e1b5a4f9e19ca3d52acae260156c74 Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 5 Dec 2024 18:49:17 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=AC=EC=83=9D=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../AudioContentPlaylistRepository.kt | 11 + .../audio_content/playlist/PlaylistApi.kt | 9 + .../AudioContentPlaylistCreateActivity.kt | 25 +- ...udioContentPlaylistCreateContentAdapter.kt | 12 +- .../AudioContentPlaylistCreateViewModel.kt | 13 +- .../PlaylistAddContentDialogFragment.kt | 9 +- .../AudioContentPlaylistDetailActivity.kt | 25 +- .../AudioContentPlaylistModifyActivity.kt | 236 ++++++++++++++++++ ...udioContentPlaylistModifyContentAdapter.kt | 57 +++++ .../AudioContentPlaylistModifyViewModel.kt | 154 ++++++++++++ .../playlist/modify/UpdatePlaylistRequest.kt | 10 + .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 2 + ...activity_audio_content_playlist_create.xml | 2 +- ...activity_audio_content_playlist_modify.xml | 167 +++++++++++++ .../layout/fragment_playlist_add_content.xml | 2 +- .../res/layout/item_playlist_add_content.xml | 1 + .../layout/item_playlist_modify_content.xml | 64 +++++ 18 files changed, 778 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyContentAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyViewModel.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/UpdatePlaylistRequest.kt create mode 100644 app/src/main/res/layout/activity_audio_content_playlist_modify.xml create mode 100644 app/src/main/res/layout/item_playlist_modify_content.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3774155..1f76c4f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -147,6 +147,7 @@ + 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 index 62115c0..3da0f79 100644 --- 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 @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.audio_content.playlist import kr.co.vividnext.sodalive.audio_content.playlist.create.CreatePlaylistRequest +import kr.co.vividnext.sodalive.audio_content.playlist.modify.UpdatePlaylistRequest class AudioContentPlaylistRepository(private val api: PlaylistApi) { fun getPlaylistList(token: String) = api.getPlaylistList(authHeader = token) @@ -25,4 +26,14 @@ class AudioContentPlaylistRepository(private val api: PlaylistApi) { id = playlistId, authHeader = token ) + + fun updatePlaylist( + playlistId: Long, + request: UpdatePlaylistRequest, + token: String + ) = api.updatePlaylist( + id = playlistId, + request = request, + 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 25f969c..6228866 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 @@ -3,12 +3,14 @@ package kr.co.vividnext.sodalive.audio_content.playlist import io.reactivex.rxjava3.core.Single import kr.co.vividnext.sodalive.audio_content.playlist.create.CreatePlaylistRequest import kr.co.vividnext.sodalive.audio_content.playlist.detail.GetPlaylistDetailResponse +import kr.co.vividnext.sodalive.audio_content.playlist.modify.UpdatePlaylistRequest import kr.co.vividnext.sodalive.common.ApiResponse import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Header import retrofit2.http.POST +import retrofit2.http.PUT import retrofit2.http.Path interface PlaylistApi { @@ -34,4 +36,11 @@ interface PlaylistApi { @Path("id") id: Long, @Header("Authorization") authHeader: String ): Single> + + @PUT("/audio-content/playlist/{id}") + fun updatePlaylist( + @Path("id") id: Long, + @Body request: UpdatePlaylistRequest, + @Header("Authorization") authHeader: String + ): Single> } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateActivity.kt index 58b6e5c..5ef4ffe 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateActivity.kt @@ -12,6 +12,7 @@ import com.jakewharton.rxbinding4.widget.textChanges import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.audio_content.playlist.create.add_content.PlaylistAddContentDialogFragment +import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistContent import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.databinding.ActivityAudioContentPlaylistCreateBinding @@ -29,15 +30,33 @@ class AudioContentPlaylistCreateActivity : BaseActivity + PlaylistAddContentDialogFragment(screenWidth, viewModel.contentList) { item, isChecked -> when { isChecked -> { - viewModel.addContentId(item) + viewModel.addContentId( + AudioContentPlaylistContent( + id = item.contentId, + title = item.title, + category = item.themeStr, + coverUrl = item.coverImageUrl, + duration = item.duration ?: "00:00:00", + creatorNickname = item.creatorNickname + ) + ) return@PlaylistAddContentDialogFragment true } !isChecked -> { - viewModel.removeContentId(item) + viewModel.removeContentId( + AudioContentPlaylistContent( + id = item.contentId, + title = item.title, + category = item.themeStr, + coverUrl = item.coverImageUrl, + duration = item.duration ?: "00:00:00", + creatorNickname = item.creatorNickname + ) + ) return@PlaylistAddContentDialogFragment true } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateContentAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateContentAdapter.kt index 11a74ea..8340a84 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateContentAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateContentAdapter.kt @@ -7,26 +7,26 @@ 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.order.GetAudioContentOrderListItem +import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistContent import kr.co.vividnext.sodalive.databinding.ItemPlaylistCreateContentBinding import kr.co.vividnext.sodalive.extensions.dpToPx class AudioContentPlaylistCreateContentAdapter : RecyclerView.Adapter() { - private val items = mutableListOf() + private val items = mutableListOf() inner class ViewHolder( private val binding: ItemPlaylistCreateContentBinding ) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: GetAudioContentOrderListItem) { - binding.ivCover.load(item.coverImageUrl) { + 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.themeStr + binding.tvTheme.text = item.category binding.tvDuration.text = item.duration } } @@ -46,7 +46,7 @@ class AudioContentPlaylistCreateContentAdapter : override fun getItemCount() = items.size @SuppressLint("NotifyDataSetChanged") - fun updateItems(items: List) { + fun updateItems(items: List) { this.items.clear() this.items.addAll(items) notifyDataSetChanged() diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateViewModel.kt index f4919ac..1e17246 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/AudioContentPlaylistCreateViewModel.kt @@ -6,6 +6,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.audio_content.order.GetAudioContentOrderListItem import kr.co.vividnext.sodalive.audio_content.playlist.AudioContentPlaylistRepository +import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistContent import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.common.SharedPreferenceManager @@ -16,25 +17,25 @@ class AudioContentPlaylistCreateViewModel( val toastLiveData: LiveData get() = _toastLiveData - private val _contentListLiveData = MutableLiveData>() - val contentListLiveData: LiveData> + private val _contentListLiveData = MutableLiveData>() + val contentListLiveData: LiveData> get() = _contentListLiveData private var _isLoading = MutableLiveData(false) val isLoading: LiveData get() = _isLoading - val contentList = mutableListOf() + val contentList = mutableListOf() var title: String = "" var desc: String = "" - fun addContentId(item: GetAudioContentOrderListItem) { + fun addContentId(item: AudioContentPlaylistContent) { contentList.add(item) _contentListLiveData.value = contentList } - fun removeContentId(item: GetAudioContentOrderListItem) { + fun removeContentId(item: AudioContentPlaylistContent) { contentList.remove(item) _contentListLiveData.value = contentList } @@ -43,7 +44,7 @@ class AudioContentPlaylistCreateViewModel( if (validate()) { _isLoading.value = true val contentIdAndOrderList = contentList.mapIndexed { index, item -> - PlaylistContentIdAndOrder(item.contentId, index + 1) + PlaylistContentIdAndOrder(item.id, index + 1) } compositeDisposable.add( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/add_content/PlaylistAddContentDialogFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/add_content/PlaylistAddContentDialogFragment.kt index 2ca42cd..9ffc9b6 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/add_content/PlaylistAddContentDialogFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/create/add_content/PlaylistAddContentDialogFragment.kt @@ -18,14 +18,15 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListViewModel import kr.co.vividnext.sodalive.audio_content.order.GetAudioContentOrderListItem import kr.co.vividnext.sodalive.audio_content.order.OrderType -import kr.co.vividnext.sodalive.audio_content.playlist.create.AudioContentPlaylistCreateActivity +import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistContent import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.databinding.FragmentPlaylistAddContentBinding import kr.co.vividnext.sodalive.extensions.dpToPx import org.koin.android.ext.android.inject class PlaylistAddContentDialogFragment( - private val selectedContentIdList: List, + private val screenWidth: Int, + private val selectedContentList: List, private val onItemClick: (GetAudioContentOrderListItem, Boolean) -> Boolean ) : BottomSheetDialogFragment() { @@ -86,7 +87,7 @@ class PlaylistAddContentDialogFragment( loadingDialog = LoadingDialog(requireActivity(), layoutInflater) adapter = PlaylistAddContentAdapter( - selectedContentIdList.map { it.contentId }.toSet(), + selectedContentList.map { it.id }.toSet(), onItemClick ) @@ -157,7 +158,7 @@ class PlaylistAddContentDialogFragment( viewModel.isLoading.observe(viewLifecycleOwner) { if (it) { loadingDialog.show( - (requireActivity() as AudioContentPlaylistCreateActivity).screenWidth, + screenWidth, "" ) } else { 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 index eaec1bc..384a2e6 100644 --- 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 @@ -1,14 +1,17 @@ package kr.co.vividnext.sodalive.audio_content.playlist.detail import android.annotation.SuppressLint +import android.content.Intent import android.graphics.Rect import android.os.Bundle import android.view.View import android.widget.ImageView import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import coil.load +import kr.co.vividnext.sodalive.audio_content.playlist.modify.AudioContentPlaylistModifyActivity import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.common.Constants @@ -28,6 +31,14 @@ class AudioContentPlaylistDetailActivity : BaseActivity + if (result.resultCode == RESULT_OK) { + viewModel.getPlaylistDetail(playlistId) + } + } + override fun onCreate(savedInstanceState: Bundle?) { playlistId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_PLAYLIST_ID, 0) super.onCreate(savedInstanceState) @@ -87,7 +98,19 @@ class AudioContentPlaylistDetailActivity : BaseActivity( + ActivityAudioContentPlaylistModifyBinding::inflate +) { + + private val viewModel: AudioContentPlaylistModifyViewModel by inject() + + private lateinit var imm: InputMethodManager + private lateinit var loadingDialog: LoadingDialog + private lateinit var adapter: AudioContentPlaylistModifyContentAdapter + + private var playlistId: Long = 0 + private val handler = Handler(Looper.getMainLooper()) + + private val addContentDialogFragment: PlaylistAddContentDialogFragment by lazy { + PlaylistAddContentDialogFragment(screenWidth, viewModel.contentList) { item, isChecked -> + when { + isChecked -> { + viewModel.addContentId( + AudioContentPlaylistContent( + id = item.contentId, + title = item.title, + category = item.themeStr, + coverUrl = item.coverImageUrl, + duration = item.duration ?: "00:00:00", + creatorNickname = item.creatorNickname + ) + ) + return@PlaylistAddContentDialogFragment true + } + + !isChecked -> { + viewModel.removeContentId( + AudioContentPlaylistContent( + id = item.contentId, + title = item.title, + category = item.themeStr, + coverUrl = item.coverImageUrl, + duration = item.duration ?: "00:00:00", + creatorNickname = item.creatorNickname + ) + ) + return@PlaylistAddContentDialogFragment true + } + + else -> { + return@PlaylistAddContentDialogFragment false + } + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + playlistId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_PLAYLIST_ID, 0) + super.onCreate(savedInstanceState) + + if (playlistId <= 0) { + showToast("잘못된 요청입니다.") + finish() + } + + imm = getSystemService( + Service.INPUT_METHOD_SERVICE + ) as InputMethodManager + + bindData() + viewModel.getPlaylistDetail(playlistId) + } + + override fun setupView() { + binding.tvBack.text = "재생목록 수정" + binding.tvBack.setOnClickListener { finish() } + + loadingDialog = LoadingDialog(this, layoutInflater) + + binding.tvAddContent.setOnClickListener { + hideKeyboard() + if (addContentDialogFragment.isAdded) return@setOnClickListener + addContentDialogFragment.show(supportFragmentManager, addContentDialogFragment.tag) + } + + binding.tvModify.setOnClickListener { + hideKeyboard() + viewModel.modifyPlaylist { + setResult(RESULT_OK) + finish() + } + } + + adapter = AudioContentPlaylistModifyContentAdapter() + + val recyclerView = binding.rvPlaylistContent + recyclerView.setHasFixedSize(true) + recyclerView.layoutManager = LinearLayoutManager( + this, + LinearLayoutManager.VERTICAL, + false + ) + recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + outRect.left = 13.3f.dpToPx().toInt() + outRect.right = 13.3f.dpToPx().toInt() + + when (parent.getChildAdapterPosition(view)) { + 0 -> { + outRect.top = 13.3f.dpToPx().toInt() + outRect.bottom = 6.7f.dpToPx().toInt() + } + + adapter.itemCount - 1 -> { + outRect.top = 6.7f.dpToPx().toInt() + outRect.bottom = 13.3f.dpToPx().toInt() + } + + else -> { + outRect.top = 6.7f.dpToPx().toInt() + outRect.bottom = 6.7f.dpToPx().toInt() + } + } + } + }) + + recyclerView.adapter = adapter + } + + private fun bindData() { + viewModel.toastLiveData.observe(this) { + it?.let { showToast(it) } + } + + viewModel.isLoading.observe(this) { + if (it) { + loadingDialog.show(screenWidth, "") + } else { + loadingDialog.dismiss() + } + } + + viewModel.contentListLiveData.observe(this) { + adapter.updateItems(it) + } + + viewModel.detailResponseLiveData.observe(this) { + binding.etTitle.setText(it.title) + binding.etDesc.setText(it.desc) + adapter.updateItems(it.contentList) + } + + compositeDisposable.add( + binding.etTitle.textChanges() + .map { it.toString() } + .distinctUntilChanged() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + if (it.length > 30) { + val truncated = it.take(30) + binding.etTitle.setText(truncated) + binding.etTitle.setSelection(truncated.length) + setTitle(truncated) + } else { + setTitle(it) + } + } + ) + + compositeDisposable.add( + binding.etDesc.textChanges() + .map { it.toString() } + .distinctUntilChanged() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + if (it.length > 40) { + val truncated = it.take(40) + binding.etDesc.setText(truncated) + binding.etDesc.setSelection(truncated.length) + setDesc(truncated) + } else { + setDesc(it) + } + } + ) + } + + @SuppressLint("SetTextI18n") + private fun setTitle(title: String) { + binding.tvTitleLength.text = "${title.length}/30" + viewModel.title = title + } + + @SuppressLint("SetTextI18n") + private fun setDesc(desc: String) { + binding.tvDescLength.text = "${desc.length}/40" + viewModel.desc = desc + } + + private fun hideKeyboard() { + handler.postDelayed({ + imm.hideSoftInputFromWindow( + window.decorView.applicationWindowToken, + InputMethodManager.HIDE_NOT_ALWAYS + ) + }, 100) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyContentAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyContentAdapter.kt new file mode 100644 index 0000000..4f9d8ab --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyContentAdapter.kt @@ -0,0 +1,57 @@ +package kr.co.vividnext.sodalive.audio_content.playlist.modify + +import android.annotation.SuppressLint +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.audio_content.playlist.detail.AudioContentPlaylistContent +import kr.co.vividnext.sodalive.databinding.ItemPlaylistCreateContentBinding +import kr.co.vividnext.sodalive.extensions.dpToPx + +class AudioContentPlaylistModifyContentAdapter : + RecyclerView.Adapter() { + private val items = mutableListOf() + + inner class ViewHolder( + private val binding: ItemPlaylistCreateContentBinding + ) : 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( + ItemPlaylistCreateContentBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder( + holder: ViewHolder, + position: Int + ) { + holder.bind(items[position]) + } + + override fun getItemCount() = items.size + + @SuppressLint("NotifyDataSetChanged") + fun updateItems(items: List) { + this.items.clear() + this.items.addAll(items) + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyViewModel.kt new file mode 100644 index 0000000..333abf3 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/AudioContentPlaylistModifyViewModel.kt @@ -0,0 +1,154 @@ +package kr.co.vividnext.sodalive.audio_content.playlist.modify + +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.audio_content.playlist.create.PlaylistContentIdAndOrder +import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistContent +import kr.co.vividnext.sodalive.audio_content.playlist.detail.GetPlaylistDetailResponse +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager + +class AudioContentPlaylistModifyViewModel( + private val repository: AudioContentPlaylistRepository +) : BaseViewModel() { + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private val _contentListLiveData = MutableLiveData>() + val contentListLiveData: LiveData> + get() = _contentListLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private val _detailResponseLiveData = MutableLiveData() + val detailResponseLiveData: LiveData + get() = _detailResponseLiveData + + val contentList = mutableListOf() + + var title: String = "" + var desc: String = "" + private var playlistId: Long = 0 + + fun addContentId(item: AudioContentPlaylistContent) { + contentList.add(item) + _contentListLiveData.value = contentList + } + + fun removeContentId(item: AudioContentPlaylistContent) { + contentList.remove(item) + _contentListLiveData.value = contentList + } + + fun modifyPlaylist(onSuccess: () -> Unit) { + if (validate()) { + _isLoading.value = true + val contentIdAndOrderList = contentList.mapIndexed { index, item -> + PlaylistContentIdAndOrder(item.id, index + 1) + } + + val request = UpdatePlaylistRequest( + title = if (_detailResponseLiveData.value!!.title != title) { + title + } else { + null + }, + desc = if (_detailResponseLiveData.value!!.desc != desc) { + desc + } else { + null + }, + contentIdAndOrderList = contentIdAndOrderList + ) + + compositeDisposable.add( + repository.updatePlaylist( + playlistId = playlistId, + request = request, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success) { + onSuccess() + } else { + if (it.message != null) { + _toastLiveData.value = it.message + } else { + _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + } + } + }, + { + _isLoading.value = false + if (it.message != null) { + _toastLiveData.value = it.message + } else { + _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + } + } + ) + ) + } + } + + private fun validate(): Boolean { + if (title.isBlank() || title.length < 3) { + _toastLiveData.value = "제목을 3자 이상 입력하세요" + return false + } + + if (contentList.isEmpty()) { + _toastLiveData.value = "콘텐츠를 1개 이상 추가하세요" + return false + } + + return true + } + + fun getPlaylistDetail(playlistId: Long) { + this.playlistId = playlistId + _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) { + val data = it.data + _detailResponseLiveData.value = data!! + this.contentList.addAll(data.contentList) + } 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/modify/UpdatePlaylistRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/UpdatePlaylistRequest.kt new file mode 100644 index 0000000..d90cd47 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/modify/UpdatePlaylistRequest.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.audio_content.playlist.modify + +import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.audio_content.playlist.create.PlaylistContentIdAndOrder + +data class UpdatePlaylistRequest( + @SerializedName("title") val title: String? = null, + @SerializedName("desc") val desc: String? = null, + @SerializedName("contentIdAndOrderList") val contentIdAndOrderList: List = emptyList() +) 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 d7a58dc..371acf1 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 @@ -29,6 +29,7 @@ import kr.co.vividnext.sodalive.audio_content.playlist.AudioContentPlaylistListV import kr.co.vividnext.sodalive.audio_content.playlist.PlaylistApi import kr.co.vividnext.sodalive.audio_content.playlist.create.AudioContentPlaylistCreateViewModel import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistDetailViewModel +import kr.co.vividnext.sodalive.audio_content.playlist.modify.AudioContentPlaylistModifyViewModel 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 @@ -274,6 +275,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { viewModel { AudioContentPlaylistListViewModel(get()) } viewModel { AudioContentPlaylistDetailViewModel(get()) } viewModel { AudioContentPlaylistCreateViewModel(get()) } + viewModel { AudioContentPlaylistModifyViewModel(get()) } } private val repositoryModule = module { diff --git a/app/src/main/res/layout/activity_audio_content_playlist_create.xml b/app/src/main/res/layout/activity_audio_content_playlist_create.xml index 83d71ce..78c59cd 100644 --- a/app/src/main/res/layout/activity_audio_content_playlist_create.xml +++ b/app/src/main/res/layout/activity_audio_content_playlist_create.xml @@ -148,7 +148,7 @@ android:drawablePadding="3.5dp" android:fontFamily="@font/gmarket_sans_bold" android:gravity="center_vertical" - android:text="새로운 콘텐츠 추가" + android:text="새로운 콘텐츠 추가/제거" android:textColor="@color/color_3bb9f1" app:layout_constraintStart_toStartOf="@+id/et_desc" app:layout_constraintTop_toBottomOf="@+id/et_desc" /> diff --git a/app/src/main/res/layout/activity_audio_content_playlist_modify.xml b/app/src/main/res/layout/activity_audio_content_playlist_modify.xml new file mode 100644 index 0000000..8e57dfd --- /dev/null +++ b/app/src/main/res/layout/activity_audio_content_playlist_modify.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_playlist_add_content.xml b/app/src/main/res/layout/fragment_playlist_add_content.xml index 71edb53..c978552 100644 --- a/app/src/main/res/layout/fragment_playlist_add_content.xml +++ b/app/src/main/res/layout/fragment_playlist_add_content.xml @@ -17,7 +17,7 @@ android:layout_height="wrap_content" android:layout_centerInParent="true" android:fontFamily="@font/gmarket_sans_bold" - android:text="새로운 콘텐츠 추가" + android:text="새로운 콘텐츠 추가/제거" android:textColor="@color/color_eeeeee" android:textSize="18sp" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/item_playlist_add_content.xml b/app/src/main/res/layout/item_playlist_add_content.xml index 7bd0e4e..ec72e16 100644 --- a/app/src/main/res/layout/item_playlist_add_content.xml +++ b/app/src/main/res/layout/item_playlist_add_content.xml @@ -67,6 +67,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@null" + android:padding="8dp" android:src="@drawable/ic_playlist_add" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/item_playlist_modify_content.xml b/app/src/main/res/layout/item_playlist_modify_content.xml new file mode 100644 index 0000000..fdeaf50 --- /dev/null +++ b/app/src/main/res/layout/item_playlist_modify_content.xml @@ -0,0 +1,64 @@ + + + + + + + + + + +