diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/HistoryStore.kt b/app/src/common/shared/org/mozilla/vrbrowser/browser/HistoryStore.kt index a8d5655f1..5fa14fb9c 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/HistoryStore.kt +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/HistoryStore.kt @@ -10,6 +10,8 @@ import android.os.Handler import android.os.Looper import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.future.future +import mozilla.components.concept.storage.PageObservation +import mozilla.components.concept.storage.VisitInfo import mozilla.components.concept.storage.VisitType import org.mozilla.vrbrowser.VRBrowserApplication import java.util.concurrent.CompletableFuture @@ -40,16 +42,53 @@ class HistoryStore constructor(val context: Context) { storage.getVisited() } - fun addHistory(aURL: String, visitType: VisitType) = GlobalScope.future { + fun getDetailedHistory(): CompletableFuture?> = GlobalScope.future { + storage.getDetailedVisits(0, excludeTypes = listOf( + VisitType.NOT_A_VISIT, + VisitType.REDIRECT_TEMPORARY, + VisitType.REDIRECT_PERMANENT, + VisitType.RELOAD)) + } + + fun recordVisit(aURL: String, visitType: VisitType) = GlobalScope.future { storage.recordVisit(aURL, visitType) notifyListeners() } + fun recordObservation(aURL: String, observation: PageObservation) = GlobalScope.future { + storage.recordObservation(aURL, observation) + notifyListeners() + } + fun deleteHistory(aUrl: String, timestamp: Long) = GlobalScope.future { storage.deleteVisit(aUrl, timestamp) notifyListeners() } + fun deleteVisitsFor(aUrl: String) = GlobalScope.future { + storage.deleteVisitsFor(aUrl) + notifyListeners() + } + + fun deleteEverything() = GlobalScope.future { + storage.deleteEverything() + notifyListeners() + } + + fun deleteVisitsSince(since: Long) = GlobalScope.future { + storage.deleteVisitsSince(since) + notifyListeners() + } + + fun deleteVisitsBetween(startTime: Long, endTime: Long) = GlobalScope.future { + storage.deleteVisitsBetween(startTime, endTime) + notifyListeners() + } + + fun getVisited(uris: List) = GlobalScope.future { + storage.getVisited(uris) + } + fun isInHistory(aURL: String): CompletableFuture = GlobalScope.future { storage.getVisited(listOf(aURL)).isNotEmpty() } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStack.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStack.java index cc85c2090..638147850 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStack.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStack.java @@ -16,6 +16,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import org.mozilla.geckoview.AllowOrDeny; import org.mozilla.geckoview.ContentBlocking; @@ -51,7 +52,8 @@ public class SessionStack implements ContentBlocking.Delegate, GeckoSession.NavigationDelegate, GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, GeckoSession.TextInputDelegate, - GeckoSession.PromptDelegate, GeckoSession.MediaDelegate, SharedPreferences.OnSharedPreferenceChangeListener { + GeckoSession.PromptDelegate, GeckoSession.MediaDelegate, GeckoSession.HistoryDelegate, + SharedPreferences.OnSharedPreferenceChangeListener { private static final String LOGTAG = "VRB"; @@ -70,6 +72,7 @@ public class SessionStack implements ContentBlocking.Delegate, GeckoSession.Navi private LinkedHashMap mSessions; private transient GeckoSession.PermissionDelegate mPermissionDelegate; private transient GeckoSession.PromptDelegate mPromptDelegate; + private transient GeckoSession.HistoryDelegate mHistoryDelegate; private int mPreviousGeckoSessionId = NO_SESSION; private String mRegion; private transient Context mContext; @@ -206,6 +209,13 @@ public void setPromptDelegate(GeckoSession.PromptDelegate aDelegate) { } } + public void setHistoryDelegate(GeckoSession.HistoryDelegate aDelegate) { + mHistoryDelegate = aDelegate; + for (HashMap.Entry entry : mSessions.entrySet()) { + entry.getValue().mSession.setHistoryDelegate(aDelegate); + } + } + public void addNavigationListener(GeckoSession.NavigationDelegate aListener) { mNavigationListeners.add(aListener); dumpState(mCurrentSession, aListener); @@ -299,6 +309,7 @@ public void restore(SessionStack store, int currentSessionId) { state.mSession.setPromptDelegate(mPromptDelegate); state.mSession.setContentBlockingDelegate(this); state.mSession.setMediaDelegate(this); + state.mSession.setHistoryDelegate(this); for (SessionChangeListener listener: mSessionChangeListeners) { listener.onNewSession(state.mSession, newSessionId); } @@ -363,6 +374,7 @@ private int createSession(@NonNull SessionSettings aSettings) { state.mSession.setPromptDelegate(mPromptDelegate); state.mSession.setContentBlockingDelegate(this); state.mSession.setMediaDelegate(this); + state.mSession.setHistoryDelegate(this); for (SessionChangeListener listener: mSessionChangeListeners) { listener.onNewSession(state.mSession, result); } @@ -402,6 +414,7 @@ private void removeSession(int aSessionId) { session.setPermissionDelegate(null); session.setContentBlockingDelegate(null); session.setMediaDelegate(null); + session.setHistoryDelegate(null); mSessions.remove(aSessionId); for (SessionChangeListener listener: mSessionChangeListeners) { listener.onRemoveSession(session, aSessionId); @@ -952,16 +965,24 @@ public void onCanGoForward(@NonNull GeckoSession aSession, boolean aCanGoForward AtomicBoolean allowed = new AtomicBoolean(false); for (GeckoSession.NavigationDelegate listener: mNavigationListeners) { GeckoResult listenerResult = listener.onLoadRequest(aSession, aRequest); - listenerResult.then(value -> { - if (AllowOrDeny.ALLOW.equals(value)) { - allowed.set(true); - } + if (listenerResult != null) { + listenerResult.then(value -> { + if (AllowOrDeny.ALLOW.equals(value)) { + allowed.set(true); + } + if (count.getAndIncrement() == mNavigationListeners.size() - 1) { + result.complete(allowed.get() ? AllowOrDeny.ALLOW : AllowOrDeny.DENY); + } + + return null; + }); + + } else { + allowed.set(true); if (count.getAndIncrement() == mNavigationListeners.size() - 1) { result.complete(allowed.get() ? AllowOrDeny.ALLOW : AllowOrDeny.DENY); } - - return null; - }); + } } return result; @@ -1334,6 +1355,35 @@ public void onMediaRemove(@NonNull GeckoSession session, @NonNull MediaElement e } } + // HistoryDelegate + + @Override + public void onHistoryStateChange(@NonNull GeckoSession geckoSession, @NonNull GeckoSession.HistoryDelegate.HistoryList historyList) { + if (mHistoryDelegate != null) { + mHistoryDelegate.onHistoryStateChange(geckoSession, historyList); + } + } + + @Nullable + @Override + public GeckoResult onVisited(@NonNull GeckoSession geckoSession, @NonNull String url, @Nullable String lastVisitedURL, int flags) { + if (mHistoryDelegate != null) { + return mHistoryDelegate.onVisited(geckoSession, url, lastVisitedURL, flags); + } + + return GeckoResult.fromValue(false); + } + + @UiThread + @Nullable + public GeckoResult getVisited(@NonNull GeckoSession geckoSession, @NonNull String[] urls) { + if (mHistoryDelegate != null) { + return mHistoryDelegate.getVisited(geckoSession, urls); + } + + return GeckoResult.fromValue(new boolean[]{}); + } + // SharedPreferences.OnSharedPreferenceChangeListener @Override diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/BindingAdapters.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/BindingAdapters.java index 1dbca1e38..17cabbf1c 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/BindingAdapters.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/BindingAdapters.java @@ -7,6 +7,12 @@ import androidx.annotation.NonNull; import androidx.databinding.BindingAdapter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + public class BindingAdapters { @@ -16,12 +22,12 @@ public static void showHide(@NonNull View view, boolean show) { } @BindingAdapter("visibleInvisible") - public static void showInvisible(View view, boolean show) { + public static void showInvisible(@NonNull View view, boolean show) { view.setVisibility(show ? View.VISIBLE : View.INVISIBLE); } @BindingAdapter("typeface") - public static void setTypeface(TextView v, String style) { + public static void setTypeface(@NonNull TextView v, String style) { switch (style) { case "bold": v.setTypeface(null, Typeface.BOLD); @@ -31,4 +37,20 @@ public static void setTypeface(TextView v, String style) { break; } } + + @BindingAdapter("bindDate") + public static void bindDate(@NonNull TextView textView, long timestamp) { + String androidDateTime = android.text.format.DateFormat.getDateFormat(textView.getContext()).format(new Date(timestamp)) + " " + + android.text.format.DateFormat.getTimeFormat(textView.getContext()).format(new Date(timestamp)); + String AmPm = ""; + if(!Character.isDigit(androidDateTime.charAt(androidDateTime.length()-1))) { + if(androidDateTime.contains(new SimpleDateFormat().getDateFormatSymbols().getAmPmStrings()[Calendar.AM])){ + AmPm = " " + new SimpleDateFormat().getDateFormatSymbols().getAmPmStrings()[Calendar.AM]; + }else{ + AmPm = " " + new SimpleDateFormat().getDateFormatSymbols().getAmPmStrings()[Calendar.PM]; + } + androidDateTime=androidDateTime.replace(AmPm, ""); + } + textView.setText(androidDateTime.concat(AmPm)); + } } \ No newline at end of file diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/BookmarkAdapter.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/BookmarkAdapter.java index 01c4178cb..a96bada13 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/BookmarkAdapter.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/BookmarkAdapter.java @@ -1,34 +1,34 @@ package org.mozilla.vrbrowser.ui.adapters; -import android.animation.Animator; -import android.animation.ValueAnimator; +import android.annotation.SuppressLint; import android.content.Context; -import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ImageView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.databinding.BookmarkItemBinding; import org.mozilla.vrbrowser.ui.callbacks.BookmarkClickCallback; import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; +import org.mozilla.vrbrowser.utils.AnimationHelper; import java.util.List; import java.util.Objects; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.databinding.DataBindingUtil; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - import mozilla.components.concept.storage.BookmarkNode; public class BookmarkAdapter extends RecyclerView.Adapter { - static final String LOGTAG = "VRB"; + + static final String LOGTAG = BookmarkAdapter.class.getSimpleName(); + private static final int ICON_ANIMATION_DURATION = 200; private List mBookmarkList; @@ -102,13 +102,14 @@ public int itemCount() { return mBookmarkList != null ? mBookmarkList.size() : 0; } + @SuppressLint("ClickableViewAccessibility") @Override public BookmarkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { BookmarkItemBinding binding = DataBindingUtil .inflate(LayoutInflater.from(parent.getContext()), R.layout.bookmark_item, parent, false); binding.setCallback(mBookmarkClickCallback); - binding.trash.setOnHoverListener(mTrashHoverListener); + binding.trash.setOnHoverListener(mIconHoverListener); binding.trash.setOnTouchListener((view, motionEvent) -> { int ev = motionEvent.getActionMasked(); switch (ev) { @@ -151,20 +152,20 @@ static class BookmarkViewHolder extends RecyclerView.ViewHolder { } } - private View.OnHoverListener mTrashHoverListener = (view, motionEvent) -> { + private View.OnHoverListener mIconHoverListener = (view, motionEvent) -> { ImageView icon = (ImageView)view; int ev = motionEvent.getActionMasked(); switch (ev) { case MotionEvent.ACTION_HOVER_ENTER: icon.setColorFilter(mIconColorHover); - animateViewPadding(view, + AnimationHelper.animateViewPadding(view, mMaxPadding, mMinPadding, ICON_ANIMATION_DURATION); return false; case MotionEvent.ACTION_HOVER_EXIT: - animateViewPadding(view, + AnimationHelper.animateViewPadding(view, mMinPadding, mMaxPadding, ICON_ANIMATION_DURATION, @@ -175,46 +176,4 @@ static class BookmarkViewHolder extends RecyclerView.ViewHolder { return false; }; - private void animateViewPadding(View view, int paddingStart, int paddingEnd, int duration) { - animateViewPadding(view, paddingStart, paddingEnd, duration, null); - } - - private void animateViewPadding(View view, int paddingStart, int paddingEnd, int duration, Runnable onAnimationEnd) { - ValueAnimator animation = ValueAnimator.ofInt(paddingStart, paddingEnd); - animation.setDuration(duration); - animation.setInterpolator(new AccelerateDecelerateInterpolator()); - animation.addUpdateListener(valueAnimator -> { - try { - int newPadding = Integer.parseInt(valueAnimator.getAnimatedValue().toString()); - view.setPadding(newPadding, newPadding, newPadding, newPadding); - } catch (NumberFormatException ex) { - Log.e(LOGTAG, "Error parsing BookmarkAdapter animation value: " + valueAnimator.getAnimatedValue().toString()); - } - }); - animation.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animator) { - - } - - @Override - public void onAnimationEnd(Animator animator) { - if (onAnimationEnd != null) { - onAnimationEnd.run(); - } - } - - @Override - public void onAnimationCancel(Animator animator) { - - } - - @Override - public void onAnimationRepeat(Animator animator) { - - } - }); - animation.start(); - } - } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/HistoryAdapter.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/HistoryAdapter.java new file mode 100644 index 000000000..abb282295 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/HistoryAdapter.java @@ -0,0 +1,226 @@ +package org.mozilla.vrbrowser.ui.adapters; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.databinding.HistoryItemBinding; +import org.mozilla.vrbrowser.ui.callbacks.HistoryItemCallback; +import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; +import org.mozilla.vrbrowser.utils.AnimationHelper; + +import java.util.List; +import java.util.Objects; + +import mozilla.components.concept.storage.VisitInfo; + +public class HistoryAdapter extends RecyclerView.Adapter { + + static final String LOGTAG = HistoryAdapter.class.getSimpleName(); + + private static final int ICON_ANIMATION_DURATION = 200; + + private List mHistoryList; + + private int mMinPadding; + private int mMaxPadding; + private int mIconColorHover; + private int mIconNormalColor; + + @Nullable + private final HistoryItemCallback mHistoryItemCallback; + + public HistoryAdapter(@Nullable HistoryItemCallback clickCallback, Context aContext) { + mHistoryItemCallback = clickCallback; + + mMinPadding = WidgetPlacement.pixelDimension(aContext, R.dimen.settings_icon_padding_min); + mMaxPadding = WidgetPlacement.pixelDimension(aContext, R.dimen.settings_icon_padding_max); + + mIconColorHover = aContext.getResources().getColor(R.color.white, aContext.getTheme()); + mIconNormalColor = aContext.getResources().getColor(R.color.rhino, aContext.getTheme()); + + setHasStableIds(true); + } + + public void setHistoryList(final List historyList) { + if (mHistoryList == null) { + mHistoryList = historyList; + notifyItemRangeInserted(0, historyList.size()); + + } else { + DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return mHistoryList.size(); + } + + @Override + public int getNewListSize() { + return historyList.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return mHistoryList.get(oldItemPosition).getVisitTime() == historyList.get(newItemPosition).getVisitTime(); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + VisitInfo newHistoryItem = historyList.get(newItemPosition); + VisitInfo oldHistoryItem = mHistoryList.get(oldItemPosition); + return newHistoryItem.getVisitTime() == oldHistoryItem.getVisitTime() + && Objects.equals(newHistoryItem.getTitle(), oldHistoryItem.getTitle()) + && Objects.equals(newHistoryItem.getUrl(), oldHistoryItem.getUrl()); + } + }); + + mHistoryList = historyList; + result.dispatchUpdatesTo(this); + } + } + + public void removeItem(VisitInfo historyItem) { + int position = mHistoryList.indexOf(historyItem); + if (position >= 0) { + mHistoryList.remove(position); + notifyItemRemoved(position); + } + } + + public int itemCount() { + return mHistoryList != null ? mHistoryList.size() : 0; + } + + public int getItemPosition(long id) { + for (int position=0; position { + int ev = motionEvent.getActionMasked(); + switch (ev) { + case MotionEvent.ACTION_HOVER_ENTER: + binding.setIsHovered(true); + return false; + + case MotionEvent.ACTION_HOVER_EXIT: + binding.setIsHovered(false); + return false; + } + + return false; + }); + binding.layout.setOnTouchListener((view, motionEvent) -> { + int ev = motionEvent.getActionMasked(); + switch (ev) { + case MotionEvent.ACTION_UP: + return false; + + case MotionEvent.ACTION_DOWN: + binding.setIsHovered(true); + return false; + } + return false; + }); + binding.more.setOnHoverListener(mIconHoverListener); + binding.more.setOnTouchListener((view, motionEvent) -> { + int ev = motionEvent.getActionMasked(); + switch (ev) { + case MotionEvent.ACTION_UP: + mHistoryItemCallback.onMore(view, binding.getItem()); + return true; + + case MotionEvent.ACTION_DOWN: + return true; + } + return false; + }); + binding.trash.setOnHoverListener(mIconHoverListener); + binding.trash.setOnTouchListener((view, motionEvent) -> { + int ev = motionEvent.getActionMasked(); + switch (ev) { + case MotionEvent.ACTION_UP: + mHistoryItemCallback.onDelete(view, binding.getItem()); + return true; + + case MotionEvent.ACTION_DOWN: + return true; + } + return false; + }); + return new HistoryItemViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull HistoryItemViewHolder holder, int position) { + holder.binding.setItem(mHistoryList.get(position)); + holder.binding.executePendingBindings(); + } + + @Override + public int getItemCount() { + return mHistoryList == null ? 0 : mHistoryList.size(); + } + + @Override + public long getItemId(int position) { + VisitInfo historyItem = mHistoryList.get(position); + return historyItem.getVisitTime(); + } + + static class HistoryItemViewHolder extends RecyclerView.ViewHolder { + + final HistoryItemBinding binding; + + HistoryItemViewHolder(@NonNull HistoryItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + + private View.OnHoverListener mIconHoverListener = (view, motionEvent) -> { + ImageView icon = (ImageView)view; + int ev = motionEvent.getActionMasked(); + switch (ev) { + case MotionEvent.ACTION_HOVER_ENTER: + icon.setColorFilter(mIconColorHover); + AnimationHelper.animateViewPadding(view, + mMaxPadding, + mMinPadding, + ICON_ANIMATION_DURATION); + return false; + + case MotionEvent.ACTION_HOVER_EXIT: + AnimationHelper.animateViewPadding(view, + mMinPadding, + mMaxPadding, + ICON_ANIMATION_DURATION, + () -> icon.setColorFilter(mIconNormalColor)); + return false; + } + + return false; + }; + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryCallback.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryCallback.java new file mode 100644 index 000000000..df1250e4c --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryCallback.java @@ -0,0 +1,10 @@ +package org.mozilla.vrbrowser.ui.callbacks; + +import android.view.View; + +import mozilla.components.concept.storage.VisitInfo; + +public interface HistoryCallback { + void onClearHistory(View view); + void onShowContextMenu(View view, VisitInfo item, boolean isLastVisibleItem); +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryItemCallback.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryItemCallback.java new file mode 100644 index 000000000..b7bddf780 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryItemCallback.java @@ -0,0 +1,11 @@ +package org.mozilla.vrbrowser.ui.callbacks; + +import android.view.View; + +import mozilla.components.concept.storage.VisitInfo; + +public interface HistoryItemCallback { + void onClick(View view, VisitInfo item); + void onDelete(View view, VisitInfo item); + void onMore(View view, VisitInfo item); +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryItemContextMenuClickCallback.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryItemContextMenuClickCallback.java new file mode 100644 index 000000000..05088a63b --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/callbacks/HistoryItemContextMenuClickCallback.java @@ -0,0 +1,9 @@ +package org.mozilla.vrbrowser.ui.callbacks; + +import mozilla.components.concept.storage.VisitInfo; + +public interface HistoryItemContextMenuClickCallback { + void onOpenInNewWindowClick(VisitInfo item); + void onAddToBookmarks(VisitInfo item); + void onRemoveFromBookmarks(VisitInfo item); +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/BookmarksView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/BookmarksView.java index f5ad8e142..71668cade 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/BookmarksView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/BookmarksView.java @@ -15,8 +15,8 @@ import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.BookmarksStore; -import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.databinding.BookmarksBinding; import org.mozilla.vrbrowser.ui.adapters.BookmarkAdapter; import org.mozilla.vrbrowser.ui.callbacks.BookmarkClickCallback; @@ -25,7 +25,6 @@ import java.util.List; import mozilla.components.concept.storage.BookmarkNode; -import mozilla.components.concept.storage.VisitType; public class BookmarksView extends FrameLayout implements BookmarksStore.BookmarkListener { @@ -77,8 +76,6 @@ public void onClick(BookmarkNode bookmark) { mAudio.playSound(AudioEngine.Sound.CLICK); } - SessionStore.get().getHistoryStore().addHistory(bookmark.getUrl(), VisitType.BOOKMARK); - SessionStack sessionStack = SessionStore.get().getActiveStore(); sessionStack.loadUri(bookmark.getUrl()); } @@ -118,7 +115,7 @@ private void showBookmarks(List aBookmarks) { mBinding.executePendingBindings(); } - // BookmarksStore.BookmarkListener + // BookmarksStore.BookmarksViewListener @Override public void onBookmarksUpdated() { if (mIgnoreNextListener) { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryItemContextMenu.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryItemContextMenu.java new file mode 100644 index 000000000..47bc9e585 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryItemContextMenu.java @@ -0,0 +1,67 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.vrbrowser.ui.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.widget.FrameLayout; + +import androidx.databinding.DataBindingUtil; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.browser.engine.SessionStore; +import org.mozilla.vrbrowser.databinding.HistoryItemContextMenuBinding; +import org.mozilla.vrbrowser.ui.callbacks.HistoryItemContextMenuClickCallback; + +import mozilla.components.concept.storage.VisitInfo; + +public class HistoryItemContextMenu extends FrameLayout { + + private static final String LOGTAG = HistoryItemContextMenu.class.getSimpleName(); + + private HistoryItemContextMenuBinding mBinding; + + public HistoryItemContextMenu(Context aContext) { + super(aContext); + initialize(aContext); + } + + public HistoryItemContextMenu(Context aContext, AttributeSet aAttrs) { + super(aContext, aAttrs); + initialize(aContext); + } + + public HistoryItemContextMenu(Context aContext, AttributeSet aAttrs, int aDefStyle) { + super(aContext, aAttrs, aDefStyle); + initialize(aContext); + } + + private void initialize(Context aContext) { + LayoutInflater inflater = LayoutInflater.from(aContext); + + mBinding = DataBindingUtil.inflate(inflater, R.layout.history_item_context_menu, this, true); + } + + public void setItem(VisitInfo item) { + SessionStore.get().getBookmarkStore().isBookmarked(item.getUrl()).thenAccept((isBookmarked -> { + mBinding.setItem(item); + mBinding.setIsBookmarked(isBookmarked); + mBinding.bookmark.setText(isBookmarked ? R.string.history_context_remove_bookmarks : R.string.history_context_add_bookmarks); + invalidate(); + + })).exceptionally(throwable -> { + Log.d(LOGTAG, "Couldn't get the bookmarked status of the history item"); + return null; + }); + } + + public void setContextMenuClickCallback(HistoryItemContextMenuClickCallback callback) { + mBinding.setCallback(callback); + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryView.java new file mode 100644 index 000000000..e9e333b21 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryView.java @@ -0,0 +1,207 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.vrbrowser.ui.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.browser.HistoryStore; +import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.SessionStore; +import org.mozilla.vrbrowser.databinding.HistoryBinding; +import org.mozilla.vrbrowser.ui.adapters.HistoryAdapter; +import org.mozilla.vrbrowser.ui.callbacks.HistoryCallback; +import org.mozilla.vrbrowser.ui.callbacks.HistoryItemCallback; +import org.mozilla.vrbrowser.utils.SystemUtils; +import org.mozilla.vrbrowser.utils.UIThreadExecutor; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.stream.Collectors; + +import mozilla.components.concept.storage.VisitInfo; +import mozilla.components.concept.storage.VisitType; + +public class HistoryView extends FrameLayout implements HistoryStore.HistoryListener { + + private static final String LOGTAG = HistoryView.class.getSimpleName(); + + private HistoryBinding mBinding; + private HistoryAdapter mHistoryAdapter; + private boolean mIgnoreNextListener; + + public HistoryView(Context aContext) { + super(aContext); + initialize(aContext); + } + + public HistoryView(Context aContext, AttributeSet aAttrs) { + super(aContext, aAttrs); + initialize(aContext); + } + + public HistoryView(Context aContext, AttributeSet aAttrs, int aDefStyle) { + super(aContext, aAttrs, aDefStyle); + initialize(aContext); + } + + @SuppressLint("ClickableViewAccessibility") + private void initialize(Context aContext) { + LayoutInflater inflater = LayoutInflater.from(aContext); + + // Inflate this data binding layout + mBinding = DataBindingUtil.inflate(inflater, R.layout.history, this, true); + mHistoryAdapter = new HistoryAdapter(mHistoryItemCallback, aContext); + mBinding.historyList.setAdapter(mHistoryAdapter); + mBinding.historyList.setOnTouchListener((v, event) -> { + v.requestFocusFromTouch(); + return false; + }); + mBinding.setIsLoading(true); + mBinding.executePendingBindings(); + syncHistory(); + SessionStore.get().getHistoryStore().addListener(this); + + setVisibility(GONE); + + setOnTouchListener((v, event) -> { + v.requestFocusFromTouch(); + return false; + }); + } + + public void onDestroy() { + SessionStore.get().getHistoryStore().removeListener(this); + } + + private final HistoryItemCallback mHistoryItemCallback = new HistoryItemCallback() { + @Override + public void onClick(View view, VisitInfo item) { + view.requestFocusFromTouch(); + SessionStack sessionStack = SessionStore.get().getActiveStore(); + sessionStack.loadUri(item.getUrl()); + } + + @Override + public void onDelete(View view, VisitInfo item) { + mBinding.historyList.requestFocusFromTouch(); + mIgnoreNextListener = true; + SessionStore.get().getHistoryStore().deleteHistory(item.getUrl(), item.getVisitTime()); + mHistoryAdapter.removeItem(item); + if (mHistoryAdapter.itemCount() == 0) { + mBinding.setIsEmpty(true); + mBinding.setIsLoading(false); + mBinding.executePendingBindings(); + } + } + + @Override + public void onMore(View view, VisitInfo item) { + view.requestFocusFromTouch(); + + int rowPosition = mHistoryAdapter.getItemPosition(item.getVisitTime()); + RecyclerView.ViewHolder row = mBinding.historyList.findViewHolderForLayoutPosition(rowPosition); + boolean isLastVisibleItem = false; + if (mBinding.historyList.getLayoutManager() instanceof LinearLayoutManager) { + LinearLayoutManager layoutManager = (LinearLayoutManager) mBinding.historyList.getLayoutManager(); + if (rowPosition == layoutManager.findLastVisibleItemPosition()) { + isLastVisibleItem = true; + } + } + + mBinding.getCallback().onShowContextMenu( + row.itemView, + item, + isLastVisibleItem); + } + }; + + public void setHistoryCallback(@NonNull HistoryCallback callback) { + mBinding.setCallback(callback); + } + + private void syncHistory() { + Calendar date = new GregorianCalendar(); + date.set(Calendar.HOUR_OF_DAY, 0); + date.set(Calendar.MINUTE, 0); + date.set(Calendar.SECOND, 0); + date.set(Calendar.MILLISECOND, 0); + + long currentTime = System.currentTimeMillis(); + long todayLimit = date.getTimeInMillis(); + long yesterdayLimit = todayLimit - SystemUtils.ONE_DAY_MILLIS; + long oneWeekLimit = todayLimit - SystemUtils.ONE_WEEK_MILLIS; + + SessionStore.get().getHistoryStore().getDetailedHistory().thenAcceptAsync((items) -> { + List orderedItems = items.stream() + .sorted((o1, o2) -> Long.valueOf(o2.getVisitTime() - o1.getVisitTime()).intValue()) + .collect(Collectors.toList()); + + addSection(orderedItems, getResources().getString(R.string.history_section_today), currentTime, todayLimit); + addSection(orderedItems, getResources().getString(R.string.history_section_yesterday), todayLimit, yesterdayLimit); + addSection(orderedItems, getResources().getString(R.string.history_section_last_week), yesterdayLimit, oneWeekLimit); + addSection(orderedItems, getResources().getString(R.string.history_section_today), oneWeekLimit, 0); + + showHistory(orderedItems); + + }, new UIThreadExecutor()).exceptionally(throwable -> { + Log.d(LOGTAG, "Can get the detailed history"); + return null; + }); + } + + private void addSection(final @NonNull List items, @NonNull String section, long rangeStart, long rangeEnd) { + for (int i=0; i< items.size(); i++) { + if (items.get(i).getVisitTime() == rangeStart && items.get(i).getVisitType() == VisitType.NOT_A_VISIT) + break; + + if (items.get(i).getVisitTime() < rangeStart && items.get(i).getVisitTime() > rangeEnd) { + items.add(i, new VisitInfo( + "", + section, + rangeStart, + VisitType.NOT_A_VISIT + )); + break; + } + } + } + + private void showHistory(List historyItems) { + if (historyItems == null || historyItems.size() == 0) { + mBinding.setIsEmpty(true); + mBinding.setIsLoading(false); + + } else { + mBinding.setIsEmpty(false); + mBinding.setIsLoading(false); + mHistoryAdapter.setHistoryList(historyItems); + } + mBinding.executePendingBindings(); + } + + // HistoryStore.HistoryListener + @Override + public void onHistoryUpdated() { + if (mIgnoreNextListener) { + mIgnoreNextListener = false; + return; + } + syncHistory(); + } +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java index 56d901fde..0b9e83481 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java @@ -33,8 +33,8 @@ import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.BookmarksStore; -import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.search.SearchEngineWrapper; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; @@ -50,7 +50,6 @@ import kotlin.Unit; import mozilla.components.browser.domains.autocomplete.DomainAutocompleteResult; import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider; -import mozilla.components.concept.storage.VisitType; import mozilla.components.ui.autocomplete.InlineAutocompleteEditText; public class NavigationURLBar extends FrameLayout { @@ -73,7 +72,7 @@ public class NavigationURLBar extends FrameLayout { private ShippedDomainsProvider mAutocompleteProvider; private UIButton mBookmarkButton; private AudioEngine mAudio; - private boolean mIsBookmarkMode; + private boolean mIsContentMode; private boolean mBookmarkEnabled = true; private boolean mIsContextButtonsEnabled = true; private UIThreadExecutor mUIThreadExecutor = new UIThreadExecutor(); @@ -182,7 +181,7 @@ private void initialize(Context aContext) { // Bookmarks mBookmarkButton = findViewById(R.id.bookmarkButton); mBookmarkButton.setOnClickListener(v -> handleBookmarkClick()); - mIsBookmarkMode = false; + mIsContentMode = false; // Prevent the URL TextEdit to get focus when user touches something outside of it setFocusable(true); @@ -211,12 +210,12 @@ public void setDelegate(NavigationURLBarDelegate delegate) { mDelegate = delegate; } - public void setIsBookmarkMode(boolean isBookmarkMode) { - if (mIsBookmarkMode == isBookmarkMode) { + public void setIsContentMode(boolean isContentMode) { + if (mIsContentMode == isContentMode) { return; } - mIsBookmarkMode = isBookmarkMode; - if (isBookmarkMode) { + mIsContentMode = isContentMode; + if (isContentMode) { mMicrophoneButton.setVisibility(GONE); mUAModeButton.setVisibility(GONE); mBookmarkButton.setVisibility(GONE); @@ -231,6 +230,21 @@ public void setIsBookmarkMode(boolean isBookmarkMode) { syncViews(); } + public boolean isInBookmarkMode() { + return mIsContentMode; + } + + private void setBookmarkEnabled(boolean aEnabled) { + if (mBookmarkEnabled != aEnabled) { + mBookmarkEnabled = aEnabled; + mBookmarkButton.setVisibility(aEnabled ? View.VISIBLE : View.GONE); + ViewGroup.LayoutParams params = mMicrophoneButton.getLayoutParams(); + params.width = (int) getResources().getDimension(aEnabled ? R.dimen.url_bar_item_width : R.dimen.url_bar_last_item_width); + mMicrophoneButton.setLayoutParams(params); + mMicrophoneButton.setBackgroundResource(aEnabled ? R.drawable.url_button : R.drawable.url_button_end); + } + } + private void handleBookmarkClick() { if (mAudio != null) { mAudio.playSound(AudioEngine.Sound.CLICK); @@ -274,6 +288,9 @@ public void setInsecureVisibility(int visibility) { } public void setURL(String aURL) { + if (mIsContentMode) { + return; + } mURL.removeTextChangedListener(mURLTextWatcher); if (StringUtils.isEmpty(aURL)) { setBookmarked(false); @@ -323,7 +340,8 @@ public void setURL(String aURL) { } mIsContextButtonsEnabled = aURL.length() > 0 && !aURL.startsWith("about://"); - if (!aURL.equals(getResources().getString(R.string.url_bookmarks_title))) { + if (!aURL.equals(getResources().getString(R.string.url_bookmarks_title)) && + !aURL.equals(getResources().getString(R.string.url_history_title))) { showContextButtons(mIsContextButtonsEnabled); } } @@ -413,13 +431,13 @@ public void showVoiceSearch(boolean enabled) { } private void syncViews() { - boolean showContainer = (mIsInsecure || mIsLoading) && !mIsBookmarkMode; + boolean showContainer = (mIsInsecure || mIsLoading) && !mIsContentMode; int leftPadding = mDefaultURLLeftPadding; if (showContainer) { mURLLeftContainer.setVisibility(View.VISIBLE); mURLLeftContainer.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); mLoadingView.setVisibility(mIsLoading ? View.VISIBLE : View.GONE); - if (!mIsBookmarkMode) { + if (!mIsContentMode) { mInsecureIcon.setVisibility(!mIsLoading && mIsInsecure ? View.VISIBLE : View.GONE); } leftPadding = mURLLeftContainer.getMeasuredWidth(); @@ -467,7 +485,6 @@ public void handleURLEdit(String text) { } if (mSessionStack.getCurrentUri() != url) { - SessionStore.get().getHistoryStore().addHistory(url, VisitType.TYPED); mSessionStack.loadUri(url); if (mDelegate != null) { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/RadioGroupSetting.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/RadioGroupSetting.java index c34963ab6..885c1039d 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/RadioGroupSetting.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/RadioGroupSetting.java @@ -24,7 +24,7 @@ public interface OnCheckedChangeListener { } private AudioEngine mAudio; - private String mDecription; + private String mDescription; private CharSequence[] mOptions; private Object[] mValues; protected RadioGroup mRadioGroup; @@ -40,7 +40,7 @@ public RadioGroupSetting(Context context, AttributeSet attrs, int defStyleAttr) super(context, attrs, defStyleAttr); TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.RadioGroupSetting, defStyleAttr, 0); - mDecription = attributes.getString(R.styleable.RadioGroupSetting_description); + mDescription = attributes.getString(R.styleable.RadioGroupSetting_description); mOptions = attributes.getTextArray(R.styleable.RadioGroupSetting_options); mLayout = attributes.getResourceId(R.styleable.RadioGroupSetting_layout, R.layout.setting_radio_group); int id = attributes.getResourceId(R.styleable.RadioGroupSetting_values, 0); @@ -68,7 +68,7 @@ protected void initialize(Context aContext, AttributeSet attrs, int defStyleAttr mRadioDescription = findViewById(R.id.setting_description); if (mRadioDescription != null) { - mRadioDescription.setText(mDecription); + mRadioDescription.setText(mDescription); } mRadioGroup = findViewById(R.id.radio_group); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/BookmarkListener.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/BookmarkListener.java deleted file mode 100644 index 534bc6467..000000000 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/BookmarkListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.mozilla.vrbrowser.ui.widgets; - -public interface BookmarkListener { - default void onBookmarksShown(WindowWidget aWindow) {}; - default void onBookmarksHidden(WindowWidget aWindow) {}; -} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java index d16612afb..1ad040621 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java @@ -26,9 +26,8 @@ import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.Media; import org.mozilla.vrbrowser.browser.SessionChangeListener; -import org.mozilla.vrbrowser.browser.engine.SessionStore; -import org.mozilla.vrbrowser.browser.engine.SessionStack; import org.mozilla.vrbrowser.browser.SettingsStore; +import org.mozilla.vrbrowser.browser.engine.SessionStack; import org.mozilla.vrbrowser.search.suggestions.SuggestionsProvider; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.ui.views.CustomUIButton; @@ -44,14 +43,12 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; -import mozilla.components.concept.storage.VisitType; - public class NavigationBarWidget extends UIWidget implements GeckoSession.NavigationDelegate, GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, WidgetManagerDelegate.WorldClickListener, WidgetManagerDelegate.UpdateListener, SessionChangeListener, NavigationURLBar.NavigationURLBarDelegate, VoiceSearchWidget.VoiceSearchDelegate, SharedPreferences.OnSharedPreferenceChangeListener, SuggestionsWidget.URLBarPopupDelegate, - BookmarkListener, TrayListener { + WindowWidget.BookmarksViewDelegate, WindowWidget.HistoryViewDelegate, TrayListener { private static final String LOGTAG = NavigationBarWidget.class.getSimpleName(); @@ -168,9 +165,6 @@ private void initialize(@NonNull Context aContext) { mReloadButton.setOnClickListener(v -> { v.requestFocusFromTouch(); - SessionStore.get().getHistoryStore().addHistory( - mAttachedWindow.getSessionStack().getCurrentUri(), - VisitType.RELOAD); if (mIsLoading) { mSessionStack.stop(); } else { @@ -384,7 +378,8 @@ public void detachFromWindow() { mSessionStack = null; } if (mAttachedWindow != null) { - mAttachedWindow.removeBookmarksListener(this); + mAttachedWindow.removeBookmarksViewListener(this); + mAttachedWindow.removeHistoryViewListener(this); } mAttachedWindow = null; } @@ -398,14 +393,19 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mWidgetPlacement.parentHandle = aWindow.getHandle(); mAttachedWindow = aWindow; - mAttachedWindow.addBookmarksListener(this); + mAttachedWindow.addBookmarksViewListener(this); + mAttachedWindow.addHistoryViewListener(this); if (mAttachedWindow != null) { - mURLBar.setIsBookmarkMode(mAttachedWindow.isBookmarksVisible()); + mURLBar.setIsContentMode(mAttachedWindow.isBookmarksVisible()); if (mAttachedWindow.isBookmarksVisible()) { mURLBar.setURL(getResources().getString(R.string.url_bookmarks_title)); mURLBar.setInsecureVisibility(View.GONE); + } if (mAttachedWindow.isHistoryVisible()) { + mURLBar.setURL(getResources().getString(R.string.url_history_title)); + mURLBar.setInsecureVisibility(View.GONE); + } else { mURLBar.setURL(mAttachedWindow.getSessionStack().getCurrentUri()); mURLBar.setHint(R.string.search_placeholder); @@ -979,21 +979,41 @@ public void OnItemClicked(SuggestionsWidget.SuggestionItem item) { mURLBar.handleURLEdit(item.url); } - // BookmarkListener + // BookmarksViewListener @Override public void onBookmarksShown(WindowWidget aWindow) { if (mAttachedWindow == aWindow) { mURLBar.setURL(""); mURLBar.setHint(R.string.url_bookmarks_title); - mURLBar.setIsBookmarkMode(true); + mURLBar.setIsContentMode(true); } } @Override public void onBookmarksHidden(WindowWidget aWindow) { if (mAttachedWindow == aWindow) { - mURLBar.setIsBookmarkMode(false); + mURLBar.setIsContentMode(false); + mURLBar.setURL(mSessionStack.getCurrentUri()); + mURLBar.setHint(R.string.search_placeholder); + } + } + + // HistoryViewListener + + @Override + public void onHistoryViewShown(WindowWidget aWindow) { + if (mAttachedWindow == aWindow) { + mURLBar.setURL(""); + mURLBar.setHint(R.string.url_history_title); + mURLBar.setIsContentMode(true); + } + } + + @Override + public void onHistoryViewHidden(WindowWidget aWindow) { + if (mAttachedWindow == aWindow) { + mURLBar.setIsContentMode(false); mURLBar.setURL(mSessionStack.getCurrentUri()); mURLBar.setHint(R.string.search_placeholder); } @@ -1019,6 +1039,19 @@ public void onPrivateBrowsingClicked() { } + @Override + public void onHistoryClicked() { + if (mIsResizing) { + exitResizeMode(ResizeAction.RESTORE_SIZE); + + } else if (mIsInFullScreenMode) { + exitFullScreenMode(); + + } else if (mIsInVRVideo) { + exitVRVideo(); + } + } + private void finishWidgetResize() { mWidgetManager.finishWidgetResize(mAttachedWindow); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TitleBarWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TitleBarWidget.java index 51da41a9f..21b018c5e 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TitleBarWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TitleBarWidget.java @@ -164,7 +164,8 @@ public void setURL(String urlString) { } public void setIsInsecure(boolean aIsInsecure) { - if (!(mAttachedWindow.getSessionStack().getCurrentUri().startsWith("data") && + if (mAttachedWindow.getSessionStack().getCurrentUri() != null && + !(mAttachedWindow.getSessionStack().getCurrentUri().startsWith("data") && mAttachedWindow.getSessionStack().isPrivateMode())) { mBinding.insecureIcon.setVisibility(aIsInsecure ? View.VISIBLE : View.GONE); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayListener.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayListener.java index 51b2cb9f9..dcf5f22bb 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayListener.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayListener.java @@ -1,7 +1,8 @@ package org.mozilla.vrbrowser.ui.widgets; public interface TrayListener { - default void onBookmarksClicked() {}; - default void onPrivateBrowsingClicked() {}; - default void onAddWindowClicked() {}; + default void onBookmarksClicked() {} + default void onPrivateBrowsingClicked() {} + default void onAddWindowClicked() {} + default void onHistoryClicked() {} } 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 269e15333..ad7635d0b 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 @@ -29,13 +29,17 @@ import java.util.Arrays; import java.util.List; -public class TrayWidget extends UIWidget implements SessionChangeListener, BookmarkListener, WidgetManagerDelegate.UpdateListener { - static final String LOGTAG = "VRB"; +public class TrayWidget extends UIWidget implements SessionChangeListener, WindowWidget.BookmarksViewDelegate, + WindowWidget.HistoryViewDelegate, WidgetManagerDelegate.UpdateListener { + + static final String LOGTAG = TrayWidget.class.getSimpleName(); + private static final int ICON_ANIMATION_DURATION = 200; private UIButton mSettingsButton; private UIButton mPrivateButton; private UIButton mBookmarksButton; + private UIButton mHistoryButton; private AudioEngine mAudio; private int mSettingsDialogHandle = -1; private boolean mIsLastSessionPrivate; @@ -105,6 +109,17 @@ private void initialize(Context aContext) { view.requestFocusFromTouch(); }); + mHistoryButton = findViewById(R.id.historyButton); + mHistoryButton.setOnHoverListener(mButtonScaleHoverListener); + mHistoryButton.setOnClickListener(view -> { + if (mAudio != null) { + mAudio.playSound(AudioEngine.Sound.CLICK); + } + + notifyHistoryClicked(); + view.requestFocusFromTouch(); + }); + UIButton addWindowButton = findViewById(R.id.addwindowButton); addWindowButton.setOnHoverListener(mButtonScaleHoverListener); addWindowButton.setOnClickListener(view -> { @@ -200,6 +215,10 @@ private void notifyBookmarksClicked() { mTrayListeners.forEach(TrayListener::onBookmarksClicked); } + private void notifyHistoryClicked() { + mTrayListeners.forEach(TrayListener::onHistoryClicked); + } + private void notifyPrivateBrowsingClicked() { mTrayListeners.forEach(TrayListener::onPrivateBrowsingClicked); } @@ -267,7 +286,8 @@ public void detachFromWindow() { mSessionStack = null; } if (mAttachedWindow != null) { - mAttachedWindow.removeBookmarksListener(this); + mAttachedWindow.removeBookmarksViewListener(this); + mAttachedWindow.removeHistoryViewListener(this); } mWidgetPlacement.parentHandle = -1; @@ -282,7 +302,8 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mAttachedWindow = aWindow; mWidgetPlacement.parentHandle = aWindow.getHandle(); - mAttachedWindow.addBookmarksListener(this); + mAttachedWindow.addBookmarksViewListener(this); + mAttachedWindow.addHistoryViewListener(this); mSessionStack = aWindow.getSessionStack(); if (mSessionStack != null) { @@ -295,6 +316,12 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { } else { onBookmarksHidden(aWindow); } + + if (mAttachedWindow.isHistoryVisible()) { + onHistoryViewShown(aWindow); + } else { + onHistoryViewHidden(aWindow); + } } // SessionStack.SessionChangeListener @@ -366,7 +393,7 @@ public boolean isDialogOpened(int aHandle) { return false; } - // BookmarkListener + // BookmarksViewListener @Override public void onBookmarksShown(WindowWidget aWindow) { @@ -380,7 +407,22 @@ public void onBookmarksHidden(WindowWidget aWindow) { mBookmarksButton.setActiveMode(false); } + // HistoryViewListener + + @Override + public void onHistoryViewShown(WindowWidget aWindow) { + mHistoryButton.setTooltip(getResources().getString(R.string.close_history_tooltip)); + mHistoryButton.setActiveMode(true); + } + + @Override + public void onHistoryViewHidden(WindowWidget aWindow) { + mHistoryButton.setTooltip(getResources().getString(R.string.open_history_tooltip)); + mHistoryButton.setActiveMode(false); + } + // WidgetManagerDelegate.UpdateListener + @Override public void onWidgetUpdate(Widget aWidget) { if (!aWidget.getClass().equals(KeyboardWidget.class)) { 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 221c6a86f..392499390 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 @@ -153,4 +153,8 @@ public static float worldToWindowRatio(Context aContext){ return (WidgetPlacement.floatDimension(aContext, R.dimen.window_world_width) / SettingsStore.WINDOW_WIDTH_DEFAULT)/ WORLD_DPI_RATIO; } + public static float viewToWidgetRatio(@NonNull Context context, @NonNull UIWidget widget) { + return WidgetPlacement.worldToWidgetRatio(widget) / context.getResources().getDisplayMetrics().density; + } + } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java index 2a80af53f..9b51c108a 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java @@ -8,6 +8,7 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.util.Log; @@ -22,23 +23,30 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.annotation.UiThread; import org.mozilla.gecko.util.ThreadUtils; -import org.mozilla.geckoview.AllowOrDeny; import org.mozilla.geckoview.GeckoDisplay; import org.mozilla.geckoview.GeckoResult; import org.mozilla.geckoview.GeckoSession; import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.browser.HistoryStore; import org.mozilla.vrbrowser.browser.SessionChangeListener; import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.browser.VideoAvailabilityListener; import org.mozilla.vrbrowser.browser.engine.SessionStack; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; +import org.mozilla.vrbrowser.ui.callbacks.HistoryCallback; +import org.mozilla.vrbrowser.ui.callbacks.HistoryItemContextMenuClickCallback; import org.mozilla.vrbrowser.ui.views.BookmarksView; -import org.mozilla.vrbrowser.ui.widgets.dialogs.AppDialogWidget; +import org.mozilla.vrbrowser.ui.views.HistoryView; +import org.mozilla.vrbrowser.ui.widgets.dialogs.BaseAppDialogWidget; +import org.mozilla.vrbrowser.ui.widgets.dialogs.ClearCacheDialogWidget; import org.mozilla.vrbrowser.ui.widgets.dialogs.ContextMenuWidget; +import org.mozilla.vrbrowser.ui.widgets.dialogs.HistoryItemContextMenuWidget; import org.mozilla.vrbrowser.ui.widgets.dialogs.MaxWindowsWidget; +import org.mozilla.vrbrowser.ui.widgets.dialogs.MessageDialogWidget; import org.mozilla.vrbrowser.ui.widgets.prompts.AlertPromptWidget; import org.mozilla.vrbrowser.ui.widgets.prompts.AuthPromptWidget; import org.mozilla.vrbrowser.ui.widgets.prompts.ChoicePromptWidget; @@ -46,18 +54,35 @@ import org.mozilla.vrbrowser.ui.widgets.prompts.PromptWidget; import org.mozilla.vrbrowser.ui.widgets.prompts.TextPromptWidget; import org.mozilla.vrbrowser.utils.ViewUtils; +import org.mozilla.vrbrowser.utils.SystemUtils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import mozilla.components.concept.storage.PageObservation; +import mozilla.components.concept.storage.VisitInfo; import mozilla.components.concept.storage.VisitType; import static org.mozilla.vrbrowser.utils.ServoUtils.isInstanceOfServoSession; public class WindowWidget extends UIWidget implements SessionChangeListener, - GeckoSession.ContentDelegate, GeckoSession.PromptDelegate, GeckoSession.ProgressDelegate, - GeckoSession.NavigationDelegate, VideoAvailabilityListener { + GeckoSession.ContentDelegate, GeckoSession.PromptDelegate, + GeckoSession.NavigationDelegate, VideoAvailabilityListener, + GeckoSession.HistoryDelegate, GeckoSession.ProgressDelegate { - private static final String LOGTAG = "VRB"; + private static final String LOGTAG = WindowWidget.class.getSimpleName(); + + public interface HistoryViewDelegate { + default void onHistoryViewShown(WindowWidget aWindow) {} + default void onHistoryViewHidden(WindowWidget aWindow) {}; + } + + public interface BookmarksViewDelegate { + default void onBookmarksShown(WindowWidget aWindow) {}; + default void onBookmarksHidden(WindowWidget aWindow) {}; + } private int mSessionId; private GeckoDisplay mDisplay; @@ -76,8 +101,10 @@ public class WindowWidget extends UIWidget implements SessionChangeListener, private TextPromptWidget mTextPrompt; private AuthPromptWidget mAuthPrompt; private NoInternetWidget mNoInternetToast; - private AppDialogWidget mAppDialog; + private MessageDialogWidget mAppDialog; + private ClearCacheDialogWidget mClearCacheDialog; private ContextMenuWidget mContextMenu; + private HistoryItemContextMenuWidget mHistoryContextMenu; private int mWidthBackup; private int mHeightBackup; private int mBorderWidth; @@ -88,7 +115,9 @@ public class WindowWidget extends UIWidget implements SessionChangeListener, private SessionStack mSessionStack; private int mWindowId; private BookmarksView mBookmarksView; - private ArrayList mBookmarksListeners; + private HistoryView mHistoryView; + private ArrayList mBookmarksViewListeners; + private ArrayList mHistoryViewListeners; private Windows.WindowPlacement mWindowPlacement = Windows.WindowPlacement.FRONT; private float mMaxWindowScale = 3; private boolean mIsRestored = false; @@ -97,6 +126,7 @@ public class WindowWidget extends UIWidget implements SessionChangeListener, boolean mHovered = false; boolean mClickedAfterFocus = false; boolean mIsBookmarksVisible = false; + boolean mIsHistoryVisible = false; public interface WindowDelegate { void onFocusRequest(@NonNull WindowWidget aWindow); @@ -116,10 +146,15 @@ public WindowWidget(Context aContext, int windowId, boolean privateMode) { mSessionStack.addVideoAvailabilityListener(this); mSessionStack.addNavigationListener(this); mSessionStack.addProgressListener(this); + mSessionStack.setHistoryDelegate(this); mSessionStack.newSession(); mBookmarksView = new BookmarksView(aContext); - mBookmarksListeners = new ArrayList<>(); + mBookmarksViewListeners = new ArrayList<>(); + + mHistoryView = new HistoryView(aContext); + mHistoryView.setHistoryCallback(mHistoryCallback); + mHistoryViewListeners = new ArrayList<>(); mHandle = ((WidgetManagerDelegate)aContext).newWidgetHandle(); mWidgetPlacement = new WidgetPlacement(aContext); @@ -186,7 +221,10 @@ public void hide(@HideFlags int aHideFlag) { @Override protected void onDismiss() { if (isBookmarksVisible()) { - switchBookmarks(); + hideBookmarks(); + + } else if (isHistoryVisible()) { + hideHistory(); } else { SessionStack activeStore = SessionStore.get().getSessionStack(mWindowId); @@ -201,6 +239,7 @@ public void close() { releaseWidget(); mBookmarksView.onDestroy(); + mHistoryView.onDestroy(); SessionStore.get().destroySessionStack(mWindowId); } @@ -266,29 +305,49 @@ private void unsetView(View view) { } public boolean isBookmarksVisible() { - return (mView != null); + return (mView != null && mView == mBookmarksView); + } + + public boolean isHistoryVisible() { + return (mView != null && mView == mHistoryView); + } + + public void addBookmarksViewListener(@NonNull BookmarksViewDelegate listener) { + mBookmarksViewListeners.add(listener); } - public void addBookmarksListener(@NonNull BookmarkListener listener) { - mBookmarksListeners.add(listener); + public void removeBookmarksViewListener(@NonNull BookmarksViewDelegate listener) { + mBookmarksViewListeners.remove(listener); } - public void removeBookmarksListener(@NonNull BookmarkListener listener) { - mBookmarksListeners.remove(listener); + public void addHistoryViewListener(@NonNull HistoryViewDelegate listener) { + mHistoryViewListeners.add(listener); + } + + public void removeHistoryViewListener(@NonNull HistoryViewDelegate listener) { + mHistoryViewListeners.remove(listener); } public void switchBookmarks() { + if (isHistoryVisible()) { + hideHistory(); + showBookmarks(); + + } else if (isBookmarksVisible()) { + hideBookmarks(); + + } else { + showBookmarks(); + } + } + + public void showBookmarks() { if (mView == null) { setView(mBookmarksView); - for (BookmarkListener listener : mBookmarksListeners) + for (BookmarksViewDelegate listener : mBookmarksViewListeners) { listener.onBookmarksShown(this); + } mIsBookmarksVisible = true; - - } else { - unsetView(mBookmarksView); - for (BookmarkListener listener : mBookmarksListeners) - listener.onBookmarksHidden(this); - mIsBookmarksVisible = false; } updateTitleBar(); @@ -297,8 +356,43 @@ public void switchBookmarks() { public void hideBookmarks() { if (mView != null) { unsetView(mBookmarksView); - for (BookmarkListener listener : mBookmarksListeners) + for (BookmarksViewDelegate listener : mBookmarksViewListeners) { listener.onBookmarksHidden(this); + } + mIsBookmarksVisible = false; + } + } + + public void switchHistory() { + if (isBookmarksVisible()) { + hideBookmarks(); + showHistory(); + + } else if (isHistoryVisible()) { + hideHistory(); + + } else { + showHistory(); + } + } + + public void showHistory() { + if (mView == null) { + setView(mHistoryView); + for (HistoryViewDelegate listener : mHistoryViewListeners) { + listener.onHistoryViewShown(this); + } + mIsHistoryVisible = true; + } + } + + public void hideHistory() { + if (mView != null) { + unsetView(mHistoryView); + for (HistoryViewDelegate listener : mHistoryViewListeners) { + listener.onHistoryViewHidden(this); + } + mIsHistoryVisible = false; } } @@ -415,6 +509,9 @@ private void updateTitleBar() { if (isBookmarksVisible()) { updateTitleBarUrl(getResources().getString(R.string.url_bookmarks_title)); + } else if (isHistoryVisible()) { + updateTitleBarUrl(getResources().getString(R.string.url_history_title)); + } else { updateTitleBarUrl(mSessionStack.getCurrentUri()); } @@ -431,7 +528,8 @@ private void updateTitleBarUrl(String url) { mTitleBar.setInsecureVisibility(GONE); mTitleBar.setURL(getResources().getString(R.string.url_home_title, getResources().getString(R.string.app_name))); - } else if (url.equals(getResources().getString(R.string.url_bookmarks_title))) { + } else if (url.equals(getResources().getString(R.string.url_bookmarks_title)) || + url.equals(getResources().getString(R.string.url_history_title))) { mTitleBar.setInsecureVisibility(GONE); mTitleBar.setURL(url); @@ -653,6 +751,7 @@ public void releaseWidget() { mSessionStack.removeVideoAvailabilityListener(this); mSessionStack.removeNavigationListener(this); mSessionStack.removeProgressListener(this); + mSessionStack.setHistoryDelegate(null); GeckoSession session = mSessionStack.getSession(mSessionId); if (session == null) { return; @@ -688,16 +787,17 @@ public void setVisible(boolean aVisible) { } mWidgetPlacement.visible = aVisible; if (!aVisible) { - if (mIsBookmarksVisible) { + if (mIsBookmarksVisible || mIsHistoryVisible) { mWidgetManager.popWorldBrightness(this); } } else { - if (mIsBookmarksVisible) { + if (mIsBookmarksVisible || mIsHistoryVisible) { mWidgetManager.pushWorldBrightness(this, WidgetManagerDelegate.DEFAULT_DIM_BRIGHTNESS); } } mIsBookmarksVisible = isBookmarksVisible(); + mIsHistoryVisible = isHistoryVisible(); mWidgetManager.updateWidget(this); if (!aVisible) { clearFocus(); @@ -861,20 +961,66 @@ public void showButtonPrompt(String title, @NonNull String msg, @NonNull String[ mConfirmPrompt.show(REQUEST_FOCUS); } - public void showAppDialog(@NonNull String title, @NonNull @StringRes int description, @NonNull @StringRes int [] btnMsg, @NonNull AppDialogWidget.Delegate callback) { - mAppDialog = new AppDialogWidget(getContext()); + public void showAppDialog(@NonNull String title, @NonNull @StringRes int description, @NonNull @StringRes int [] btnMsg, + @NonNull BaseAppDialogWidget.Delegate buttonsCallback, @NonNull MessageDialogWidget.Delegate messageCallback) { + mAppDialog = new MessageDialogWidget(getContext()); mAppDialog.mWidgetPlacement.parentHandle = getHandle(); mAppDialog.setTitle(title); mAppDialog.setMessage(description); mAppDialog.setButtons(btnMsg); - mAppDialog.setDelegate(callback); + mAppDialog.setButtonsDelegate(buttonsCallback); + mAppDialog.setMessageDelegate(messageCallback); mAppDialog.show(REQUEST_FOCUS); } + public void showClearCacheDialog() { + mClearCacheDialog = new ClearCacheDialogWidget(getContext()); + mClearCacheDialog.mWidgetPlacement.parentHandle = getHandle(); + mClearCacheDialog.setTitle(R.string.history_clear); + mClearCacheDialog.setButtons(new int[] { + R.string.history_clear_cancel, + R.string.history_clear_now + }); + mClearCacheDialog.setButtonsDelegate((index) -> { + if (index == BaseAppDialogWidget.LEFT) { + mClearCacheDialog.hide(REMOVE_WIDGET); + + } else { + Calendar date = new GregorianCalendar(); + date.set(Calendar.HOUR_OF_DAY, 0); + date.set(Calendar.MINUTE, 0); + date.set(Calendar.SECOND, 0); + date.set(Calendar.MILLISECOND, 0); + + long currentTime = System.currentTimeMillis(); + long todayLimit = date.getTimeInMillis(); + long yesterdayLimit = todayLimit - SystemUtils.ONE_DAY_MILLIS; + long oneWeekLimit = todayLimit - SystemUtils.ONE_WEEK_MILLIS; + + HistoryStore store = SessionStore.get().getHistoryStore(); + switch (mClearCacheDialog.getSelectedRange()) { + case ClearCacheDialogWidget.TODAY: + store.deleteVisitsBetween(todayLimit, currentTime); + break; + case ClearCacheDialogWidget.YESTERDAY: + store.deleteVisitsBetween(yesterdayLimit, todayLimit); + break; + case ClearCacheDialogWidget.LAST_WEEK: + store.deleteVisitsBetween(oneWeekLimit, yesterdayLimit); + break; + case ClearCacheDialogWidget.EVERYTHING: + store.deleteEverything(); + break; + } + } + }); + mClearCacheDialog.show(REQUEST_FOCUS); + } + public void showMaxWindowsDialog(int maxDialogs) { mMaxWindowsDialog = new MaxWindowsWidget(getContext()); mMaxWindowsDialog.mWidgetPlacement.parentHandle = getHandle(); - mMaxWindowsDialog.setMessage(getContext().getString(R.string.max_windows_message, String.valueOf(maxDialogs))); + mMaxWindowsDialog.setMessage(getContext().getString(R.string.max_windows_msg, String.valueOf(maxDialogs))); mMaxWindowsDialog.show(REQUEST_FOCUS); } @@ -915,6 +1061,69 @@ private int getWindowWidth(float aWorldWidth) { return (int) Math.floor(SettingsStore.WINDOW_WIDTH_DEFAULT * aWorldWidth / WidgetPlacement.floatDimension(getContext(), R.dimen.window_world_width)); } + private HistoryCallback mHistoryCallback = new HistoryCallback() { + @Override + public void onClearHistory(View view) { + view.requestFocusFromTouch(); + showClearCacheDialog(); + } + + @Override + public void onShowContextMenu(View view, VisitInfo item, boolean isLastVisibleItem) { + view.requestFocusFromTouch(); + + if (mHistoryContextMenu != null) { + mHistoryContextMenu.hide(REMOVE_WIDGET); + } + + float ratio = WidgetPlacement.viewToWidgetRatio(getContext(), WindowWidget.this); + + Rect offsetViewBounds = new Rect(); + getDrawingRect(offsetViewBounds); + offsetDescendantRectToMyCoords(view, offsetViewBounds); + + mHistoryContextMenu = new HistoryItemContextMenuWidget(getContext()); + mHistoryContextMenu.mWidgetPlacement.parentHandle = getHandle(); + + PointF position; + if (isLastVisibleItem) { + mHistoryContextMenu.mWidgetPlacement.anchorY = 0.0f; + position = new PointF( + (offsetViewBounds.left + view.getWidth()) * ratio, + -(offsetViewBounds.top) * ratio); + + } else { + mHistoryContextMenu.mWidgetPlacement.anchorY = 1.0f; + position = new PointF( + (offsetViewBounds.left + view.getWidth()) * ratio, + -(offsetViewBounds.top + view.getHeight()) * ratio); + } + + mHistoryContextMenu.setPosition(position); + mHistoryContextMenu.setItem(item); + mHistoryContextMenu.setHistoryContextMenuItemCallback((new HistoryItemContextMenuClickCallback() { + @Override + public void onOpenInNewWindowClick(VisitInfo item) { + mWidgetManager.openNewWindow(item.getUrl()); + mHistoryContextMenu.hide(REMOVE_WIDGET); + } + + @Override + public void onAddToBookmarks(VisitInfo item) { + SessionStore.get().getBookmarkStore().addBookmark(item.getUrl(), item.getTitle()); + mHistoryContextMenu.hide(REMOVE_WIDGET); + } + + @Override + public void onRemoveFromBookmarks(VisitInfo item) { + SessionStore.get().getBookmarkStore().deleteBookmarkByURL(item.getUrl()); + mHistoryContextMenu.hide(REMOVE_WIDGET); + } + })); + mHistoryContextMenu.show(REQUEST_FOCUS); + } + }; + // PromptDelegate @Nullable @@ -1089,43 +1298,76 @@ public void onVideoAvailabilityChanged(boolean aVideosAvailable) { @Override public void onLocationChange(@NonNull GeckoSession session, @Nullable String url) { if (isBookmarksVisible()) { - switchBookmarks(); + hideBookmarks(); + + } else if (isHistoryVisible()) { + hideHistory(); } - if (mTitleBar != null && url != null) { - if (url.startsWith("data") && mSessionStack.isPrivateMode()) { - mTitleBar.setInsecureVisibility(GONE); - mTitleBar.setURL(getResources().getString(R.string.private_browsing_title)); + updateTitleBarUrl(url); + } - } else if (url.equals(mSessionStack.getHomeUri())) { - mTitleBar.setInsecureVisibility(VISIBLE); - mTitleBar.setURL(getResources().getString(R.string.url_home_title, getResources().getString(R.string.app_name))); + // GeckoSession.HistoryDelegate - } else if (url.equals(getResources().getString(R.string.url_bookmarks_title))) { - mTitleBar.setInsecureVisibility(GONE); - mTitleBar.setURL(url); + @Override + public void onHistoryStateChange(@NonNull GeckoSession geckoSession, @NonNull HistoryList historyList) { + for (HistoryItem item : historyList) { + SessionStore.get().getHistoryStore().recordObservation(item.getUri(), new PageObservation(item.getTitle())); + } + } - } else if (url.equals(getResources().getString(R.string.about_blank))) { - mTitleBar.setInsecureVisibility(GONE); - mTitleBar.setURL(""); + @Nullable + @Override + public GeckoResult onVisited(@NonNull GeckoSession geckoSession, @NonNull String url, @Nullable String lastVisitedURL, int flags) { + if (mSessionStack.isPrivateMode() || + (flags & VISIT_TOP_LEVEL) == 0 || + (flags & VISIT_UNRECOVERABLE_ERROR) != 0) { + return GeckoResult.fromValue(false); + } + + boolean isReload = lastVisitedURL != null && lastVisitedURL.equals(url); + + VisitType visitType; + if (isReload) { + visitType = VisitType.RELOAD; + + } else { + if ((flags & VISIT_REDIRECT_SOURCE_PERMANENT) != 0) { + visitType = VisitType.REDIRECT_PERMANENT; + + } else if ((flags & VISIT_REDIRECT_SOURCE) != 0) { + visitType = VisitType.REDIRECT_TEMPORARY; } else { - mTitleBar.setInsecureVisibility(View.VISIBLE); - mTitleBar.setURL(url); + visitType = VisitType.LINK; } } + + SessionStore.get().getHistoryStore().deleteVisitsFor(url); + SessionStore.get().getHistoryStore().recordVisit(url, visitType); + + return GeckoResult.fromValue(true); } + @UiThread @Nullable - @Override - public GeckoResult onLoadRequest(@NonNull GeckoSession session, @NonNull LoadRequest request) { - if (request.isRedirect) { - SessionStore.get().getHistoryStore().addHistory(request.uri, VisitType.EMBED); - } else if (request.triggerUri != null) { - SessionStore.get().getHistoryStore().addHistory(request.uri, VisitType.LINK); + public GeckoResult getVisited(@NonNull GeckoSession geckoSession, @NonNull String[] urls) { + if (mSessionStack.isPrivateMode()) { + return GeckoResult.fromValue(new boolean[]{}); } - return GeckoResult.ALLOW; + GeckoResult result = new GeckoResult<>(); + + SessionStore.get().getHistoryStore().getVisited(Arrays.asList(urls)).thenAcceptAsync(list -> { + final boolean[] primitives = new boolean[list.size()]; + int index = 0; + for (Boolean object : list) { + primitives[index++] = object; + } + result.complete(primitives); + }); + + return result; } // GeckoSession.ProgressDelegate diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Windows.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Windows.java index 92fab8785..4fc075f97 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Windows.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Windows.java @@ -234,6 +234,7 @@ public void closeWindow(@NonNull WindowWidget aWindow) { WindowWidget rightWindow = getRightWindow(); aWindow.hideBookmarks(); + aWindow.hideHistory(); if (leftWindow == aWindow) { removeWindow(leftWindow); @@ -750,6 +751,11 @@ public void onAddWindowClicked() { } } + @Override + public void onHistoryClicked() { + mFocusedWindow.switchHistory(); + } + // TopBarWidget Delegate @Override public void onCloseClicked(TopBarWidget aWidget) { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/AppDialogWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/BaseAppDialogWidget.java similarity index 74% rename from app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/AppDialogWidget.java rename to app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/BaseAppDialogWidget.java index 2364bc758..4908ad150 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/AppDialogWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/BaseAppDialogWidget.java @@ -16,35 +16,33 @@ import androidx.databinding.DataBindingUtil; import org.mozilla.vrbrowser.R; -import org.mozilla.vrbrowser.databinding.AppDialogBinding; +import org.mozilla.vrbrowser.databinding.BaseAppDialogBinding; import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate; import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; -import org.mozilla.vrbrowser.utils.ViewUtils; -public class AppDialogWidget extends UIDialog { +public class BaseAppDialogWidget extends UIDialog { public interface Delegate { void onButtonClicked(int index); - void onMessageLinkClicked(@NonNull String url); } public static final int LEFT = 0; public static final int RIGHT = 1; - private AppDialogBinding mBinding; + protected BaseAppDialogBinding mBinding; private Delegate mAppDialogDelegate; - public AppDialogWidget(Context aContext) { + public BaseAppDialogWidget(Context aContext) { super(aContext); initialize(aContext); } - public AppDialogWidget(Context aContext, AttributeSet aAttrs) { + public BaseAppDialogWidget(Context aContext, AttributeSet aAttrs) { super(aContext, aAttrs); initialize(aContext); } - public AppDialogWidget(Context aContext, AttributeSet aAttrs, int aDefStyle) { + public BaseAppDialogWidget(Context aContext, AttributeSet aAttrs, int aDefStyle) { super(aContext, aAttrs, aDefStyle); initialize(aContext); } @@ -53,42 +51,41 @@ private void initialize(Context aContext) { LayoutInflater inflater = LayoutInflater.from(aContext); // Inflate this data binding layout - mBinding = DataBindingUtil.inflate(inflater, R.layout.app_dialog, this, true); + mBinding = DataBindingUtil.inflate(inflater, R.layout.base_app_dialog, this, true); mBinding.leftButton.setOnClickListener(v -> { if (mAppDialogDelegate != null) { mAppDialogDelegate.onButtonClicked(LEFT); } - AppDialogWidget.this.onDismiss(); + BaseAppDialogWidget.this.onDismiss(); }); mBinding.rightButton.setOnClickListener(v -> { if (mAppDialogDelegate != null) { mAppDialogDelegate.onButtonClicked(RIGHT); } - AppDialogWidget.this.onDismiss(); + BaseAppDialogWidget.this.onDismiss(); }); } @Override protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { aPlacement.visible = false; - aPlacement.width = WidgetPlacement.dpDimension(getContext(), R.dimen.app_dialog_width); + aPlacement.width = WidgetPlacement.dpDimension(getContext(), R.dimen.base_app_dialog_width); aPlacement.height = WidgetPlacement.pixelDimension(getContext(), R.dimen.browser_width_pixels)/2; aPlacement.parentAnchorX = 0.5f; aPlacement.parentAnchorY = 0.5f; aPlacement.anchorX = 0.5f; aPlacement.anchorY = 0.5f; - aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.app_dialog_z_distance); + aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.base_app_dialog_z_distance); } @Override public void show(@ShowFlags int aShowFlags) { - measure(View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - + measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); super.show(aShowFlags); mWidgetManager.pushWorldBrightness(this, WidgetManagerDelegate.DEFAULT_DIM_BRIGHTNESS); @@ -100,7 +97,7 @@ public void show(@ShowFlags int aShowFlags) { public void onGlobalLayout() { getViewTreeObserver().removeOnGlobalLayoutListener(this); mWidgetPlacement.height = (int)(getHeight()/mWidgetPlacement.density); - mWidgetManager.updateWidget(AppDialogWidget.this); + mWidgetManager.updateWidget(BaseAppDialogWidget.this); } }); } @@ -112,6 +109,7 @@ public void hide(@HideFlags int aHideFlags) { } // WidgetManagerDelegate.FocusChangeListener + @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) { if (oldFocus == this && isVisible() && findViewById(newFocus.getId()) == null) { @@ -119,7 +117,7 @@ public void onGlobalFocusChanged(View oldFocus, View newFocus) { } } - public void setDelegate(Delegate delegate) { + public void setButtonsDelegate(Delegate delegate) { mAppDialogDelegate = delegate; } @@ -131,19 +129,6 @@ public void setTitle(String title) { mBinding.title.setText(title); } - public void setMessage(@StringRes int message) { - ViewUtils.setTextViewHTML(mBinding.message, getResources().getString(message), (widget, url) -> { - if (mAppDialogDelegate != null) { - mAppDialogDelegate.onMessageLinkClicked(url); - onDismiss(); - } - }); - } - - public void setMessage(String message) { - mBinding.message.setText(message); - } - public void setButtons(@StringRes int[] buttons) { if (buttons.length > 0) { mBinding.leftButton.setText(buttons[LEFT]); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/ClearCacheDialogWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/ClearCacheDialogWidget.java new file mode 100644 index 000000000..201811fcc --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/ClearCacheDialogWidget.java @@ -0,0 +1,59 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.vrbrowser.ui.widgets.dialogs; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; + +import androidx.annotation.IntDef; +import androidx.databinding.DataBindingUtil; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.databinding.ClearCacheDialogBinding; +import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; + +public class ClearCacheDialogWidget extends BaseAppDialogWidget { + + @IntDef(value = { TODAY, YESTERDAY, LAST_WEEK, EVERYTHING}) + public @interface ClearCacheRange {} + public static final int TODAY = 0; + public static final int YESTERDAY = 1; + public static final int LAST_WEEK = 2; + public static final int EVERYTHING = 3; + + private ClearCacheDialogBinding mClearCacheBinding; + + public ClearCacheDialogWidget(Context aContext) { + super(aContext); + initialize(aContext); + } + + public ClearCacheDialogWidget(Context aContext, AttributeSet aAttrs) { + super(aContext, aAttrs); + initialize(aContext); + } + + public ClearCacheDialogWidget(Context aContext, AttributeSet aAttrs, int aDefStyle) { + super(aContext, aAttrs, aDefStyle); + initialize(aContext); + } + + private void initialize(Context aContext) { + LayoutInflater inflater = LayoutInflater.from(aContext); + + // Inflate this data binding layout + mClearCacheBinding = DataBindingUtil.inflate(inflater, R.layout.clear_cache_dialog, mBinding.dialogContent, true); + mClearCacheBinding.clearCacheRadio.setChecked(0, false); + + mWidgetPlacement.width = WidgetPlacement.dpDimension(getContext(), R.dimen.cache_app_dialog_width); + } + + public @ClearCacheRange int getSelectedRange() { + return mClearCacheBinding.clearCacheRadio.getCheckedRadioButtonId(); + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/HistoryItemContextMenuWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/HistoryItemContextMenuWidget.java new file mode 100644 index 000000000..f32d6f992 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/HistoryItemContextMenuWidget.java @@ -0,0 +1,133 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.vrbrowser.ui.widgets.dialogs; + +import android.content.Context; +import android.graphics.PointF; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewTreeObserver; + +import androidx.annotation.NonNull; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.ui.callbacks.HistoryItemContextMenuClickCallback; +import org.mozilla.vrbrowser.ui.views.HistoryItemContextMenu; +import org.mozilla.vrbrowser.ui.widgets.UIWidget; +import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate; +import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; +import org.mozilla.vrbrowser.utils.ViewUtils; + +import mozilla.components.concept.storage.VisitInfo; + +public class HistoryItemContextMenuWidget extends UIWidget implements WidgetManagerDelegate.FocusChangeListener { + + private HistoryItemContextMenu mContextMenu; + private int mMaxHeight; + private PointF mTranslation; + + public HistoryItemContextMenuWidget(Context aContext) { + super(aContext); + + mContextMenu = new HistoryItemContextMenu(aContext); + initialize(); + } + + public HistoryItemContextMenuWidget(Context aContext, AttributeSet aAttrs) { + super(aContext, aAttrs); + + mContextMenu = new HistoryItemContextMenu(aContext, aAttrs); + initialize(); + } + + public HistoryItemContextMenuWidget(Context aContext, AttributeSet aAttrs, int aDefStyle) { + super(aContext, aAttrs, aDefStyle); + + mContextMenu = new HistoryItemContextMenu(aContext, aAttrs, aDefStyle); + initialize(); + } + + private void initialize() { + addView(mContextMenu); + } + + @Override + protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { + aPlacement.visible = false; + aPlacement.width = WidgetPlacement.pixelDimension(getContext(), R.dimen.browser_width_pixels)/2; + mMaxHeight = WidgetPlacement.dpDimension(getContext(), R.dimen.prompt_height); + aPlacement.height = mMaxHeight; + aPlacement.parentAnchorX = 0.0f; + aPlacement.parentAnchorY = 1.0f; + aPlacement.anchorX = 0.0f; + aPlacement.anchorY = 1.0f; + aPlacement.opaque = false; + aPlacement.cylinder = true; + aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.library_context_menu_z_distance); + } + + @Override + public void show(@ShowFlags int aShowFlags) { + mWidgetManager.addFocusChangeListener(HistoryItemContextMenuWidget.this); + + measure(View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + mWidgetPlacement.width = (int)(getMeasuredWidth()/mWidgetPlacement.density); + mWidgetPlacement.height = (int)(getMeasuredHeight()/mWidgetPlacement.density); + super.show(aShowFlags); + + ViewTreeObserver viewTreeObserver = mContextMenu.getViewTreeObserver(); + if (viewTreeObserver.isAlive()) { + viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + + mWidgetPlacement.translationX = mTranslation.x - (getWidth()/mWidgetPlacement.density); + mWidgetPlacement.translationY = mTranslation.y + getResources().getDimension(R.dimen.library_context_menu_top_margin)/mWidgetPlacement.density; + + mWidgetPlacement.width = (int)(getWidth()/mWidgetPlacement.density); + mWidgetPlacement.height = (int)(getHeight()/mWidgetPlacement.density); + mWidgetManager.updateWidget(HistoryItemContextMenuWidget.this); + } + }); + } + } + + @Override + public void hide(@HideFlags int aHideFlags) { + super.hide(aHideFlags); + + mWidgetManager.removeFocusChangeListener(this); + } + + @Override + protected void onDismiss() { + hide(REMOVE_WIDGET); + } + + public void setHistoryContextMenuItemCallback(HistoryItemContextMenuClickCallback callback) { + mContextMenu.setContextMenuClickCallback(callback); + } + + public void setItem(@NonNull VisitInfo item) { + mContextMenu.setItem(item); + } + + public void setPosition(@NonNull PointF position) { + mTranslation = position; + } + + // WidgetManagerDelegate.FocusChangeListener + + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + if (!ViewUtils.isChildrenOf(mContextMenu, newFocus)) { + onDismiss(); + } + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/MessageDialogWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/MessageDialogWidget.java new file mode 100644 index 000000000..038894251 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/MessageDialogWidget.java @@ -0,0 +1,68 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.vrbrowser.ui.widgets.dialogs; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.databinding.DataBindingUtil; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.databinding.MessageDialogBinding; +import org.mozilla.vrbrowser.utils.ViewUtils; + +public class MessageDialogWidget extends BaseAppDialogWidget { + + public interface Delegate { + void onMessageLinkClicked(@NonNull String url); + } + + private MessageDialogBinding mMessageBinding; + private Delegate mMessageDialogDelegate; + + public MessageDialogWidget(Context aContext) { + super(aContext); + initialize(aContext); + } + + public MessageDialogWidget(Context aContext, AttributeSet aAttrs) { + super(aContext, aAttrs); + initialize(aContext); + } + + public MessageDialogWidget(Context aContext, AttributeSet aAttrs, int aDefStyle) { + super(aContext, aAttrs, aDefStyle); + initialize(aContext); + } + + private void initialize(Context aContext) { + LayoutInflater inflater = LayoutInflater.from(aContext); + + // Inflate this data binding layout + mMessageBinding = DataBindingUtil.inflate(inflater, R.layout.message_dialog, mBinding.dialogContent, true); + } + + public void setMessageDelegate(Delegate delegate) { + mMessageDialogDelegate = delegate; + } + + public void setMessage(@StringRes int message) { + ViewUtils.setTextViewHTML(mMessageBinding.message, getResources().getString(message), (widget, url) -> { + if (mMessageDialogDelegate != null) { + mMessageDialogDelegate.onMessageLinkClicked(url); + onDismiss(); + } + }); + } + + public void setMessage(String message) { + mMessageBinding.message.setText(message); + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java index e41291424..f8e0d625d 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java @@ -291,21 +291,16 @@ public void show(@ShowFlags int aShowFlags) { new int[]{ R.string.voice_samples_collect_dialog_do_not_allow, R.string.voice_samples_collect_dialog_allow}, - new AppDialogWidget.Delegate() { - @Override - public void onButtonClicked(int index) { - SettingsStore.getInstance(getContext()).setSpeechDataCollectionReviewed(true); - if (index == AppDialogWidget.RIGHT) { - SettingsStore.getInstance(getContext()).setSpeechDataCollectionEnabled(true); - } - ThreadUtils.postToUiThread(() -> show(aShowFlags)); - } - - @Override - public void onMessageLinkClicked(@NonNull String url) { - mWidgetManager.getFocusedWindow().getSessionStack().loadUri(getResources().getString(R.string.private_policy_url)); - onDismiss(); + index -> { + SettingsStore.getInstance(getContext()).setSpeechDataCollectionReviewed(true); + if (index == MessageDialogWidget.RIGHT) { + SettingsStore.getInstance(getContext()).setSpeechDataCollectionEnabled(true); } + ThreadUtils.postToUiThread(() -> show(aShowFlags)); + }, + url -> { + mWidgetManager.getFocusedWindow().getSessionStack().loadUri(getResources().getString(R.string.private_policy_url)); + onDismiss(); }); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/utils/AnimationHelper.java b/app/src/common/shared/org/mozilla/vrbrowser/utils/AnimationHelper.java index c5341f355..fefd683f2 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/utils/AnimationHelper.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/utils/AnimationHelper.java @@ -1,6 +1,10 @@ package org.mozilla.vrbrowser.utils; +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.util.Log; import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -64,4 +68,46 @@ public void onAnimationRepeat(Animation animation) { aView.setAnimation(animation); } + + public static void animateViewPadding(View view, int paddingStart, int paddingEnd, int duration) { + animateViewPadding(view, paddingStart, paddingEnd, duration, null); + } + + public static void animateViewPadding(View view, int paddingStart, int paddingEnd, int duration, Runnable onAnimationEnd) { + ValueAnimator animation = ValueAnimator.ofInt(paddingStart, paddingEnd); + animation.setDuration(duration); + animation.setInterpolator(new AccelerateDecelerateInterpolator()); + animation.addUpdateListener(valueAnimator -> { + try { + int newPadding = Integer.parseInt(valueAnimator.getAnimatedValue().toString()); + view.setPadding(newPadding, newPadding, newPadding, newPadding); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + } + }); + animation.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + + } + + @Override + public void onAnimationEnd(Animator animator) { + if (onAnimationEnd != null) { + onAnimationEnd.run(); + } + } + + @Override + public void onAnimationCancel(Animator animator) { + + } + + @Override + public void onAnimationRepeat(Animator animator) { + + } + }); + animation.start(); + } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/utils/SystemUtils.java b/app/src/common/shared/org/mozilla/vrbrowser/utils/SystemUtils.java index 65462dd6b..10452a4c8 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/utils/SystemUtils.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/utils/SystemUtils.java @@ -12,6 +12,10 @@ public class SystemUtils { + public static final long ONE_DAY_MILLIS = 86400000; + public static final long TWO_DAYS_MILLIS = 172800000; + public static final long ONE_WEEK_MILLIS = 604800000; + public static final void restart(@NonNull Context context) { Intent i = new Intent(context, VRBrowserActivity.class); i.setPackage(BuildConfig.APPLICATION_ID); diff --git a/app/src/main/res/color/library_panel_button_text_color.xml b/app/src/main/res/color/library_panel_button_text_color.xml new file mode 100644 index 000000000..08f85705a --- /dev/null +++ b/app/src/main/res/color/library_panel_button_text_color.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/library_panel_context_menu_item_color.xml b/app/src/main/res/color/library_panel_context_menu_item_color.xml new file mode 100644 index 000000000..c944e626d --- /dev/null +++ b/app/src/main/res/color/library_panel_context_menu_item_color.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bookmark_description_color.xml b/app/src/main/res/color/library_panel_description_color.xml similarity index 72% rename from app/src/main/res/drawable/bookmark_description_color.xml rename to app/src/main/res/color/library_panel_description_color.xml index a03ebe987..863ea1a6e 100644 --- a/app/src/main/res/drawable/bookmark_description_color.xml +++ b/app/src/main/res/color/library_panel_description_color.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bookmark_icon_color.xml b/app/src/main/res/color/library_panel_icon_color.xml similarity index 100% rename from app/src/main/res/drawable/bookmark_icon_color.xml rename to app/src/main/res/color/library_panel_icon_color.xml diff --git a/app/src/main/res/drawable/bookmark_text_color.xml b/app/src/main/res/color/library_panel_title_text_color.xml similarity index 100% rename from app/src/main/res/drawable/bookmark_text_color.xml rename to app/src/main/res/color/library_panel_title_text_color.xml diff --git a/app/src/main/res/drawable/content_panel_button_background.xml b/app/src/main/res/drawable/content_panel_button_background.xml new file mode 100644 index 000000000..66a1ed8e8 --- /dev/null +++ b/app/src/main/res/drawable/content_panel_button_background.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_icon_library_new_window.xml b/app/src/main/res/drawable/ic_icon_library_new_window.xml new file mode 100644 index 000000000..a64efb5d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_library_new_window.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_icon_more_options.xml b/app/src/main/res/drawable/ic_icon_more_options.xml new file mode 100644 index 000000000..d352cd74e --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_more_options.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/library_panel_context_menu_item_background_color_bottom.xml b/app/src/main/res/drawable/library_panel_context_menu_item_background_color_bottom.xml new file mode 100644 index 000000000..0aeeefce1 --- /dev/null +++ b/app/src/main/res/drawable/library_panel_context_menu_item_background_color_bottom.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/library_panel_context_menu_item_background_color_middle.xml b/app/src/main/res/drawable/library_panel_context_menu_item_background_color_middle.xml new file mode 100644 index 000000000..0c7d42ab7 --- /dev/null +++ b/app/src/main/res/drawable/library_panel_context_menu_item_background_color_middle.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/library_panel_context_menu_item_background_color_top.xml b/app/src/main/res/drawable/library_panel_context_menu_item_background_color_top.xml new file mode 100644 index 000000000..93fbfdd02 --- /dev/null +++ b/app/src/main/res/drawable/library_panel_context_menu_item_background_color_top.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bookmark_background_color.xml b/app/src/main/res/drawable/library_panel_item_background_color.xml similarity index 100% rename from app/src/main/res/drawable/bookmark_background_color.xml rename to app/src/main/res/drawable/library_panel_item_background_color.xml diff --git a/app/src/main/res/layout/app_dialog.xml b/app/src/main/res/layout/base_app_dialog.xml similarity index 78% rename from app/src/main/res/layout/app_dialog.xml rename to app/src/main/res/layout/base_app_dialog.xml index 29b8e30ff..263752283 100644 --- a/app/src/main/res/layout/app_dialog.xml +++ b/app/src/main/res/layout/base_app_dialog.xml @@ -28,30 +28,24 @@ android:typeface="sans" tools:text="Title" /> - + android:gravity="top|center_horizontal" /> - + android:gravity="center_horizontal">