Use a ViewModel for the CommentsSheet

This commit is contained in:
Bnyro 2022-12-26 15:55:14 +01:00
parent 39a21ddab4
commit 0045e41902
4 changed files with 106 additions and 79 deletions

View File

@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class CommentsPage( data class CommentsPage(
val comments: MutableList<Comment> = arrayListOf(), var comments: MutableList<Comment> = arrayListOf(),
val disabled: Boolean? = null, val disabled: Boolean? = null,
val nextpage: String? = null val nextpage: String? = null
) )

View File

@ -36,7 +36,6 @@ import com.github.libretube.R
import com.github.libretube.api.CronetHelper import com.github.libretube.api.CronetHelper
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.ChapterSegment import com.github.libretube.api.obj.ChapterSegment
import com.github.libretube.api.obj.Comment
import com.github.libretube.api.obj.PipedStream import com.github.libretube.api.obj.PipedStream
import com.github.libretube.api.obj.Segment import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.SegmentData import com.github.libretube.api.obj.SegmentData
@ -76,6 +75,7 @@ import com.github.libretube.ui.extensions.setFormattedHtml
import com.github.libretube.ui.extensions.setInvisible import com.github.libretube.ui.extensions.setInvisible
import com.github.libretube.ui.extensions.setupSubscriptionButton import com.github.libretube.ui.extensions.setupSubscriptionButton
import com.github.libretube.ui.interfaces.OnlinePlayerOptions import com.github.libretube.ui.interfaces.OnlinePlayerOptions
import com.github.libretube.ui.models.CommentsViewModel
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.ui.sheets.BaseBottomSheet import com.github.libretube.ui.sheets.BaseBottomSheet
import com.github.libretube.ui.sheets.CommentsSheet import com.github.libretube.ui.sheets.CommentsSheet
@ -124,6 +124,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
private lateinit var doubleTapOverlayBinding: DoubleTapOverlayBinding private lateinit var doubleTapOverlayBinding: DoubleTapOverlayBinding
private lateinit var playerGestureControlsViewBinding: PlayerGestureControlsViewBinding private lateinit var playerGestureControlsViewBinding: PlayerGestureControlsViewBinding
private val viewModel: PlayerViewModel by activityViewModels() private val viewModel: PlayerViewModel by activityViewModels()
private val commentsViewModel: CommentsViewModel by activityViewModels()
/** /**
* video information * video information
@ -152,8 +153,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
* Chapters and comments * Chapters and comments
*/ */
private lateinit var chapters: List<ChapterSegment> private lateinit var chapters: List<ChapterSegment>
private val comments: MutableList<Comment> = mutableListOf()
private var commentsNextPage: String? = null
/** /**
* for the player view * for the player view
@ -330,15 +329,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
} }
binding.commentsToggle.setOnClickListener { binding.commentsToggle.setOnClickListener {
CommentsSheet( videoId ?: return@setOnClickListener
videoId!!, // set the max height to not cover the currently playing video
comments, commentsViewModel.maxHeight = binding.root.height - binding.player.height
commentsNextPage, commentsViewModel.videoId = videoId
binding.root.height - binding.player.height CommentsSheet().show(childFragmentManager)
) { comments, nextPage ->
this.comments.addAll(comments)
this.commentsNextPage = nextPage
}.show(childFragmentManager)
} }
playerBinding.queueToggle.visibility = View.VISIBLE playerBinding.queueToggle.visibility = View.VISIBLE
@ -604,6 +599,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.exoProgress.clearSegments() playerBinding.exoProgress.clearSegments()
playerBinding.sbToggle.visibility = View.GONE playerBinding.sbToggle.visibility = View.GONE
// reset the comments to become reloaded later
commentsViewModel.reset()
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
streams = try { streams = try {
RetrofitInstance.api.getStreams(videoId!!) RetrofitInstance.api.getStreams(videoId!!)
@ -754,10 +752,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
if (nextVideoId != null) { if (nextVideoId != null) {
videoId = nextVideoId videoId = nextVideoId
// reset the comments to be reloaded later
comments.clear()
commentsNextPage = null
// play the next video // play the next video
playVideo() playVideo()
} }

View File

@ -0,0 +1,65 @@
package com.github.libretube.ui.models
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.CommentsPage
import com.github.libretube.extensions.TAG
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class CommentsViewModel : ViewModel() {
private val scope = CoroutineScope(Dispatchers.IO)
private var isLoading = false
val commentsPage = MutableLiveData<CommentsPage?>()
private var nextPage: String? = null
var videoId: String? = null
var maxHeight: Int = 0
fun fetchComments() {
videoId ?: return
scope.launch {
isLoading = true
val response = try {
RetrofitInstance.api.getComments(videoId!!)
} catch (e: Exception) {
Log.e(TAG(), e.toString())
return@launch
}
nextPage = response.nextpage
commentsPage.postValue(response)
isLoading = false
}
}
fun fetchNextComments() {
if (isLoading || nextPage == null || videoId == null) return
scope.launch {
isLoading = true
val response = try {
RetrofitInstance.api.getCommentsNextPage(videoId!!, nextPage!!)
} catch (e: Exception) {
Log.e(TAG(), e.toString())
return@launch
}
val updatedPage = commentsPage.value?.apply {
comments = comments.plus(response.comments).toMutableList()
}
nextPage = response.nextpage
commentsPage.postValue(updatedPage)
isLoading = false
}
}
fun reset() {
isLoading = false
nextPage = null
commentsPage.value = null
videoId = null
}
}

View File

@ -1,32 +1,22 @@
package com.github.libretube.ui.sheets package com.github.libretube.ui.sheets
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.Comment
import com.github.libretube.databinding.CommentsSheetBinding import com.github.libretube.databinding.CommentsSheetBinding
import com.github.libretube.extensions.TAG
import com.github.libretube.ui.adapters.CommentsAdapter import com.github.libretube.ui.adapters.CommentsAdapter
import kotlinx.coroutines.Dispatchers import com.github.libretube.ui.models.CommentsViewModel
import kotlinx.coroutines.withContext
class CommentsSheet( class CommentsSheet : ExpandedBottomSheet() {
private val videoId: String,
private val comments: List<Comment>,
private var nextPage: String?,
private val maxHeight: Int,
private val onMoreComments: (comments: List<Comment>, nextPage: String?) -> Unit
) : ExpandedBottomSheet() {
private lateinit var binding: CommentsSheetBinding private lateinit var binding: CommentsSheetBinding
private lateinit var commentsAdapter: CommentsAdapter private lateinit var commentsAdapter: CommentsAdapter
private var isLoading = false
private val viewModel: CommentsViewModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -35,7 +25,7 @@ class CommentsSheet(
): View { ): View {
binding = CommentsSheetBinding.inflate(layoutInflater) binding = CommentsSheetBinding.inflate(layoutInflater)
// set a fixed maximum height // set a fixed maximum height
binding.root.maxHeight = maxHeight binding.root.maxHeight = viewModel.maxHeight
return binding.root return binding.root
} }
@ -48,62 +38,40 @@ class CommentsSheet(
binding.commentsRV.viewTreeObserver binding.commentsRV.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (!binding.commentsRV.canScrollVertically(1)) { if (!binding.commentsRV.canScrollVertically(1)) {
fetchNextComments() viewModel.fetchNextComments()
} }
} }
commentsAdapter = CommentsAdapter(videoId, comments.toMutableList()) { commentsAdapter = CommentsAdapter(
viewModel.videoId!!,
viewModel.commentsPage.value?.comments.orEmpty().toMutableList()
) {
dialog?.dismiss() dialog?.dismiss()
} }
binding.commentsRV.adapter = commentsAdapter binding.commentsRV.adapter = commentsAdapter
if (comments.isEmpty()) fetchComments() if (viewModel.commentsPage.value?.comments.orEmpty().isEmpty()) {
} binding.progress.visibility = View.VISIBLE
viewModel.fetchComments()
private fun fetchComments() {
binding.progress.visibility = View.VISIBLE
lifecycleScope.launchWhenCreated {
isLoading = true
val response = try {
RetrofitInstance.api.getComments(videoId)
} catch (e: Exception) {
return@launchWhenCreated
}
binding.progress.visibility = View.GONE
if (response.disabled == true) {
withContext(Dispatchers.Main) {
binding.errorTV.visibility = View.VISIBLE
}
return@launchWhenCreated
}
if (response.comments.isEmpty()) {
withContext(Dispatchers.Main) {
binding.errorTV.text = getString(R.string.no_comments_available)
binding.errorTV.visibility = View.VISIBLE
}
return@launchWhenCreated
}
commentsAdapter.updateItems(response.comments)
nextPage = response.nextpage
onMoreComments.invoke(response.comments, response.nextpage)
isLoading = false
} }
}
private fun fetchNextComments() { // listen for new comments to be loaded
if (isLoading || nextPage == null) return viewModel.commentsPage.observe(viewLifecycleOwner) {
lifecycleScope.launchWhenCreated { it ?: return@observe
isLoading = true binding.progress.visibility = View.GONE
val response = try { if (it.disabled == true) {
RetrofitInstance.api.getCommentsNextPage(videoId, nextPage!!) binding.errorTV.visibility = View.VISIBLE
} catch (e: Exception) { return@observe
Log.e(TAG(), e.toString())
return@launchWhenCreated
} }
nextPage = response.nextpage if (it.comments.isEmpty()) {
commentsAdapter.updateItems(response.comments) binding.errorTV.text = getString(R.string.no_comments_available)
onMoreComments.invoke(response.comments, response.nextpage) binding.errorTV.visibility = View.VISIBLE
isLoading = false return@observe
}
commentsAdapter.updateItems(
// only add the new comments to the recycler view
it.comments.subList(commentsAdapter.itemCount, it.comments.size)
)
} }
} }
} }