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.ScheduledStatus;
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.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
@@ -68,11 +70,12 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
instanceUser.emojis = List.of();
Status fakeStatus = a.toStatus();
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;
return List.of(
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;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Parcel
@@ -20,6 +22,7 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
public Instant updatedAt;
public boolean read;
public List<Emoji> emojis;
public List<EmojiReaction> reactions;
public List<Mention> mentions;
public List<Hashtag> tags;
@@ -41,10 +44,17 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
'}';
}
public Status toStatus() {
Status s = Status.ofFake(id, content, publishedAt);
s.createdAt = startsAt != null ? startsAt : publishedAt;
if (updatedAt != null) s.editedAt = updatedAt;
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();
if(reactions==null) reactions=new ArrayList<>();
}
public Status toStatus() {
Status s=Status.ofFake(id, content, publishedAt);
s.createdAt=startsAt != null ? startsAt : publishedAt;
s.reactions=reactions;
if(updatedAt != null) s.editedAt=updatedAt;
return s;
}

View File

@@ -100,7 +100,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
t.postprocess();
for(Emoji e:emojis)
e.postprocess();
if (mediaAttachments == null) mediaAttachments = List.of();
if (mediaAttachments == null) mediaAttachments=List.of();
for(Attachment a:mediaAttachments)
a.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) {
Status s = new Status();
s.id = id;
s.mediaAttachments = List.of();
s.createdAt = createdAt;
s.content = s.text = text;
s.spoilerText = "";
s.visibility = StatusPrivacy.PUBLIC;
s.mentions = List.of();
s.tags = List.of();
s.emojis = List.of();
s.filtered = List.of();
Status s=new Status();
s.id=id;
s.mediaAttachments=List.of();
s.createdAt=createdAt;
s.content=s.text=text;
s.spoilerText="";
s.visibility=StatusPrivacy.PUBLIC;
s.reactions=List.of();
s.mentions=List.of();
s.tags =List.of();
s.emojis=List.of();
s.filtered=List.of();
return s;
}
@@ -223,21 +224,21 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public static class StatusDeserializer implements JsonDeserializer<Status> {
@Override
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())
quote = gson.fromJson(obj.get("quote"), Status.class);
quote=gson.fromJson(obj.get("quote"), Status.class);
obj.remove("quote");
Status reblog = null;
Status reblog=null;
if (obj.has("reblog"))
reblog = gson.fromJson(obj.get("reblog"), Status.class);
reblog=gson.fromJson(obj.get("reblog"), Status.class);
obj.remove("reblog");
Status status = gsonWithoutDeserializer.fromJson(json, Status.class);
status.quote = quote;
status.reblog = reblog;
Status status=gsonWithoutDeserializer.fromJson(json, Status.class);
status.quote=quote;
status.reblog=reblog;
return status;
}

View File

@@ -7,7 +7,6 @@ import android.widget.Space;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Status;
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.R;
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.DeleteStatusReaction;
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.account_list.StatusEmojiReactionsListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Announcement;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiReaction;
import org.joinmastodon.android.model.Status;
@@ -54,14 +57,15 @@ import me.grishka.appkit.views.UsableRecyclerView;
public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
public final Status status;
private final Drawable placeholder;
private final boolean hideAdd;
private final boolean hideAdd, forAnnouncement;
private final String accountID;
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);
this.status=status;
this.hideAdd=hideAdd;
this.forAnnouncement=forAnnouncement;
this.accountID=accountID;
placeholder=parentFragment.getContext().getDrawable(R.drawable.image_placeholder).mutate();
placeholder.setBounds(0, 0, V.sp(24), V.sp(24));
@@ -91,6 +95,31 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
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 {
private final UsableRecyclerView list;
private final LinearLayout root, line;
@@ -129,7 +158,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
item.updateHidden();
root.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();
adapter.notifyDataSetChanged();
}
@@ -155,36 +189,24 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
private void addEmojiReaction(String emoji, Emoji info) {
if(item.status.reactions.stream().filter(r->r.name.equals(emoji) && r.me).findAny().isPresent()) return;
MastodonAPIRequest<Status> req = item.parentFragment.isInstanceAkkoma()
? 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;
boolean found=false;
for(int i=0; i<item.status.reactions.size(); i++){
EmojiReaction r=item.status.reactions.get(i);
if(r.name.equals(emoji)){
found=true;
r.add(me);
adapter.notifyItemChanged(i);
break;
}
}
if(!found){
item.status.reactions.add(info!=null ? EmojiReaction.of(info, me) : EmojiReaction.of(emoji, me));
adapter.notifyItemRangeInserted(item.status.reactions.size() - 1, 1);
}
E.post(new StatusCountersUpdatedEvent(item.status, adapter.parentHolder));
}
@Override
public void onError(ErrorResponse error) {
error.showToast(item.parentFragment.getContext());
}
})
.exec(item.accountID);
item.createRequest(emoji, false, ()->{
Account me=AccountSessionManager.get(item.accountID).self;
boolean found=false;
for(int i=0; i<item.status.reactions.size(); i++){
EmojiReaction r=item.status.reactions.get(i);
if(r.name.equals(emoji)){
found=true;
r.add(me);
adapter.notifyItemChanged(i);
break;
}
}
if(!found){
item.status.reactions.add(info!=null ? EmojiReaction.of(info, me) : EmojiReaction.of(emoji, me));
adapter.notifyItemRangeInserted(item.status.reactions.size() - 1, 1);
}
E.post(new StatusCountersUpdatedEvent(item.status, adapter.parentHolder));
}).exec(item.accountID);
}
@Override
@@ -298,42 +320,29 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null);
}
btn.setSelected(reaction.me);
btn.setOnClickListener(e -> {
btn.setOnClickListener(e->{
boolean deleting=reaction.me;
boolean ak=parent.parentFragment.isInstanceAkkoma();
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();
parent.createRequest(reaction.name, deleting, ()->{
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
for(int i=0; i<parent.status.reactions.size(); i++){
EmojiReaction r=parent.status.reactions.get(i);
if(!r.name.equals(reaction.name)) continue;
if(deleting && r.count==1) {
parent.status.reactions.remove(i);
adapter.notifyItemRemoved(i);
break;
}
r.me=!deleting;
if(deleting) r.count--;
else r.count++;
adapter.notifyItemChanged(i);
break;
}
for(int i=0; i<parent.status.reactions.size(); i++){
EmojiReaction r=parent.status.reactions.get(i);
if(!r.name.equals(reaction.name)) continue;
if(deleting && r.count==1) {
parent.status.reactions.remove(i);
adapter.notifyItemRemoved(i);
break;
}
r.me=!deleting;
if(deleting) r.count--;
else r.count++;
adapter.notifyItemChanged(i);
break;
}
E.post(new StatusCountersUpdatedEvent(parent.status, adapter.parentHolder));
adapter.parentHolder.imgLoader.updateImages();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(itemView.getContext());
}
})
.exec(parent.parentFragment.getAccountID());
E.post(new StatusCountersUpdatedEvent(parent.status, adapter.parentHolder));
adapter.parentHolder.imgLoader.updateImages();
}).exec(parent.parentFragment.getAccountID());
});
if (parent.parentFragment.isInstanceAkkoma()) {

View File

@@ -294,7 +294,7 @@ public abstract class StatusDisplayItem{
if((flags & FLAG_NO_EMOJI_REACTIONS)==0
&& AccountSessionManager.get(accountID).getLocalPreferences().emojiReactionsEnabled){
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;
if((flags & FLAG_NO_FOOTER)==0){