emoji reactions for announcements!

This commit is contained in:
sk
2023-08-23 22:56:11 +02:00
parent d96c3c3c8a
commit 0afcdb2cdf
8 changed files with 137 additions and 95 deletions

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.announcements;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class AddAnnouncementReaction extends MastodonAPIRequest<Object> {
public AddAnnouncementReaction(String id, String emoji) {
super(HttpMethod.PUT, "/announcements/" + id + "/reactions/" + emoji, Object.class);
setRequestBody(new Object());
}
}

View File

@@ -0,0 +1,9 @@
package org.joinmastodon.android.api.requests.announcements;
import org.joinmastodon.android.api.MastodonAPIRequest;
public class DeleteAnnouncementReaction extends MastodonAPIRequest<Object> {
public DeleteAnnouncementReaction(String id, String emoji) {
super(HttpMethod.DELETE, "/announcements/" + id + "/reactions/" + emoji, Object.class);
}
}

View File

@@ -26,6 +26,8 @@ import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.ScheduledStatus; import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
@@ -68,11 +70,12 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
instanceUser.emojis = List.of(); instanceUser.emojis = List.of();
Status fakeStatus = a.toStatus(); Status fakeStatus = a.toStatus();
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true); TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
// TODO: emoji reactions!
textItem.textSelectable = true; textItem.textSelectable = true;
return List.of( return List.of(
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead), HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
textItem textItem,
new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true)
// new DummyStatusDisplayItem(a.id, this)
); );
} }

View File

@@ -1,9 +1,11 @@
package org.joinmastodon.android.model; package org.joinmastodon.android.model;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField; import org.joinmastodon.android.api.RequiredField;
import org.parceler.Parcel; import org.parceler.Parcel;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Parcel @Parcel
@@ -20,6 +22,7 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
public Instant updatedAt; public Instant updatedAt;
public boolean read; public boolean read;
public List<Emoji> emojis; public List<Emoji> emojis;
public List<EmojiReaction> reactions;
public List<Mention> mentions; public List<Mention> mentions;
public List<Hashtag> tags; public List<Hashtag> tags;
@@ -41,10 +44,17 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
'}'; '}';
} }
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();
if(reactions==null) reactions=new ArrayList<>();
}
public Status toStatus() { public Status toStatus() {
Status s = Status.ofFake(id, content, publishedAt); Status s=Status.ofFake(id, content, publishedAt);
s.createdAt = startsAt != null ? startsAt : publishedAt; s.createdAt=startsAt != null ? startsAt : publishedAt;
if (updatedAt != null) s.editedAt = updatedAt; s.reactions=reactions;
if(updatedAt != null) s.editedAt=updatedAt;
return s; return s;
} }

View File

