]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/FabManager.java
96e9137286aa8d88227f8161371064752be4b77c
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / FabManager.java
1 /*
2  * Copyright © 2021 Damyan Ivanov.
3  * This file is part of MoLe.
4  * MoLe is free software: you can distribute it and/or modify it
5  * under the term of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your opinion), any later version.
8  *
9  * MoLe is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License terms for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
16  */
17
18 package net.ktnx.mobileledger.ui;
19
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.TimeInterpolator;
23 import android.annotation.SuppressLint;
24 import android.content.Context;
25 import android.os.Handler;
26 import android.view.MotionEvent;
27 import android.view.ViewGroup;
28 import android.view.ViewPropertyAnimator;
29
30 import androidx.annotation.NonNull;
31 import androidx.recyclerview.widget.RecyclerView;
32
33 import com.google.android.material.floatingactionbutton.FloatingActionButton;
34
35 import net.ktnx.mobileledger.utils.DimensionUtils;
36 import net.ktnx.mobileledger.utils.Logger;
37
38 public class FabManager {
39     private static final boolean FAB_SHOWN = true;
40     private static final boolean FAB_HIDDEN = false;
41     private static final int AUTO_SHOW_DELAY_MILLS = 4000;
42     private final FloatingActionButton fab;
43     private boolean wantedFabState = FAB_SHOWN;
44     private ViewPropertyAnimator fabSlideAnimator;
45     private int fabVerticalOffset;
46     public FabManager(FloatingActionButton fab) {
47         this.fab = fab;
48     }
49     public static void handle(FabHandler fabHandler, RecyclerView recyclerView) {
50         new ScrollFabHandler(fabHandler, recyclerView);
51     }
52     private void slideFabTo(int target, long duration, TimeInterpolator interpolator) {
53         fabSlideAnimator = fab.animate()
54                               .translationY((float) target)
55                               .setInterpolator(interpolator)
56                               .setDuration(duration)
57                               .setListener(new AnimatorListenerAdapter() {
58                                   public void onAnimationEnd(Animator animation) {
59                                       fabSlideAnimator = null;
60                                   }
61                               });
62     }
63     public void showFab() {
64         if (wantedFabState == FAB_SHOWN)
65             return;
66
67 //        b.btnAddTransaction.show();
68         if (this.fabSlideAnimator != null) {
69             this.fabSlideAnimator.cancel();
70             fab.clearAnimation();
71         }
72
73         wantedFabState = FAB_SHOWN;
74         slideFabTo(0, 200L,
75                 com.google.android.material.animation.AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
76     }
77     public void hideFab() {
78         if (wantedFabState == FAB_HIDDEN)
79             return;
80
81         calcVerticalFabOffset();
82
83 //        b.btnAddTransaction.hide();
84         if (this.fabSlideAnimator != null) {
85             this.fabSlideAnimator.cancel();
86             fab.clearAnimation();
87         }
88
89         wantedFabState = FAB_HIDDEN;
90         slideFabTo(fabVerticalOffset, 150L,
91                 com.google.android.material.animation.AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
92     }
93     private void calcVerticalFabOffset() {
94         if (fabVerticalOffset > 0)
95             return;// already calculated
96         fab.measure(0, 0);
97
98         int height = fab.getMeasuredHeight();
99
100         int bottomMargin;
101
102         ViewGroup.LayoutParams layoutParams = fab.getLayoutParams();
103         if (layoutParams instanceof ViewGroup.MarginLayoutParams)
104             bottomMargin = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
105         else
106             throw new RuntimeException("Unsupported layout params " + layoutParams.getClass()
107                                                                                   .getCanonicalName());
108
109         fabVerticalOffset = height + bottomMargin;
110     }
111     public interface FabHandler {
112         Context getContext();
113
114         void showManagedFab();
115
116         void hideManagedFab();
117     }
118
119     public static class ScrollFabHandler {
120         final private FabHandler fabHandler;
121         private int generation = 0;
122         @SuppressLint("ClickableViewAccessibility")
123         ScrollFabHandler(FabHandler fabHandler, RecyclerView recyclerView) {
124             this.fabHandler = fabHandler;
125             final float triggerAbsolutePixels = DimensionUtils.dp2px(fabHandler.getContext(), 20f);
126             final float triggerRelativePixels = triggerAbsolutePixels / 4f;
127             recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
128                 @Override
129                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
130                     Logger.debug("touch", "Scrolled " + dy);
131                     if (dy <= 0) {
132                         showFab();
133                     }
134                     else
135                         hideFab();
136
137                     super.onScrolled(recyclerView, dx, dy);
138                 }
139             });
140             recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
141                 private float absoluteAnchor = -1;
142                 @Override
143                 public boolean onInterceptTouchEvent(@NonNull RecyclerView rv,
144                                                      @NonNull MotionEvent e) {
145                     switch (e.getActionMasked()) {
146                         case MotionEvent.ACTION_DOWN:
147                             absoluteAnchor = e.getRawY();
148 //                        Logger.debug("touch",
149 //                                String.format(Locale.US, "Touch down at %4.2f", absoluteAnchor));
150                             break;
151                         case MotionEvent.ACTION_MOVE:
152                             if (absoluteAnchor < 0)
153                                 break;
154
155                             final float absoluteY = e.getRawY();
156 //                        Logger.debug("touch", String.format(Locale.US, "Move to %4.2f",
157 //                        absoluteY));
158
159                             if (absoluteY > absoluteAnchor + triggerAbsolutePixels) {
160                                 // swipe down
161 //                            Logger.debug("touch", "SHOW");
162                                 showFab();
163                                 absoluteAnchor = absoluteY;
164                             }
165                             else if (absoluteY < absoluteAnchor - triggerAbsolutePixels) {
166                                 // swipe up
167 //                            Logger.debug("touch", "HIDE");
168                                 hideFab();
169                                 absoluteAnchor = absoluteY;
170                             }
171
172                             break;
173                     }
174                     return false;
175                 }
176             });
177         }
178         private void hideFab() {
179             generation++;
180             int thisGeneration = generation;
181             fabHandler.hideManagedFab();
182             new Handler().postDelayed(() -> {
183                 if (generation != thisGeneration)
184                     return;
185
186                 showFab();
187             }, AUTO_SHOW_DELAY_MILLS);
188         }
189         private void showFab() {
190             generation++;
191             fabHandler.showManagedFab();
192         }
193     }
194 }