New and improved™ "new posts" button (AND-102)

This commit is contained in:
Grishka 2023-10-17 04:31:07 +03:00
parent d3dc774492
commit 26b99f5f68
10 changed files with 203 additions and 69 deletions

View File

@ -4,10 +4,11 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.StateListAnimator;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
@ -15,6 +16,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
@ -42,7 +44,6 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewcontrollers.HomeTimelineMenuController;
import org.joinmastodon.android.ui.viewcontrollers.ToolbarDropdownMenuController;
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
@ -68,7 +69,8 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
private LinearLayout listsDropdown;
private FixedAspectRatioImageView listsDropdownArrow;
private TextView listsDropdownText;
private Button toolbarShowNewPostsBtn;
private Button newPostsBtn;
private StateListAnimator newPostsBtnStateAnimator;
private boolean newPostsBtnShown;
private AnimatorSet currentNewPostsAnim;
private ToolbarDropdownMenuController dropdownController;
@ -81,7 +83,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
private String lastSavedMarkerID;
public HomeTimelineFragment(){
setListLayoutId(R.layout.recycler_fragment_with_fab);
setListLayoutId(R.layout.fragment_timeline);
}
@Override
@ -191,6 +193,20 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
newPostsBtn=view.findViewById(R.id.new_posts_btn);
newPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
newPostsBtnStateAnimator=newPostsBtn.getStateListAnimator();
if(newPostsBtnShown){
newPostsBtn.setVisibility(View.VISIBLE);
}else{
newPostsBtn.setVisibility(View.GONE);
newPostsBtn.setStateListAnimator(null);
newPostsBtn.setScaleX(0.9f);
newPostsBtn.setScaleY(0.9f);
newPostsBtn.setAlpha(0f);
newPostsBtn.setTranslationY(V.dp(-56));
}
updateToolbarLogo();
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
@ -467,44 +483,10 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
listsDropdown.setBackgroundTintList(listsDropdownText.getTextColors());
listsDropdown.addView(listsDropdownText, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
toolbarShowNewPostsBtn=new Button(getActivity());
toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium);
toolbarShowNewPostsBtn.setTextColor(0xffffffff);
toolbarShowNewPostsBtn.setStateListAnimator(null);
toolbarShowNewPostsBtn.setBackgroundResource(R.drawable.bg_button_new_posts);
toolbarShowNewPostsBtn.setText(R.string.see_new_posts);
toolbarShowNewPostsBtn.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_fluent_arrow_up_16_filled, 0, 0, 0);
toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors());
toolbarShowNewPostsBtn.setCompoundDrawablePadding(V.dp(8));
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn);
toolbarShowNewPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
if(newPostsBtnShown){
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
listsDropdown.setVisibility(View.INVISIBLE);
listsDropdown.setAlpha(0f);
}else{
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
toolbarShowNewPostsBtn.setAlpha(0f);
toolbarShowNewPostsBtn.setScaleX(.8f);
toolbarShowNewPostsBtn.setScaleY(.8f);
listsDropdown.setVisibility(View.VISIBLE);
}
FrameLayout logoWrap=new FrameLayout(getActivity()){
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom){
super.onLayout(changed, left, top, right, bottom);
// I'm sorry for doing this. This centers the button within the entire toolbar
int rightGap=getToolbar().getWidth()-right;
toolbarShowNewPostsBtn.offsetLeftAndRight((rightGap-left)/2);
}
};
FrameLayout logoWrap=new FrameLayout(getActivity());
FrameLayout.LayoutParams ddlp=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.START);
ddlp.topMargin=ddlp.bottomMargin=V.dp(8);
logoWrap.addView(listsDropdown, ddlp);
logoWrap.addView(toolbarShowNewPostsBtn, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(32), Gravity.CENTER));
Toolbar toolbar=getToolbar();
toolbar.addView(logoWrap, new Toolbar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
@ -518,21 +500,21 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
if(currentNewPostsAnim!=null){
currentNewPostsAnim.cancel();
}
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
newPostsBtn.setVisibility(View.VISIBLE);
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(listsDropdown, View.ALPHA, 0f),
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 1f),
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, 1f),
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f)
ObjectAnimator.ofFloat(newPostsBtn, View.ALPHA, 1f),
ObjectAnimator.ofFloat(newPostsBtn, View.SCALE_X, 1f),
ObjectAnimator.ofFloat(newPostsBtn, View.SCALE_Y, 1f),
ObjectAnimator.ofFloat(newPostsBtn, View.TRANSLATION_Y, 0f)
);
set.setDuration(300);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.setDuration(getResources().getInteger(R.integer.m3_sys_motion_duration_medium3));
set.setInterpolator(AnimationUtils.loadInterpolator(getActivity(), R.interpolator.m3_sys_motion_easing_standard_decelerate));
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
listsDropdown.setVisibility(View.INVISIBLE);
currentNewPostsAnim=null;
newPostsBtn.setStateListAnimator(newPostsBtnStateAnimator);
}
});
currentNewPostsAnim=set;
@ -546,20 +528,23 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
if(currentNewPostsAnim!=null){
currentNewPostsAnim.cancel();
}
listsDropdown.setVisibility(View.VISIBLE);
float scale=newPostsBtn.getScaleX();
float alpha=newPostsBtn.getAlpha();
newPostsBtnStateAnimator.jumpToCurrentState();
newPostsBtn.setStateListAnimator(null);
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(listsDropdown, View.ALPHA, 1f),
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 0f),
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, .8f),
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f)
ObjectAnimator.ofFloat(newPostsBtn, View.ALPHA, alpha, 0f),
ObjectAnimator.ofFloat(newPostsBtn, View.SCALE_X, scale, scale*.9f),
ObjectAnimator.ofFloat(newPostsBtn, View.SCALE_Y, scale, scale*.9f),
ObjectAnimator.ofFloat(newPostsBtn, View.TRANSLATION_Y, V.dp(-56))
);
set.setDuration(300);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.setDuration(getResources().getInteger(R.integer.m3_sys_motion_duration_medium3));
set.setInterpolator(AnimationUtils.loadInterpolator(getActivity(), R.interpolator.m3_sys_motion_easing_standard_accelerate));
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
newPostsBtn.setVisibility(View.GONE);
currentNewPostsAnim=null;
}
});

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
android:propertyName="scaleX"
android:valueTo="0.8"
android:valueType="floatType"/>
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
android:propertyName="scaleY"
android:valueTo="0.8"
android:valueType="floatType"/>
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
android:propertyName="alpha"
android:valueTo="0.5"
android:valueType="floatType"/>
</set>
</item>
<item>
<set>
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
android:propertyName="scaleX"
android:valueTo="1"
android:valueType="floatType"/>
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
android:propertyName="scaleY"
android:valueTo="1"
android:valueType="floatType"/>
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
android:propertyName="alpha"
android:valueTo="1"
android:valueType="floatType"/>
</set>
</item>
</selector>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/highlight_over_dark">
<item>
<shape>
<solid android:color="?android:colorAccent"/>
<corners android:radius="16dp"/>
<padding android:left="16dp" android:right="16dp"/>
</shape>
</item>
</ripple>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11,20V7.825L5.4,13.425L4,12L12,4L20,12L18.6,13.425L13,7.825V20Z"/>
</vector>

