emoji_reactions: implement adding and removing on existing reactions(LOW PERFORMANCE)

main
Alibek Omarov 4 years ago
parent 7e10c531bc
commit 7bb0f1955d
  1. 2
      app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java
  2. 2
      app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt
  3. 4
      app/src/main/java/com/keylesspalace/tusky/entity/Status.kt
  4. 7
      app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
  5. 44
      app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
  6. 46
      app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
  7. 4
      app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
  8. 12
      app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt

@ -57,7 +57,7 @@ public class EmojiReactionsAdapter extends RecyclerView.Adapter<EmojiReactionVie
holder.emojiReaction.setText(str); holder.emojiReaction.setText(str);
holder.emojiReaction.setActivated(reaction.getMe()); holder.emojiReaction.setActivated(reaction.getMe());
holder.emojiReaction.setOnClickListener(v -> { holder.emojiReaction.setOnClickListener(v -> {
listener.onEmojiReactMenu(v, reaction, statusId, position); listener.onEmojiReactMenu(v, reaction, statusId);
}); });
} }

@ -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 ReblogEvent(val statusId: String, val reblog: Boolean) : Dispatchable
data class BookmarkEvent(val statusId: String, val bookmark: Boolean) : Dispatchable data class BookmarkEvent(val statusId: String, val bookmark: Boolean) : Dispatchable
data class MuteStatusEvent(val statusId: String, val mute: 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 UnfollowEvent(val accountId: String) : Dispatchable
data class BlockEvent(val accountId: String) : Dispatchable data class BlockEvent(val accountId: String) : Dispatchable
data class MuteEvent(val accountId: String) : Dispatchable data class MuteEvent(val accountId: String) : Dispatchable

@ -145,8 +145,8 @@ data class Status(
fun getEmojiReactions(): List<EmojiReaction>? { fun getEmojiReactions(): List<EmojiReaction>? {
return pleroma?.emojiReactions; return pleroma?.emojiReactions;
} }
private fun getEditableText(): String { private fun getEditableText(): String {
val builder = SpannableStringBuilder(content) val builder = SpannableStringBuilder(content)
for (span in content.getSpans(0, content.length, URLSpan::class.java)) { for (span in content.getSpans(0, content.length, URLSpan::class.java)) {

@ -62,6 +62,7 @@ import com.keylesspalace.tusky.entity.EmojiReaction;
import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.network.MastodonApi;
import com.keylesspalace.tusky.network.TimelineCases; import com.keylesspalace.tusky.network.TimelineCases;
import com.keylesspalace.tusky.viewdata.AttachmentViewData; import com.keylesspalace.tusky.viewdata.AttachmentViewData;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -175,7 +176,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
getActivity().startActivity(intent); 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); PopupMenu popup = new PopupMenu(getContext(), view);
popup.inflate(R.menu.emoji_reaction_more); popup.inflate(R.menu.emoji_reaction_more);
@ -186,10 +187,10 @@ public abstract class SFragment extends BaseFragment implements Injectable {
popup.setOnMenuItemClickListener(item -> { popup.setOnMenuItemClickListener(item -> {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.emoji_react: case R.id.emoji_react:
// TODO listener.onEmojiReact(true, reaction.getName(), statusId);
return true; return true;
case R.id.emoji_unreact: case R.id.emoji_unreact:
// TODO listener.onEmojiReact(false, reaction.getName(), statusId);
return true; return true;
case R.id.emoji_reacted_by: case R.id.emoji_reacted_by:
Intent intent = AccountListActivity.newIntent(getContext(), AccountListActivity.Type.REACTED, statusId, reaction.getName()); Intent intent = AccountListActivity.newIntent(getContext(), AccountListActivity.Type.REACTED, statusId, reaction.getName());

@ -531,6 +531,8 @@ public class TimelineFragment extends SFragment implements
handleStatusComposeEvent(status); handleStatusComposeEvent(status);
} else if (event instanceof PreferenceChangedEvent) { } else if (event instanceof PreferenceChangedEvent) {
onPreferenceChanged(((PreferenceChangedEvent) event).getPreferenceKey()); onPreferenceChanged(((PreferenceChangedEvent) event).getPreferenceKey());
} else if (event instanceof EmojiReactEvent) {
handleEmojiReactEvent((EmojiReactEvent)event);
} }
}); });
eventRegistered = true; eventRegistered = true;
@ -1497,9 +1499,47 @@ public class TimelineFragment extends SFragment implements
isNeedRefresh = true; 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<StatusViewData.Concrete, Integer> 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 @Override
public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId, final int position) { public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId) {
super.emojiReactMenu(statusId, emoji, view, position); super.emojiReactMenu(statusId, emoji, view, this);
} }
} }

@ -44,13 +44,7 @@ import com.keylesspalace.tusky.BuildConfig;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.ViewThreadActivity; import com.keylesspalace.tusky.ViewThreadActivity;
import com.keylesspalace.tusky.adapter.ThreadAdapter; import com.keylesspalace.tusky.adapter.ThreadAdapter;
import com.keylesspalace.tusky.appstore.BlockEvent; import com.keylesspalace.tusky.appstore.*;
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.di.Injectable; import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.*; import com.keylesspalace.tusky.entity.*;
import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusActionListener;
@ -196,6 +190,8 @@ public final class ViewThreadFragment extends SFragment implements
handleStatusComposedEvent((StatusComposedEvent) event); handleStatusComposedEvent((StatusComposedEvent) event);
} else if (event instanceof StatusDeletedEvent) { } else if (event instanceof StatusDeletedEvent) {
handleStatusDeletedEvent((StatusDeletedEvent) event); handleStatusDeletedEvent((StatusDeletedEvent) event);
} else if (event instanceof EmojiReactEvent) {
handleEmojiReactEvent((EmojiReactEvent)event);
} }
}); });
@ -745,8 +741,40 @@ public final class ViewThreadFragment extends SFragment implements
onRefresh(); 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<Integer, Status> 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<Integer, Status> 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 @Override
public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId, final int position) { public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId) {
super.emojiReactMenu(statusId, emoji, view, position); super.emojiReactMenu(statusId, emoji, view, this);
} }
} }

@ -65,7 +65,7 @@ public interface StatusActionListener extends LinkListener {
void onVoteInPoll(int position, @NonNull List<Integer> choices); void onVoteInPoll(int position, @NonNull List<Integer> choices);
default void onMute(int position, boolean isMuted) {} default void onMute(int position, boolean isMuted) {}
// default void onEmojiReact(final boolean react, final String emoji, 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, final int position) {}; default void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId) {};
} }

@ -43,7 +43,7 @@ interface TimelineCases {
fun delete(id: String): Single<DeletedStatus> fun delete(id: String): Single<DeletedStatus>
fun pin(status: Status, pin: Boolean) fun pin(status: Status, pin: Boolean)
fun voteInPoll(status: Status, choices: List<Int>): Single<Poll> fun voteInPoll(status: Status, choices: List<Int>): Single<Poll>
fun react(emoji: String, id: String, react: Boolean) : Single<Status>
} }
class TimelineCasesImpl( class TimelineCasesImpl(
@ -157,4 +157,14 @@ class TimelineCasesImpl(
} }
} }
override fun react(emoji: String, id: String, react: Boolean): Single<Status> {
val call = if (react) {
mastodonApi.reactWithEmoji(id, emoji)
} else {
mastodonApi.unreactWithEmoji(id, emoji)
}
return call.doAfterSuccess { status ->
eventHub.dispatch(EmojiReactEvent(status))
}
}
} }

Loading…
Cancel
Save