feat: 메인 라이브

- 최근 종료한 라이브 UI 추가
This commit is contained in:
2025-07-18 18:40:10 +09:00
parent 440104a7d1
commit bb23f9cf93
19 changed files with 326 additions and 13 deletions

View File

@@ -0,0 +1,13 @@
package kr.co.vividnext.sodalive.live
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class GetLatestFinishedLiveResponse(
@SerializedName("memberId") val memberId: Long,
@SerializedName("nickname") val nickname: String,
@SerializedName("profileImageUrl") val profileImageUrl: String,
@SerializedName("title") val title: String,
@SerializedName("timeAgo") val timeAgo: String
)

View File

@@ -0,0 +1,57 @@
package kr.co.vividnext.sodalive.live
import android.annotation.SuppressLint
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import kr.co.vividnext.sodalive.databinding.ItemLatestFinishedLiveBinding
class LatestFinishedLiveAdapter(
private val onClick: (Long) -> Unit
) : RecyclerView.Adapter<LatestFinishedLiveAdapter.ViewHolder>() {
private val items = mutableListOf<GetLatestFinishedLiveResponse>()
override fun onCreateViewHolder(parent: android.view.ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
parent.context,
ItemLatestFinishedLiveBinding.inflate(
android.view.LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount(): Int = items.count()
@SuppressLint("NotifyDataSetChanged")
fun addItems(items: List<GetLatestFinishedLiveResponse>) {
this.items.clear()
this.items.addAll(items)
notifyDataSetChanged()
}
inner class ViewHolder(
private val context: Context,
private val binding: ItemLatestFinishedLiveBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetLatestFinishedLiveResponse) {
Glide
.with(context)
.load(item.profileImageUrl)
.apply(RequestOptions.circleCropTransform())
.into(binding.ivProfile)
binding.tvNickname.text = item.nickname
binding.tvTimeAgo.text = item.timeAgo
binding.tvTitle.text = item.title
binding.root.setOnClickListener { onClick(item.memberId) }
}
}
}

View File

@@ -233,4 +233,9 @@ interface LiveApi {
@Path("id") id: Long,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetLiveRoomHeartListResponse>>
@GET("/live/room/latest-finished-live")
fun getLatestFinishedLive(
@Header("Authorization") authHeader: String
): Flowable<ApiResponse<List<GetLatestFinishedLiveResponse>>>
}

View File

@@ -161,6 +161,7 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
setupCommunityPost()
setupRecommendLive()
setupRecommendChannel()
setupLatestFinishedLiveChannel()
setupLiveReservation()
}
@@ -348,6 +349,60 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
}
}
private fun setupLatestFinishedLiveChannel() {
val adapter = LatestFinishedLiveAdapter {
startActivity(
Intent(
requireContext(),
UserProfileActivity::class.java
).apply {
putExtra(Constants.EXTRA_USER_ID, it)
}
)
}
val recyclerView = binding.rvLatestFinishedLiveChannel
recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
recyclerView.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()
}
liveRecommendChannelAdapter.itemCount - 1 -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 8f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = adapter
viewModel.latestFinishedLiveListLiveData.observe(viewLifecycleOwner) {
adapter.addItems(it)
}
}
@SuppressLint("NotifyDataSetChanged")
private fun setupLiveNow() {
binding

View File

@@ -261,4 +261,8 @@ class LiveRepository(
roomId,
authHeader = token
)
fun getLatestFinishedLive(token: String) = api.getLatestFinishedLive(
authHeader = token
)
}

View File

@@ -8,8 +8,14 @@ import kr.co.vividnext.sodalive.settings.event.GetEventResponse
@Keep
data class LiveSummary(
@SerializedName("liveNow") val liveNow: ApiResponse<List<GetRoomListResponse>>,
@SerializedName("liveReservation") val liveReservation: ApiResponse<List<GetRoomListResponse>>,
@SerializedName("event") val event: ApiResponse<GetEventResponse>,
@SerializedName("recommendLive") val recommendLive: ApiResponse<List<GetRecommendLiveResponse>>,
@SerializedName("liveNow")
val liveNow: ApiResponse<List<GetRoomListResponse>>,
@SerializedName("liveReservation")
val liveReservation: ApiResponse<List<GetRoomListResponse>>,
@SerializedName("event")
val event: ApiResponse<GetEventResponse>,
@SerializedName("recommendLive")
val recommendLive: ApiResponse<List<GetRecommendLiveResponse>>,
@SerializedName("latestFinishedLive")
val latestFinishedLive: ApiResponse<List<GetLatestFinishedLiveResponse>>
)

View File

@@ -61,6 +61,11 @@ class LiveViewModel(
val communityPostItemLiveData: LiveData<List<GetCommunityPostListResponse>>
get() = _communityPostItemLiveData
private val _latestFinishedLiveListLiveData =
MutableLiveData<List<GetLatestFinishedLiveResponse>>()
val latestFinishedLiveListLiveData: LiveData<List<GetLatestFinishedLiveResponse>>
get() = _latestFinishedLiveListLiveData
var page = 1
var isLast = false
private val pageSize = 10
@@ -157,6 +162,10 @@ class LiveViewModel(
token = "Bearer ${SharedPreferenceManager.token}"
)
val latestFinishedLive = repository.getLatestFinishedLive(
token = "Bearer ${SharedPreferenceManager.token}"
)
_isLoading.postValue(true)
compositeDisposable.add(
@@ -165,7 +174,9 @@ class LiveViewModel(
liveReservation,
event,
recommendLive,
) { t1, t2, t3, t4 -> LiveSummary(t1, t2, t3, t4) }
latestFinishedLive
) { t1, t2, t3, t4, t5 -> LiveSummary(t1, t2, t3, t4, t5) }
.subscribeOn(Schedulers.io())
.subscribe(
{
val now = it.liveNow
@@ -223,6 +234,17 @@ class LiveViewModel(
_recommendLiveData.postValue(emptyList())
}
val latestFinishedLiveResponse = it.latestFinishedLive
if (
latestFinishedLiveResponse.success &&
latestFinishedLiveResponse.data != null
) {
val data = latestFinishedLiveResponse.data!!
_latestFinishedLiveListLiveData.postValue(data)
} else {
_latestFinishedLiveListLiveData.postValue(emptyList())
}
_isLoading.postValue(false)
},
{