View File

@ -1,3 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="16dp" android:height="16dp" android:viewportWidth="16" android:viewportHeight="16">
<path android:pathData="M8 14c-0.414 0-0.75-0.336-0.75-0.75V4.463L4.309 7.75c-0.276 0.31-0.75 0.335-1.06 0.06-0.308-0.276-0.334-0.75-0.058-1.06L7.441 2C7.583 1.842 7.787 1.75 8 1.75c0.213 0 0.417 0.09 0.559 0.25l4.25 4.75c0.276 0.309 0.25 0.783-0.059 1.059-0.309 0.276-0.783 0.25-1.059-0.059L8.75 4.463v8.787C8.75 13.664 8.414 14 8 14z" android:fillColor="#fff"/>
</vector>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="@dimen/m3_sys_motion_easing_emphasized_accelerate_control_x1"
android:controlY1="@dimen/m3_sys_motion_easing_emphasized_accelerate_control_y1"
android:controlX2="@dimen/m3_sys_motion_easing_emphasized_accelerate_control_x2"
android:controlY2="@dimen/m3_sys_motion_easing_emphasized_accelerate_control_y2"/>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="@dimen/m3_sys_motion_easing_standard_accelerate_control_x1"
android:controlY1="@dimen/m3_sys_motion_easing_standard_accelerate_control_y1"
android:controlX2="@dimen/m3_sys_motion_easing_standard_accelerate_control_x2"
android:controlY2="@dimen/m3_sys_motion_easing_standard_accelerate_control_y2"/>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<me.grishka.appkit.views.RecursiveSwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/content_wrap"
android:layout_width="match_parent"
android:layout_height="match_parent">
<me.grishka.appkit.views.UsableRecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:clipToPadding="false"/>
<ViewStub android:layout="?emptyViewLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/empty"/>
<ImageButton
android:id="@+id/fab"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_gravity="end|bottom"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/bg_fab"
android:tint="?colorM3Primary"
android:scaleType="center"
android:stateListAnimator="@animator/fab_shadow"
android:contentDescription="@string/new_post"
android:src="@drawable/ic_edit_24px"/>
<Button
android:id="@+id/new_posts_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="16dp"
android:background="@drawable/round_rect"
android:backgroundTint="?colorM3Primary"
android:paddingHorizontal="16dp"
android:paddingVertical="0dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3OnPrimary"
android:drawableStart="@drawable/ic_arrow_upward_24px"
android:drawableTint="?colorM3OnPrimary"
android:drawablePadding="8dp"
android:elevation="@dimen/m3_sys_elevation_level4"
android:stateListAnimator="@animator/squish"
android:text="@string/see_new_posts"/>
</FrameLayout>
</me.grishka.appkit.views.RecursiveSwipeRefreshLayout>

View File

@ -267,7 +267,7 @@
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">These are all the posts from all users in your server (%s).</string>
<string name="recommended_accounts_info_banner">You might like these accounts based on others you follow.</string>
<string name="see_new_posts">See new posts</string>
<string name="see_new_posts">New posts</string>
<string name="load_missing_posts">Load missing posts</string>
<string name="follow_back">Follow Back</string>
<string name="button_follow_pending">Pending</string>

View File

@ -109,4 +109,9 @@
<!-- Linear motion path. -->
<integer name="m3_sys_motion_path">0</integer>
<dimen name="m3_sys_elevation_level1">1dp</dimen>
<dimen name="m3_sys_elevation_level2">3dp</dimen>
<dimen name="m3_sys_elevation_level3">6dp</dimen>
<dimen name="m3_sys_elevation_level4">8dp</dimen>
<dimen name="m3_sys_elevation_level5">12dp</dimen>
</resources>