2 * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
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.
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.
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/>.
20 package com.stoutner.privacybrowser.views;
22 import android.content.Context;
23 import android.util.AttributeSet;
24 import android.view.MotionEvent;
25 import android.webkit.WebView;
27 import androidx.core.view.NestedScrollingChild2;
28 import androidx.core.view.NestedScrollingChildHelper;
29 import androidx.core.view.ViewCompat;
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;
36 // The previous Y position needs to be tracked between motion events.
37 private int previousYPosition;
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;
44 public NestedScrollWebView(Context context) {
45 // Roll up to the next constructor.
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);
56 public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
57 // Run the default commands.
58 super(context, attributeSet, defaultStyle);
60 // Initialize the nested scrolling child helper.
61 nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
63 // Enable nested scrolling by default.
64 nestedScrollingChildHelper.setNestedScrollingEnabled(true);
67 public void setDomainSettingsApplied(boolean applied) {
68 // Store the domain settings applied status.
69 domainSettingsApplied = applied;
72 public boolean getDomainSettingsApplied() {
73 // Return the domain settings applied status.
74 return domainSettingsApplied;
77 public void setDomainSettingsDatabaseId(int databaseId) {
78 // Store the domain settings database ID.
79 domainSettingsDatabaseId = databaseId;
82 public int getDomainSettingsDatabaseId() {
83 // Return the domain settings database ID.
84 return domainSettingsDatabaseId;
88 public boolean onTouchEvent(MotionEvent motionEvent) {
89 // Initialize a tracker to return if this motion event is handled.
90 boolean motionEventHandled;
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);
98 // Save the current Y position. Action down will not be called again until a new motion starts.
99 previousYPosition = (int) motionEvent.getY();
101 // Run the default commands.
102 motionEventHandled = super.onTouchEvent(motionEvent);
105 case MotionEvent.ACTION_MOVE:
106 // Get the current Y position.
107 int currentYMotionPosition = (int) motionEvent.getY();
109 // Calculate the pre-scroll delta Y.
110 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
112 // Initialize a variable to track how much of the scroll is consumed.
113 int[] consumedScroll = new int[2];
115 // Initialize a variable to track the offset in the window.
116 int[] offsetInWindow = new int[2];
118 // Get the WebView Y position.
119 int webViewYPosition = getScrollY();
121 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
122 int scrollDeltaY = preScrollDeltaY;
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];
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.
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);
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);
141 // Store the current Y position for use in the next action move.
142 previousYPosition = previousYPosition - scrollDeltaY;
145 // Run the default commands.
146 motionEventHandled = super.onTouchEvent(motionEvent);
151 // Stop nested scrolling.
154 // Run the default commands.
155 motionEventHandled = super.onTouchEvent(motionEvent);
158 // Perform a click. This is required by the Android accessibility guidelines.
161 // Return the status of the motion event.
162 return motionEventHandled;
165 // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
167 public boolean performClick() {
168 return super.performClick();
172 // Method from NestedScrollingChild.
174 public void setNestedScrollingEnabled(boolean status) {
175 // Set the status of the nested scrolling.
176 nestedScrollingChildHelper.setNestedScrollingEnabled(status);
179 // Method from NestedScrollingChild.
181 public boolean isNestedScrollingEnabled() {
182 // Return the status of nested scrolling.
183 return nestedScrollingChildHelper.isNestedScrollingEnabled();
187 // Method from NestedScrollingChild.
189 public boolean startNestedScroll(int axes) {
190 // Start a nested scroll along the indicated axes.
191 return nestedScrollingChildHelper.startNestedScroll(axes);
194 // Method from NestedScrollingChild2.
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);
202 // Method from NestedScrollingChild.
204 public void stopNestedScroll() {
205 // Stop the nested scroll.
206 nestedScrollingChildHelper.stopNestedScroll();
209 // Method from NestedScrollingChild2.
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);
217 // Method from NestedScrollingChild.
219 public boolean hasNestedScrollingParent() {
220 // Return the status of the nested scrolling parent.
221 return nestedScrollingChildHelper.hasNestedScrollingParent();
224 // Method from NestedScrollingChild2.
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);
232 // Method from NestedScrollingChild.
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);
239 // Method from NestedScrollingChild2.
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);
247 // Method from NestedScrollingChild.
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);
254 // Method from NestedScrollingChild2.
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);
262 // Method from NestedScrollingChild.
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);
269 // Method from NestedScrollingChild.
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);