diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java b/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java index 4d260489c..3b6315e39 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java @@ -11,6 +11,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.model.Status; import java.util.HashMap; +import java.util.function.Consumer; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; @@ -25,7 +26,7 @@ public class StatusInteractionController{ this.accountID=accountID; } - public void setFavorited(Status status, boolean favorited){ + public void setFavorited(Status status, boolean favorited, Consumer cb){ if(!Looper.getMainLooper().isCurrentThread()) throw new IllegalStateException("Can only be called from main thread"); @@ -38,6 +39,8 @@ public class StatusInteractionController{ @Override public void onSuccess(Status result){ runningFavoriteRequests.remove(status.id); + result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1); + cb.accept(result); E.post(new StatusCountersUpdatedEvent(result)); } @@ -46,24 +49,17 @@ public class StatusInteractionController{ runningFavoriteRequests.remove(status.id); error.showToast(MastodonApp.context); status.favourited=!favorited; - if(favorited) - status.favouritesCount--; - else - status.favouritesCount++; + cb.accept(status); E.post(new StatusCountersUpdatedEvent(status)); } }) .exec(accountID); runningFavoriteRequests.put(status.id, req); status.favourited=favorited; - if(favorited) - status.favouritesCount++; - else - status.favouritesCount--; E.post(new StatusCountersUpdatedEvent(status)); } - public void setReblogged(Status status, boolean reblogged){ + public void setReblogged(Status status, boolean reblogged, Consumer cb){ if(!Looper.getMainLooper().isCurrentThread()) throw new IllegalStateException("Can only be called from main thread"); @@ -76,6 +72,8 @@ public class StatusInteractionController{ @Override public void onSuccess(Status result){ runningReblogRequests.remove(status.id); + result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1); + cb.accept(result); E.post(new StatusCountersUpdatedEvent(result)); } @@ -84,24 +82,21 @@ public class StatusInteractionController{ runningReblogRequests.remove(status.id); error.showToast(MastodonApp.context); status.reblogged=!reblogged; - if(reblogged) - status.reblogsCount--; - else - status.reblogsCount++; + cb.accept(status); E.post(new StatusCountersUpdatedEvent(status)); } }) .exec(accountID); runningReblogRequests.put(status.id, req); status.reblogged=reblogged; - if(reblogged) - status.reblogsCount++; - else - status.reblogsCount--; E.post(new StatusCountersUpdatedEvent(status)); } public void setBookmarked(Status status, boolean bookmarked){ + setBookmarked(status, bookmarked, r->{}); + } + + public void setBookmarked(Status status, boolean bookmarked, Consumer cb){ if(!Looper.getMainLooper().isCurrentThread()) throw new IllegalStateException("Can only be called from main thread"); @@ -114,6 +109,7 @@ public class StatusInteractionController{ @Override public void onSuccess(Status result){ runningBookmarkRequests.remove(status.id); + cb.accept(result); E.post(new StatusCountersUpdatedEvent(result)); } @@ -122,6 +118,7 @@ public class StatusInteractionController{ runningBookmarkRequests.remove(status.id); error.showToast(MastodonApp.context); status.bookmarked=!bookmarked; + cb.accept(status); E.post(new StatusCountersUpdatedEvent(status)); } }) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java index dbabdd6bc..65cb7eff6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java @@ -4,9 +4,14 @@ import android.app.Activity; import android.content.Intent; import android.os.Build; import android.os.Bundle; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.ScaleAnimation; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -20,10 +25,8 @@ import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.ui.utils.UiUtils; import org.parceler.Parcels; -import java.text.DecimalFormat; - import me.grishka.appkit.Nav; -import me.grishka.appkit.utils.BindableViewHolder; +import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.V; public class FooterStatusDisplayItem extends StatusDisplayItem{ @@ -45,6 +48,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{ public static class Holder extends StatusDisplayItem.Holder{ private final TextView reply, boost, favorite; private final ImageView share; + private static final AnimationSet scaleDown, scaleUp; private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){ @Override @@ -55,6 +59,27 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{ } }; + static { + // 20dp to center in middle of icon, because: (icon width = 24dp) / 2 + (paddingStart = 8dp) + Animation scaleDownAnim = new ScaleAnimation(1, 0.85f, 1, 0.85f, Animation.ABSOLUTE, V.dp(20), Animation.RELATIVE_TO_SELF, 0.5f); + Animation scaleUpAnim = new ScaleAnimation(0.85f, 1, 0.85f, 1, Animation.ABSOLUTE, V.dp(20), Animation.RELATIVE_TO_SELF, 0.5f); + Animation opacityOutAnim = new AlphaAnimation(1, 0.75f); + Animation opacityInAnim = new AlphaAnimation(0.75f, 1); + + scaleDown = new AnimationSet(true); + scaleDown.setDuration(350); + scaleDown.setInterpolator(CubicBezierInterpolator.DEFAULT); + scaleDown.setFillAfter(true); + scaleDown.addAnimation(scaleDownAnim); + scaleDown.addAnimation(opacityOutAnim); + + scaleUp = new AnimationSet(true); + scaleUp.setDuration(100); + scaleUp.setInterpolator(CubicBezierInterpolator.DEFAULT); + scaleUp.addAnimation(scaleUpAnim); + scaleUp.addAnimation(opacityInAnim); + } + public Holder(Activity activity, ViewGroup parent){ super(activity, R.layout.display_item_footer, parent); reply=findViewById(R.id.reply); @@ -70,12 +95,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{ View boost=findViewById(R.id.boost_btn); View favorite=findViewById(R.id.favorite_btn); View share=findViewById(R.id.share_btn); + reply.setOnTouchListener(this::onButtonTouch); reply.setOnClickListener(this::onReplyClick); reply.setAccessibilityDelegate(buttonAccessibilityDelegate); + boost.setOnTouchListener(this::onButtonTouch); boost.setOnClickListener(this::onBoostClick); boost.setAccessibilityDelegate(buttonAccessibilityDelegate); + favorite.setOnTouchListener(this::onButtonTouch); favorite.setOnClickListener(this::onFavoriteClick); favorite.setAccessibilityDelegate(buttonAccessibilityDelegate); + share.setOnTouchListener(this::onButtonTouch); share.setOnClickListener(this::onShareClick); share.setAccessibilityDelegate(buttonAccessibilityDelegate); } @@ -102,25 +131,38 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{ } private void onReplyClick(View v){ + v.startAnimation(scaleUp); Bundle args=new Bundle(); args.putString("account", item.accountID); args.putParcelable("replyTo", Parcels.wrap(item.status)); Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); } + private boolean onButtonTouch(View v, MotionEvent event){ + if (event.getAction() == MotionEvent.ACTION_UP) v.performClick(); + else if (event.getAction() == MotionEvent.ACTION_DOWN) v.startAnimation(scaleDown); + else if (event.getAction() == MotionEvent.ACTION_CANCEL) v.startAnimation(scaleUp); + return true; + } + private void onBoostClick(View v){ - AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged); - boost.setSelected(item.status.reblogged); - bindButton(boost, item.status.reblogsCount); + AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, r->{ + v.startAnimation(scaleUp); + boost.setSelected(item.status.reblogged); + bindButton(boost, r.reblogsCount); + }); } private void onFavoriteClick(View v){ - AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited); - favorite.setSelected(item.status.favourited); - bindButton(favorite, item.status.favouritesCount); + AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{ + v.startAnimation(scaleUp); + favorite.setSelected(r.favourited); + bindButton(favorite, r.favouritesCount); + }); } private void onShareClick(View v){ + v.startAnimation(scaleUp); Intent intent=new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, item.status.url); diff --git a/mastodon/src/main/res/layout/display_item_footer.xml b/mastodon/src/main/res/layout/display_item_footer.xml index 594abc0e5..c084993ff 100644 --- a/mastodon/src/main/res/layout/display_item_footer.xml +++ b/mastodon/src/main/res/layout/display_item_footer.xml @@ -4,8 +4,7 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="48dp" - android:paddingLeft="20dp" - android:paddingRight="20dp"> + android:paddingHorizontal="28dp"> + android:layout_height="match_parent">