diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt index eb1bb34b..8e03e4e4 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt @@ -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 = IdentityHashMap() private val _taskLoadComments = if(!isInEditMode) TaskHandler>( 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) }); diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt index af91da21..73468d3f 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt @@ -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 { @@ -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]; diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt index 45b1be78..f0581abd 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt @@ -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; + private var _likesDislikesReplies: StatePolycentric.LikesDislikesReplies? = null; - private val _taskGetLiveComment = TaskHandler(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 { Logger.w(TAG, "Failed to get live comment.", it); //TODO: Show error + hideLikesDislikesReplies() } var onRepliesClick = Event1(); @@ -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) : 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 { diff --git a/app/src/main/java/com/futo/platformplayer/views/pills/PillRatingLikesDislikes.kt b/app/src/main/java/com/futo/platformplayer/views/pills/PillRatingLikesDislikes.kt index 0266cf5a..8854f606 100644 --- a/app/src/main/java/com/futo/platformplayer/views/pills/PillRatingLikesDislikes.kt +++ b/app/src/main/java/com/futo/platformplayer/views/pills/PillRatingLikesDislikes.kt @@ -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; diff --git a/app/src/main/res/layout/fragment_comments.xml b/app/src/main/res/layout/fragment_comments.xml index a1e660e3..cbaccaeb 100644 --- a/app/src/main/res/layout/fragment_comments.xml +++ b/app/src/main/res/layout/fragment_comments.xml @@ -61,6 +61,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + - -