feat: 메인 홈
- 최신 콘텐츠 UI 추가
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
package kr.co.vividnext.sodalive.home
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
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.ItemHomeContentBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
|
|
||||||
|
class HomeContentAdapter(
|
||||||
|
private val onClickItem: (Long) -> Unit,
|
||||||
|
) : RecyclerView.Adapter<HomeContentAdapter.ViewHolder>() {
|
||||||
|
private val items = mutableListOf<AudioContentMainItem>()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
) = ViewHolder(
|
||||||
|
ItemHomeContentBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: ViewHolder,
|
||||||
|
position: Int
|
||||||
|
) {
|
||||||
|
holder.bind(items[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
inner class ViewHolder(
|
||||||
|
private val binding: ItemHomeContentBinding
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
fun bind(item: AudioContentMainItem) {
|
||||||
|
binding.ivPoint.visibility = if (item.isPointAvailable) {
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.ivContentCoverImage.load(item.coverImageUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.ic_place_holder)
|
||||||
|
transformations(RoundedCornersTransformation(2.7f.dpToPx()))
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvContentTitle.text = item.title
|
||||||
|
binding.tvNickname.text = item.creatorNickname
|
||||||
|
|
||||||
|
binding.root.setOnClickListener { onClickItem(item.contentId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun addItems(items: List<AudioContentMainItem>) {
|
||||||
|
this.items.clear()
|
||||||
|
this.items.addAll(items)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package kr.co.vividnext.sodalive.home
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ItemHomeContentThemeBinding
|
||||||
|
|
||||||
|
class HomeContentThemeAdapter(
|
||||||
|
private val onClickItem: (String) -> Unit
|
||||||
|
) : RecyclerView.Adapter<HomeContentThemeAdapter.ViewHolder>() {
|
||||||
|
private val themeList = mutableListOf<String>()
|
||||||
|
private var selectedTheme = ""
|
||||||
|
|
||||||
|
inner class ViewHolder(
|
||||||
|
private val binding: ItemHomeContentThemeBinding
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun bind(theme: String) {
|
||||||
|
if (theme == selectedTheme ||
|
||||||
|
(selectedTheme == "" && theme == "전체")
|
||||||
|
) {
|
||||||
|
binding.tvTheme.setBackgroundResource(R.drawable.bg_round_corner_999_3bb9f1)
|
||||||
|
} else {
|
||||||
|
binding.tvTheme.setBackgroundResource(R.drawable.bg_round_corner_999_263238)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvTheme.text = theme
|
||||||
|
|
||||||
|
binding.root.setOnClickListener {
|
||||||
|
onClickItem(theme)
|
||||||
|
selectedTheme = theme
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun addItems(themeList: List<String>) {
|
||||||
|
this.themeList.clear()
|
||||||
|
this.themeList.addAll(themeList)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||||
|
ItemHomeContentThemeBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getItemCount() = themeList.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.bind(themeList[position])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,11 +14,14 @@ import android.widget.Toast
|
|||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.all.AudioContentNewAllActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity
|
import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
||||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
@@ -50,6 +53,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
|
|||||||
|
|
||||||
private lateinit var liveAdapter: HomeLiveAdapter
|
private lateinit var liveAdapter: HomeLiveAdapter
|
||||||
private lateinit var creatorRankingAdapter: CreatorRankingAdapter
|
private lateinit var creatorRankingAdapter: CreatorRankingAdapter
|
||||||
|
private lateinit var latestContentThemeAdapter: HomeContentThemeAdapter
|
||||||
|
|
||||||
|
private lateinit var homeContentAdapter: HomeContentAdapter
|
||||||
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
@@ -301,7 +307,111 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupLatestContent() {
|
private fun setupLatestContent() {
|
||||||
|
val spSectionTitle = SpannableString(binding.tvNewContent.text)
|
||||||
|
spSectionTitle.setSpan(
|
||||||
|
ForegroundColorSpan(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
requireContext(),
|
||||||
|
R.color.color_3bb9f1
|
||||||
|
)
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
binding.tvNewContent.text = spSectionTitle
|
||||||
|
|
||||||
|
latestContentThemeAdapter = HomeContentThemeAdapter {
|
||||||
|
viewModel.getLatestContentByTheme(theme = it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val rvTheme = binding.rvNewContentTheme
|
||||||
|
rvTheme.layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
rvTheme.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.left = 0
|
||||||
|
outRect.right = 8f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
latestContentThemeAdapter.itemCount - 1 -> {
|
||||||
|
outRect.left = 8f.dpToPx().toInt()
|
||||||
|
outRect.right = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
outRect.left = 8f.dpToPx().toInt()
|
||||||
|
outRect.right = 8f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
rvTheme.adapter = latestContentThemeAdapter
|
||||||
|
|
||||||
|
viewModel.latestContentThemeListLiveData.observe(viewLifecycleOwner) {
|
||||||
|
binding.llNewContent.visibility = View.VISIBLE
|
||||||
|
latestContentThemeAdapter.addItems(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvNewContentAll.setOnClickListener {
|
||||||
|
if (SharedPreferenceManager.token.isNotBlank()) {
|
||||||
|
startActivity(Intent(requireContext(), AudioContentNewAllActivity::class.java))
|
||||||
|
} else {
|
||||||
|
(requireActivity() as MainActivity).showLoginActivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
homeContentAdapter = HomeContentAdapter {
|
||||||
|
startActivity(
|
||||||
|
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val rvContent = binding.rvNewContent
|
||||||
|
rvContent.layoutManager = GridLayoutManager(context, 2, RecyclerView.HORIZONTAL, false)
|
||||||
|
rvContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
outRect.top = 8f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 8f.dpToPx().toInt()
|
||||||
|
|
||||||
|
val position = parent.getChildAdapterPosition(view)
|
||||||
|
|
||||||
|
if (position == 0 || position == 1) {
|
||||||
|
outRect.left = 0f.dpToPx().toInt()
|
||||||
|
} else {
|
||||||
|
outRect.left = 8f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
outRect.right = 8f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
rvContent.adapter = homeContentAdapter
|
||||||
|
|
||||||
|
viewModel.latestContentListLiveData.observe(viewLifecycleOwner) {
|
||||||
|
homeContentAdapter.addItems(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindData() {
|
private fun bindData() {
|
||||||
|
|||||||
@@ -90,7 +90,10 @@ class HomeViewModel(private val repository: HomeRepository) : BaseViewModel() {
|
|||||||
if (it.success && data != null) {
|
if (it.success && data != null) {
|
||||||
_liveListLiveData.value = data.liveList
|
_liveListLiveData.value = data.liveList
|
||||||
_creatorRankingLiveData.value = data.creatorRanking
|
_creatorRankingLiveData.value = data.creatorRanking
|
||||||
_latestContentThemeListLiveData.value = data.latestContentThemeList
|
|
||||||
|
val themeList = data.latestContentThemeList.toMutableList()
|
||||||
|
themeList.add(0, "전체")
|
||||||
|
_latestContentThemeListLiveData.value = themeList
|
||||||
_latestContentListLiveData.value = data.latestContentList
|
_latestContentListLiveData.value = data.latestContentList
|
||||||
_eventBannerListLiveData.value = data.bannerList
|
_eventBannerListLiveData.value = data.bannerList
|
||||||
_originalAudioDramaListLiveData.value = data.originalAudioDramaList
|
_originalAudioDramaListLiveData.value = data.originalAudioDramaList
|
||||||
@@ -121,7 +124,11 @@ class HomeViewModel(private val repository: HomeRepository) : BaseViewModel() {
|
|||||||
|
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
repository.getLatestContentByTheme(
|
repository.getLatestContentByTheme(
|
||||||
theme,
|
theme = if (theme == "전체") {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
theme
|
||||||
|
},
|
||||||
token = "Bearer ${SharedPreferenceManager.token}"
|
token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
)
|
)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@@ -129,6 +136,9 @@ class HomeViewModel(private val repository: HomeRepository) : BaseViewModel() {
|
|||||||
.subscribe(
|
.subscribe(
|
||||||
{
|
{
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
|
if (it.success && it.data != null) {
|
||||||
|
_latestContentListLiveData.value = it.data!!
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
|
|||||||
5
app/src/main/res/drawable/bg_round_corner_999_263238.xml
Normal file
5
app/src/main/res/drawable/bg_round_corner_999_263238.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#263238" />
|
||||||
|
<corners android:radius="999dp" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/bg_round_corner_999_3bb9f1.xml
Normal file
5
app/src/main/res/drawable/bg_round_corner_999_3bb9f1.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/color_3bb9f1" />
|
||||||
|
<corners android:radius="999dp" />
|
||||||
|
</shape>
|
||||||
@@ -130,6 +130,7 @@
|
|||||||
android:paddingHorizontal="24dp">
|
android:paddingHorizontal="24dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_new_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="@font/pretendard_bold"
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
|||||||
58
app/src/main/res/layout/item_home_content.xml
Normal file
58
app/src/main/res/layout/item_home_content.xml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?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="160dp"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_content_cover_image"
|
||||||
|
android:layout_width="160dp"
|
||||||
|
android:layout_height="160dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_point"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_point"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/iv_content_cover_image"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/iv_content_cover_image" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_content_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/iv_content_cover_image"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/iv_content_cover_image"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/iv_content_cover_image"
|
||||||
|
tools:text="빛이 나는 사람" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_nickname"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="#78909C"
|
||||||
|
android:textSize="13.3sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/tv_content_title"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/tv_content_title"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tv_content_title"
|
||||||
|
tools:text="빛이 나는 사람" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
21
app/src/main/res/layout/item_home_content_theme.xml
Normal file
21
app/src/main/res/layout/item_home_content_theme.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
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:layout_gravity="center"
|
||||||
|
android:background="@drawable/bg_round_corner_999_263238"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="자작곡" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
Reference in New Issue
Block a user