From 7bb0f1955d3dff68e2f9eb67c754f8a3b18c9fe0 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 6 Mar 2020 22:33:25 +0300 Subject: [PATCH] emoji_reactions: implement adding and removing on existing reactions(LOW PERFORMANCE) --- .../tusky/adapter/EmojiReactionsAdapter.java | 2 +- .../keylesspalace/tusky/appstore/Events.kt | 2 +- .../com/keylesspalace/tusky/entity/Status.kt | 4 +- .../tusky/fragment/SFragment.java | 7 +-- .../tusky/fragment/TimelineFragment.java | 44 +++++++++++++++++- .../tusky/fragment/ViewThreadFragment.java | 46 +++++++++++++++---- .../interfaces/StatusActionListener.java | 4 +- .../tusky/network/TimelineCases.kt | 12 ++++- 8 files changed, 100 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java index 62ee1c4a..d1e5308a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java @@ -57,7 +57,7 @@ public class EmojiReactionsAdapter extends RecyclerView.Adapter { - listener.onEmojiReactMenu(v, reaction, statusId, position); + listener.onEmojiReactMenu(v, reaction, statusId); }); } diff --git a/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt b/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt index ed4477a7..f5f4fd6f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt +++ b/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt @@ -9,7 +9,7 @@ data class FavoriteEvent(val statusId: String, val favourite: Boolean) : Dispatc data class ReblogEvent(val statusId: String, val reblog: Boolean) : Dispatchable data class BookmarkEvent(val statusId: String, val bookmark: Boolean) : Dispatchable data class MuteStatusEvent(val statusId: String, val mute: Boolean) : Dispatchable -data class EmojiReactEvent(val statusId: String, val reacted: Boolean, val emoji: String) : Dispatchable +data class EmojiReactEvent(val newStatus: Status) : Dispatchable data class UnfollowEvent(val accountId: String) : Dispatchable data class BlockEvent(val accountId: String) : Dispatchable data class MuteEvent(val accountId: String) : Dispatchable diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt index a20b05e2..599e2fba 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt @@ -145,8 +145,8 @@ data class Status( fun getEmojiReactions(): List? { return pleroma?.emojiReactions; - } - + } + private fun getEditableText(): String { val builder = SpannableStringBuilder(content) for (span in content.getSpans(0, content.length, URLSpan::class.java)) { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index 3395c396..33be8033 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -62,6 +62,7 @@ import com.keylesspalace.tusky.entity.EmojiReaction; import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.network.TimelineCases; import com.keylesspalace.tusky.viewdata.AttachmentViewData; +import com.keylesspalace.tusky.interfaces.StatusActionListener; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -175,7 +176,7 @@ public abstract class SFragment extends BaseFragment implements Injectable { getActivity().startActivity(intent); } - protected void emojiReactMenu(@NonNull final String statusId, @NonNull final EmojiReaction reaction, View view, final int position) { + protected void emojiReactMenu(@NonNull final String statusId, @NonNull final EmojiReaction reaction, View view, final StatusActionListener listener) { PopupMenu popup = new PopupMenu(getContext(), view); popup.inflate(R.menu.emoji_reaction_more); @@ -186,10 +187,10 @@ public abstract class SFragment extends BaseFragment implements Injectable { popup.setOnMenuItemClickListener(item -> { switch (item.getItemId()) { case R.id.emoji_react: - // TODO + listener.onEmojiReact(true, reaction.getName(), statusId); return true; case R.id.emoji_unreact: - // TODO + listener.onEmojiReact(false, reaction.getName(), statusId); return true; case R.id.emoji_reacted_by: Intent intent = AccountListActivity.newIntent(getContext(), AccountListActivity.Type.REACTED, statusId, reaction.getName()); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index d3ab4bf3..e732bc54 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -531,6 +531,8 @@ public class TimelineFragment extends SFragment implements handleStatusComposeEvent(status); } else if (event instanceof PreferenceChangedEvent) { onPreferenceChanged(((PreferenceChangedEvent) event).getPreferenceKey()); + } else if (event instanceof EmojiReactEvent) { + handleEmojiReactEvent((EmojiReactEvent)event); } }); eventRegistered = true; @@ -1497,9 +1499,47 @@ public class TimelineFragment extends SFragment implements isNeedRefresh = true; } + private void setEmojiReactionForStatus(int position, Status newStatus) { + StatusViewData newViewData = ViewDataUtils.statusToViewData(newStatus, false, false); + statuses.setPairedItem(position, newViewData); + updateAdapter(); + } + + private void setEmojiReactForStatus(int position, Status status, Status newStatus) { + Pair actual = + findStatusAndPosition(position, status); + if (actual == null) return; + + setEmojiReactionForStatus(actual.second, newStatus); + } + + public void handleEmojiReactEvent(EmojiReactEvent event) { + int pos = findStatusOrReblogPositionById(event.getNewStatus().getActionableId()); + if (pos < 0) return; + Status status = statuses.get(pos).asRight(); + setEmojiReactForStatus(pos, status, event.getNewStatus()); + } + + @Override + public void onEmojiReact(final boolean react, final String emoji, final String statusId) { + int position = findStatusOrReblogPositionById(statusId); + if (position < 0) return; + + timelineCases.react(emoji, statusId, react) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this))) + .subscribe( + (newStatus) -> setEmojiReactionForStatus(position, newStatus), + (t) -> Log.d(TAG, + "Failed to react with " + emoji + " on status: " + statusId, t) + ); + + } + + @Override - public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId, final int position) { - super.emojiReactMenu(statusId, emoji, view, position); + public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId) { + super.emojiReactMenu(statusId, emoji, view, this); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index efcc4757..1d23ab3e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -44,13 +44,7 @@ import com.keylesspalace.tusky.BuildConfig; import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.ViewThreadActivity; import com.keylesspalace.tusky.adapter.ThreadAdapter; -import com.keylesspalace.tusky.appstore.BlockEvent; -import com.keylesspalace.tusky.appstore.BookmarkEvent; -import com.keylesspalace.tusky.appstore.EventHub; -import com.keylesspalace.tusky.appstore.FavoriteEvent; -import com.keylesspalace.tusky.appstore.ReblogEvent; -import com.keylesspalace.tusky.appstore.StatusComposedEvent; -import com.keylesspalace.tusky.appstore.StatusDeletedEvent; +import com.keylesspalace.tusky.appstore.*; import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.*; import com.keylesspalace.tusky.interfaces.StatusActionListener; @@ -196,6 +190,8 @@ public final class ViewThreadFragment extends SFragment implements handleStatusComposedEvent((StatusComposedEvent) event); } else if (event instanceof StatusDeletedEvent) { handleStatusDeletedEvent((StatusDeletedEvent) event); + } else if (event instanceof EmojiReactEvent) { + handleEmojiReactEvent((EmojiReactEvent)event); } }); @@ -745,8 +741,40 @@ public final class ViewThreadFragment extends SFragment implements onRefresh(); } + private void setEmojiReactionForStatus(int position, Status status) { + StatusViewData.Concrete newViewData = ViewDataUtils.statusToViewData(status, false, false); + + statuses.setPairedItem(position, newViewData); + adapter.setItem(position, newViewData, true); + } + + public void handleEmojiReactEvent(EmojiReactEvent event) { + Pair posAndStatus = findStatusAndPos(event.getNewStatus().getActionableId()); + if (posAndStatus == null) return; + setEmojiReactionForStatus(posAndStatus.first, event.getNewStatus()); + } + + @Override + public void onEmojiReact(final boolean react, final String emoji, final String statusId) { + Pair statusAndPos = findStatusAndPos(statusId); + + if(statusAndPos == null) + return; + int position = statusAndPos.first; + + timelineCases.react(emoji, statusId, react) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this))) + .subscribe( + (newStatus) -> setEmojiReactionForStatus(position, newStatus), + (t) -> Log.d(TAG, + "Failed to react with " + emoji + " on status: " + statusId, t) + ); + + } + @Override - public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId, final int position) { - super.emojiReactMenu(statusId, emoji, view, position); + public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId) { + super.emojiReactMenu(statusId, emoji, view, this); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java b/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java index 00fd4794..a30fffd2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java +++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java @@ -65,7 +65,7 @@ public interface StatusActionListener extends LinkListener { void onVoteInPoll(int position, @NonNull List choices); default void onMute(int position, boolean isMuted) {} - // default void onEmojiReact(final boolean react, final String emoji, final int position) {}; - default void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId, final int position) {}; + default void onEmojiReact(final boolean react, final String emoji, final String statusId) {}; + default void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId) {}; } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt index c2c0f988..7eeb0700 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt @@ -43,7 +43,7 @@ interface TimelineCases { fun delete(id: String): Single fun pin(status: Status, pin: Boolean) fun voteInPoll(status: Status, choices: List): Single - + fun react(emoji: String, id: String, react: Boolean) : Single } class TimelineCasesImpl( @@ -157,4 +157,14 @@ class TimelineCasesImpl( } } + override fun react(emoji: String, id: String, react: Boolean): Single { + val call = if (react) { + mastodonApi.reactWithEmoji(id, emoji) + } else { + mastodonApi.unreactWithEmoji(id, emoji) + } + return call.doAfterSuccess { status -> + eventHub.dispatch(EmojiReactEvent(status)) + } + } }