diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentActivity.kt index d66eb47..399128f 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentActivity.kt @@ -14,6 +14,8 @@ 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.category.AudioContentCategoryAdapter +import kr.co.vividnext.sodalive.audio_content.category.GetCategoryListResponse import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity import kr.co.vividnext.sodalive.base.BaseActivity @@ -32,6 +34,7 @@ class AudioContentActivity : BaseActivity( private lateinit var loadingDialog: LoadingDialog private lateinit var audioContentAdapter: AudioContentAdapter + private lateinit var categoryAdapter: AudioContentCategoryAdapter private var userId: Long = 0 private lateinit var activityResultLauncher: ActivityResultLauncher @@ -55,6 +58,7 @@ class AudioContentActivity : BaseActivity( } bindData() + viewModel.getCategoryList(userId = userId) viewModel.getAudioContentList(userId = userId) { finish() } } @@ -71,6 +75,60 @@ class AudioContentActivity : BaseActivity( activityResultLauncher.launch(intent) } + categoryAdapter = AudioContentCategoryAdapter { + viewModel.selectCategory(it, userId = userId) + } + + binding.rvCategory.layoutManager = LinearLayoutManager( + applicationContext, + LinearLayoutManager.HORIZONTAL, + false + ) + + binding.rvCategory.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)) { + categoryAdapter.itemCount - 1 -> { + outRect.right = 0 + } + + else -> { + outRect.right = 13.3f.dpToPx().toInt() + } + } + } + }) + + binding.rvCategory.adapter = categoryAdapter + + binding.rvAudioContent.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)) { + audioContentAdapter.itemCount - 1 -> { + outRect.bottom = 0 + } + + else -> { + outRect.bottom = 13.3f.dpToPx().toInt() + } + } + } + }) + binding.rvAudioContent.layoutManager = LinearLayoutManager( applicationContext, LinearLayoutManager.VERTICAL, @@ -189,6 +247,17 @@ class AudioContentActivity : BaseActivity( ) viewModel.getAudioContentList(userId = userId) { finish() } } + + viewModel.categoryListLiveData.observe(this) { + if (it.isNotEmpty()) { + binding.rvCategory.visibility = View.VISIBLE + val items = it as MutableList + items.add(0, GetCategoryListResponse(0, "전체")) + categoryAdapter.addItems(items = items) + } else { + binding.rvCategory.visibility = View.GONE + } + } } private fun deselectSort() { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt index 57f77cf..10e4755 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt @@ -37,6 +37,7 @@ interface AudioContentApi { @GET("/audio-content") fun getAudioContentList( @Query("creator-id") id: Long, + @Query("category-id") categoryId: Long, @Query("page") page: Int, @Query("size") size: Int, @Query("sort-type") sort: AudioContentViewModel.Sort, 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 7536911..278e5da 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 @@ -1,5 +1,6 @@ package kr.co.vividnext.sodalive.audio_content +import kr.co.vividnext.sodalive.audio_content.category.CategoryApi import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeRequest import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationRequest import kr.co.vividnext.sodalive.audio_content.order.OrderRequest @@ -12,7 +13,8 @@ import java.util.TimeZone class AudioContentRepository( private val api: AudioContentApi, - private val userApi: UserApi + private val userApi: UserApi, + private val categoryApi: CategoryApi ) { fun getAudioContentListByCurationId( curationId: Long, @@ -30,12 +32,14 @@ class AudioContentRepository( fun getAudioContentList( id: Long, + categoryId: Long, page: Int, size: Int, sort: AudioContentViewModel.Sort, token: String ) = api.getAudioContentList( id = id, + categoryId = categoryId, page = page - 1, size = size, sort = sort, @@ -197,4 +201,9 @@ class AudioContentRepository( audioContentId: Long, token: String ) = api.unpinContent(audioContentId, authHeader = token) + + fun getCategoryList( + creatorId: Long, + token: String + ) = categoryApi.getCategoryList(creatorId, authHeader = token) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentViewModel.kt index feaeeba..186fd6a 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentViewModel.kt @@ -6,6 +6,7 @@ import com.google.gson.annotations.SerializedName 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.category.GetCategoryListResponse import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListResponse @@ -24,6 +25,10 @@ class AudioContentViewModel(private val repository: AudioContentRepository) : Ba val audioContentListLiveData: LiveData get() = _audioContentListLiveData + private var _categoryListLiveData = MutableLiveData>() + val categoryListLiveData: LiveData> + get() = _categoryListLiveData + private val _sort = MutableLiveData(Sort.NEWEST) val sort: LiveData get() = _sort @@ -42,6 +47,7 @@ class AudioContentViewModel(private val repository: AudioContentRepository) : Ba var isLast = false var page = 1 private val size = 10 + private var selectedCategoryId = 0L fun getAudioContentList(userId: Long, onFailure: (() -> Unit)? = null) { if (!_isLoading.value!! && !isLast) { @@ -49,6 +55,7 @@ class AudioContentViewModel(private val repository: AudioContentRepository) : Ba compositeDisposable.add( repository.getAudioContentList( id = userId, + categoryId = selectedCategoryId, page = page, size = size, token = "Bearer ${SharedPreferenceManager.token}", @@ -59,12 +66,12 @@ class AudioContentViewModel(private val repository: AudioContentRepository) : Ba .subscribe( { if (it.success && it.data != null) { - if (it.data.items.isNotEmpty()) { - page += 1 - _audioContentListLiveData.postValue(it.data!!) - } else { + if (it.data.items.isEmpty()) { isLast = true } + + page += 1 + _audioContentListLiveData.postValue(it.data!!) } else { if (it.message != null) { _toastLiveData.postValue(it.message) @@ -99,4 +106,44 @@ class AudioContentViewModel(private val repository: AudioContentRepository) : Ba isLast = false _sort.postValue(sort) } + + fun selectCategory(categoryId: Long, userId: Long) { + isLast = false + page = 1 + selectedCategoryId = categoryId + getAudioContentList(userId = userId) + } + + fun getCategoryList(userId: Long) { + compositeDisposable.add( + repository.getCategoryList( + creatorId = userId, + token = "Bearer ${SharedPreferenceManager.token}", + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + _categoryListLiveData.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/category/AudioContentCategoryAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/category/AudioContentCategoryAdapter.kt new file mode 100644 index 0000000..22f21f3 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/category/AudioContentCategoryAdapter.kt @@ -0,0 +1,79 @@ +package kr.co.vividnext.sodalive.audio_content.category + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.ItemContentCategoryBinding + +class AudioContentCategoryAdapter( + private val onClick: (Long) -> Unit +) : RecyclerView.Adapter() { + + private val items = mutableListOf() + private var selectedCategory = "" + + inner class ViewHolder( + private val context: Context, + private val binding: ItemContentCategoryBinding + ) : RecyclerView.ViewHolder(binding.root) { + @SuppressLint("NotifyDataSetChanged") + fun bind(item: GetCategoryListResponse) { + if ( + item.category == selectedCategory || + (selectedCategory == "" && item.category == "전체") + ) { + binding.tvCategory.setBackgroundResource( + R.drawable.bg_round_corner_16_7_transparent_3bb9f1 + ) + binding.tvCategory.setTextColor( + ContextCompat.getColor(context, R.color.color_3bb9f1) + ) + } else { + binding.tvCategory.setBackgroundResource( + R.drawable.bg_round_corner_16_7_transparent_777777 + ) + binding.tvCategory.setTextColor( + ContextCompat.getColor(context, R.color.color_777777) + ) + } + + binding.tvCategory.text = item.category + binding.root.setOnClickListener { + onClick(item.categoryId) + selectedCategory = item.category + notifyDataSetChanged() + } + } + } + + @SuppressLint("NotifyDataSetChanged") + fun addItems(items: List) { + this.selectedCategory = "" + this.items.clear() + this.items.addAll(items) + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + parent.context, + ItemContentCategoryBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun getItemCount() = items.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(items[position]) + } + + fun clear() { + this.items.clear() + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/category/CategoryApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/category/CategoryApi.kt new file mode 100644 index 0000000..90ec40b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/category/CategoryApi.kt @@ -0,0 +1,15 @@ +package kr.co.vividnext.sodalive.audio_content.category + +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.common.ApiResponse +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Query + +interface CategoryApi { + @GET("/category") + fun getCategoryList( + @Query("creatorId") creatorId: Long, + @Header("Authorization") authHeader: String + ): Single>> +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/category/GetCategoryListResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/category/GetCategoryListResponse.kt new file mode 100644 index 0000000..1ca5319 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/category/GetCategoryListResponse.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.audio_content.category + +import com.google.gson.annotations.SerializedName + +data class GetCategoryListResponse( + @SerializedName("categoryId") val categoryId: Long, + @SerializedName("category") val category: String +) 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 4ab691a..e72f6f1 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 @@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.audio_content.AudioContentViewModel import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingRepository import kr.co.vividnext.sodalive.audio_content.all.AudioContentNewAllViewModel import kr.co.vividnext.sodalive.audio_content.all.AudioContentRankingAllViewModel +import kr.co.vividnext.sodalive.audio_content.category.CategoryApi 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 @@ -162,6 +163,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { single { ApiBuilder().build(get(), MemberTagApi::class.java) } single { ApiBuilder().build(get(), RouletteApi::class.java) } single { ApiBuilder().build(get(), CreatorCommunityApi::class.java) } + single { ApiBuilder().build(get(), CategoryApi::class.java) } } private val viewModelModule = module { @@ -240,7 +242,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { factory { ExplorerRepository(get()) } factory { MessageRepository(get()) } factory { NoticeRepository(get()) } - factory { AudioContentRepository(get(), get()) } + factory { AudioContentRepository(get(), get(), get()) } factory { AudioContentCommentRepository(get()) } factory { PlaybackTrackingRepository(get()) } factory { FollowingCreatorRepository(get(), get()) } diff --git a/app/src/main/res/layout/activity_audio_content.xml b/app/src/main/res/layout/activity_audio_content.xml index 27b93b1..fd4c5ae 100644 --- a/app/src/main/res/layout/activity_audio_content.xml +++ b/app/src/main/res/layout/activity_audio_content.xml @@ -9,6 +9,15 @@ android:id="@+id/toolbar" layout="@layout/detail_toolbar" /> + + + + + +