animate footer icons

closes sk22#154
This commit is contained in:
sk 2022-12-17 19:48:20 +01:00
parent e0febda372
commit a7c707f62e
3 changed files with 79 additions and 38 deletions

View File

@ -11,6 +11,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import java.util.HashMap; import java.util.HashMap;
import java.util.function.Consumer;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@ -25,7 +26,7 @@ public class StatusInteractionController{
this.accountID=accountID; this.accountID=accountID;
} }
public void setFavorited(Status status, boolean favorited){ public void setFavorited(Status status, boolean favorited, Consumer<Status> cb){
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");
@ -38,6 +39,8 @@ public class StatusInteractionController{
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
runningFavoriteRequests.remove(status.id); runningFavoriteRequests.remove(status.id);
result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1);
cb.accept(result);
E.post(new StatusCountersUpdatedEvent(result)); E.post(new StatusCountersUpdatedEvent(result));
} }
@ -46,24 +49,17 @@ public class StatusInteractionController{
runningFavoriteRequests.remove(status.id); runningFavoriteRequests.remove(status.id);
error.showToast(MastodonApp.context); error.showToast(MastodonApp.context);
status.favourited=!favorited; status.favourited=!favorited;
if(favorited) cb.accept(status);
status.favouritesCount--;
else
status.favouritesCount++;
E.post(new StatusCountersUpdatedEvent(status)); E.post(new StatusCountersUpdatedEvent(status));
} }
}) })
.exec(accountID); .exec(accountID);
runningFavoriteRequests.put(status.id, req); runningFavoriteRequests.put(status.id, req);
status.favourited=favorited; status.favourited=favorited;
if(favorited)
status.favouritesCount++;
else
status.favouritesCount--;
E.post(new StatusCountersUpdatedEvent(status)); E.post(new StatusCountersUpdatedEvent(status));
} }
public void setReblogged(Status status, boolean reblogged){ public void setReblogged(Status status, boolean reblogged, Consumer<Status> cb){
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");
@ -76,6 +72,8 @@ public class StatusInteractionController{
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
runningReblogRequests.remove(status.id); runningReblogRequests.remove(status.id);
result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1);
cb.accept(result);
E.post(new StatusCountersUpdatedEvent(result)); E.post(new StatusCountersUpdatedEvent(result));
} }
@ -84,24 +82,21 @@ public class StatusInteractionController{
runningReblogRequests.remove(status.id); runningReblogRequests.remove(status.id);
error.showToast(MastodonApp.context); error.showToast(MastodonApp.context);
status.reblogged=!reblogged; status.reblogged=!reblogged;
if(reblogged) cb.accept(status);
status.reblogsCount--;
else
status.reblogsCount++;
E.post(new StatusCountersUpdatedEvent(status)); E.post(new StatusCountersUpdatedEvent(status));
} }
}) })
.exec(accountID); .exec(accountID);
runningReblogRequests.put(status.id, req); runningReblogRequests.put(status.id, req);
status.reblogged=reblogged; status.reblogged=reblogged;
if(reblogged)
status.reblogsCount++;
else
status.reblogsCount--;
E.post(new StatusCountersUpdatedEvent(status)); E.post(new StatusCountersUpdatedEvent(status));
} }
public void setBookmarked(Status status, boolean bookmarked){ public void setBookmarked(Status status, boolean bookmarked){
setBookmarked(status, bookmarked, r->{});
}
public void setBookmarked(Status status, boolean bookmarked, Consumer<Status> cb){
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");
@ -114,6 +109,7 @@ public class StatusInteractionController{
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
runningBookmarkRequests.remove(status.id); runningBookmarkRequests.remove(status.id);
cb.accept(result);
E.post(new StatusCountersUpdatedEvent(result)); E.post(new StatusCountersUpdatedEvent(result));
} }
@ -122,6 +118,7 @@ public class StatusInteractionController{
runningBookmarkRequests.remove(status.id); runningBookmarkRequests.remove(status.id);
error.showToast(MastodonApp.context); error.showToast(MastodonApp.context);
status.bookmarked=!bookmarked; status.bookmarked=!bookmarked;
cb.accept(status);
E.post(new StatusCountersUpdatedEvent(status)); E.post(new StatusCountersUpdatedEvent(status));
} }
}) })

View File

