mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 22:30:30 +05:30
Merge pull request #2890 from faisalcodes/master
Fixes #2878 : Using multi-level comments display.
This commit is contained in:
commit
d9985e2567
@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
|
|||||||
data class Comment(
|
data class Comment(
|
||||||
val author: String,
|
val author: String,
|
||||||
val commentId: String,
|
val commentId: String,
|
||||||
val commentText: String,
|
val commentText: String?,
|
||||||
val commentedTime: String,
|
val commentedTime: String,
|
||||||
val commentorUrl: String,
|
val commentorUrl: String,
|
||||||
val repliesPage: String? = null,
|
val repliesPage: String? = null,
|
||||||
|
@ -18,4 +18,5 @@ object IntentData {
|
|||||||
const val downloading = "downloading"
|
const val downloading = "downloading"
|
||||||
const val openAudioPlayer = "openAudioPlayer"
|
const val openAudioPlayer = "openAudioPlayer"
|
||||||
const val fragmentToOpen = "fragmentToOpen"
|
const val fragmentToOpen = "fragmentToOpen"
|
||||||
|
const val replyPage = "replyPage"
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,33 @@
|
|||||||
package com.github.libretube.ui.adapters
|
package com.github.libretube.ui.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.util.Log
|
import android.os.Bundle
|
||||||
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 android.widget.Button
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.text.parseAsHtml
|
import androidx.core.text.parseAsHtml
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
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.api.obj.Comment
|
||||||
import com.github.libretube.api.obj.CommentsPage
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.CommentsRowBinding
|
import com.github.libretube.databinding.CommentsRowBinding
|
||||||
import com.github.libretube.extensions.TAG
|
|
||||||
import com.github.libretube.extensions.formatShort
|
import com.github.libretube.extensions.formatShort
|
||||||
|
import com.github.libretube.ui.fragments.CommentsRepliesFragment
|
||||||
import com.github.libretube.ui.viewholders.CommentsViewHolder
|
import com.github.libretube.ui.viewholders.CommentsViewHolder
|
||||||
import com.github.libretube.util.ClipboardHelper
|
import com.github.libretube.util.ClipboardHelper
|
||||||
import com.github.libretube.util.ImageHelper
|
import com.github.libretube.util.ImageHelper
|
||||||
import com.github.libretube.util.NavigationHelper
|
import com.github.libretube.util.NavigationHelper
|
||||||
import com.github.libretube.util.TextUtils
|
import com.github.libretube.util.TextUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class CommentsAdapter(
|
class CommentsAdapter(
|
||||||
|
private val fragment: Fragment?,
|
||||||
private val videoId: String,
|
private val videoId: String,
|
||||||
private val comments: MutableList<Comment>,
|
private val comments: MutableList<Comment>,
|
||||||
private val isRepliesAdapter: Boolean = false,
|
private val isRepliesAdapter: Boolean = false,
|
||||||
private val dismiss: () -> Unit
|
private val dismiss: () -> Unit
|
||||||
) : RecyclerView.Adapter<CommentsViewHolder>() {
|
) : RecyclerView.Adapter<CommentsViewHolder>() {
|
||||||
|
|
||||||
private var isLoading = false
|
|
||||||
private lateinit var repliesPage: CommentsPage
|
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
val size: Int = comments.size
|
val size: Int = comments.size
|
||||||
comments.clear()
|
comments.clear()
|
||||||
@ -59,15 +50,8 @@ class CommentsAdapter(
|
|||||||
override fun onBindViewHolder(holder: CommentsViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: CommentsViewHolder, position: Int) {
|
||||||
val comment = comments[position]
|
val comment = comments[position]
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
if (isRepliesAdapter) {
|
|
||||||
root.scaleX = REPLIES_ADAPTER_SCALE
|
|
||||||
root.scaleY = REPLIES_ADAPTER_SCALE
|
|
||||||
commentorImage.scaleX = REPLIES_ADAPTER_SCALE
|
|
||||||
commentorImage.scaleY = REPLIES_ADAPTER_SCALE
|
|
||||||
}
|
|
||||||
|
|
||||||
commentInfos.text = comment.author + TextUtils.SEPARATOR + comment.commentedTime
|
commentInfos.text = comment.author + TextUtils.SEPARATOR + comment.commentedTime
|
||||||
commentText.text = comment.commentText.parseAsHtml()
|
commentText.text = comment.commentText?.parseAsHtml()
|
||||||
|
|
||||||
ImageHelper.loadImage(comment.thumbnail, commentorImage)
|
ImageHelper.loadImage(comment.thumbnail, commentorImage)
|
||||||
likesTextView.text = comment.likeCount.formatShort()
|
likesTextView.text = comment.likeCount.formatShort()
|
||||||
@ -85,79 +69,31 @@ class CommentsAdapter(
|
|||||||
dismiss.invoke()
|
dismiss.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
repliesRecView.layoutManager = LinearLayoutManager(root.context)
|
|
||||||
val repliesAdapter = CommentsAdapter(videoId, mutableListOf(), true, dismiss)
|
|
||||||
repliesRecView.adapter = repliesAdapter
|
|
||||||
if (!isRepliesAdapter && comment.repliesPage != null) {
|
if (!isRepliesAdapter && comment.repliesPage != null) {
|
||||||
|
val repliesFragment = CommentsRepliesFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putString(IntentData.videoId, videoId)
|
||||||
|
putString(IntentData.replyPage, comment.repliesPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
showMoreReplies(comment.repliesPage, showMore, repliesAdapter)
|
fragment!!.parentFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.commentFragContainer, repliesFragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
root.setOnLongClickListener {
|
root.setOnLongClickListener {
|
||||||
ClipboardHelper(root.context).save(comment.commentText)
|
ClipboardHelper(root.context).save(comment.commentText ?: "")
|
||||||
Toast.makeText(root.context, R.string.copied, Toast.LENGTH_SHORT).show()
|
Toast.makeText(root.context, R.string.copied, Toast.LENGTH_SHORT).show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showMoreReplies(
|
|
||||||
nextPage: String,
|
|
||||||
showMoreBtn: Button,
|
|
||||||
repliesAdapter: CommentsAdapter
|
|
||||||
) {
|
|
||||||
when (repliesAdapter.itemCount) {
|
|
||||||
0 -> {
|
|
||||||
fetchReplies(nextPage) {
|
|
||||||
repliesAdapter.updateItems(it.comments)
|
|
||||||
if (repliesPage.nextpage == null) {
|
|
||||||
showMoreBtn.visibility = View.GONE
|
|
||||||
return@fetchReplies
|
|
||||||
}
|
|
||||||
showMoreBtn.visibility = View.VISIBLE
|
|
||||||
showMoreBtn.setOnClickListener { view ->
|
|
||||||
if (repliesPage.nextpage == null) {
|
|
||||||
view.visibility = View.GONE
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
fetchReplies(
|
|
||||||
repliesPage.nextpage!!
|
|
||||||
) {
|
|
||||||
repliesAdapter.updateItems(repliesPage.comments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
repliesAdapter.clear()
|
|
||||||
showMoreBtn.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return comments.size
|
return comments.size
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchReplies(nextPage: String, onFinished: (CommentsPage) -> Unit) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
if (isLoading) return@launch
|
|
||||||
isLoading = true
|
|
||||||
repliesPage = try {
|
|
||||||
RetrofitInstance.api.getCommentsNextPage(videoId, nextPage)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG(), "IOException, you might not have internet connection")
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
onFinished.invoke(repliesPage)
|
|
||||||
}
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val REPLIES_ADAPTER_SCALE = 0.9f
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
package com.github.libretube.ui.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.databinding.FragmentCommentsBinding
|
||||||
|
import com.github.libretube.ui.adapters.CommentsAdapter
|
||||||
|
import com.github.libretube.ui.models.CommentsViewModel
|
||||||
|
|
||||||
|
class CommentsMainFragment : Fragment() {
|
||||||
|
private lateinit var binding: FragmentCommentsBinding
|
||||||
|
private lateinit var commentsAdapter: CommentsAdapter
|
||||||
|
|
||||||
|
private val viewModel: CommentsViewModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentCommentsBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.commentsRV.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
binding.commentsRV.setItemViewCacheSize(20)
|
||||||
|
|
||||||
|
binding.commentsRV.viewTreeObserver
|
||||||
|
.addOnScrollChangedListener {
|
||||||
|
if (!binding.commentsRV.canScrollVertically(1)) {
|
||||||
|
viewModel.fetchNextComments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commentsAdapter = CommentsAdapter(
|
||||||
|
this,
|
||||||
|
viewModel.videoId!!,
|
||||||
|
viewModel.commentsPage.value?.comments.orEmpty().toMutableList()
|
||||||
|
) {
|
||||||
|
viewModel.commentsSheetDismiss?.invoke()
|
||||||
|
}
|
||||||
|
binding.commentsRV.adapter = commentsAdapter
|
||||||
|
|
||||||
|
if (viewModel.commentsPage.value?.comments.orEmpty().isEmpty()) {
|
||||||
|
binding.progress.visibility = View.VISIBLE
|
||||||
|
viewModel.fetchComments()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package com.github.libretube.ui.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.github.libretube.api.RetrofitInstance
|
||||||
|
import com.github.libretube.api.obj.CommentsPage
|
||||||
|
import com.github.libretube.constants.IntentData
|
||||||
|
import com.github.libretube.databinding.FragmentCommentsBinding
|
||||||
|
import com.github.libretube.extensions.TAG
|
||||||
|
import com.github.libretube.ui.adapters.CommentsAdapter
|
||||||
|
import com.github.libretube.ui.models.CommentsViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class CommentsRepliesFragment : Fragment() {
|
||||||
|
private lateinit var binding: FragmentCommentsBinding
|
||||||
|
private lateinit var repliesPage: CommentsPage
|
||||||
|
private lateinit var repliesAdapter: CommentsAdapter
|
||||||
|
private val viewModel: CommentsViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private var isLoading = false
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentCommentsBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
val videoId = arguments?.getString(IntentData.videoId) ?: ""
|
||||||
|
val nextPage = arguments?.getString(IntentData.replyPage) ?: ""
|
||||||
|
|
||||||
|
repliesAdapter = CommentsAdapter(null, videoId, mutableListOf(), true) {
|
||||||
|
viewModel.commentsSheetDismiss?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.commentsRV.layoutManager = LinearLayoutManager(view.context)
|
||||||
|
binding.commentsRV.adapter = repliesAdapter
|
||||||
|
|
||||||
|
binding.commentsRV.viewTreeObserver
|
||||||
|
.addOnScrollChangedListener {
|
||||||
|
if (!binding.commentsRV.canScrollVertically(1) &&
|
||||||
|
::repliesPage.isInitialized &&
|
||||||
|
repliesPage.nextpage != null
|
||||||
|
) {
|
||||||
|
fetchReplies(videoId, repliesPage.nextpage!!) {
|
||||||
|
repliesAdapter.updateItems(repliesPage.comments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadInitialReplies(videoId, nextPage, repliesAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadInitialReplies(
|
||||||
|
videoId: String,
|
||||||
|
nextPage: String,
|
||||||
|
repliesAdapter: CommentsAdapter
|
||||||
|
) {
|
||||||
|
when (repliesAdapter.itemCount) {
|
||||||
|
0 -> {
|
||||||
|
binding.progress.visibility = View.VISIBLE
|
||||||
|
fetchReplies(videoId, nextPage) {
|
||||||
|
repliesAdapter.updateItems(it.comments)
|
||||||
|
binding.progress.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
repliesAdapter.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchReplies(
|
||||||
|
videoId: String,
|
||||||
|
nextPage: String,
|
||||||
|
onFinished: (CommentsPage) -> Unit
|
||||||
|
) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
if (isLoading) return@launch
|
||||||
|
isLoading = true
|
||||||
|
repliesPage = try {
|
||||||
|
RetrofitInstance.api.getCommentsNextPage(videoId, nextPage)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG(), "IOException, you might not have internet connection")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
onFinished.invoke(repliesPage)
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ class CommentsViewModel : ViewModel() {
|
|||||||
|
|
||||||
var videoId: String? = null
|
var videoId: String? = null
|
||||||
var maxHeight: Int = 0
|
var maxHeight: Int = 0
|
||||||
|
var commentsSheetDismiss: (() -> Unit)? = null
|
||||||
|
|
||||||
fun fetchComments() {
|
fun fetchComments() {
|
||||||
videoId ?: return
|
videoId ?: return
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
package com.github.libretube.ui.sheets
|
package com.github.libretube.ui.sheets
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.KeyEvent
|
||||||
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 android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import androidx.core.view.updateLayoutParams
|
import android.view.WindowManager
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.databinding.CommentsSheetBinding
|
import com.github.libretube.databinding.CommentsSheetBinding
|
||||||
import com.github.libretube.extensions.dpToPx
|
import com.github.libretube.ui.fragments.CommentsMainFragment
|
||||||
import com.github.libretube.ui.adapters.CommentsAdapter
|
import com.github.libretube.ui.fragments.CommentsRepliesFragment
|
||||||
import com.github.libretube.ui.models.CommentsViewModel
|
import com.github.libretube.ui.models.CommentsViewModel
|
||||||
|
|
||||||
class CommentsSheet : ExpandedBottomSheet() {
|
class CommentsSheet : ExpandedBottomSheet() {
|
||||||
private lateinit var binding: CommentsSheetBinding
|
private lateinit var binding: CommentsSheetBinding
|
||||||
|
private val commentsViewModel: CommentsViewModel by activityViewModels()
|
||||||
private lateinit var commentsAdapter: CommentsAdapter
|
|
||||||
|
|
||||||
private val viewModel: CommentsViewModel by activityViewModels()
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@ -33,56 +32,83 @@ class CommentsSheet : ExpandedBottomSheet() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
binding.dragHandle.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
commentsViewModel.commentsSheetDismiss = this::dismiss
|
||||||
override fun onGlobalLayout() {
|
|
||||||
binding.dragHandle.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
binding.apply {
|
||||||
// limit the recyclerview height to not cover the video
|
dragHandle.viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||||
binding.commentsRV.updateLayoutParams {
|
ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
height = viewModel.maxHeight - (binding.dragHandle.height + 20.dpToPx().toInt())
|
override fun onGlobalLayout() {
|
||||||
|
dragHandle.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||||
|
|
||||||
|
// limit the recyclerview height to not cover the video
|
||||||
|
binding.standardBottomSheet.layoutParams =
|
||||||
|
binding.commentFragContainer.layoutParams.apply {
|
||||||
|
height = commentsViewModel.maxHeight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
|
|
||||||
binding.commentsRV.layoutManager = LinearLayoutManager(requireContext())
|
btnBack.setOnClickListener {
|
||||||
binding.commentsRV.setItemViewCacheSize(20)
|
if (childFragmentManager.backStackEntryCount > 0) {
|
||||||
|
childFragmentManager.popBackStack()
|
||||||
binding.commentsRV.viewTreeObserver
|
|
||||||
.addOnScrollChangedListener {
|
|
||||||
if (!binding.commentsRV.canScrollVertically(1)) {
|
|
||||||
viewModel.fetchNextComments()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commentsAdapter = CommentsAdapter(
|
btnClose.setOnClickListener { dismiss() }
|
||||||
viewModel.videoId!!,
|
|
||||||
viewModel.commentsPage.value?.comments.orEmpty().toMutableList()
|
|
||||||
) {
|
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
|
||||||
binding.commentsRV.adapter = commentsAdapter
|
|
||||||
|
|
||||||
if (viewModel.commentsPage.value?.comments.orEmpty().isEmpty()) {
|
|
||||||
binding.progress.visibility = View.VISIBLE
|
|
||||||
viewModel.fetchComments()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen for new comments to be loaded
|
childFragmentManager.apply {
|
||||||
viewModel.commentsPage.observe(viewLifecycleOwner) {
|
addOnBackStackChangedListener(this@CommentsSheet::onFragmentChanged)
|
||||||
it ?: return@observe
|
|
||||||
binding.progress.visibility = View.GONE
|
beginTransaction()
|
||||||
if (it.disabled == true) {
|
.replace(R.id.commentFragContainer, CommentsMainFragment())
|
||||||
binding.errorTV.visibility = View.VISIBLE
|
.runOnCommit(this@CommentsSheet::onFragmentChanged)
|
||||||
return@observe
|
.commit()
|
||||||
}
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onFragmentChanged() {
|
||||||
|
childFragmentManager.findFragmentById(R.id.commentFragContainer)?.let {
|
||||||
|
when (it) {
|
||||||
|
is CommentsRepliesFragment -> {
|
||||||
|
binding.btnBack.visibility = View.VISIBLE
|
||||||
|
binding.commentsTitle.text = getString(R.string.replies)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
binding.btnBack.visibility = View.GONE
|
||||||
|
binding.commentsTitle.text = getString(R.string.comments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
|
super.onDismiss(dialog)
|
||||||
|
commentsViewModel.commentsSheetDismiss = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val dialog = super.onCreateDialog(savedInstanceState)
|
||||||
|
|
||||||
|
dialog.apply {
|
||||||
|
setOnKeyListener { _, keyCode, _ ->
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
if (childFragmentManager.backStackEntryCount > 0) {
|
||||||
|
childFragmentManager.popBackStack()
|
||||||
|
return@setOnKeyListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@setOnKeyListener false
|
||||||
|
}
|
||||||
|
|
||||||
|
window?.let {
|
||||||
|
it.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
|
||||||
|
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||||
|
}
|
||||||
|
|
||||||
|
setCanceledOnTouchOutside(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/rounded_ripple">
|
android:background="?selectableItemBackground">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -25,9 +25,10 @@
|
|||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/commentor_image"
|
android:id="@+id/commentor_image"
|
||||||
android:layout_width="36dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="36dp"
|
android:layout_height="32dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
|
android:background="@android:color/darker_gray"
|
||||||
app:shapeAppearance="@style/CircleImageView"
|
app:shapeAppearance="@style/CircleImageView"
|
||||||
app:srcCompat="@mipmap/ic_launcher" />
|
app:srcCompat="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
@ -47,8 +48,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:textSize="15sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold"
|
android:textColor="@color/text_color_secondary"
|
||||||
tools:text="Author and Time" />
|
tools:text="Author and Time" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@ -74,7 +75,9 @@
|
|||||||
android:id="@+id/comment_text"
|
android:id="@+id/comment_text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:textSize="15sp"
|
||||||
android:autoLink="web"
|
android:autoLink="web"
|
||||||
tools:text="Comment Text" />
|
tools:text="Comment Text" />
|
||||||
|
|
||||||
@ -120,7 +123,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="6dp"
|
android:layout_marginStart="6dp"
|
||||||
tools:text="LikeCount" />
|
tools:text="ReplyCount" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@ -128,26 +131,5 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/replies_recView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@null"
|
|
||||||
android:nestedScrollingEnabled="false" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/show_more"
|
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
android:stateListAnimator="@null"
|
|
||||||
android:text="@string/show_more"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:cornerRadius="20dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@ -9,7 +10,6 @@
|
|||||||
style="@style/Widget.Material3.BottomSheet"
|
style="@style/Widget.Material3.BottomSheet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingBottom="20dp"
|
|
||||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -20,35 +20,56 @@
|
|||||||
<!-- Drag handle for accessibility -->
|
<!-- Drag handle for accessibility -->
|
||||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||||
android:id="@+id/drag_handle"
|
android:id="@+id/drag_handle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
|
app:tint="@color/drag_handle_color" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:paddingBottom="7dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/btnBack"
|
||||||
|
android:layout_width="35dp"
|
||||||
|
android:layout_height="35dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:src="?homeAsUpIndicator"
|
||||||
|
android:padding="7dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/commentsTitle"
|
||||||
|
style="@style/TextAppearance.Material3.ActionBar.Title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
tools:text="Title" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/btnClose"
|
||||||
|
android:layout_width="35dp"
|
||||||
|
android:layout_height="35dp"
|
||||||
|
android:src="@drawable/ic_close"
|
||||||
|
android:padding="7dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/commentFragContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/commentsRV"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progress"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/errorTV"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginVertical="5dp"
|
|
||||||
android:text="@string/comments_disabled"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
29
app/src/main/res/layout/fragment_comments.xml
Normal file
29
app/src/main/res/layout/fragment_comments.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/commentsRV"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="15dp"
|
||||||
|
android:clipToPadding="false" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/errorTV"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginVertical="5dp"
|
||||||
|
android:text="@string/comments_disabled"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
5
app/src/main/res/values-night/colors.xml
Normal file
5
app/src/main/res/values-night/colors.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="text_color_secondary">#BFBFBF</color>
|
||||||
|
<color name="drag_handle_color">#3A3A3A</color>
|
||||||
|
</resources>
|
@ -3,6 +3,8 @@
|
|||||||
<color name="duration_background_color">#AA000000</color>
|
<color name="duration_background_color">#AA000000</color>
|
||||||
<color name="duration_text_color">#EEFFFFFF</color>
|
<color name="duration_text_color">#EEFFFFFF</color>
|
||||||
<color name="shortcut_color">#0061A6</color>
|
<color name="shortcut_color">#0061A6</color>
|
||||||
|
<color name="text_color_secondary">#505050</color>
|
||||||
|
<color name="drag_handle_color">#CCCCCC</color>
|
||||||
|
|
||||||
<color name="blue_md_theme_light_primary">#0058CB</color>
|
<color name="blue_md_theme_light_primary">#0058CB</color>
|
||||||
<color name="blue_md_theme_light_onPrimary">#FFFFFF</color>
|
<color name="blue_md_theme_light_onPrimary">#FFFFFF</color>
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
<string name="noInternet">Connect to the Internet first.</string>
|
<string name="noInternet">Connect to the Internet first.</string>
|
||||||
<string name="retry">Retry</string>
|
<string name="retry">Retry</string>
|
||||||
<string name="comments">Comments</string>
|
<string name="comments">Comments</string>
|
||||||
|
<string name="replies">Replies</string>
|
||||||
<string name="choose_filter">Choose search filter</string>
|
<string name="choose_filter">Choose search filter</string>
|
||||||
<string name="channels">Channels</string>
|
<string name="channels">Channels</string>
|
||||||
<string name="all">All</string>
|
<string name="all">All</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user