diff --git a/app/src/main/java/com/github/libretube/api/obj/CommentsPage.kt b/app/src/main/java/com/github/libretube/api/obj/CommentsPage.kt index cc0b4ad66..88bb66a1b 100644 --- a/app/src/main/java/com/github/libretube/api/obj/CommentsPage.kt +++ b/app/src/main/java/com/github/libretube/api/obj/CommentsPage.kt @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties @JsonIgnoreProperties(ignoreUnknown = true) data class CommentsPage( - val comments: MutableList = arrayListOf(), + var comments: MutableList = arrayListOf(), val disabled: Boolean? = null, val nextpage: String? = null ) diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index 0c86be3e7..04d3fc591 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -36,7 +36,6 @@ import com.github.libretube.R import com.github.libretube.api.CronetHelper import com.github.libretube.api.RetrofitInstance 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.Segment 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.setupSubscriptionButton 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.sheets.BaseBottomSheet import com.github.libretube.ui.sheets.CommentsSheet @@ -124,6 +124,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { private lateinit var doubleTapOverlayBinding: DoubleTapOverlayBinding private lateinit var playerGestureControlsViewBinding: PlayerGestureControlsViewBinding private val viewModel: PlayerViewModel by activityViewModels() + private val commentsViewModel: CommentsViewModel by activityViewModels() /** * video information @@ -152,8 +153,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { * Chapters and comments */ private lateinit var chapters: List - private val comments: MutableList = mutableListOf() - private var commentsNextPage: String? = null /** * for the player view @@ -330,15 +329,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } binding.commentsToggle.setOnClickListener { - CommentsSheet( - videoId!!, - comments, - commentsNextPage, - binding.root.height - binding.player.height - ) { comments, nextPage -> - this.comments.addAll(comments) - this.commentsNextPage = nextPage - }.show(childFragmentManager) + videoId ?: return@setOnClickListener + // set the max height to not cover the currently playing video + commentsViewModel.maxHeight = binding.root.height - binding.player.height + commentsViewModel.videoId = videoId + CommentsSheet().show(childFragmentManager) } playerBinding.queueToggle.visibility = View.VISIBLE @@ -604,6 +599,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { playerBinding.exoProgress.clearSegments() playerBinding.sbToggle.visibility = View.GONE + // reset the comments to become reloaded later + commentsViewModel.reset() + lifecycleScope.launchWhenCreated { streams = try { RetrofitInstance.api.getStreams(videoId!!) @@ -754,10 +752,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { if (nextVideoId != null) { videoId = nextVideoId - // reset the comments to be reloaded later - comments.clear() - commentsNextPage = null - // play the next video playVideo() } diff --git a/app/src/main/java/com/github/libretube/ui/models/CommentsViewModel.kt b/app/src/main/java/com/github/libretube/ui/models/CommentsViewModel.kt new file mode 100644 index 000000000..b8f4246b2 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/models/CommentsViewModel.kt @@ -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() + + 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 + } +} diff --git a/app/src/main/java/com/github/libretube/ui/sheets/CommentsSheet.kt b/app/src/main/java/com/github/libretube/ui/sheets/CommentsSheet.kt index 8935c644b..c3afd6ea9 100644 --- a/app/src/main/java/com/github/libretube/ui/sheets/CommentsSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/sheets/CommentsSheet.kt @@ -1,32 +1,22 @@ package com.github.libretube.ui.sheets import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.lifecycle.lifecycleScope +import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.LinearLayoutManager 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.extensions.TAG import com.github.libretube.ui.adapters.CommentsAdapter -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import com.github.libretube.ui.models.CommentsViewModel -class CommentsSheet( - private val videoId: String, - private val comments: List, - private var nextPage: String?, - private val maxHeight: Int, - private val onMoreComments: (comments: List, nextPage: String?) -> Unit -) : ExpandedBottomSheet() { +class CommentsSheet : ExpandedBottomSheet() { private lateinit var binding: CommentsSheetBinding private lateinit var commentsAdapter: CommentsAdapter - private var isLoading = false + + private val viewModel: CommentsViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, @@ -35,7 +25,7 @@ class CommentsSheet( ): View { binding = CommentsSheetBinding.inflate(layoutInflater) // set a fixed maximum height - binding.root.maxHeight = maxHeight + binding.root.maxHeight = viewModel.maxHeight return binding.root } @@ -48,62 +38,40 @@ class CommentsSheet( binding.commentsRV.viewTreeObserver .addOnScrollChangedListener { 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() } binding.commentsRV.adapter = commentsAdapter - if (comments.isEmpty()) 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 + if (viewModel.commentsPage.value?.comments.orEmpty().isEmpty()) { + binding.progress.visibility = View.VISIBLE + viewModel.fetchComments() } - } - private fun fetchNextComments() { - if (isLoading || nextPage == null) return - lifecycleScope.launchWhenCreated { - isLoading = true - val response = try { - RetrofitInstance.api.getCommentsNextPage(videoId, nextPage!!) - } catch (e: Exception) { - Log.e(TAG(), e.toString()) - return@launchWhenCreated + // listen for new comments to be loaded + viewModel.commentsPage.observe(viewLifecycleOwner) { + it ?: return@observe + binding.progress.visibility = View.GONE + if (it.disabled == true) { + binding.errorTV.visibility = View.VISIBLE + return@observe } - nextPage = response.nextpage - commentsAdapter.updateItems(response.comments) - onMoreComments.invoke(response.comments, response.nextpage) - isLoading = false + if (it.comments.isEmpty()) { + binding.errorTV.text = getString(R.string.no_comments_available) + binding.errorTV.visibility = View.VISIBLE + return@observe + } + commentsAdapter.updateItems( + // only add the new comments to the recycler view + it.comments.subList(commentsAdapter.itemCount, it.comments.size) + ) } } }