@ -4,9 +4,14 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo; 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.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@ -20,10 +25,8 @@ import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.text.DecimalFormat;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class FooterStatusDisplayItem extends StatusDisplayItem{ public class FooterStatusDisplayItem extends StatusDisplayItem{
@ -45,6 +48,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{ public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
private final TextView reply, boost, favorite; private final TextView reply, boost, favorite;
private final ImageView share; private final ImageView share;
private static final AnimationSet scaleDown, scaleUp;
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){ private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
@Override @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){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_footer, parent); super(activity, R.layout.display_item_footer, parent);
reply=findViewById(R.id.reply); reply=findViewById(R.id.reply);
@ -70,12 +95,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
View boost=findViewById(R.id.boost_btn); View boost=findViewById(R.id.boost_btn);
View favorite=findViewById(R.id.favorite_btn); View favorite=findViewById(R.id.favorite_btn);
View share=findViewById(R.id.share_btn); View share=findViewById(R.id.share_btn);
reply.setOnTouchListener(this::onButtonTouch);
reply.setOnClickListener(this::onReplyClick); reply.setOnClickListener(this::onReplyClick);
reply.setAccessibilityDelegate(buttonAccessibilityDelegate); reply.setAccessibilityDelegate(buttonAccessibilityDelegate);
boost.setOnTouchListener(this::onButtonTouch);
boost.setOnClickListener(this::onBoostClick); boost.setOnClickListener(this::onBoostClick);
boost.setAccessibilityDelegate(buttonAccessibilityDelegate); boost.setAccessibilityDelegate(buttonAccessibilityDelegate);
favorite.setOnTouchListener(this::onButtonTouch);
favorite.setOnClickListener(this::onFavoriteClick); favorite.setOnClickListener(this::onFavoriteClick);
favorite.setAccessibilityDelegate(buttonAccessibilityDelegate); favorite.setAccessibilityDelegate(buttonAccessibilityDelegate);
share.setOnTouchListener(this::onButtonTouch);
share.setOnClickListener(this::onShareClick); share.setOnClickListener(this::onShareClick);
share.setAccessibilityDelegate(buttonAccessibilityDelegate); share.setAccessibilityDelegate(buttonAccessibilityDelegate);
} }
@ -102,25 +131,38 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void onReplyClick(View v){ private void onReplyClick(View v){
v.startAnimation(scaleUp);
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", item.accountID); args.putString("account", item.accountID);
args.putParcelable("replyTo", Parcels.wrap(item.status)); args.putParcelable("replyTo", Parcels.wrap(item.status));
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); 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){ private void onBoostClick(View v){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged); AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, r->{
boost.setSelected(item.status.reblogged); v.startAnimation(scaleUp);
bindButton(boost, item.status.reblogsCount); boost.setSelected(item.status.reblogged);
bindButton(boost, r.reblogsCount);
});
} }
private void onFavoriteClick(View v){ private void onFavoriteClick(View v){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited); AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
favorite.setSelected(item.status.favourited); v.startAnimation(scaleUp);
bindButton(favorite, item.status.favouritesCount); favorite.setSelected(r.favourited);
bindButton(favorite, r.favouritesCount);
});
} }
private void onShareClick(View v){ private void onShareClick(View v){
v.startAnimation(scaleUp);
Intent intent=new Intent(Intent.ACTION_SEND); Intent intent=new Intent(Intent.ACTION_SEND);
intent.setType("text/plain"); intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, item.status.url); intent.putExtra(Intent.EXTRA_TEXT, item.status.url);

View File

@ -4,8 +4,7 @@
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:paddingLeft="20dp" android:paddingHorizontal="28dp">
android:paddingRight="20dp">
<FrameLayout <FrameLayout
android:id="@+id/reply_btn" android:id="@+id/reply_btn"
@ -15,10 +14,11 @@
<TextView <TextView
android:id="@+id/reply" android:id="@+id/reply"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center_vertical"
android:drawableStart="@drawable/ic_fluent_chat_multiple_24_regular" android:drawableStart="@drawable/ic_fluent_chat_multiple_24_regular"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:paddingHorizontal="8dp"
android:drawableTint="?android:textColorSecondary" android:drawableTint="?android:textColorSecondary"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large" android:textAppearance="@style/m3_label_large"
@ -38,10 +38,11 @@
<TextView <TextView
android:id="@+id/boost" android:id="@+id/boost"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center_vertical"
android:drawableStart="@drawable/ic_boost" android:drawableStart="@drawable/ic_boost"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:paddingHorizontal="8dp"
android:drawableTint="@color/boost_icon" android:drawableTint="@color/boost_icon"
android:textColor="@color/boost_icon" android:textColor="@color/boost_icon"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -62,10 +63,11 @@
<TextView <TextView
android:id="@+id/favorite" android:id="@+id/favorite"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center_vertical"
android:drawableStart="@drawable/ic_fluent_star_24_selector" android:drawableStart="@drawable/ic_fluent_star_24_selector"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:paddingHorizontal="8dp"
android:drawableTint="@color/favorite_icon" android:drawableTint="@color/favorite_icon"
android:textColor="@color/favorite_icon" android:textColor="@color/favorite_icon"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -81,14 +83,14 @@
<FrameLayout <FrameLayout
android:id="@+id/share_btn" android:id="@+id/share_btn"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent">
android:minWidth="56dp">
<ImageView <ImageView
android:id="@+id/share" android:id="@+id/share"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center_vertical"
android:src="@drawable/ic_fluent_share_24_regular" android:src="@drawable/ic_fluent_share_24_regular"
android:paddingHorizontal="8dp"
android:tint="?android:textColorSecondary" android:tint="?android:textColorSecondary"
android:gravity="center_vertical"/> android:gravity="center_vertical"/>
</FrameLayout> </FrameLayout>