@@ -100,7 +100,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
t.postprocess(); t.postprocess();
for(Emoji e:emojis) for(Emoji e:emojis)
e.postprocess(); e.postprocess();
if (mediaAttachments == null) mediaAttachments = List.of(); if (mediaAttachments == null) mediaAttachments=List.of();
for(Attachment a:mediaAttachments) for(Attachment a:mediaAttachments)
a.postprocess(); a.postprocess();
account.postprocess(); account.postprocess();
@@ -201,17 +201,18 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
} }
public static Status ofFake(String id, String text, Instant createdAt) { public static Status ofFake(String id, String text, Instant createdAt) {
Status s = new Status(); Status s=new Status();
s.id = id; s.id=id;
s.mediaAttachments = List.of(); s.mediaAttachments=List.of();
s.createdAt = createdAt; s.createdAt=createdAt;
s.content = s.text = text; s.content=s.text=text;
s.spoilerText = ""; s.spoilerText="";
s.visibility = StatusPrivacy.PUBLIC; s.visibility=StatusPrivacy.PUBLIC;
s.mentions = List.of(); s.reactions=List.of();
s.tags = List.of(); s.mentions=List.of();
s.emojis = List.of(); s.tags =List.of();
s.filtered = List.of(); s.emojis=List.of();
s.filtered=List.of();
return s; return s;
} }
@@ -223,21 +224,21 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public static class StatusDeserializer implements JsonDeserializer<Status> { public static class StatusDeserializer implements JsonDeserializer<Status> {
@Override @Override
public Status deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { public Status deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject obj = json.getAsJsonObject(); JsonObject obj=json.getAsJsonObject();
Status quote = null; Status quote=null;
if (obj.has("quote") && obj.get("quote").isJsonObject()) if (obj.has("quote") && obj.get("quote").isJsonObject())
quote = gson.fromJson(obj.get("quote"), Status.class); quote=gson.fromJson(obj.get("quote"), Status.class);
obj.remove("quote"); obj.remove("quote");
Status reblog = null; Status reblog=null;
if (obj.has("reblog")) if (obj.has("reblog"))
reblog = gson.fromJson(obj.get("reblog"), Status.class); reblog=gson.fromJson(obj.get("reblog"), Status.class);
obj.remove("reblog"); obj.remove("reblog");
Status status = gsonWithoutDeserializer.fromJson(json, Status.class); Status status=gsonWithoutDeserializer.fromJson(json, Status.class);
status.quote = quote; status.quote=quote;
status.reblog = reblog; status.reblog=reblog;
return status; return status;
} }

View File

@@ -7,7 +7,6 @@ import android.widget.Space;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Status;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;

View File

@@ -21,6 +21,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.announcements.AddAnnouncementReaction;
import org.joinmastodon.android.api.requests.announcements.DeleteAnnouncementReaction;
import org.joinmastodon.android.api.requests.statuses.AddStatusReaction; import org.joinmastodon.android.api.requests.statuses.AddStatusReaction;
import org.joinmastodon.android.api.requests.statuses.DeleteStatusReaction; import org.joinmastodon.android.api.requests.statuses.DeleteStatusReaction;
import org.joinmastodon.android.api.requests.statuses.PleromaAddStatusReaction; import org.joinmastodon.android.api.requests.statuses.PleromaAddStatusReaction;
@@ -31,6 +33,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment; import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Announcement;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiReaction; import org.joinmastodon.android.model.EmojiReaction;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -54,14 +57,15 @@ import me.grishka.appkit.views.UsableRecyclerView;
public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem { public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
public final Status status; public final Status status;
private final Drawable placeholder; private final Drawable placeholder;
private final boolean hideAdd; private final boolean hideAdd, forAnnouncement;
private final String accountID; private final String accountID;
private boolean hidden; private boolean hidden;
public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideAdd) { public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideAdd, boolean forAnnouncement) {
super(parentID, parentFragment); super(parentID, parentFragment);
this.status=status; this.status=status;
this.hideAdd=hideAdd; this.hideAdd=hideAdd;
this.forAnnouncement=forAnnouncement;
this.accountID=accountID; this.accountID=accountID;
placeholder=parentFragment.getContext().getDrawable(R.drawable.image_placeholder).mutate(); placeholder=parentFragment.getContext().getDrawable(R.drawable.image_placeholder).mutate();
placeholder.setBounds(0, 0, V.sp(24), V.sp(24)); placeholder.setBounds(0, 0, V.sp(24), V.sp(24));
@@ -91,6 +95,31 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
hidden=status.reactions.isEmpty() && hideAdd; hidden=status.reactions.isEmpty() && hideAdd;
} }
private MastodonAPIRequest<?> createRequest(String name, boolean delete, Runnable cb){
boolean ak=parentFragment.isInstanceAkkoma();
if(forAnnouncement){
MastodonAPIRequest<Object> req=delete
? new DeleteAnnouncementReaction(status.id, name)
: new AddAnnouncementReaction(status.id, name);
return req.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){ cb.run(); }
@Override
public void onError(ErrorResponse error){ error.showToast(parentFragment.getContext()); }
});
}else{
MastodonAPIRequest<Status> req=delete
? (ak ? new PleromaDeleteStatusReaction(status.id, name) : new DeleteStatusReaction(status.id, name))
: (ak ? new PleromaAddStatusReaction(status.id, name) : new AddStatusReaction(status.id, name));
return req.setCallback(new Callback<>(){
@Override
public void onSuccess(Status result){ cb.run(); }
@Override
public void onError(ErrorResponse error){ error.showToast(parentFragment.getContext()); }
});
}
}
public static class Holder extends StatusDisplayItem.Holder<EmojiReactionsStatusDisplayItem> implements ImageLoaderViewHolder, CustomEmojiPopupKeyboard.Listener { public static class Holder extends StatusDisplayItem.Holder<EmojiReactionsStatusDisplayItem> implements ImageLoaderViewHolder, CustomEmojiPopupKeyboard.Listener {
private final UsableRecyclerView list; private final UsableRecyclerView list;
private final LinearLayout root, line; private final LinearLayout root, line;
@@ -129,7 +158,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
item.updateHidden(); item.updateHidden();
root.setVisibility(item.hidden ? View.GONE : View.VISIBLE); root.setVisibility(item.hidden ? View.GONE : View.VISIBLE);
line.setVisibility(item.hidden ? View.GONE : View.VISIBLE); line.setVisibility(item.hidden ? View.GONE : View.VISIBLE);
line.setPadding(list.getPaddingLeft(), item.hidden ? 0 : V.dp(8), list.getPaddingRight(), 0); line.setPadding(
list.getPaddingLeft(),
item.hidden ? 0 : V.dp(8),
list.getPaddingRight(),
item.forAnnouncement ? V.dp(8) : 0
);
imgLoader.updateImages(); imgLoader.updateImages();
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }
@@ -155,12 +189,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
private void addEmojiReaction(String emoji, Emoji info) { private void addEmojiReaction(String emoji, Emoji info) {
if(item.status.reactions.stream().filter(r->r.name.equals(emoji) && r.me).findAny().isPresent()) return; if(item.status.reactions.stream().filter(r->r.name.equals(emoji) && r.me).findAny().isPresent()) return;
MastodonAPIRequest<Status> req = item.parentFragment.isInstanceAkkoma() item.createRequest(emoji, false, ()->{
? new PleromaAddStatusReaction(item.status.id, emoji)
: new AddStatusReaction(item.status.id, emoji);
req.setCallback(new Callback<>() {
@Override
public void onSuccess(Status result) {
Account me=AccountSessionManager.get(item.accountID).self; Account me=AccountSessionManager.get(item.accountID).self;
boolean found=false; boolean found=false;
for(int i=0; i<item.status.reactions.size(); i++){ for(int i=0; i<item.status.reactions.size(); i++){
@@ -177,14 +206,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
adapter.notifyItemRangeInserted(item.status.reactions.size() - 1, 1); adapter.notifyItemRangeInserted(item.status.reactions.size() - 1, 1);
} }
E.post(new StatusCountersUpdatedEvent(item.status, adapter.parentHolder)); E.post(new StatusCountersUpdatedEvent(item.status, adapter.parentHolder));
} }).exec(item.accountID);
@Override
public void onError(ErrorResponse error) {
error.showToast(item.parentFragment.getContext());
}
})
.exec(item.accountID);
} }
@Override @Override
@@ -298,15 +320,9 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null); btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null);
} }
btn.setSelected(reaction.me); btn.setSelected(reaction.me);
btn.setOnClickListener(e -> { btn.setOnClickListener(e->{
boolean deleting=reaction.me; boolean deleting=reaction.me;
boolean ak=parent.parentFragment.isInstanceAkkoma(); parent.createRequest(reaction.name, deleting, ()->{
MastodonAPIRequest<Status> req = deleting
? (ak ? new PleromaDeleteStatusReaction(parent.status.id, reaction.name) : new DeleteStatusReaction(parent.status.id, reaction.name))
: (ak ? new PleromaAddStatusReaction(parent.status.id, reaction.name) : new AddStatusReaction(parent.status.id, reaction.name));
req.setCallback(new Callback<>() {
@Override
public void onSuccess(Status result) {
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter(); EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
for(int i=0; i<parent.status.reactions.size(); i++){ for(int i=0; i<parent.status.reactions.size(); i++){
@@ -326,14 +342,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
E.post(new StatusCountersUpdatedEvent(parent.status, adapter.parentHolder)); E.post(new StatusCountersUpdatedEvent(parent.status, adapter.parentHolder));
adapter.parentHolder.imgLoader.updateImages(); adapter.parentHolder.imgLoader.updateImages();
} }).exec(parent.parentFragment.getAccountID());
@Override
public void onError(ErrorResponse error) {
error.showToast(itemView.getContext());
}
})
.exec(parent.parentFragment.getAccountID());
}); });
if (parent.parentFragment.isInstanceAkkoma()) { if (parent.parentFragment.isInstanceAkkoma()) {

View File

@@ -294,7 +294,7 @@ public abstract class StatusDisplayItem{
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 if((flags & FLAG_NO_EMOJI_REACTIONS)==0
&& AccountSessionManager.get(accountID).getLocalPreferences().emojiReactionsEnabled){ && AccountSessionManager.get(accountID).getLocalPreferences().emojiReactionsEnabled){
boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id); boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id);
items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !isMainStatus)); items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !isMainStatus, false));
} }
FooterStatusDisplayItem footer=null; FooterStatusDisplayItem footer=null;
if((flags & FLAG_NO_FOOTER)==0){ if((flags & FLAG_NO_FOOTER)==0){