From 46b423e3e60957d9403e3f067babb4542d6afddc Mon Sep 17 00:00:00 2001 From: klaus Date: Tue, 26 Sep 2023 22:04:41 +0900 Subject: [PATCH] =?UTF-8?q?=ED=81=90=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=A0=84=EC=B2=B4=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../sodalive/audio_content/AudioContentApi.kt | 10 + .../audio_content/AudioContentRepository.kt | 14 ++ .../curation/AudioContentCurationActivity.kt | 199 ++++++++++++++++++ .../curation/AudioContentCurationViewModel.kt | 86 ++++++++ .../curation/GetCurationContentResponse.kt | 9 + .../main/AudioContentMainCurationAdapter.kt | 2 + .../main/AudioContentMainFragment.kt | 10 + .../main/GetAudioContentMainResponse.kt | 1 + .../co/vividnext/sodalive/common/Constants.kt | 2 + .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 2 + .../activity_audio_content_curation.xml | 100 +++++++++ 12 files changed, 436 insertions(+) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/AudioContentCurationActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/AudioContentCurationViewModel.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/GetCurationContentResponse.kt create mode 100644 app/src/main/res/layout/activity_audio_content_curation.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 082eaa5..09b08bb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,6 +115,7 @@ + > + + @GET("/audio-content/curation/{id}") + fun getAudioContentListByCurationId( + @Path("id") id: Long, + @Query("page") page: Int, + @Query("size") size: Int, + @Query("sort-type") sort: AudioContentViewModel.Sort, + @Header("Authorization") authHeader: String + ): Single> } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt index 726edc4..bb25cfe 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt @@ -14,6 +14,20 @@ class AudioContentRepository( private val api: AudioContentApi, private val userApi: UserApi ) { + fun getAudioContentListByCurationId( + curationId: Long, + page: Int, + size: Int, + sort: AudioContentViewModel.Sort = AudioContentViewModel.Sort.NEWEST, + token: String + ) = api.getAudioContentListByCurationId( + id = curationId, + page = page - 1, + size = size, + sort = sort, + authHeader = token + ) + fun getAudioContentList( id: Long, page: Int, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/AudioContentCurationActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/AudioContentCurationActivity.kt new file mode 100644 index 0000000..48a4c4c --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/AudioContentCurationActivity.kt @@ -0,0 +1,199 @@ +package kr.co.vividnext.sodalive.audio_content.curation + +import android.annotation.SuppressLint +import android.content.Intent +import android.graphics.Rect +import android.os.Bundle +import android.view.View +import android.widget.TextView +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.audio_content.AudioContentAdapter +import kr.co.vividnext.sodalive.audio_content.AudioContentViewModel +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.ActivityAudioContentCurationBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import org.koin.android.ext.android.inject + +class AudioContentCurationActivity : BaseActivity( + ActivityAudioContentCurationBinding::inflate +) { + + private val viewModel: AudioContentCurationViewModel by inject() + + private lateinit var loadingDialog: LoadingDialog + private lateinit var adapter: AudioContentAdapter + + private var curationId: Long = 0 + private lateinit var title: String + + override fun onCreate(savedInstanceState: Bundle?) { + title = intent.getStringExtra(Constants.EXTRA_AUDIO_CONTENT_CURATION_TITLE) ?: "" + curationId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_CURATION_ID, 0) + super.onCreate(savedInstanceState) + + if (title.isBlank() || curationId <= 0) { + Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show() + finish() + } + + bindData() + viewModel.getContentList(curationId = curationId) + } + + override fun setupView() { + loadingDialog = LoadingDialog(this, layoutInflater) + binding.toolbar.tvBack.text = title + binding.toolbar.tvBack.setOnClickListener { finish() } + + adapter = AudioContentAdapter { + startActivity( + Intent(applicationContext, AudioContentDetailActivity::class.java).apply { + putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it) + } + ) + } + + binding.rvCuration.layoutManager = LinearLayoutManager( + applicationContext, + LinearLayoutManager.VERTICAL, + false + ) + + binding.rvCuration.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + when (parent.getChildAdapterPosition(view)) { + 0 -> { + outRect.top = 40f.dpToPx().toInt() + outRect.bottom = 20f.dpToPx().toInt() + } + + adapter.itemCount - 1 -> { + outRect.top = 20f.dpToPx().toInt() + outRect.bottom = 40f.dpToPx().toInt() + } + + else -> { + outRect.top = 20f.dpToPx().toInt() + outRect.bottom = 20f.dpToPx().toInt() + } + } + + outRect.left = 13.3f.dpToPx().toInt() + outRect.right = 13.3f.dpToPx().toInt() + } + }) + + binding.rvCuration.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.getContentList(curationId) + } + } + }) + + binding.rvCuration.adapter = adapter + + binding.tvSortNewest.setOnClickListener { + viewModel.changeSort(AudioContentViewModel.Sort.NEWEST) + } + + binding.tvSortPriceLow.setOnClickListener { + viewModel.changeSort(AudioContentViewModel.Sort.PRICE_LOW) + } + + binding.tvSortPriceHigh.setOnClickListener { + viewModel.changeSort(AudioContentViewModel.Sort.PRICE_HIGH) + } + } + + @SuppressLint("NotifyDataSetChanged") + 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.contentListLiveData.observe(this) { + if (viewModel.page - 1 == 1) { + adapter.items.clear() + binding.rvCuration.scrollToPosition(0) + } + + binding.tvTotalCount.text = "${it.totalCount}" + + adapter.items.addAll(it.items) + adapter.notifyDataSetChanged() + } + + viewModel.sort.observe(this) { + deselectSort() + selectSort( + when (it) { + AudioContentViewModel.Sort.PRICE_HIGH -> { + binding.tvSortPriceHigh + } + + AudioContentViewModel.Sort.PRICE_LOW -> { + binding.tvSortPriceLow + } + + else -> { + binding.tvSortNewest + } + } + ) + + viewModel.getContentList(curationId = curationId) + } + } + + private fun deselectSort() { + val color = ContextCompat.getColor( + applicationContext, + R.color.color_88e2e2e2 + ) + + binding.tvSortNewest.setTextColor(color) + binding.tvSortPriceLow.setTextColor(color) + binding.tvSortPriceHigh.setTextColor(color) + } + + private fun selectSort(view: TextView) { + view.setTextColor( + ContextCompat.getColor( + applicationContext, + R.color.color_e2e2e2 + ) + ) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/AudioContentCurationViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/AudioContentCurationViewModel.kt new file mode 100644 index 0000000..c8f5c75 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/AudioContentCurationViewModel.kt @@ -0,0 +1,86 @@ +package kr.co.vividnext.sodalive.audio_content.curation + +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.AudioContentRepository +import kr.co.vividnext.sodalive.audio_content.AudioContentViewModel +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager + +class AudioContentCurationViewModel( + private val repository: AudioContentRepository +) : BaseViewModel() { + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private var _contentListLiveData = MutableLiveData() + val contentListLiveData: LiveData + get() = _contentListLiveData + + private val _sort = MutableLiveData(AudioContentViewModel.Sort.NEWEST) + val sort: LiveData + get() = _sort + + private var isLast = false + var page = 1 + private val size = 10 + + fun getContentList(curationId: Long) { + if (!_isLoading.value!! && !isLast) { + _isLoading.value = true + + compositeDisposable.add( + repository.getAudioContentListByCurationId( + curationId = curationId, + page = page, + size = size, + sort = _sort.value!!, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + if (it.data.items.isNotEmpty()) { + page += 1 + _contentListLiveData.postValue(it.data!!) + } else { + isLast = true + } + } 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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + } + + fun changeSort(sort: AudioContentViewModel.Sort) { + page = 1 + isLast = false + _sort.postValue(sort) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/GetCurationContentResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/GetCurationContentResponse.kt new file mode 100644 index 0000000..125fc5c --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/curation/GetCurationContentResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.audio_content.curation + +import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListItem + +data class GetCurationContentResponse( + @SerializedName("totalCount") val totalCount: Int, + @SerializedName("items") val items: List +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainCurationAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainCurationAdapter.kt index 34b3883..54012ae 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainCurationAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainCurationAdapter.kt @@ -14,6 +14,7 @@ import kr.co.vividnext.sodalive.extensions.dpToPx class AudioContentMainCurationAdapter( private val onClickItem: (Long) -> Unit, private val onClickCreator: (Long) -> Unit, + private val onClickCurationMore: (Long, String) -> Unit ) : RecyclerView.Adapter() { private val items = mutableListOf() @@ -25,6 +26,7 @@ class AudioContentMainCurationAdapter( fun bind(item: GetAudioContentCurationResponse) { binding.tvTitle.text = item.title binding.tvDesc.text = item.description + binding.ivAll.setOnClickListener { onClickCurationMore(item.curationId, item.title) } setAudioContentList(item.audioContents) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainFragment.kt index 5675a0a..38f0f16 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/AudioContentMainFragment.kt @@ -18,6 +18,7 @@ import com.zhpan.indicator.enums.IndicatorSlideMode import com.zhpan.indicator.enums.IndicatorStyle import kr.co.pointclick.sdk.offerwall.core.PointClickAd import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.audio_content.curation.AudioContentCurationActivity import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListActivity import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity @@ -387,6 +388,15 @@ class AudioContentMainFragment : BaseFragment( putExtra(Constants.EXTRA_USER_ID, it) } ) + }, + + onClickCurationMore = { curationId, title -> + startActivity( + Intent(requireContext(), AudioContentCurationActivity::class.java).apply { + putExtra(Constants.EXTRA_AUDIO_CONTENT_CURATION_ID, curationId) + putExtra(Constants.EXTRA_AUDIO_CONTENT_CURATION_TITLE, title) + } + ) } ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/GetAudioContentMainResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/GetAudioContentMainResponse.kt index 3394576..2781910 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/GetAudioContentMainResponse.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/main/GetAudioContentMainResponse.kt @@ -30,6 +30,7 @@ data class GetAudioContentMainItem( ) data class GetAudioContentCurationResponse( + @SerializedName("curationId") val curationId: Long, @SerializedName("title") val title: String, @SerializedName("description") val description: String, @SerializedName("contents") val audioContents: List 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 6eb7224..015812b 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 @@ -46,6 +46,8 @@ object Constants { const val EXTRA_AUDIO_CONTENT_COMMENT = "audio_content_comment" const val EXTRA_AUDIO_CONTENT_LOADING = "audio_content_loading" const val EXTRA_AUDIO_CONTENT_CREATOR_ID = "audio_content_creator_id" + const val EXTRA_AUDIO_CONTENT_CURATION_ID = "extra_audio_content_curation_id" + const val EXTRA_AUDIO_CONTENT_CURATION_TITLE = "extra_audio_content_curation_title" const val EXTRA_AUDIO_CONTENT_NEXT_ACTION = "audio_content_next_action" const val EXTRA_AUDIO_CONTENT_ALERT_PREVIEW = "audio_content_alert_preview" const val EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL = "audio_content_cover_image_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 37c62c0..6bf9cc7 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 @@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingRepository import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentListViewModel import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentReplyViewModel import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentRepository +import kr.co.vividnext.sodalive.audio_content.curation.AudioContentCurationViewModel import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailViewModel import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainViewModel import kr.co.vividnext.sodalive.audio_content.modify.AudioContentModifyViewModel @@ -191,6 +192,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { viewModel { NicknameUpdateViewModel(get()) } viewModel { MemberTagViewModel(get()) } viewModel { UserProfileDonationAllViewModel(get()) } + viewModel { AudioContentCurationViewModel(get()) } } private val repositoryModule = module { diff --git a/app/src/main/res/layout/activity_audio_content_curation.xml b/app/src/main/res/layout/activity_audio_content_curation.xml new file mode 100644 index 0000000..a260fa1 --- /dev/null +++ b/app/src/main/res/layout/activity_audio_content_curation.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +