Impliment scrolling of the app bar. https://redmine.stoutner.com/issues/8
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / views / NestedScrollWebView.java
1 /*
2  * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.views;
21
22 import android.content.Context;
23 import android.util.AttributeSet;
24 import android.view.MotionEvent;
25 import android.webkit.WebView;
26
27 import androidx.core.view.NestedScrollingChild2;
28 import androidx.core.view.NestedScrollingChildHelper;
29 import androidx.core.view.ViewCompat;
30
31 // NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen).
32 public class NestedScrollWebView extends WebView implements NestedScrollingChild2 {
33     // The nested scrolling child helper is used throughout the class.
34     private NestedScrollingChildHelper nestedScrollingChildHelper;
35
36     // The previous Y position needs to be tracked between motion events.
37     private int previousYPosition;
38
39     // Basic constructor.
40     public NestedScrollWebView(Context context) {
41         // Roll up to the next constructor.
42         this(context, null);
43     }
44
45     // Intermediate constructor.
46     public NestedScrollWebView(Context context, AttributeSet attributeSet) {
47         // Roll up to the next constructor.
48         this(context, attributeSet, android.R.attr.webViewStyle);
49     }
50
51     // Full constructor.
52     public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
53         // Run the default commands.
54         super(context, attributeSet, defaultStyle);
55
56         // Initialize the nested scrolling child helper.
57         nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
58
59         // Enable nested scrolling by default.
60         nestedScrollingChildHelper.setNestedScrollingEnabled(true);
61     }
62
63     @Override
64     public boolean onTouchEvent(MotionEvent motionEvent) {
65         // Initialize a tracker to return if this motion event is handled.
66         boolean motionEventHandled;
67
68         // Run the commands for the given motion event action.
69         switch (motionEvent.getAction()) {
70             case MotionEvent.ACTION_DOWN:
71                 // Start nested scrolling along the vertical axis.  `ViewCompat` must be used until the minimum API >= 21.
72                 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
73
74                 // Save the current Y position.  Action down will not be called again until a new motion starts.
75                 previousYPosition = (int) motionEvent.getY();
76
77                 // Run the default commands.
78                 motionEventHandled = super.onTouchEvent(motionEvent);
79                 break;
80
81             case MotionEvent.ACTION_MOVE:
82                 // Get the current Y position.
83                 int currentYMotionPosition = (int) motionEvent.getY();
84
85                 // Calculate the pre-scroll delta Y.
86                 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
87
88                 // Initialize a variable to track how much of the scroll is consumed.
89                 int[] consumedScroll = new int[2];
90
91                 // Initialize a variable to track the offset in the window.
92                 int[] offsetInWindow = new int[2];
93
94                 // Get the WebView Y position.
95                 int webViewYPosition = getScrollY();
96
97                 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
98                 int scrollDeltaY = preScrollDeltaY;
99
100                 // Dispatch the nested pre-school.  This scrolls the app bar if it needs it.  `offsetInWindow` will be returned with an updated value.
101                 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
102                     // Update the scroll delta Y if some of it was consumed.
103                     scrollDeltaY = preScrollDeltaY - consumedScroll[1];
104                 }
105
106                 // Check to see if the WebView is at the top and and the scroll action is downward.
107                 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) {  // Swipe to refresh is being engaged.
108                     // Stop the nested scroll so that swipe to refresh has complete control.
109                     stopNestedScroll();
110                 } else {  // Swipe to refresh is not being engaged.
111                     // Start the nested scroll so that the app bar can scroll off the screen.
112                     startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
113
114                     // Dispatch the nested scroll.  This scrolls the WebView.  The delta Y unconsumed normally controls the swipe refresh layout, but that is handled with the `if` statement above.
115                     dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
116
117                     // Store the current Y position for use in the next action move.
118                     previousYPosition = previousYPosition - scrollDeltaY;
119                 }
120
121                 // Run the default commands.
122                 motionEventHandled = super.onTouchEvent(motionEvent);
123                 break;
124
125
126             default:
127                 // Stop nested scrolling.
128                 stopNestedScroll();
129
130                 // Run the default commands.
131                 motionEventHandled = super.onTouchEvent(motionEvent);
132         }
133
134         // Perform a click.  This is required by the Android accessibility guidelines.
135         performClick();
136
137         // Return the status of the motion event.
138         return motionEventHandled;
139     }
140
141     // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
142     @Override
143     public boolean performClick() {
144         return super.performClick();
145     }
146
147
148     // Method from NestedScrollingChild.
149     @Override
150     public void setNestedScrollingEnabled(boolean status) {
151         // Set the status of the nested scrolling.
152         nestedScrollingChildHelper.setNestedScrollingEnabled(status);
153     }
154
155     // Method from NestedScrollingChild.
156     @Override
157     public boolean isNestedScrollingEnabled() {
158         // Return the status of nested scrolling.
159         return nestedScrollingChildHelper.isNestedScrollingEnabled();
160     }
161
162
163     // Method from NestedScrollingChild.
164     @Override
165     public boolean startNestedScroll(int axes) {
166         // Start a nested scroll along the indicated axes.
167         return nestedScrollingChildHelper.startNestedScroll(axes);
168     }
169
170     // Method from NestedScrollingChild2.
171     @Override
172     public boolean startNestedScroll(int axes, int type) {
173         // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
174         return nestedScrollingChildHelper.startNestedScroll(axes, type);
175     }
176
177
178     // Method from NestedScrollingChild.
179     @Override
180     public void stopNestedScroll() {
181         // Stop the nested scroll.
182         nestedScrollingChildHelper.stopNestedScroll();
183     }
184
185     // Method from NestedScrollingChild2.
186     @Override
187     public void stopNestedScroll(int type) {
188         // Stop the nested scroll of the given type of input which caused the scroll event.
189         nestedScrollingChildHelper.stopNestedScroll(type);
190     }
191
192
193     // Method from NestedScrollingChild.
194     @Override
195     public boolean hasNestedScrollingParent() {
196         // Return the status of the nested scrolling parent.
197         return nestedScrollingChildHelper.hasNestedScrollingParent();
198     }
199
200     // Method from NestedScrollingChild2.
201     @Override
202     public boolean hasNestedScrollingParent(int type) {
203         // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
204         return nestedScrollingChildHelper.hasNestedScrollingParent(type);
205     }
206
207
208     // Method from NestedScrollingChild.
209     @Override
210     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
211         // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
212         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
213     }
214
215     // Method from NestedScrollingChild2.
216     @Override
217     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
218         // Dispatch a nested pre-scroll with the specified deltas for the given type of input which caused the scroll event, which lets a parent to consume some of the scroll if desired.
219         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
220     }
221
222
223     // Method from NestedScrollingChild.
224     @Override
225     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
226         // Dispatch a nested scroll with the specified deltas.
227         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
228     }
229
230     // Method from NestedScrollingChild2.
231     @Override
232     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
233         // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
234         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
235     }
236
237
238     // Method from NestedScrollingChild.
239     @Override
240     public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
241         // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
242         return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
243     }
244
245     // Method from NestedScrollingChild.
246     @Override
247     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
248         // Dispatch a nested fling with the specified velocity.
249         return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
250     }
251 }