diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/UIButton.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/UIButton.java index 46e4141d8..402ed2cad 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/UIButton.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/UIButton.java @@ -5,17 +5,23 @@ package org.mozilla.vrbrowser.ui.views; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.AttributeSet; - -import org.mozilla.vrbrowser.R; +import android.view.MotionEvent; import androidx.annotation.IdRes; import androidx.appcompat.widget.AppCompatImageButton; +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.ui.widgets.TooltipWidget; +import org.mozilla.vrbrowser.ui.widgets.UIWidget; +import org.mozilla.vrbrowser.utils.ViewUtils; + public class UIButton extends AppCompatImageButton implements CustomUIButton { private enum State { @@ -31,7 +37,12 @@ private enum State { private @IdRes int mTintColorListRes; private @IdRes int mPrivateModeTintColorListRes; private @IdRes int mActiveModeTintColorListRes; + private TooltipWidget mTooltipView; + private String mTooltipText; private State mState; + private int mTooltipDelay; + private float mTooltipDensity; + private ViewUtils.TooltipPosition mTooltipPosition; public UIButton(Context context, AttributeSet attrs) { this(context, attrs, R.attr.imageButtonStyle); @@ -45,22 +56,17 @@ public UIButton(Context context, AttributeSet attrs, int defStyleAttr) { if (mTintColorListRes != 0) { setTintColorList(mTintColorListRes); } - attributes.recycle(); - - attributes = context.obtainStyledAttributes(attrs, R.styleable.UIButton, defStyleAttr, 0); mPrivateModeBackground = attributes.getDrawable(R.styleable.UIButton_privateModeBackground); - attributes.recycle(); - - attributes = context.obtainStyledAttributes(attrs, R.styleable.UIButton, defStyleAttr, 0); mActiveModeBackground = attributes.getDrawable(R.styleable.UIButton_activeModeBackground); - attributes.recycle(); - - attributes = context.obtainStyledAttributes(attrs, R.styleable.UIButton, defStyleAttr, 0); mPrivateModeTintColorListRes = attributes.getResourceId(R.styleable.UIButton_privateModeTintColorList, 0); - attributes.recycle(); - - attributes = context.obtainStyledAttributes(attrs, R.styleable.UIButton, defStyleAttr, 0); mActiveModeTintColorListRes = attributes.getResourceId(R.styleable.UIButton_activeModeTintColorList, 0); + mTooltipDelay = attributes.getInt(R.styleable.UIButton_tooltipDelay, getResources().getInteger(R.integer.tooltip_delay)); + mTooltipPosition = ViewUtils.TooltipPosition.fromId(attributes.getInt(R.styleable.UIButton_tooltipPosition, ViewUtils.TooltipPosition.BOTTOM.ordinal())); + mTooltipDensity = attributes.getFloat(R.styleable.UIButton_tooltipDensity, getContext().getResources().getDisplayMetrics().density); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + TypedArray arr = context.obtainStyledAttributes(attrs, new int [] {android.R.attr.tooltipText}); + mTooltipText = arr.getString(0); + } attributes.recycle(); mBackground = getBackground(); @@ -68,6 +74,37 @@ public UIButton(Context context, AttributeSet attrs, int defStyleAttr) { mState = State.NORMAL; } + @TargetApi(Build.VERSION_CODES.O) + public String getTooltip() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + return mTooltipText; + else + return getTooltipText().toString(); + } + + @TargetApi(Build.VERSION_CODES.O) + public void setTooltip(String text) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + mTooltipText = text; + else + setTooltipText(text); + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + if (getTooltip() != null) { + if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { + getHandler().postDelayed(mShowTooltipRunnable, mTooltipDelay); + + } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { + getHandler().removeCallbacks(mShowTooltipRunnable); + getHandler().post(mHideTooltipRunnable); + } + } + + return super.onHoverEvent(event); + } + public void setTintColorList(int aColorListId) { mTintColorList = getContext().getResources().getColorStateList( aColorListId, @@ -144,4 +181,26 @@ private void setActive() { } } + private Runnable mShowTooltipRunnable = new Runnable() { + @Override + public void run() { + if (mTooltipView != null && mTooltipView.isVisible()) + return; + + mTooltipView = new TooltipWidget(getContext()); + mTooltipView.setText(getTooltip()); + mTooltipView.setLayoutParams(UIButton.this, mTooltipPosition, mTooltipDensity); + mTooltipView.show(UIWidget.CLEAR_FOCUS); + } + }; + + private Runnable mHideTooltipRunnable = new Runnable() { + @Override + public void run() { + if (mTooltipView != null) { + mTooltipView.hide(UIWidget.REMOVE_WIDGET); + } + } + }; + } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TooltipWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TooltipWidget.java new file mode 100644 index 000000000..b86f37a85 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TooltipWidget.java @@ -0,0 +1,101 @@ +package org.mozilla.vrbrowser.ui.widgets; + +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; +import android.view.View; +import android.widget.TextView; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.utils.ViewUtils; + +public class TooltipWidget extends UIWidget { + + private View mTargetView; + private UIWidget mParentWidget; + protected TextView mText; + private PointF mTranslation; + private float mRatio; + private float mDensityRatio; + + public TooltipWidget(Context aContext) { + super(aContext); + + initialize(); + } + + private void initialize() { + inflate(getContext(), R.layout.tooltip, this); + + mText = findViewById(R.id.tooltipText); + } + + @Override + protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { + aPlacement.visible = false; + aPlacement.width = 0; + aPlacement.height = 0; + aPlacement.parentAnchorX = 0.0f; + aPlacement.parentAnchorY = 1.0f; + aPlacement.anchorX = 0.5f; + aPlacement.anchorY = 0.5f; + aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.tooltip_z_distance); + } + + @Override + public void show(@ShowFlags int aShowFlags) { + measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + mWidgetPlacement.translationX = mTranslation.x * (mRatio / mWidgetPlacement.density); + mWidgetPlacement.translationY = mTranslation.y * (mRatio / mWidgetPlacement.density); + int paddingH = getPaddingStart() + getPaddingEnd(); + int paddingV = getPaddingTop() + getPaddingBottom(); + mWidgetPlacement.width = (int)(WidgetPlacement.convertPixelsToDp(getContext(), getMeasuredWidth() + paddingH)/mDensityRatio); + mWidgetPlacement.height = (int)(WidgetPlacement.convertPixelsToDp(getContext(), getMeasuredHeight() + paddingV)/mDensityRatio); + + super.show(aShowFlags); + } + + public void setLayoutParams(View targetView) { + this.setLayoutParams(targetView, ViewUtils.TooltipPosition.BOTTOM); + } + + public void setLayoutParams(View targetView, ViewUtils.TooltipPosition position) { + this.setLayoutParams(targetView, position, mWidgetPlacement.density); + } + + public void setLayoutParams(View targetView, ViewUtils.TooltipPosition position, float density) { + mTargetView = targetView; + mParentWidget = ViewUtils.getParentWidget(mTargetView); + if (mParentWidget != null) { + mRatio = WidgetPlacement.worldToWidgetRatio(mParentWidget); + mWidgetPlacement.density = density; + mDensityRatio = mWidgetPlacement.density / getContext().getResources().getDisplayMetrics().density; + + Rect offsetViewBounds = new Rect(); + getDrawingRect(offsetViewBounds); + mParentWidget.offsetDescendantRectToMyCoords(mTargetView, offsetViewBounds); + + mWidgetPlacement.parentHandle = mParentWidget.getHandle(); + // At the moment we only support showing tooltips on top or bottom of the target view + if (position == ViewUtils.TooltipPosition.BOTTOM) { + mWidgetPlacement.anchorY = 1.0f; + mWidgetPlacement.parentAnchorY = 0.0f; + mTranslation = new PointF( + (offsetViewBounds.left + mTargetView.getWidth() / 2) * mDensityRatio, + -offsetViewBounds.top * mDensityRatio); + } else { + mWidgetPlacement.anchorY = 0.0f; + mWidgetPlacement.parentAnchorY = 1.0f; + mTranslation = new PointF( + (offsetViewBounds.left + mTargetView.getWidth() / 2) * mDensityRatio, + offsetViewBounds.top * mDensityRatio); + } + } + } + + public void setText(String text) { + mText.setText(text); + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java index 7b99a9deb..8a033bb04 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java @@ -248,10 +248,12 @@ private void handleSessionState() { if (isPrivateMode) { mWidgetManager.pushWorldBrightness(this, WidgetManagerDelegate.DEFAULT_DIM_BRIGHTNESS); mPrivateButton.setImageResource(R.drawable.ic_icon_private_browsing_on); + mPrivateButton.setTooltip(getResources().getString(R.string.private_browsing_exit_tooltip)); } else { mWidgetManager.popWorldBrightness(this); mPrivateButton.setImageResource(R.drawable.ic_icon_private_browsing); + mPrivateButton.setTooltip(getResources().getString(R.string.private_browsing_enter_tooltip)); } } @@ -329,11 +331,13 @@ private void onHelpButtonClicked() { @Override public void onBookmarksShown() { + mBookmarksButton.setTooltip(getResources().getString(R.string.close_bookmarks_tooltip)); mBookmarksButton.setActiveMode(true); } @Override public void onBookmarksHidden() { + mBookmarksButton.setTooltip(getResources().getString(R.string.open_bookmarks_tooltip)); mBookmarksButton.setActiveMode(false); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WidgetPlacement.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WidgetPlacement.java index be907f509..7a15daff0 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WidgetPlacement.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WidgetPlacement.java @@ -10,6 +10,8 @@ import android.util.DisplayMetrics; import android.util.TypedValue; +import androidx.annotation.NonNull; + public class WidgetPlacement { static final float WORLD_DPI_RATIO = 2.0f/720.0f; @@ -126,4 +128,9 @@ public static float convertPixelsToDp(Context aContext, float px){ return px / ((float) aContext.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT); } + public static float worldToWidgetRatio(@NonNull UIWidget widget) { + float widgetWorldWidth = widget.mWidgetPlacement.worldWidth; + return ((widgetWorldWidth/widget.mWidgetPlacement.width)/WORLD_DPI_RATIO); + } + } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/utils/ViewUtils.java b/app/src/common/shared/org/mozilla/vrbrowser/utils/ViewUtils.java new file mode 100644 index 000000000..07b3cb65e --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/utils/ViewUtils.java @@ -0,0 +1,44 @@ +package org.mozilla.vrbrowser.utils; + +import android.view.View; +import android.view.ViewParent; + +import androidx.annotation.NonNull; + +import org.mozilla.vrbrowser.ui.widgets.UIWidget; + +public class ViewUtils { + + public enum TooltipPosition { + TOP(0), BOTTOM(1); + int id; + + TooltipPosition(int id) { + this.id = id; + } + + public static TooltipPosition fromId(int id) { + for (TooltipPosition f : values()) { + if (f.id == id) return f; + } + throw new IllegalArgumentException(); + } + } + + public static UIWidget getParentWidget(@NonNull View view) { + if (view == null) + return null; + + ViewParent v = view.getParent(); + if (v instanceof UIWidget) { + return (UIWidget)v; + + } else if (v instanceof View){ + return getParentWidget((View)v); + + } else { + return null; + } + } + +} diff --git a/app/src/main/res/drawable/tooltip_background.xml b/app/src/main/res/drawable/tooltip_background.xml new file mode 100644 index 000000000..85a6c45b6 --- /dev/null +++ b/app/src/main/res/drawable/tooltip_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/navigation_bar.xml b/app/src/main/res/layout/navigation_bar.xml index 5738ac407..42d149850 100644 --- a/app/src/main/res/layout/navigation_bar.xml +++ b/app/src/main/res/layout/navigation_bar.xml @@ -1,5 +1,6 @@ - + @@ -85,6 +92,7 @@ diff --git a/app/src/main/res/layout/tooltip.xml b/app/src/main/res/layout/tooltip.xml new file mode 100644 index 000000000..c014e9506 --- /dev/null +++ b/app/src/main/res/layout/tooltip.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tray.xml b/app/src/main/res/layout/tray.xml index 4805dc8d7..9ddbe97f3 100644 --- a/app/src/main/res/layout/tray.xml +++ b/app/src/main/res/layout/tray.xml @@ -1,5 +1,6 @@ - + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index a9e831577..c042640a9 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -20,9 +20,18 @@ + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 4fe175ae2..a4d267985 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -31,7 +31,9 @@ #5d5d5d #805d5d5d #8c898a + #808c898a #d73e5a #8F000000 #7FFFFFFF + #f5f6fa diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 148d25e0c..a0cd6170b 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -121,6 +121,7 @@ 1.2 165dp 40dp + 4.0 585dp @@ -164,6 +165,7 @@ 30dp + 28sp 24sp 22sp 18sp @@ -187,4 +189,8 @@ 2dp 1dp + + + 0.02 + 1000 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e14e3d6d5..d73c973e1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -629,10 +629,22 @@ speech-to-text recognition. --> Voice Search + + User Agent + Show site info. + + Use Servo + + + Resize + Enter Private Browsing @@ -644,11 +656,32 @@ Open in a new window. + + Bookmark this page + Bookmarks + + Open Bookmarks + + + Close Bookmarks + + + Settings + + + Open a New Window +