diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomFastScroller.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomFastScroller.java
new file mode 100644
index 000000000..13b726c58
--- /dev/null
+++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomFastScroller.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package org.mozilla.vrbrowser.ui.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.view.MotionEvent;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.view.ViewCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class responsible to animate and provide a fast scroller.
+ */
+@VisibleForTesting
+class CustomFastScroller extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener {
+ @IntDef({STATE_HIDDEN, STATE_VISIBLE, STATE_DRAGGING})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface State { }
+ // Scroll thumb not showing
+ private static final int STATE_HIDDEN = 0;
+ // Scroll thumb visible and moving along with the scrollbar
+ private static final int STATE_VISIBLE = 1;
+ // Scroll thumb being dragged by user
+ private static final int STATE_DRAGGING = 2;
+
+ @IntDef({DRAG_X, DRAG_Y, DRAG_NONE})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface DragState{ }
+ private static final int DRAG_NONE = 0;
+ private static final int DRAG_X = 1;
+ private static final int DRAG_Y = 2;
+
+ @IntDef({ANIMATION_STATE_OUT, ANIMATION_STATE_FADING_IN, ANIMATION_STATE_IN,
+ ANIMATION_STATE_FADING_OUT})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface AnimationState { }
+ private static final int ANIMATION_STATE_OUT = 0;
+ private static final int ANIMATION_STATE_FADING_IN = 1;
+ private static final int ANIMATION_STATE_IN = 2;
+ private static final int ANIMATION_STATE_FADING_OUT = 3;
+
+ private static final int SHOW_DURATION_MS = 500;
+ private static final int HIDE_DELAY_AFTER_VISIBLE_MS = 1500;
+ private static final int HIDE_DELAY_AFTER_DRAGGING_MS = 1200;
+ private static final int HIDE_DURATION_MS = 500;
+ private static final int SCROLLBAR_FULL_OPAQUE = 255;
+
+ private static final int[] PRESSED_STATE_SET = new int[]{android.R.attr.state_pressed};
+ private static final int[] EMPTY_STATE_SET = new int[]{};
+
+ private final int mScrollbarMinimumRange;
+ private final int mMargin;
+ private final boolean mAlwaysVisible;
+
+ // Final values for the vertical scroll bar
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final StateListDrawable mVerticalThumbDrawable;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Drawable mVerticalTrackDrawable;
+ private final int mVerticalThumbWidth;
+ private final int mVerticalTrackWidth;
+
+ // Final values for the horizontal scroll bar
+ private final StateListDrawable mHorizontalThumbDrawable;
+ private final Drawable mHorizontalTrackDrawable;
+ private final int mHorizontalThumbHeight;
+ private final int mHorizontalTrackHeight;
+
+ // Dynamic values for the vertical scroll bar
+ @VisibleForTesting int mVerticalThumbHeight;
+ @VisibleForTesting int mVerticalThumbCenterY;
+ @VisibleForTesting float mVerticalDragY;
+
+ // Dynamic values for the horizontal scroll bar
+ @VisibleForTesting int mHorizontalThumbWidth;
+ @VisibleForTesting int mHorizontalThumbCenterX;
+ @VisibleForTesting float mHorizontalDragX;
+
+ private int mRecyclerViewWidth = 0;
+ private int mRecyclerViewHeight = 0;
+
+ private RecyclerView mRecyclerView;
+ /**
+ * Whether the document is long/wide enough to require scrolling. If not, we don't show the
+ * relevant scroller.
+ */
+ private boolean mNeedVerticalScrollbar = false;
+ private boolean mNeedHorizontalScrollbar = false;
+ @State private int mState = STATE_HIDDEN;
+ @DragState private int mDragState = DRAG_NONE;
+
+ private final int[] mVerticalRange = new int[2];
+ private final int[] mHorizontalRange = new int[2];
+ private final int mDefaultWidth;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final ValueAnimator mShowHideAnimator = ValueAnimator.ofFloat(0, 1);
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @AnimationState int mAnimationState = ANIMATION_STATE_OUT;
+ private final Runnable mHideRunnable = () -> hide(HIDE_DURATION_MS);
+ private final RecyclerView.OnScrollListener
+ mOnScrollListener = new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ updateScrollPosition(recyclerView.computeHorizontalScrollOffset(),
+ recyclerView.computeVerticalScrollOffset());
+ }
+ };
+
+ CustomFastScroller(RecyclerView recyclerView, StateListDrawable verticalThumbDrawable,
+ Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable,
+ Drawable horizontalTrackDrawable, int defaultWidth, int scrollbarMinimumRange,
+ int margin, boolean alwaysVisible) {
+ mVerticalThumbDrawable = verticalThumbDrawable;
+ mVerticalTrackDrawable = verticalTrackDrawable;
+ mHorizontalThumbDrawable = horizontalThumbDrawable;
+ mHorizontalTrackDrawable = horizontalTrackDrawable;
+ mVerticalThumbWidth = Math.max(defaultWidth, verticalThumbDrawable.getIntrinsicWidth());
+ mVerticalTrackWidth = Math.max(defaultWidth, verticalTrackDrawable.getIntrinsicWidth());
+ mHorizontalThumbHeight = Math
+ .max(defaultWidth, horizontalThumbDrawable.getIntrinsicWidth());
+ mHorizontalTrackHeight = Math
+ .max(defaultWidth, horizontalTrackDrawable.getIntrinsicWidth());
+ mDefaultWidth = defaultWidth;
+ mScrollbarMinimumRange = scrollbarMinimumRange;
+ mMargin = margin;
+ mAlwaysVisible = alwaysVisible;
+ mVerticalThumbDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE);
+ mVerticalTrackDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE);
+
+ mShowHideAnimator.addListener(new AnimatorListener());
+ mShowHideAnimator.addUpdateListener(new AnimatorUpdater());
+
+ attachToRecyclerView(recyclerView);
+ }
+
+ public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
+ if (mRecyclerView == recyclerView) {
+ return; // nothing to do
+ }
+ if (mRecyclerView != null) {
+ destroyCallbacks();
+ }
+ mRecyclerView = recyclerView;
+ if (mRecyclerView != null) {
+ setupCallbacks();
+ }
+ }
+
+ private void setupCallbacks() {
+ mRecyclerView.addItemDecoration(this);
+ mRecyclerView.addOnItemTouchListener(this);
+ mRecyclerView.addOnScrollListener(mOnScrollListener);
+ }
+
+ private void destroyCallbacks() {
+ mRecyclerView.removeItemDecoration(this);
+ mRecyclerView.removeOnItemTouchListener(this);
+ mRecyclerView.removeOnScrollListener(mOnScrollListener);
+ cancelHide();
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void requestRedraw() {
+ mRecyclerView.invalidate();
+ }
+
+ void setState(@State int state) {
+ if (state == STATE_DRAGGING && mState != STATE_DRAGGING) {
+ mVerticalThumbDrawable.setState(PRESSED_STATE_SET);
+ cancelHide();
+ }
+
+ if (!mAlwaysVisible) {
+ if (state == STATE_HIDDEN) {
+ requestRedraw();
+ } else {
+ show();
+ }
+
+ } else {
+ mVerticalThumbDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE);
+ mVerticalTrackDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE);
+ requestRedraw();
+ }
+
+ if (mState == STATE_DRAGGING && state != STATE_DRAGGING) {
+ mVerticalThumbDrawable.setState(EMPTY_STATE_SET);
+ if (!mAlwaysVisible) {
+ resetHideDelay(HIDE_DELAY_AFTER_DRAGGING_MS);
+ }
+ } else if (state == STATE_VISIBLE) {
+ if (!mAlwaysVisible) {
+ resetHideDelay(HIDE_DELAY_AFTER_VISIBLE_MS);
+ }
+ }
+ mState = state;
+ }
+
+ private boolean isLayoutRTL() {
+ return ViewCompat.getLayoutDirection(mRecyclerView) == ViewCompat.LAYOUT_DIRECTION_RTL;
+ }
+
+ public boolean isDragging() {
+ return mState == STATE_DRAGGING;
+ }
+
+ @VisibleForTesting boolean isVisible() {
+ return mState == STATE_VISIBLE;
+ }
+
+ @VisibleForTesting boolean isHidden() {
+ return mState == STATE_HIDDEN;
+ }
+
+
+ public void show() {
+ switch (mAnimationState) {
+ case ANIMATION_STATE_FADING_OUT:
+ mShowHideAnimator.cancel();
+ // fall through
+ case ANIMATION_STATE_OUT:
+ mAnimationState = ANIMATION_STATE_FADING_IN;
+ mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 1);
+ mShowHideAnimator.setDuration(SHOW_DURATION_MS);
+ mShowHideAnimator.setStartDelay(0);
+ mShowHideAnimator.start();
+ break;
+ }
+ }
+
+ public void hide() {
+ hide(0);
+ }
+
+ @VisibleForTesting
+ void hide(int duration) {
+ switch (mAnimationState) {
+ case ANIMATION_STATE_FADING_IN:
+ mShowHideAnimator.cancel();
+ // fall through
+ case ANIMATION_STATE_IN:
+ mAnimationState = ANIMATION_STATE_FADING_OUT;
+ mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 0);
+ mShowHideAnimator.setDuration(duration);
+ mShowHideAnimator.start();
+ break;
+ }
+ }
+
+ private void cancelHide() {
+ mRecyclerView.removeCallbacks(mHideRunnable);
+ }
+
+ private void resetHideDelay(int delay) {
+ cancelHide();
+ mRecyclerView.postDelayed(mHideRunnable, delay);
+ }
+
+ @Override
+ public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
+ if (mRecyclerViewWidth != mRecyclerView.getWidth()
+ || mRecyclerViewHeight != mRecyclerView.getHeight()) {
+ mRecyclerViewWidth = mRecyclerView.getWidth();
+ mRecyclerViewHeight = mRecyclerView.getHeight();
+ // This is due to the different events ordering when keyboard is opened or
+ // retracted vs rotate. Hence to avoid corner cases we just disable the
+ // scroller when size changed, and wait until the scroll position is recomputed
+ // before showing it back.
+ if (!mAlwaysVisible) {
+ setState(STATE_HIDDEN);
+ }
+ return;
+ }
+
+ if (mAnimationState != ANIMATION_STATE_OUT || mAlwaysVisible) {
+ if (mNeedVerticalScrollbar) {
+ drawVerticalScrollbar(canvas);
+ }
+ if (mNeedHorizontalScrollbar) {
+ drawHorizontalScrollbar(canvas);
+ }
+ }
+ }
+
+ private void drawVerticalScrollbar(Canvas canvas) {
+ int viewWidth = mRecyclerViewWidth;
+
+ int left = viewWidth - mVerticalThumbWidth;
+ int top = mVerticalThumbCenterY - mVerticalThumbHeight / 2;
+ mVerticalThumbDrawable.setBounds(0, 0, mVerticalThumbWidth, mVerticalThumbHeight);
+ mVerticalTrackDrawable
+ .setBounds(0, 0, mVerticalTrackWidth, mRecyclerViewHeight);
+
+ if (isLayoutRTL()) {
+ mVerticalTrackDrawable.draw(canvas);
+ canvas.translate(mVerticalThumbWidth, top);
+ canvas.scale(-1, 1);
+ mVerticalThumbDrawable.draw(canvas);
+ canvas.scale(1, 1);
+ canvas.translate(-mVerticalThumbWidth, -top);
+ } else {
+ canvas.translate(left, 0);
+ mVerticalTrackDrawable.draw(canvas);
+ canvas.translate(0, top);
+ mVerticalThumbDrawable.draw(canvas);
+ canvas.translate(-left, -top);
+ }
+ }
+
+ private void drawHorizontalScrollbar(Canvas canvas) {
+ int viewHeight = mRecyclerViewHeight;
+
+ int top = viewHeight - mHorizontalThumbHeight;
+ int left = mHorizontalThumbCenterX - mHorizontalThumbWidth / 2;
+ mHorizontalThumbDrawable.setBounds(0, 0, mHorizontalThumbWidth, mHorizontalThumbHeight);
+ mHorizontalTrackDrawable
+ .setBounds(0, 0, mRecyclerViewWidth, mHorizontalTrackHeight);
+
+ canvas.translate(0, top);
+ mHorizontalTrackDrawable.draw(canvas);
+ canvas.translate(left, 0);
+ mHorizontalThumbDrawable.draw(canvas);
+ canvas.translate(-left, -top);
+ }
+
+ /**
+ * Notify the scroller of external change of the scroll, e.g. through dragging or flinging on
+ * the view itself.
+ *
+ * @param offsetX The new scroll X offset.
+ * @param offsetY The new scroll Y offset.
+ */
+ void updateScrollPosition(int offsetX, int offsetY) {
+ int verticalContentLength = mRecyclerView.computeVerticalScrollRange();
+ int verticalVisibleLength = mRecyclerViewHeight;
+ mNeedVerticalScrollbar = verticalContentLength - verticalVisibleLength > 0
+ && mRecyclerViewHeight >= mScrollbarMinimumRange || mNeedHorizontalScrollbar;
+
+ int horizontalContentLength = mRecyclerView.computeHorizontalScrollRange();
+ int horizontalVisibleLength = mRecyclerViewWidth;
+ mNeedHorizontalScrollbar = horizontalContentLength - horizontalVisibleLength > 0
+ && mRecyclerViewWidth >= mScrollbarMinimumRange || mNeedHorizontalScrollbar;
+
+ if (!mNeedVerticalScrollbar && !mNeedHorizontalScrollbar) {
+ if (mState != STATE_HIDDEN && !mAlwaysVisible) {
+ setState(STATE_HIDDEN);
+ }
+ return;
+ }
+
+ if (mNeedVerticalScrollbar) {
+ mVerticalThumbHeight = Math.max(mDefaultWidth * 4, Math.min(verticalVisibleLength,
+ (verticalVisibleLength * verticalVisibleLength) / verticalContentLength));
+ mVerticalThumbCenterY = (int)((verticalVisibleLength - mVerticalThumbHeight) * offsetY
+ / ((float)verticalContentLength - verticalVisibleLength) + mVerticalThumbHeight / 2.0);
+ }
+
+ if (mNeedHorizontalScrollbar && horizontalContentLength > 0) {
+ mHorizontalThumbWidth = Math.max(mDefaultWidth * 4, Math.min(horizontalVisibleLength,
+ (horizontalVisibleLength * horizontalVisibleLength) / horizontalContentLength));
+ mHorizontalThumbCenterX = (int)((horizontalVisibleLength - mHorizontalThumbHeight) * offsetX
+ / ((float)horizontalContentLength - horizontalVisibleLength) + mHorizontalThumbHeight / 2.0);
+ }
+
+ if (mState == STATE_HIDDEN || mState == STATE_VISIBLE) {
+ setState(STATE_VISIBLE);
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
+ @NonNull MotionEvent ev) {
+ final boolean handled;
+ if (mState == STATE_VISIBLE) {
+ boolean insideVerticalThumb = isPointInsideVerticalThumb(ev.getX(), ev.getY());
+ boolean insideHorizontalThumb = isPointInsideHorizontalThumb(ev.getX(), ev.getY());
+ if (ev.getAction() == MotionEvent.ACTION_DOWN
+ && (insideVerticalThumb || insideHorizontalThumb)) {
+ if (insideHorizontalThumb) {
+ mDragState = DRAG_X;
+ mHorizontalDragX = (int) ev.getX();
+ } else if (insideVerticalThumb) {
+ mDragState = DRAG_Y;
+ mVerticalDragY = (int) ev.getY();
+ }
+
+ setState(STATE_DRAGGING);
+ handled = true;
+ } else {
+ handled = false;
+ }
+ } else if (mState == STATE_DRAGGING) {
+ handled = true;
+ } else {
+ handled = false;
+ }
+ return handled;
+ }
+
+ @Override
+ public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent me) {
+ if (mState == STATE_HIDDEN) {
+ return;
+ }
+
+ if (me.getAction() == MotionEvent.ACTION_DOWN) {
+ boolean insideVerticalThumb = isPointInsideVerticalThumb(me.getX(), me.getY());
+ boolean insideHorizontalThumb = isPointInsideHorizontalThumb(me.getX(), me.getY());
+ if (insideVerticalThumb || insideHorizontalThumb) {
+ if (insideHorizontalThumb) {
+ mDragState = DRAG_X;
+ mHorizontalDragX = (int) me.getX();
+ } else if (insideVerticalThumb) {
+ mDragState = DRAG_Y;
+ mVerticalDragY = (int) me.getY();
+ }
+ setState(STATE_DRAGGING);
+ }
+ } else if (me.getAction() == MotionEvent.ACTION_UP && mState == STATE_DRAGGING) {
+ mVerticalDragY = 0;
+ mHorizontalDragX = 0;
+ setState(STATE_VISIBLE);
+ mDragState = DRAG_NONE;
+ } else if (me.getAction() == MotionEvent.ACTION_MOVE && mState == STATE_DRAGGING) {
+ if (!mAlwaysVisible) {
+ show();
+ }
+ if (mDragState == DRAG_X) {
+ horizontalScrollTo(me.getX());
+ }
+ if (mDragState == DRAG_Y) {
+ verticalScrollTo(me.getY());
+ }
+ }
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
+
+ private void verticalScrollTo(float y) {
+ final int[] scrollbarRange = getVerticalRange();
+ y = Math.max(scrollbarRange[0], Math.min(scrollbarRange[1], y));
+ if (Math.abs(mVerticalThumbCenterY - y) < 2) {
+ return;
+ }
+ int scrollingBy = scrollTo(mVerticalDragY, y, scrollbarRange,
+ mRecyclerView.computeVerticalScrollRange(),
+ mRecyclerView.computeVerticalScrollOffset(), mRecyclerViewHeight);
+ if (scrollingBy != 0) {
+ mRecyclerView.scrollBy(0, scrollingBy);
+ }
+ mVerticalDragY = y;
+ }
+
+ private void horizontalScrollTo(float x) {
+ final int[] scrollbarRange = getHorizontalRange();
+ x = Math.max(scrollbarRange[0], Math.min(scrollbarRange[1], x));
+ if (Math.abs(mHorizontalThumbCenterX - x) < 2) {
+ return;
+ }
+
+ int scrollingBy = scrollTo(mHorizontalDragX, x, scrollbarRange,
+ mRecyclerView.computeHorizontalScrollRange(),
+ mRecyclerView.computeHorizontalScrollOffset(), mRecyclerViewWidth);
+ if (scrollingBy != 0) {
+ mRecyclerView.scrollBy(scrollingBy, 0);
+ }
+
+ mHorizontalDragX = x;
+ }
+
+ private int scrollTo(float oldDragPos, float newDragPos, int[] scrollbarRange, int scrollRange,
+ int scrollOffset, int viewLength) {
+ int scrollbarLength = scrollbarRange[1] - scrollbarRange[0];
+ if (scrollbarLength == 0) {
+ return 0;
+ }
+ float percentage = ((newDragPos - oldDragPos) / (float) scrollbarLength);
+ int totalPossibleOffset = scrollRange - viewLength;
+ int scrollingBy = (int) (percentage * totalPossibleOffset);
+ int absoluteOffset = scrollOffset + scrollingBy;
+ if (absoluteOffset < totalPossibleOffset && absoluteOffset >= 0) {
+ return scrollingBy;
+ } else {
+ return 0;
+ }
+ }
+
+ @VisibleForTesting
+ boolean isPointInsideVerticalThumb(float x, float y) {
+ return (isLayoutRTL() ? x <= mVerticalThumbWidth / 2
+ : x >= mRecyclerViewWidth - mVerticalThumbWidth)
+ && y >= mVerticalThumbCenterY - mVerticalThumbHeight / 2
+ && y <= mVerticalThumbCenterY + mVerticalThumbHeight / 2;
+ }
+
+ @VisibleForTesting
+ boolean isPointInsideHorizontalThumb(float x, float y) {
+ return (y >= mRecyclerViewHeight - mHorizontalThumbHeight)
+ && x >= mHorizontalThumbCenterX - mHorizontalThumbWidth / 2
+ && x <= mHorizontalThumbCenterX + mHorizontalThumbWidth / 2;
+ }
+
+ @VisibleForTesting
+ Drawable getHorizontalTrackDrawable() {
+ return mHorizontalTrackDrawable;
+ }
+
+ @VisibleForTesting
+ Drawable getHorizontalThumbDrawable() {
+ return mHorizontalThumbDrawable;
+ }
+
+ @VisibleForTesting
+ Drawable getVerticalTrackDrawable() {
+ return mVerticalTrackDrawable;
+ }
+
+ @VisibleForTesting
+ Drawable getVerticalThumbDrawable() {
+ return mVerticalThumbDrawable;
+ }
+
+ /**
+ * Gets the (min, max) vertical positions of the vertical scroll bar.
+ */
+ private int[] getVerticalRange() {
+ mVerticalRange[0] = mMargin;
+ mVerticalRange[1] = mRecyclerViewHeight - mMargin;
+ return mVerticalRange;
+ }
+
+ /**
+ * Gets the (min, max) horizontal positions of the horizontal scroll bar.
+ */
+ private int[] getHorizontalRange() {
+ mHorizontalRange[0] = mMargin;
+ mHorizontalRange[1] = mRecyclerViewWidth - mMargin;
+ return mHorizontalRange;
+ }
+
+ private class AnimatorListener extends AnimatorListenerAdapter {
+
+ private boolean mCanceled = false;
+
+ AnimatorListener() {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Cancel is always followed by a new directive, so don't update state.
+ if (mCanceled) {
+ mCanceled = false;
+ return;
+ }
+ if ((float) mShowHideAnimator.getAnimatedValue() == 0) {
+ mAnimationState = ANIMATION_STATE_OUT;
+ setState(STATE_HIDDEN);
+ } else {
+ mAnimationState = ANIMATION_STATE_IN;
+ requestRedraw();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+ }
+
+ private class AnimatorUpdater implements AnimatorUpdateListener {
+ AnimatorUpdater() {
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ int alpha = (int) (SCROLLBAR_FULL_OPAQUE * ((float) valueAnimator.getAnimatedValue()));
+ mVerticalThumbDrawable.setAlpha(alpha);
+ mVerticalTrackDrawable.setAlpha(alpha);
+ requestRedraw();
+ }
+ }
+}
+
diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomRecyclerView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomRecyclerView.java
new file mode 100644
index 000000000..eabfe6bf8
--- /dev/null
+++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomRecyclerView.java
@@ -0,0 +1,92 @@
+package org.mozilla.vrbrowser.ui.views;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.mozilla.vrbrowser.R;
+
+public class CustomRecyclerView extends RecyclerView {
+
+ private CustomFastScroller mFastScroller;
+
+ public CustomRecyclerView(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ if (attrs != null) {
+ int defStyleRes = 0;
+ TypedArray a = context.obtainStyledAttributes(attrs, androidx.recyclerview.R.styleable.RecyclerView,
+ defStyle, defStyleRes);
+ StateListDrawable verticalThumbDrawable = (StateListDrawable) a
+ .getDrawable(androidx.recyclerview.R.styleable.RecyclerView_fastScrollVerticalThumbDrawable);
+ Drawable verticalTrackDrawable = a
+ .getDrawable(androidx.recyclerview.R.styleable.RecyclerView_fastScrollVerticalTrackDrawable);
+ StateListDrawable horizontalThumbDrawable = (StateListDrawable) a
+ .getDrawable(androidx.recyclerview.R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable);
+ Drawable horizontalTrackDrawable = a
+ .getDrawable(androidx.recyclerview.R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable);
+ a.recycle();
+
+ TypedArray customAttributes = context.obtainStyledAttributes(attrs, R.styleable.CustomRecyclerView,
+ defStyle, defStyleRes);
+ boolean alwaysVisible = customAttributes.getBoolean(R.styleable.CustomRecyclerView_android_fastScrollAlwaysVisible, false);
+ boolean enabled = customAttributes.getBoolean(R.styleable.CustomRecyclerView_customFastScrollEnabled, false);
+ customAttributes.recycle();
+ if (enabled) {
+ initFastScroller(alwaysVisible, verticalThumbDrawable, verticalTrackDrawable, horizontalThumbDrawable, horizontalTrackDrawable);
+ }
+
+ } else {
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ }
+
+ getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ if (getVisibility() == VISIBLE) {
+ mFastScroller.updateScrollPosition(computeHorizontalScrollOffset(),
+ computeVerticalScrollOffset());
+ }
+ });
+ }
+
+ String exceptionLabel() {
+ return " " + super.toString()
+ + ", adapter:" + getAdapter()
+ + ", layout:" + getLayoutManager()
+ + ", context:" + getContext();
+ }
+
+ @VisibleForTesting
+ void initFastScroller(boolean alwaysVisible, StateListDrawable verticalThumbDrawable,
+ Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable,
+ Drawable horizontalTrackDrawable) {
+ if (verticalThumbDrawable == null || verticalTrackDrawable == null
+ || horizontalThumbDrawable == null || horizontalTrackDrawable == null) {
+ throw new IllegalArgumentException(
+ "Trying to set fast scroller without both required drawables." + exceptionLabel());
+ }
+
+ Resources resources = getContext().getResources();
+ mFastScroller = new CustomFastScroller(this, verticalThumbDrawable, verticalTrackDrawable,
+ horizontalThumbDrawable, horizontalTrackDrawable,
+ resources.getDimensionPixelSize(R.dimen.fastscroll_default_thickness),
+ resources.getDimensionPixelSize(R.dimen.fastscroll_minimum_range),
+ resources.getDimensionPixelOffset(R.dimen.fastscroll_margin), alwaysVisible);
+ }
+}
diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomScrollView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomScrollView.java
new file mode 100644
index 000000000..66e219e3d
--- /dev/null
+++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/CustomScrollView.java
@@ -0,0 +1,413 @@
+package org.mozilla.vrbrowser.ui.views;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Interpolator;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.widget.ScrollView;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+
+import org.mozilla.vrbrowser.R;
+
+public class CustomScrollView extends ScrollView {
+
+ public static long SCROLLER_FADE_TIMEOUT = 1500;
+
+ private static final int[] DRAWABLE_STATE_PRESSED = new int[] { android.R.attr.state_pressed };
+ private static final int[] DRAWABLE_STATE_HOVER = new int[] { android.R.attr.state_hovered };
+ private static final int[] DRAWABLE_STATE_DEFAULT = new int[] {};
+
+ private static final float[] OPAQUE = { 255 };
+ private static final float[] TRANSPARENT = { 0.0f };
+
+ private float mDownY;
+ private Rect mThumbRect;
+ private Drawable mThumbDrawable;
+ private int mThumbMinHeight;
+ private ScrollAnimator mScrollCache;
+ private boolean mThumbDynamicHeight;
+ private boolean mIsAlwaysVisible;
+ private boolean mIsHandlingTouchEvent = false;
+ private int mSize;
+
+ public CustomScrollView(Context context) {
+ super(context);
+ createScrollDelegate(context, null, 0);
+ }
+
+ public CustomScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ createScrollDelegate(context, attrs, 0);
+ }
+
+ public CustomScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ createScrollDelegate(context, attrs, defStyle);
+ }
+
+ private void createScrollDelegate(Context context, AttributeSet attrs, int defStyle) {
+
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomScrollView, 0, defStyle);
+ mThumbDrawable = a.getDrawable(R.styleable.CustomScrollView_android_fastScrollThumbDrawable);
+ if (mThumbDrawable == null) {
+ mThumbDrawable = getResources().getDrawable(R.drawable.fast_scroll_thumb, getContext().getTheme());
+ }
+ mIsAlwaysVisible = a.getBoolean(R.styleable.CustomScrollView_android_fastScrollAlwaysVisible, false);
+ mThumbDynamicHeight = a.getBoolean(R.styleable.CustomScrollView_dynamicHeight, true);
+ mSize = a.getDimensionPixelSize(R.styleable.CustomScrollView_android_scrollbarSize, getResources().getDimensionPixelSize(R.dimen.scrollbarWidth));
+ a.recycle();
+ }
+
+ setVerticalScrollBarEnabled(false);
+ mScrollCache = new ScrollAnimator(this);
+ mThumbRect = new Rect(0, 0, mSize, mSize);
+
+ setThumbDrawable(mThumbDrawable);
+ setAlwaysVisible(mIsAlwaysVisible);
+ setThumbSize(mSize, mSize);
+ setThumbDynamicHeight(mThumbDynamicHeight);
+ }
+
+ public void setThumbDrawable(@NonNull Drawable drawable) {
+ mThumbDrawable = drawable;
+ updateThumbRect(0);
+ }
+
+ public void setThumbSize(int widthDp, int heightDp) {
+ mThumbRect.left = mThumbRect.right - widthDp;
+ mThumbMinHeight = heightDp;
+ updateThumbRect(0);
+ }
+
+ public void setThumbDynamicHeight(boolean isDynamicHeight) {
+ if (mThumbDynamicHeight != isDynamicHeight) {
+ mThumbDynamicHeight = isDynamicHeight;
+ updateThumbRect(0);
+ }
+ }
+
+ public void setAlwaysVisible(boolean visible) {
+ mIsAlwaysVisible = visible;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (onInterceptTouchEventInternal(ev)) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (onTouchEventInternal(event)) {
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ if (onHoverEventInternal(event)) {
+ return true;
+ }
+ return super.onHoverEvent(event);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ initialAwakenScrollBars();
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (visibility == VISIBLE) {
+ if (ViewCompat.isAttachedToWindow(this)) {
+ initialAwakenScrollBars();
+ }
+
+ }
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+
+ if (visibility == VISIBLE) {
+ initialAwakenScrollBars();
+ }
+ }
+
+ @Override
+ protected boolean awakenScrollBars() {
+ return awakenScrollBarsInternal(SCROLLER_FADE_TIMEOUT);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ drawScrollBars(canvas);
+ }
+
+ // Internal methods
+
+ private boolean onInterceptTouchEventInternal(@NonNull MotionEvent ev) {
+ final int action = ev.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ return onTouchEventInternal(ev);
+ }
+ return false;
+ }
+
+ private boolean onTouchEventInternal(@NonNull MotionEvent event) {
+ final int action = event.getActionMasked();
+ final float y = event.getY();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ if (mScrollCache.mState == ScrollAnimatorState.OFF) {
+ mIsHandlingTouchEvent = false;
+ return false;
+ }
+ if (!mIsHandlingTouchEvent) {
+ updateThumbRect(0);
+ final float x = event.getX();
+ if (y >= mThumbRect.top && y <= mThumbRect.bottom && x >= mThumbRect.left && x <= mThumbRect.right) {
+ mIsHandlingTouchEvent = true;
+ mDownY = y;
+ super.onTouchEvent(event);
+ MotionEvent fakeCancelMotionEvent = MotionEvent.obtain(event);
+ fakeCancelMotionEvent.setAction(MotionEvent.ACTION_CANCEL);
+ super.onTouchEvent(fakeCancelMotionEvent);
+ fakeCancelMotionEvent.recycle();
+ setHoveredThumb(false);
+ setPressedThumb(true);
+ updateThumbRect(0, true);
+ removeCallbacks(mScrollCache);
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mIsHandlingTouchEvent) {
+ final int touchDeltaY = Math.round(y - mDownY);
+ if (touchDeltaY != 0) {
+ updateThumbRect(touchDeltaY);
+ mDownY = y;
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ if (mIsHandlingTouchEvent) {
+ mIsHandlingTouchEvent = false;
+ setPressedThumb(false);
+ awakenScrollBars();
+ }
+ break;
+ }
+
+ }
+ if (mIsHandlingTouchEvent) {
+ invalidate();
+ getParent().requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean onHoverEventInternal(@NonNull MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE: {
+ setHoveredThumb(true);
+ return true;
+ }
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ setHoveredThumb(false);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean initialAwakenScrollBars() {
+ return awakenScrollBarsInternal(mScrollCache.mScrollBarDefaultDelayBeforeFade * 4);
+ }
+
+ private boolean awakenScrollBarsInternal(long startDelay) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ if (!mIsHandlingTouchEvent) {
+ if (mScrollCache.mState == ScrollAnimatorState.OFF) {
+ final int KEY_REPEAT_FIRST_DELAY = 750;
+ startDelay = Math.max(KEY_REPEAT_FIRST_DELAY, startDelay);
+ }
+ long fadeStartTime = AnimationUtils.currentAnimationTimeMillis() + startDelay;
+ mScrollCache.mFadeStartTime = fadeStartTime;
+ mScrollCache.mState = ScrollAnimatorState.ON;
+ removeCallbacks(mScrollCache);
+ postDelayed(mScrollCache, fadeStartTime - AnimationUtils.currentAnimationTimeMillis());
+ }
+ return false;
+ }
+
+ private void drawScrollBars(Canvas canvas) {
+ boolean invalidate = false;
+ if (mIsHandlingTouchEvent) {
+ mThumbDrawable.setAlpha(255);
+
+ } else {
+ if (!mIsAlwaysVisible) {
+ final ScrollAnimator cache = mScrollCache;
+ final ScrollAnimatorState state = cache.mState;
+ if (state == ScrollAnimatorState.OFF) {
+ return;
+ }
+ if (state == ScrollAnimatorState.FADING) {
+ if (cache.mInterpolatorValues == null) {
+ cache.mInterpolatorValues = new float[1];
+ }
+ float[] values = cache.mInterpolatorValues;
+ if (cache.mScrollBarInterpolator.timeToValues(values) == Interpolator.Result.FREEZE_END) {
+ cache.mState = ScrollAnimatorState.OFF;
+ } else {
+ mThumbDrawable.setAlpha(Math.round(values[0]));
+ }
+ invalidate = true;
+
+ } else {
+ mThumbDrawable.setAlpha(255);
+ }
+ }
+ }
+
+ if (updateThumbRect(0)) {
+ final int scrollY = getScrollY();
+ final int scrollX = getScrollX();
+ mThumbDrawable.setBounds(mThumbRect.left + scrollX, mThumbRect.top + scrollY, mThumbRect.right + scrollX,
+ mThumbRect.bottom + scrollY);
+ mThumbDrawable.draw(canvas);
+ }
+ if (invalidate) {
+ invalidate();
+ }
+ }
+
+ private void setPressedThumb(boolean pressed) {
+ mThumbDrawable.setState(pressed ? DRAWABLE_STATE_PRESSED : DRAWABLE_STATE_DEFAULT);
+ invalidate();
+ }
+
+ private void setHoveredThumb(boolean hover) {
+ mThumbDrawable.setState(hover ? DRAWABLE_STATE_HOVER : DRAWABLE_STATE_DEFAULT);
+ invalidate();
+ }
+
+ private boolean updateThumbRect(int touchDeltaY) {
+ return updateThumbRect(touchDeltaY, false);
+ }
+
+ private boolean updateThumbRect(int touchDeltaY, boolean forceReportFastScrolled) {
+ final int thumbWidth = mThumbRect.width();
+ mThumbRect.right = getWidth();
+ mThumbRect.left = mThumbRect.right - thumbWidth;
+ final int scrollRange = super.computeVerticalScrollRange();
+ if (scrollRange <= 0) {
+ return false;
+ }
+ final int scrollOffset = super.computeVerticalScrollOffset();
+ final int scrollExtent = super.computeVerticalScrollExtent();
+ final int scrollMaxOffset = scrollRange - scrollExtent;
+ if (scrollMaxOffset <= 0) {
+ return false;
+ }
+ final float scrollPercent = scrollOffset * 1f / (scrollMaxOffset);
+ final float visiblePercent = scrollExtent * 1f / scrollRange;
+ final int viewHeight = getHeight();
+ final int thumbHeight = mThumbDynamicHeight ? Math
+ .max(mThumbMinHeight, Math.round(visiblePercent * viewHeight)) : mThumbMinHeight;
+ mThumbRect.bottom = mThumbRect.top + thumbHeight;
+ final int thumbTop = Math.round((viewHeight - thumbHeight) * scrollPercent);
+ mThumbRect.offsetTo(mThumbRect.left, thumbTop);
+
+ if (touchDeltaY != 0) {
+ int newThumbTop = thumbTop + touchDeltaY;
+ final int minThumbTop = 0;
+ final int maxThumbTop = viewHeight - thumbHeight;
+ if (newThumbTop > maxThumbTop) {
+ newThumbTop = maxThumbTop;
+
+ } else if (newThumbTop < minThumbTop) {
+ newThumbTop = minThumbTop;
+ }
+
+ final float newScrollPercent = newThumbTop * 1f / maxThumbTop;
+ final int newScrollOffset = Math.round((scrollRange - scrollExtent) * newScrollPercent);
+ final int viewScrollDeltaY = newScrollOffset - scrollOffset;
+ scrollBy(0, viewScrollDeltaY);
+ }
+
+ return true;
+ }
+
+ public enum ScrollAnimatorState {
+ OFF,
+ ON,
+ FADING
+ }
+
+ private class ScrollAnimator implements Runnable {
+
+ final int mScrollBarDefaultDelayBeforeFade;
+ final int mScrollBarFadeDuration;
+ float[] mInterpolatorValues;
+ View mHost;
+ long mFadeStartTime;
+ public ScrollAnimatorState mState = ScrollAnimatorState.OFF;
+ final Interpolator mScrollBarInterpolator = new Interpolator(1, 2);
+
+ public ScrollAnimator(View host) {
+ mScrollBarDefaultDelayBeforeFade = ViewConfiguration.getScrollDefaultDelay();
+ mScrollBarFadeDuration = ViewConfiguration.getScrollBarFadeDuration();
+ mHost = host;
+ }
+
+ public void run() {
+ long now = AnimationUtils.currentAnimationTimeMillis();
+ if (now >= mFadeStartTime) {
+ int nextFrame = (int) now;
+ int framesCount = 0;
+
+ Interpolator interpolator = mScrollBarInterpolator;
+ interpolator.setKeyFrame(framesCount++, nextFrame, OPAQUE);
+
+ nextFrame += mScrollBarFadeDuration;
+ interpolator.setKeyFrame(framesCount, nextFrame, TRANSPARENT);
+
+ mState = ScrollAnimatorState.FADING;
+
+ mHost.invalidate();
+ }
+ }
+ }
+
+}
diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsView.java
index 51a41d527..df3b2b11b 100644
--- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsView.java
+++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsView.java
@@ -9,13 +9,14 @@
import androidx.annotation.NonNull;
import org.mozilla.vrbrowser.R;
+import org.mozilla.vrbrowser.ui.views.CustomScrollView;
import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate;
import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement;
abstract class SettingsView extends FrameLayout {
protected Delegate mDelegate;
protected WidgetManagerDelegate mWidgetManager;
- protected ScrollView mScrollbar;
+ protected CustomScrollView mScrollbar;
public interface Delegate {
void onDismiss();
@@ -65,6 +66,7 @@ protected boolean isVisible() {
public void onShown() {
if (mScrollbar != null) {
mScrollbar.fullScroll(ScrollView.FOCUS_UP);
+ mScrollbar.setSmoothScrollingEnabled(true);
}
setFocusableInTouchMode(true);
diff --git a/app/src/main/res/drawable/empty_drawable.xml b/app/src/main/res/drawable/empty_drawable.xml
new file mode 100644
index 000000000..b56e31ded
--- /dev/null
+++ b/app/src/main/res/drawable/empty_drawable.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+ -
+
+
+
+ -
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/bookmarks.xml b/app/src/main/res/layout/bookmarks.xml
index 94a96518b..cf36205ab 100644
--- a/app/src/main/res/layout/bookmarks.xml
+++ b/app/src/main/res/layout/bookmarks.xml
@@ -23,9 +23,7 @@
android:layout_height="match_parent"
android:background="@drawable/panel_background"
android:orientation="vertical"
- android:paddingStart="30dp"
android:paddingTop="30dp"
- android:paddingEnd="30dp"
android:paddingBottom="30dp">
@@ -113,12 +113,14 @@
android:textStyle="bold" />
-
-
+ app:layoutManager="org.mozilla.vrbrowser.ui.adapters.CustomLinearLayoutManager"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/history.xml b/app/src/main/res/layout/history.xml
index 5fd10800f..2d8a57f97 100644
--- a/app/src/main/res/layout/history.xml
+++ b/app/src/main/res/layout/history.xml
@@ -22,9 +22,7 @@
android:layout_height="match_parent"
android:background="@drawable/panel_background"
android:orientation="vertical"
- android:paddingStart="30dp"
android:paddingTop="30dp"
- android:paddingEnd="30dp"
android:paddingBottom="30dp">
@@ -128,12 +128,14 @@
android:textStyle="bold" />
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ app:layout="@layout/setting_radio_group_v"
+ app:options="@array/developer_options_voice_search_languages"
+ app:values="@array/developer_options_voice_search_languages_values" />
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml
index aba8c7be4..8843fbfb3 100644
--- a/app/src/main/res/values/dimen.xml
+++ b/app/src/main/res/values/dimen.xml
@@ -266,4 +266,7 @@
585dp
+
+ 10dp
+ 20dp
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index c21924ed6..24074507e 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -209,6 +209,24 @@
- vertical
+
+
+
+