인기 콘텐츠 전체 보기 페이지 추가

This commit is contained in:
klaus 2023-10-15 04:38:07 +09:00
parent fe1a1cc3cb
commit e6b8e55966
12 changed files with 489 additions and 1 deletions

View File

@ -117,6 +117,7 @@
<activity android:name=".mypage.profile.password.ModifyPasswordActivity" />
<activity android:name=".audio_content.curation.AudioContentCurationActivity" />
<activity android:name=".audio_content.all.AudioContentNewAllActivity" />
<activity android:name=".audio_content.all.AudioContentRankingAllActivity" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"

View File

@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeResponse
import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationRequest
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainResponse
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentRanking
import kr.co.vividnext.sodalive.audio_content.order.GetAudioContentOrderListResponse
import kr.co.vividnext.sodalive.audio_content.order.OrderRequest
import kr.co.vividnext.sodalive.audio_content.upload.theme.GetAudioContentThemeResponse
@ -168,4 +169,11 @@ interface AudioContentApi {
fun getNewContentThemeList(
@Header("Authorization") authHeader: String
): Single<ApiResponse<List<String>>>
@GET("/audio-content/ranking")
fun getContentRanking(
@Query("page") page: Int,
@Query("size") size: Int,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetAudioContentRanking>>
}

View File

@ -163,4 +163,14 @@ class AudioContentRepository(
),
authHeader = token
)
fun getContentRanking(
page: Int,
size: Int,
token: String
) = api.getContentRanking(
page = page - 1,
size = size,
authHeader = token
)
}

View File

@ -0,0 +1,131 @@
package kr.co.vividnext.sodalive.audio_content.all
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Rect
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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.ActivityAudioContentRankingAllBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject
class AudioContentRankingAllActivity : BaseActivity<ActivityAudioContentRankingAllBinding>(
ActivityAudioContentRankingAllBinding::inflate
) {
private val viewModel: AudioContentRankingAllViewModel by inject()
private lateinit var loadingDialog: LoadingDialog
private lateinit var adapter: AudioContentRankingAllAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindData()
viewModel.getAudioContentRanking()
}
override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater)
binding.toolbar.tvBack.setOnClickListener { finish() }
binding.toolbar.tvBack.text = "인기 콘텐츠"
adapter = AudioContentRankingAllAdapter {
val intent = Intent(applicationContext, AudioContentDetailActivity::class.java)
.apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
}
startActivity(intent)
}
binding.rvContentRanking.layoutManager = LinearLayoutManager(
applicationContext,
LinearLayoutManager.VERTICAL,
false
)
binding.rvContentRanking.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 = 0
outRect.bottom = 10f.dpToPx().toInt()
}
adapter.itemCount - 1 -> {
outRect.top = 10f.dpToPx().toInt()
outRect.bottom = 0
}
else -> {
outRect.top = 10f.dpToPx().toInt()
outRect.bottom = 10f.dpToPx().toInt()
}
}
}
})
binding.rvContentRanking.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.getAudioContentRanking()
}
}
})
binding.rvContentRanking.adapter = adapter
}
@SuppressLint("NotifyDataSetChanged")
private fun bindData() {
viewModel.isLoading.observe(this) {
if (it) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
}
viewModel.toastLiveData.observe(this) {
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
}
viewModel.dateStringLiveData.observe(this) {
binding.tvDate.text = it
}
viewModel.contentRankingItemsLiveData.observe(this) {
if (viewModel.page == 0) {
adapter.items.clear()
}
adapter.items.addAll(it)
adapter.notifyDataSetChanged()
}
}
}

View File

@ -0,0 +1,83 @@
package kr.co.vividnext.sodalive.audio_content.all
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.ContextCompat
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.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.databinding.ItemAudioContentRankingAllBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
class AudioContentRankingAllAdapter(
private val onItemClick: (Long) -> Unit
) : RecyclerView.Adapter<AudioContentRankingAllAdapter.ViewHolder>() {
inner class ViewHolder(
private val context: Context,
private val binding: ItemAudioContentRankingAllBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetAudioContentRankingItem, index: Int) {
binding.root.setOnClickListener { onItemClick(item.contentId) }
binding.ivCover.load(item.coverImageUrl) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
}
binding.tvTitle.text = item.title
binding.tvRank.text = index.plus(1).toString()
binding.tvTheme.text = item.themeStr
binding.tvDuration.text = item.duration
binding.tvNickname.text = item.creatorNickname
if (item.price < 1) {
binding.tvPrice.text = "무료"
binding.tvPrice.setTextColor(ContextCompat.getColor(context, R.color.white))
binding.tvPrice.setCompoundDrawables(null, null, null, null)
binding.tvPrice.setPadding(
5.3f.dpToPx().toInt(),
2.7f.dpToPx().toInt(),
5.3f.dpToPx().toInt(),
2.7f.dpToPx().toInt()
)
binding.tvPrice.setBackgroundResource(R.drawable.bg_round_corner_2_6_cf5c37)
} else {
binding.tvPrice.text = item.price.moneyFormat()
binding.tvPrice.setTextColor(ContextCompat.getColor(context, R.color.color_909090))
binding.tvPrice.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_can,
0,
0,
0
)
binding.tvPrice.setPadding(0, 0, 0, 0)
binding.tvPrice.setBackgroundResource(0)
}
}
}
val items = mutableListOf<GetAudioContentRankingItem>()
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
) = ViewHolder(
parent.context,
ItemAudioContentRankingAllBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun getItemCount() = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position], position)
}
}

