Update the URL edit text when switching tabs.
[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     // Track if domain settings are applied to this nested scroll WebView and, if so, the database ID.
40     private boolean domainSettingsApplied;
41     private int domainSettingsDatabaseId;
42
43     // Basic constructor.
44     public NestedScrollWebView(Context context) {
45         // Roll up to the next constructor.
46         this(context, null);
47     }
48
49     // Intermediate constructor.
50     public NestedScrollWebView(Context context, AttributeSet attributeSet) {
51         // Roll up to the next constructor.
52         this(context, attributeSet, android.R.attr.webViewStyle);
53     }
54
55     // Full constructor.
56     public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
57         // Run the default commands.
58         super(context, attributeSet, defaultStyle);
59
60         // Initialize the nested scrolling child helper.
61         nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
62
63         // Enable nested scrolling by default.
64         nestedScrollingChildHelper.setNestedScrollingEnabled(true);
65     }
66
67     public void setDomainSettingsApplied(boolean applied) {
68         // Store the domain settings applied status.
69         domainSettingsApplied = applied;
70     }
71
72     public boolean getDomainSettingsApplied() {
73         // Return the domain settings applied status.
74         return domainSettingsApplied;
75     }
76
77     public void setDomainSettingsDatabaseId(int databaseId) {
78         // Store the domain settings database ID.
79         domainSettingsDatabaseId = databaseId;
80     }
81
82     public int getDomainSettingsDatabaseId() {
83         // Return the domain settings database ID.
84         return domainSettingsDatabaseId;
85     }
86
87     @Override
88     public boolean onTouchEvent(MotionEvent motionEvent) {
89         // Initialize a tracker to return if this motion event is handled.
90         boolean motionEventHandled;
91
92         // Run the commands for the given motion event action.
93         switch (motionEvent.getAction()) {
94             case MotionEvent.ACTION_DOWN:
95                 // Start nested scrolling along the vertical axis.  `ViewCompat` must be used until the minimum API >= 21.
96                 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
97
98                 // Save the current Y position.  Action down will not be called again until a new motion starts.
99                 previousYPosition = (int) motionEvent.getY();
100
101                 // Run the default commands.
102                 motionEventHandled = super.onTouchEvent(motionEvent);
103                 break;
104
105             case MotionEvent.ACTION_MOVE:
106                 // Get the current Y position.
107                 int currentYMotionPosition = (int) motionEvent.getY();
108
109                 // Calculate the pre-scroll delta Y.
110                 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
111
112                 // Initialize a variable to track how much of the scroll is consumed.
113                 int[] consumedScroll = new int[2];
114
115                 // Initialize a variable to track the offset in the window.
116                 int[] offsetInWindow = new int[2];
117
118                 // Get the WebView Y position.
119                 int webViewYPosition = getScrollY();
120
121                 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
122                 int scrollDeltaY = preScrollDeltaY;
123
124                 // Dispatch the nested pre-school.  This scrolls the app bar if it needs it.  `offsetInWindow` will be returned with an updated value.
125                 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
126                     // Update the scroll delta Y if some of it was consumed.
127                     scrollDeltaY = preScrollDeltaY - consumedScroll[1];
128                 }
129
130                 // Check to see if the WebView is at the top and and the scroll action is downward.
131                 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) {  // Swipe to refresh is being engaged.
132                     // Stop the nested scroll so that swipe to refresh has complete control.
133                     stopNestedScroll();
134                 } else {  // Swipe to refresh is not being engaged.
135                     // Start the nested scroll so that the app bar can scroll off the screen.
136                     startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
137
138                     // 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.
139                     dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
140
141                     // Store the current Y position for use in the next action move.
142                     previousYPosition = previousYPosition - scrollDeltaY;
143                 }
144
145                 // Run the default commands.
146                 motionEventHandled = super.onTouchEvent(motionEvent);
147                 break;
148
149
150             default:
151                 // Stop nested scrolling.
152                 stopNestedScroll();
153
154                 // Run the default commands.
155                 motionEventHandled = super.onTouchEvent(motionEvent);
156         }
157
158         // Perform a click.  This is required by the Android accessibility guidelines.
159         performClick();
160
161         // Return the status of the motion event.
162         return motionEventHandled;
163     }
164
165     // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
166     @Override
167     public boolean performClick() {
168         return super.performClick();
169     }
170
171
172     // Method from NestedScrollingChild.
173     @Override
174     public void setNestedScrollingEnabled(boolean status) {
175         // Set the status of the nested scrolling.
176         nestedScrollingChildHelper.setNestedScrollingEnabled(status);
177     }
178
179     // Method from NestedScrollingChild.
180     @Override
181     public boolean isNestedScrollingEnabled() {
182         // Return the status of nested scrolling.
183         return nestedScrollingChildHelper.isNestedScrollingEnabled();
184     }
185
186
187     // Method from NestedScrollingChild.
188     @Override
189     public boolean startNestedScroll(int axes) {
190         // Start a nested scroll along the indicated axes.
191         return nestedScrollingChildHelper.startNestedScroll(axes);
192     }
193
194     // Method from NestedScrollingChild2.
195     @Override
196     public boolean startNestedScroll(int axes, int type) {
197         // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
198         return nestedScrollingChildHelper.startNestedScroll(axes, type);
199     }
200
201
202     // Method from NestedScrollingChild.
203     @Override
204     public void stopNestedScroll() {
205         // Stop the nested scroll.
206         nestedScrollingChildHelper.stopNestedScroll();
207     }
208
209     // Method from NestedScrollingChild2.
210     @Override
211     public void stopNestedScroll(int type) {
212         // Stop the nested scroll of the given type of input which caused the scroll event.
213         nestedScrollingChildHelper.stopNestedScroll(type);
214     }
215
216
217     // Method from NestedScrollingChild.
218     @Override
219     public boolean hasNestedScrollingParent() {
220         // Return the status of the nested scrolling parent.
221         return nestedScrollingChildHelper.hasNestedScrollingParent();
222     }
223
224     // Method from NestedScrollingChild2.
225     @Override
226     public boolean hasNestedScrollingParent(int type) {
227         // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
228         return nestedScrollingChildHelper.hasNestedScrollingParent(type);
229     }
230
231
232     // Method from NestedScrollingChild.
233     @Override
234     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
235         // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
236         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
237     }
238
239     // Method from NestedScrollingChild2.
240     @Override
241     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
242         // 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.
243         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
244     }
245
246
247     // Method from NestedScrollingChild.
248     @Override
249     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
250         // Dispatch a nested scroll with the specified deltas.
251         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
252     }
253
254     // Method from NestedScrollingChild2.
255     @Override
256     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
257         // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
258         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
259     }
260
261
262     // Method from NestedScrollingChild.
263     @Override
264     public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
265         // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
266         return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
267     }
268
269     // Method from NestedScrollingChild.
270     @Override
271     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
272         // Dispatch a nested fling with the specified velocity.
273         return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
274     }
275 }