diff --git a/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonStatusesService.java b/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonStatusesService.java index 975fc291..f4392fcb 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonStatusesService.java +++ b/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonStatusesService.java @@ -318,4 +318,18 @@ public interface MastodonStatusesService { @Header("Authorization") String token, @Path("id") String id ); + + @POST("statuses/{id}/react/{name}") + Call addReaction( + @Header("Authorization") String app_token, + @Path("id") String id, + @Path("name") String name + ); + + @POST("statuses/{id}/unreact/{name}") + Call removeReaction( + @Header("Authorization") String app_token, + @Path("id") String id, + @Path("name") String name + ); } diff --git a/app/src/main/java/app/fedilab/android/mastodon/client/entities/api/Status.java b/app/src/main/java/app/fedilab/android/mastodon/client/entities/api/Status.java index 7dec8be3..cf89f809 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/client/entities/api/Status.java +++ b/app/src/main/java/app/fedilab/android/mastodon/client/entities/api/Status.java @@ -109,6 +109,8 @@ public class Status implements Serializable, Cloneable { public boolean cached = false; @SerializedName("is_maths") public Boolean isMaths; + @SerializedName("reactions") + public List reactions; public Attachment art_attachment; public boolean isExpended = false; diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ReactionAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ReactionAdapter.java index 56777244..cf6d17f8 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ReactionAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ReactionAdapter.java @@ -34,6 +34,7 @@ import app.fedilab.android.mastodon.client.entities.api.Reaction; import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.ThemeHelper; import app.fedilab.android.mastodon.viewmodel.mastodon.AnnouncementsVM; +import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM; import app.fedilab.android.mastodon.viewmodel.pleroma.ActionsVM; @@ -46,18 +47,29 @@ public class ReactionAdapter extends RecyclerView.Adapter reactions; private final String announcementId; private final boolean statusReaction; + private final boolean isPleroma; private Context context; + + ReactionAdapter(String announcementId, List reactions, boolean statusReaction, boolean isPleroma) { + this.reactions = reactions; + this.announcementId = announcementId; + this.statusReaction = statusReaction; + this.isPleroma = isPleroma; + } + ReactionAdapter(String announcementId, List reactions, boolean statusReaction) { this.reactions = reactions; this.announcementId = announcementId; this.statusReaction = statusReaction; + this.isPleroma = true; } ReactionAdapter(String announcementId, List reactions) { this.reactions = reactions; this.announcementId = announcementId; this.statusReaction = false; + this.isPleroma = true; } @NonNull @@ -101,7 +113,7 @@ public class ReactionAdapter extends RecyclerView.Adapter { if (reaction.me) { @@ -115,6 +127,20 @@ public class ReactionAdapter extends RecyclerView.Adapter { + if (reaction.me) { + statusesVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, announcementId, reaction.name); + reaction.me = false; + reaction.count -= 1; + } else { + statusesVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, announcementId, reaction.name); + reaction.me = true; + reaction.count += 1; + } + notifyItemChanged(position); + }); } } diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java index a8ddf856..0dda7c97 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java @@ -50,6 +50,7 @@ import android.os.Looper; import android.text.Html; import android.text.SpannableString; import android.text.TextUtils; +import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -519,7 +520,7 @@ public class StatusAdapter extends RecyclerView.Adapter holder.binding.quotedMessage.cardviewContainer.setVisibility(View.GONE); } - if (currentAccount != null && currentAccount.api == Account.API.PLEROMA) { + if (currentAccount != null && currentAccount.api == Account.API.PLEROMA || status.reactions != null) { if (status.pleroma != null && status.pleroma.emoji_reactions != null && status.pleroma.emoji_reactions.size() > 0) { holder.binding.layoutReactions.getRoot().setVisibility(View.VISIBLE); ReactionAdapter reactionAdapter = new ReactionAdapter(status.id, status.pleroma.emoji_reactions, true); @@ -527,6 +528,13 @@ public class StatusAdapter extends RecyclerView.Adapter LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager); + } else if (status.reactions != null && status.reactions.size() > 0) { + holder.binding.layoutReactions.getRoot().setVisibility(View.VISIBLE); + ReactionAdapter reactionAdapter = new ReactionAdapter(status.id, status.reactions, true, false); + holder.binding.layoutReactions.reactionsView.setAdapter(reactionAdapter); + LinearLayoutManager layoutManager + = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); + holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager); } else { holder.binding.layoutReactions.getRoot().setVisibility(View.GONE); holder.binding.layoutReactions.reactionsView.setAdapter(null); @@ -539,33 +547,57 @@ public class StatusAdapter extends RecyclerView.Adapter }).setOnEmojiClickListener((emoji, imageView) -> { String emojiStr = imageView.getUnicode(); boolean alreadyAdded = false; - if (status.pleroma == null || status.pleroma.emoji_reactions == null) { - return; - } - for (Reaction reaction : status.pleroma.emoji_reactions) { - if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) { - alreadyAdded = true; - reaction.count = (reaction.count - 1); - if (reaction.count == 0) { - status.pleroma.emoji_reactions.remove(reaction); + if (status.pleroma != null && status.pleroma.emoji_reactions != null) { + for (Reaction reaction : status.pleroma.emoji_reactions) { + if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) { + alreadyAdded = true; + reaction.count = (reaction.count - 1); + if (reaction.count == 0) { + status.pleroma.emoji_reactions.remove(reaction); + } + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + break; } - adapter.notifyItemChanged(holder.getBindingAdapterPosition()); - break; } - } - if (!alreadyAdded) { - Reaction reaction = new Reaction(); - reaction.me = true; - reaction.count = 1; - reaction.name = emojiStr; - status.pleroma.emoji_reactions.add(0, reaction); - adapter.notifyItemChanged(holder.getBindingAdapterPosition()); - } - ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); - if (alreadyAdded) { - actionVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); - } else { - actionVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + if (!alreadyAdded) { + Reaction reaction = new Reaction(); + reaction.me = true; + reaction.count = 1; + reaction.name = emojiStr; + status.pleroma.emoji_reactions.add(0, reaction); + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + } + ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); + if (alreadyAdded) { + actionVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } else { + actionVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } + } else if (status.reactions != null) { + for (Reaction reaction : status.reactions) { + if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) { + alreadyAdded = true; + reaction.count = (reaction.count - 1); + if (reaction.count == 0) { + status.reactions.remove(reaction); + } + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + break; + } + } + if (!alreadyAdded) { + Reaction reaction = new Reaction(); + reaction.me = true; + reaction.count = 1; + reaction.name = emojiStr; + status.reactions.add(0, reaction); + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + } + if (alreadyAdded) { + statusesVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } else { + statusesVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } } }) .build(holder.binding.layoutReactions.fakeEdittext); @@ -591,32 +623,61 @@ public class StatusAdapter extends RecyclerView.Adapter String url = emojis.get(BaseMainActivity.currentInstance).get(index).url; String static_url = emojis.get(BaseMainActivity.currentInstance).get(index).static_url; boolean alreadyAdded = false; - for (Reaction reaction : status.pleroma.emoji_reactions) { - if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) { - alreadyAdded = true; - reaction.count = (reaction.count - 1); - if (reaction.count == 0) { - status.pleroma.emoji_reactions.remove(reaction); + if (status.pleroma != null && status.pleroma.emoji_reactions != null) { + for (Reaction reaction : status.pleroma.emoji_reactions) { + if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) { + alreadyAdded = true; + reaction.count = (reaction.count - 1); + if (reaction.count == 0) { + status.pleroma.emoji_reactions.remove(reaction); + } + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + break; } - adapter.notifyItemChanged(holder.getBindingAdapterPosition()); - break; } - } - if (!alreadyAdded) { - Reaction reaction = new Reaction(); - reaction.me = true; - reaction.count = 1; - reaction.name = emojiStr; - reaction.url = url; - reaction.static_url = static_url; - status.pleroma.emoji_reactions.add(0, reaction); - adapter.notifyItemChanged(holder.getBindingAdapterPosition()); - } - ActionsVM actionsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); - if (alreadyAdded) { - actionsVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); - } else { - actionsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + if (!alreadyAdded) { + Reaction reaction = new Reaction(); + reaction.me = true; + reaction.count = 1; + reaction.name = emojiStr; + reaction.url = url; + reaction.static_url = static_url; + status.pleroma.emoji_reactions.add(0, reaction); + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + } + ActionsVM actionsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); + if (alreadyAdded) { + actionsVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } else { + actionsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } + } else if (status.reactions != null) { + for (Reaction reaction : status.reactions) { + if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) { + alreadyAdded = true; + reaction.count = (reaction.count - 1); + if (reaction.count == 0) { + status.reactions.remove(reaction); + } + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + break; + } + } + if (!alreadyAdded) { + Reaction reaction = new Reaction(); + reaction.me = true; + reaction.count = 1; + reaction.name = emojiStr; + reaction.url = url; + reaction.static_url = static_url; + status.reactions.add(0, reaction); + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + } + if (alreadyAdded) { + statusesVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } else { + statusesVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } } }); gridView.setPadding(paddingDp, paddingDp, paddingDp, paddingDp); diff --git a/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/StatusesVM.java b/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/StatusesVM.java index 4c207439..b64baf06 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/StatusesVM.java +++ b/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/StatusesVM.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import app.fedilab.android.R; +import app.fedilab.android.mastodon.client.endpoints.MastodonAnnouncementsService; import app.fedilab.android.mastodon.client.endpoints.MastodonStatusesService; import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Accounts; @@ -1292,4 +1293,48 @@ public class StatusesVM extends AndroidViewModel { }).start(); return voidMutableLiveData; } + + /** + * React to a status with an emoji. + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @param id Local ID of an announcement + * @param name Unicode emoji, or shortcode of custom emoji + */ + public void addReaction(@NonNull String instance, String token, @NonNull String id, @NonNull String name) { + MastodonStatusesService mastodonStatusesService = init(instance); + new Thread(() -> { + Call addReactionCall = mastodonStatusesService.addReaction(token, id, name); + if (addReactionCall != null) { + try { + addReactionCall.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + + /** + * Undo a react emoji to a status. + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @param id Local ID of an announcement + * @param name Unicode emoji, or shortcode of custom emoji + */ + public void removeReaction(@NonNull String instance, String token, @NonNull String id, @NonNull String name) { + MastodonStatusesService mastodonStatusesService = init(instance); + new Thread(() -> { + Call removeReactionCall = mastodonStatusesService.removeReaction(token, id, name); + if (removeReactionCall != null) { + try { + removeReactionCall.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } }