Add reaction when favoriting post on Iceshrimp

This commit is contained in:
Jacocococo 2024-08-05 18:02:27 +02:00
parent 86b6adf228
commit 3266a490be
6 changed files with 175 additions and 18 deletions

View File

@ -7,14 +7,23 @@ import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked; import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited; import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged; import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.ReblogDeletedEvent; import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.EmojiReaction;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@ -40,6 +49,9 @@ public class StatusInteractionController{
if(!Looper.getMainLooper().isCurrentThread()) if(!Looper.getMainLooper().isCurrentThread())
throw new IllegalStateException("Can only be called from main thread"); throw new IllegalStateException("Can only be called from main thread");
AccountSession session=AccountSessionManager.get(accountID);
Instance instance=session.getInstance().get();
SetStatusFavorited current=runningFavoriteRequests.remove(status.id); SetStatusFavorited current=runningFavoriteRequests.remove(status.id);
if(current!=null){ if(current!=null){
current.cancel(); current.cancel();
@ -52,6 +64,7 @@ public class StatusInteractionController{
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1)); result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
cb.accept(result); cb.accept(result);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
if(instance.isIceshrimp()) E.post(new EmojiReactionsUpdatedEvent(status.id, result.reactions, false, null));
} }
@Override @Override
@ -61,12 +74,55 @@ public class StatusInteractionController{
status.favourited=!favorited; status.favourited=!favorited;
cb.accept(status); cb.accept(status);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(instance.isIceshrimp()) E.post(new EmojiReactionsUpdatedEvent(status.id, status.reactions, false, null));
} }
}) })
.exec(accountID); .exec(accountID);
runningFavoriteRequests.put(status.id, req); runningFavoriteRequests.put(status.id, req);
status.favourited=favorited; status.favourited=favorited;
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
String defaultReactionEmojiRaw=instance.configuration.reactions.defaultReaction;
if(!instance.isIceshrimp() || defaultReactionEmojiRaw==null)
return;
boolean reactionIsCustom=defaultReactionEmojiRaw.startsWith(":");
String defaultReactionEmoji=reactionIsCustom ? defaultReactionEmojiRaw.substring(1, defaultReactionEmojiRaw.length()-1) : defaultReactionEmojiRaw;
ArrayList<EmojiReaction> reactions=new ArrayList<>(status.reactions.size());
for(EmojiReaction reaction:status.reactions){
reactions.add(reaction.copy());
}
Optional<EmojiReaction> existingReaction=reactions.stream().filter(r->r.me).findFirst();
Optional<EmojiReaction> existingDefaultReaction=reactions.stream().filter(r->r.name.equals(defaultReactionEmoji)).findFirst();
if(existingReaction.isPresent() && !favorited){
existingReaction.get().me=false;
existingReaction.get().count--;
existingReaction.get().pendingChange=true;
}else if(existingDefaultReaction.isPresent() && favorited){
existingDefaultReaction.get().count++;
existingDefaultReaction.get().me=true;
existingDefaultReaction.get().pendingChange=true;
}else if(favorited){
EmojiReaction reaction=null;
if(reactionIsCustom){
List<EmojiCategory> customEmojis=AccountSessionManager.getInstance().getCustomEmojis(session.domain);
for(EmojiCategory category:customEmojis){
for(Emoji emoji:category.emojis){
if(emoji.shortcode.equals(defaultReactionEmoji)){
reaction=EmojiReaction.of(emoji, session.self);
break;
}
}
}
if(reaction==null)
reaction=EmojiReaction.of(defaultReactionEmoji, session.self);
}else{
reaction=EmojiReaction.of(defaultReactionEmoji, session.self);
}
reaction.pendingChange=true;
reactions.add(reaction);
}
E.post(new EmojiReactionsUpdatedEvent(status.id, reactions, false, null));
} }
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){ public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){

View File

@ -275,13 +275,17 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){ public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
for(Notification n : data){ for(Notification n : data){
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){ if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
n.status.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
for(int i=0; i<list.getChildCount(); i++){ for(int i=0; i<list.getChildCount(); i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i)); RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){ if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
reactions.rebind(); reactions.updateReactions(ev.reactions);
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){ }
}
n.status.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
text.rebind(); text.rebind();
} }
} }

View File

@ -286,13 +286,17 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){ public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
for(Status s:data){ for(Status s:data){
if(s.getContentStatus().id.equals(ev.id)){ if(s.getContentStatus().id.equals(ev.id)){
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
reactions.updateReactions(ev.reactions);
}
}
s.getContentStatus().update(ev); s.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateStatus(s); AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
for(int i=0;i<list.getChildCount();i++){ for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i)); RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){ if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
reactions.rebind();
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
text.rebind(); text.rebind();
} }
} }

View File

@ -22,6 +22,7 @@ public class EmojiReaction {
public String staticUrl; public String staticUrl;
public transient ImageLoaderRequest request; public transient ImageLoaderRequest request;
public transient boolean pendingChange=false;
public String getUrl(boolean playGifs){ public String getUrl(boolean playGifs){
String idealUrl=playGifs ? url : staticUrl; String idealUrl=playGifs ? url : staticUrl;
@ -60,4 +61,18 @@ public class EmojiReaction {
accounts.add(self); accounts.add(self);
accountIds.add(self.id); accountIds.add(self.id);
} }
public EmojiReaction copy() {
EmojiReaction r=new EmojiReaction();
r.accounts=accounts;
r.accountIds=accountIds;
r.count=count;
r.me=me;
r.name=name;
r.url=url;
r.staticUrl=staticUrl;
r.request=request;
r.pendingChange=pendingChange;
return r;
}
} }

View File

@ -227,6 +227,7 @@ public class Instance extends BaseModel{
@Parcel @Parcel
public static class ReactionsConfiguration { public static class ReactionsConfiguration {
public int maxReactions; public int maxReactions;
public String defaultReaction;
} }
@Parcel @Parcel

View File

@ -47,6 +47,8 @@ import org.joinmastodon.android.ui.utils.TextDrawable;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.EmojiReactionButton; import org.joinmastodon.android.ui.views.EmojiReactionButton;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
@ -196,17 +198,23 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
emojiKeyboard.setListener(this); emojiKeyboard.setListener(this);
space.setVisibility(View.GONE); space.setVisibility(View.GONE);
root.addView(emojiKeyboard.getView()); root.addView(emojiKeyboard.getView());
boolean hidden=item.isHidden(); updateVisibility(item.isHidden(), true);
root.setVisibility(hidden ? View.GONE : View.VISIBLE); imgLoader.updateImages();
line.setVisibility(hidden ? View.GONE : View.VISIBLE); adapter.notifyDataSetChanged();
}
private void updateVisibility(boolean hidden, boolean force){
int visibility=hidden ? View.GONE : View.VISIBLE;
if(!force && visibility==root.getVisibility())
return;
root.setVisibility(visibility);
line.setVisibility(visibility);
line.setPadding( line.setPadding(
list.getPaddingLeft(), list.getPaddingLeft(),
hidden ? 0 : V.dp(8), hidden ? 0 : V.dp(8),
list.getPaddingRight(), list.getPaddingRight(),
item.forAnnouncement ? V.dp(8) : 0 item.forAnnouncement ? V.dp(8) : 0
); );
imgLoader.updateImages();
adapter.notifyDataSetChanged();
} }
private void hideEmojiKeyboard(){ private void hideEmojiKeyboard(){
@ -267,7 +275,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext()); RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
scroller.setTargetPosition(pos); scroller.setTargetPosition(pos);
list.getLayoutManager().startSmoothScroll(scroller); list.getLayoutManager().startSmoothScroll(scroller);
updateAddButtonClickable(false); updateMeReactionCount(false);
}else{ }else{
finalExisting.add(me); finalExisting.add(me);
adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting)); adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting));
@ -297,8 +305,9 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
} }
} }
private void updateAddButtonClickable(boolean deleting) { private void updateAddButtonClickable() {
meReactionCount+=deleting ? -1 : 1; if(instance==null || instance.configuration==null || instance.configuration.reactions==null || instance.configuration.reactions.maxReactions==0)
return;
boolean canReact=meReactionCount<instance.configuration.reactions.maxReactions; boolean canReact=meReactionCount<instance.configuration.reactions.maxReactions;
addButton.setClickable(canReact); addButton.setClickable(canReact);
@ -310,6 +319,68 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
anim.start(); anim.start();
} }
private void updateMeReactionCount(boolean deleting) {
meReactionCount=Math.max(0, meReactionCount + (deleting ? -1 : 1));
updateAddButtonClickable();
}
public void updateReactions(List<EmojiReaction> reactions){
for(int i=0;i<item.status.reactions.size();i++){
EmojiReaction reaction=item.status.reactions.get(i);
Optional<EmojiReaction> newReactionOptional=reactions.stream().filter(r->r.name.equals(reaction.name)).findFirst();
if(newReactionOptional.isEmpty()){ // deleted reactions
adapter.notifyItemRemoved(i);
continue;
}
// changed reactions
EmojiReaction newReaction=newReactionOptional.get();
if(reaction.count!=newReaction.count || reaction.me!=newReaction.me || reaction.pendingChange!=newReaction.pendingChange){
if(newReaction.pendingChange){
View holderView=list.getChildAt(i);
if(holderView!=null){
EmojiReactionViewHolder reactionHolder=(EmojiReactionViewHolder) list.getChildViewHolder(holderView);
item.setActionProgressVisible(reactionHolder, true);
}
}else{
adapter.notifyItemChanged(i);
}
}
}
boolean pendingAddReaction=false;
for(EmojiReaction reaction:reactions){
if(item.status.reactions.stream().anyMatch(r->r.name.equals(reaction.name)))
continue;
// new reactions
if(reaction.pendingChange){
pendingAddReaction=true;
continue;
}
int pos=item.status.reactions.size();
item.status.reactions.add(pos, reaction);
adapter.notifyItemInserted(pos);
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
scroller.setTargetPosition(pos);
list.getLayoutManager().startSmoothScroll(scroller);
}
if(pendingAddReaction){
progress.setVisibility(View.VISIBLE);
addButton.setClickable(false);
addButton.setAlpha(ALPHA_DISABLED);
}else{
progress.setVisibility(View.GONE);
}
int newMeReactionCount=(int) reactions.stream().filter(r->r.me || r.pendingChange).count();
if (newMeReactionCount!=meReactionCount){
meReactionCount=newMeReactionCount;
updateAddButtonClickable();
}
updateVisibility(reactions.isEmpty() && item.hideEmpty, false);
}
@Override @Override
public void setImage(int index, Drawable image){ public void setImage(int index, Drawable image){
View child=list.getChildAt(index); View child=list.getChildAt(index);
@ -386,6 +457,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
@Override @Override
public void onBind(Pair<EmojiReactionsStatusDisplayItem, EmojiReaction> item){ public void onBind(Pair<EmojiReactionsStatusDisplayItem, EmojiReaction> item){
if(item.second.pendingChange){
itemView.setVisibility(View.GONE);
return;
}else{
itemView.setVisibility(View.VISIBLE);
}
item.first.setActionProgressVisible(this, false); item.first.setActionProgressVisible(this, false);
EmojiReactionsStatusDisplayItem parent=item.first; EmojiReactionsStatusDisplayItem parent=item.first;
EmojiReaction reaction=item.second; EmojiReaction reaction=item.second;
@ -435,7 +512,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
Instance instance=parent.parentFragment.getInstance().get(); Instance instance=parent.parentFragment.getInstance().get();
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){ if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){
adapter.parentHolder.updateAddButtonClickable(deleting); adapter.parentHolder.updateMeReactionCount(deleting);
} }
if(instance.isIceshrimp() && status!=null){ if(instance.isIceshrimp() && status!=null){
parent.parentFragment.onFavoriteChanged(status, adapter.parentHolder.getItemID()); parent.parentFragment.onFavoriteChanged(status, adapter.parentHolder.getItemID());