Proper implementation for replies/likes/dislikes in the comment tab.

This commit is contained in:
Koen 2023-11-29 15:48:34 +01:00
parent c806ff2e33
commit 3387c727d1
5 changed files with 113 additions and 69 deletions

View file

@ -33,6 +33,7 @@ import com.futo.polycentric.core.PublicKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.UnknownHostException
import java.util.IdentityHashMap
class CommentsFragment : MainFragment() {
override val isMainView : Boolean = true
@ -84,6 +85,7 @@ class CommentsFragment : MainFragment() {
private var _loading = false;
private val _repliesOverlay: RepliesOverlay;
private var _repliesAnimator: ViewPropertyAnimator? = null;
private val _cache: IdentityHashMap<IPlatformComment, StatePolycentric.LikesDislikesReplies> = IdentityHashMap()
private val _taskLoadComments = if(!isInEditMode) TaskHandler<PublicKey, List<IPlatformComment>>(
StateApp.instance.scopeGetter, { StatePolycentric.instance.getSystemComments(context, it) })
@ -111,7 +113,7 @@ class CommentsFragment : MainFragment() {
childCountGetter = { _comments.size },
childViewHolderBinder = { viewHolder, position -> viewHolder.bind(_comments[position]); },
childViewHolderFactory = { viewGroup, _ ->
val holder = CommentWithReferenceViewHolder(viewGroup);
val holder = CommentWithReferenceViewHolder(viewGroup, _cache);
holder.onDelete.subscribe(::onDelete);
holder.onRepliesClick.subscribe(::onRepliesClick);
return@InsertedViewAdapterWithLoader holder;
@ -202,15 +204,21 @@ class CommentsFragment : MainFragment() {
}
if (c is PolycentricPlatformComment) {
var parentComment: PolycentricPlatformComment = c;
_repliesOverlay.load(false, metadata, c.contextUrl, c.reference, c,
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
{
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
val index = _comments.indexOf(c);
_comments[index] = newComment;
_adapterComments.notifyItemChanged(_adapterComments.childToParentPosition(index));
parentComment = newComment;
{ newComment ->
synchronized(_cache) {
_cache.remove(c)
}
val newCommentIndex = if (_spinnerSortBy.selectedItemPosition == 0) {
_comments.indexOfFirst { it.date!! < newComment.date!! }.takeIf { it != -1 } ?: _comments.size
} else {
_comments.indexOfFirst { it.date!! > newComment.date!! }.takeIf { it != -1 } ?: _comments.size
}
_comments.add(newCommentIndex, newComment)
_adapterComments.notifyItemInserted(_adapterComments.childToParentPosition(newCommentIndex))
});
} else {
_repliesOverlay.load(true, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });

View file

@ -11,17 +11,12 @@ import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder
import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes
import com.futo.platformplayer.api.media.structures.DedupContentPager
import com.futo.platformplayer.api.media.structures.EmptyPager
import com.futo.platformplayer.api.media.structures.IAsyncPager
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.api.media.structures.MultiChronoContentPager
import com.futo.platformplayer.api.media.structures.PlaceholderPager
import com.futo.platformplayer.api.media.structures.RefreshChronoContentPager
import com.futo.platformplayer.api.media.structures.RefreshDedupContentPager
import com.futo.platformplayer.api.media.structures.RefreshDistributionContentPager
import com.futo.platformplayer.awaitFirstDeferred
import com.futo.platformplayer.dp
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
@ -247,28 +242,36 @@ class StatePolycentric {
return posts
}
suspend fun getLiveComment(contextUrl: String, reference: Protocol.Reference): PolycentricPlatformComment {
val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null,
Protocol.QueryReferencesRequestEvents.newBuilder()
.setFromType(ContentType.POST.value)
.addAllCountLwwElementReferences(arrayListOf(
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
.setFromType(ContentType.OPINION.value)
.setValue(ByteString.copyFrom(Opinion.like.data))
.build(),
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
.setFromType(ContentType.OPINION.value)
.setValue(ByteString.copyFrom(Opinion.dislike.data))
.build()
))
.addCountReferences(
Protocol.QueryReferencesRequestCountReferences.newBuilder()
.setFromType(ContentType.POST.value)
.build())
.build()
)
data class LikesDislikesReplies(
var likes: Long,
var dislikes: Long,
var replyCount: Long
)
return mapQueryReferences(contextUrl, response).first()
suspend fun getLikesDislikesReplies(reference: Protocol.Reference): LikesDislikesReplies {
val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null,
null,
listOf(
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
.setFromType(ContentType.OPINION.value)
.setValue(ByteString.copyFrom(Opinion.like.data))
.build(),
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
.setFromType(ContentType.OPINION.value)
.setValue(ByteString.copyFrom(Opinion.dislike.data))
.build()
),
listOf(
Protocol.QueryReferencesRequestCountReferences.newBuilder()
.setFromType(ContentType.POST.value)
.build()
)
);
val likes = response.countsList[0];
val dislikes = response.countsList[1];
val replyCount = response.countsList[2];
return LikesDislikesReplies(likes, dislikes, replyCount)
}
suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference): IPager<IPlatformComment> {
@ -347,7 +350,6 @@ class StatePolycentric {
try {
val post = Protocol.Post.parseFrom(ev.content);
val id = ev.system.toProto().key.toByteArray().toBase64();
val likes = it.countsList[0];
val dislikes = it.countsList[1];
val replies = it.countsList[2];

View file

@ -1,5 +1,6 @@
package com.futo.platformplayer.views.adapters
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -22,6 +23,8 @@ import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
import com.futo.polycentric.core.Opinion
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import userpackage.Protocol
import java.util.IdentityHashMap
class CommentWithReferenceViewHolder : ViewHolder {
private val _creatorThumbnail: CreatorThumbnail;
@ -32,14 +35,18 @@ class CommentWithReferenceViewHolder : ViewHolder {
private val _pillRatingLikesDislikes: PillRatingLikesDislikes;
private val _layoutComment: ConstraintLayout;
private val _buttonDelete: FrameLayout;
private val _cache: IdentityHashMap<IPlatformComment, StatePolycentric.LikesDislikesReplies>;
private var _likesDislikesReplies: StatePolycentric.LikesDislikesReplies? = null;
private val _taskGetLiveComment = TaskHandler<PolycentricPlatformComment, PolycentricPlatformComment>(StateApp.instance.scopeGetter, { StatePolycentric.instance.getLiveComment(it.contextUrl, it.reference) })
private val _taskGetLiveComment = TaskHandler(StateApp.instance.scopeGetter, ::getLikesDislikesReplies)
.success {
bind(it, true);
_likesDislikesReplies = it
updateLikesDislikesReplies()
}
.exception<Throwable> {
Logger.w(TAG, "Failed to get live comment.", it);
//TODO: Show error
hideLikesDislikesReplies()
}
var onRepliesClick = Event1<IPlatformComment>();
@ -47,7 +54,7 @@ class CommentWithReferenceViewHolder : ViewHolder {
var comment: IPlatformComment? = null
private set;
constructor(viewGroup: ViewGroup) : super(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_comment_with_reference, viewGroup, false)) {
constructor(viewGroup: ViewGroup, cache: IdentityHashMap<IPlatformComment, StatePolycentric.LikesDislikesReplies>) : super(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_comment_with_reference, viewGroup, false)) {
_layoutComment = itemView.findViewById(R.id.layout_comment);
_creatorThumbnail = itemView.findViewById(R.id.image_thumbnail);
_textAuthor = itemView.findViewById(R.id.text_author);
@ -56,6 +63,7 @@ class CommentWithReferenceViewHolder : ViewHolder {
_buttonReplies = itemView.findViewById(R.id.button_replies);
_pillRatingLikesDislikes = itemView.findViewById(R.id.rating);
_buttonDelete = itemView.findViewById(R.id.button_delete)
_cache = cache
_pillRatingLikesDislikes.onLikeDislikeUpdated.subscribe { args ->
val c = comment
@ -99,7 +107,18 @@ class CommentWithReferenceViewHolder : ViewHolder {
_textBody.setPlatformPlayerLinkMovementMethod(viewGroup.context);
}
fun bind(comment: IPlatformComment, live: Boolean = false) {
private suspend fun getLikesDislikesReplies(c: PolycentricPlatformComment): StatePolycentric.LikesDislikesReplies {
val likesDislikesReplies = StatePolycentric.instance.getLikesDislikesReplies(c.reference)
synchronized(_cache) {
_cache[c] = likesDislikesReplies
}
return likesDislikesReplies
}
fun bind(comment: IPlatformComment) {
Log.i(TAG, "bind")
_likesDislikesReplies = null;
_taskGetLiveComment.cancel()
_creatorThumbnail.setThumbnail(comment.author.thumbnail, false);
@ -123,40 +142,51 @@ class CommentWithReferenceViewHolder : ViewHolder {
_textBody.text = comment.message.fixHtmlLinks();
if (comment is PolycentricPlatformComment) {
if (live) {
val hasLiked = StatePolycentric.instance.hasLiked(comment.reference);
val hasDisliked = StatePolycentric.instance.hasDisliked(comment.reference);
_pillRatingLikesDislikes.setRating(comment.rating, hasLiked, hasDisliked);
} else {
_pillRatingLikesDislikes.setLoading(true)
this.comment = comment;
updateLikesDislikesReplies();
}
private fun updateLikesDislikesReplies() {
Log.i(TAG, "updateLikesDislikesReplies")
val c = comment ?: return
if (c is PolycentricPlatformComment) {
if (_likesDislikesReplies == null) {
Log.i(TAG, "updateLikesDislikesReplies retrieving from cache")
synchronized(_cache) {
_likesDislikesReplies = _cache[c]
}
}
if (live) {
val likesDislikesReplies = _likesDislikesReplies
if (likesDislikesReplies != null) {
Log.i(TAG, "updateLikesDislikesReplies set")
val hasLiked = StatePolycentric.instance.hasLiked(c.reference);
val hasDisliked = StatePolycentric.instance.hasDisliked(c.reference);
_pillRatingLikesDislikes.setRating(RatingLikeDislikes(likesDislikesReplies.likes, likesDislikesReplies.dislikes), hasLiked, hasDisliked);
_buttonReplies.setLoading(false)
val replies = comment.replyCount ?: 0;
if (replies > 0) {
_buttonReplies.visibility = View.VISIBLE;
_buttonReplies.text.text = "$replies " + itemView.context.getString(R.string.replies);
} else {
_buttonReplies.visibility = View.GONE;
}
val replies = likesDislikesReplies.replyCount ?: 0;
_buttonReplies.visibility = View.VISIBLE;
_buttonReplies.text.text = "$replies " + itemView.context.getString(R.string.replies);
} else {
_buttonReplies.setLoading(true)
}
Log.i(TAG, "updateLikesDislikesReplies to load")
if (false) {
//Restore from cached
} else {
//_taskGetLiveComment.run(comment)
_pillRatingLikesDislikes.setLoading(true)
_buttonReplies.setLoading(true)
_taskGetLiveComment.run(c)
}
} else {
_pillRatingLikesDislikes.visibility = View.GONE
_buttonReplies.visibility = View.GONE
hideLikesDislikesReplies()
}
}
this.comment = comment;
private fun hideLikesDislikesReplies() {
_pillRatingLikesDislikes.visibility = View.GONE
_buttonReplies.visibility = View.GONE
}
companion object {

View file

@ -159,6 +159,8 @@ class PillRatingLikesDislikes : LinearLayout {
}
fun setRating(rating: RatingLikeDislikes, hasLiked: Boolean = false, hasDisliked: Boolean = false) {
setLoading(false)
_textLikes.text = rating.likes.toHumanNumber();
_textDislikes.text = rating.dislikes.toHumanNumber();
_textLikes.visibility = View.VISIBLE;
@ -172,6 +174,8 @@ class PillRatingLikesDislikes : LinearLayout {
updateColors();
}
fun setRating(rating: RatingLikes, hasLiked: Boolean = false) {
setLoading(false)
_textLikes.text = rating.likes.toHumanNumber();
_textLikes.visibility = View.VISIBLE;
_textDislikes.visibility = View.GONE;

View file

@ -61,6 +61,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.futo.platformplayer.views.overlays.RepliesOverlay
android:id="@+id/replies_overlay"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout android:id="@+id/layout_not_logged_in"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -81,12 +87,6 @@
android:paddingEnd="28dp"
android:layout_marginBottom="20dp"/>
<com.futo.platformplayer.views.overlays.RepliesOverlay
android:id="@+id/replies_overlay"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/button_login"
android:layout_width="wrap_content"