View File

@ -0,0 +1,81 @@
package kr.co.vividnext.sodalive.audio_content.all
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.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class AudioContentRankingAllViewModel(
private val repository: AudioContentRepository
) : BaseViewModel() {
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
private var _dateStringLiveData = MutableLiveData<String>()
val dateStringLiveData: LiveData<String>
get() = _dateStringLiveData
private var _contentRankingItemsLiveData = MutableLiveData<List<GetAudioContentRankingItem>>()
val contentRankingItemsLiveData: LiveData<List<GetAudioContentRankingItem>>
get() = _contentRankingItemsLiveData
var page = 1
private var pageSize = 10
private var isLast = false
fun getAudioContentRanking() {
if (!_isLoading.value!! && !isLast) {
_isLoading.value = true
compositeDisposable.add(
repository.getContentRanking(
page = page,
size = pageSize,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
_isLoading.value = false
_dateStringLiveData.value =
"${it.data.startDate}~${it.data.endDate}"
if (it.data.items.isNotEmpty()) {
page += 1
isLast = false
_contentRankingItemsLiveData.value = it.data.items
} else {
isLast = true
_contentRankingItemsLiveData.value = listOf()
}
} else {
_isLoading.value = false
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
}
},
{
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}
}

View File

@ -20,6 +20,7 @@ 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.all.AudioContentNewAllActivity
import kr.co.vividnext.sodalive.audio_content.all.AudioContentRankingAllActivity
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
@ -367,7 +368,9 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
}
private fun setupContentRanking() {
binding.ivContentRankingAll.setOnClickListener {}
binding.ivContentRankingAll.setOnClickListener {
startActivity(Intent(requireContext(), AudioContentRankingAllActivity::class.java))
}
contentRankingAdapter = AudioContentMainRankingAdapter {
startActivity(

View File

@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
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.comment.AudioContentCommentListViewModel
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentReplyViewModel
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentRepository
@ -195,6 +196,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { UserProfileDonationAllViewModel(get(), get()) }
viewModel { AudioContentCurationViewModel(get()) }
viewModel { AudioContentNewAllViewModel(get()) }
viewModel { AudioContentRankingAllViewModel(get()) }
}
private val repositoryModule = module {

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_cf5c37" />
<corners android:radius="2.6dp" />
<stroke
android:width="1dp"
android:color="@color/color_cf5c37" />
</shape>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/detail_toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:background="@color/color_222222"
android:gravity="center"
android:orientation="vertical"
android:paddingVertical="8dp">
<TextView
android:id="@+id/tv_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:textColor="@color/color_eeeeee"
android:textSize="14.7sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:fontFamily="@font/gmarket_sans_light"
android:text="※ 인기 콘텐츠의 순위는 매주 업데이트됩니다."
android:textColor="@color/color_bbbbbb"
android:textSize="13.3sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_content_ranking"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingHorizontal="6.7dp"
android:paddingVertical="13.3dp" />
</LinearLayout>

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_cover"
android:layout_width="66.7dp"
android:layout_height="66.7dp"
android:contentDescription="@null"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_rank"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="11dp"
android:fontFamily="@font/gmarket_sans_bold"
android:textColor="@color/color_3bb9f1"
android:textSize="16.7sp"
app:layout_constraintBottom_toBottomOf="@+id/iv_cover"
app:layout_constraintStart_toEndOf="@+id/iv_cover"
app:layout_constraintTop_toTopOf="@+id/iv_cover"
tools:text="1" />
<LinearLayout
android:id="@+id/ll_desc"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginHorizontal="10dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/iv_cover"
app:layout_constraintEnd_toStartOf="@+id/tv_price"
app:layout_constraintStart_toEndOf="@+id/tv_rank"
app:layout_constraintTop_toTopOf="@+id/iv_cover">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_theme"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_round_corner_2_6_28312b"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center"
android:padding="2.6dp"
android:textColor="@color/color_3bac6a"
android:textSize="8sp"
tools:ignore="SmallSp"
tools:text="커버곡" />
<TextView
android:id="@+id/tv_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:background="@drawable/bg_round_corner_2_6_222222"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center"
android:padding="2.6dp"
android:textColor="@color/color_777777"
android:textSize="8sp"
tools:ignore="SmallSp"
tools:text="00:30:20" />
</LinearLayout>
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="2.7dp"
android:ellipsize="end"
android:fontFamily="@font/gmarket_sans_medium"
android:maxLines="2"
android:textColor="@color/color_777777"
android:textSize="10.7sp"
tools:ignore="SmallSp"
tools:text="sfdfdssdf" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2.6dp"
android:ellipsize="end"
android:fontFamily="@font/gmarket_sans_medium"
android:maxLines="2"
android:textColor="@color/color_d2d2d2"
android:textSize="12sp"
tools:text="안녕하세요 오늘은 커버곡을 들려드리려고 해요안녕하세요 오늘은 커버곡을 들려드리려고 해요" />
</LinearLayout>
<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="5.3dp"
android:drawablePadding="5.3dp"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center"
android:textColor="@color/color_909090"
android:textSize="12sp"
app:drawableStartCompat="@drawable/ic_can"
app:layout_constraintBottom_toBottomOf="@+id/iv_cover"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/iv_cover"
tools:text="300" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -100,4 +100,5 @@
<color name="color_13181b">#13181B</color>
<color name="color_3bb9f1">#3BB9F1</color>
<color name="color_2e6279">#2E6279</color>
<color name="color_cf5c37">#CF5C37</color>
</resources>