diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/HistoryStore.kt b/app/src/common/shared/org/mozilla/vrbrowser/browser/HistoryStore.kt new file mode 100644 index 000000000..78db218ec --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/HistoryStore.kt @@ -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.browser + +import android.content.Context +import android.os.Handler +import android.os.Looper +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.future.future +import mozilla.components.concept.storage.VisitType +import org.mozilla.vrbrowser.VRBrowserApplication +import java.util.concurrent.CompletableFuture + +class HistoryStore constructor(val context: Context) { + private var listeners = ArrayList() + private val storage = (context.applicationContext as VRBrowserApplication).places.history + + interface HistoryListener { + fun onHistoryUpdated() + } + + fun addListener(aListener: HistoryListener) { + if (!listeners.contains(aListener)) { + listeners.add(aListener) + } + } + + fun removeListener(aListener: HistoryListener) { + listeners.remove(aListener) + } + + fun removeAllListeners() { + listeners.clear() + } + + fun getHistory(): CompletableFuture?> = GlobalScope.future { + storage.getVisited() + } + + fun addHistory(aURL: String, visitType: VisitType) = GlobalScope.future { + storage.recordVisit(aURL, visitType) + notifyListeners() + } + + fun deleteHistory(aUrl: String, timestamp: Long) = GlobalScope.future { + storage.deleteVisit(aUrl, timestamp) + notifyListeners() + } + + fun isInHistory(aURL: String): CompletableFuture = GlobalScope.future { + storage.getVisited(listOf(aURL)) != null + } + + private fun notifyListeners() { + if (listeners.size > 0) { + val listenersCopy = ArrayList(listeners) + Handler(Looper.getMainLooper()).post { + for (listener in listenersCopy) { + listener.onHistoryUpdated() + } + } + } + } +} + diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/Places.kt b/app/src/common/shared/org/mozilla/vrbrowser/browser/Places.kt index 37c9ef784..fe6a6410c 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/Places.kt +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/Places.kt @@ -7,10 +7,12 @@ package org.mozilla.vrbrowser.browser import android.content.Context import mozilla.components.browser.storage.sync.PlacesBookmarksStorage +import mozilla.components.browser.storage.sync.PlacesHistoryStorage /** * Entry point for interacting with places-backed storage layers. */ class Places(context: Context) { val bookmarks by lazy { PlacesBookmarksStorage(context) } + val history by lazy { PlacesHistoryStorage(context) } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java index f1792768f..13ab34f60 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionStore.java @@ -54,6 +54,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import mozilla.components.concept.storage.VisitType; + import static org.mozilla.vrbrowser.utils.ServoUtils.createServoSession; import static org.mozilla.vrbrowser.utils.ServoUtils.isInstanceOfServoSession; import static org.mozilla.vrbrowser.utils.ServoUtils.isServoAvailable; @@ -134,6 +136,7 @@ class State { private Context mContext; private SharedPreferences mPrefs; private BookmarksStore mBookmarksStore; + private HistoryStore mHistoryStore; private SessionStore() { mSessions = new LinkedHashMap<>(); @@ -170,6 +173,9 @@ public void unregisterListeners() { if (mBookmarksStore != null) { mBookmarksStore.removeAllListeners(); } + if (mHistoryStore!= null) { + mHistoryStore.removeAllListeners(); + } } public void setContext(Context aContext, Bundle aExtras) { @@ -208,6 +214,7 @@ public void setContext(Context aContext, Bundle aExtras) { mContext = aContext; mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); mBookmarksStore = new BookmarksStore(mContext); + mHistoryStore = new HistoryStore(mContext); if (mUserAgentOverride == null) { mUserAgentOverride = new UserAgentOverride(); mUserAgentOverride.loadOverridesFromAssets((Activity)aContext, aContext.getString(R.string.user_agent_override_file)); @@ -218,6 +225,10 @@ public BookmarksStore getBookmarkStore() { return mBookmarksStore; } + public HistoryStore getHistoryStore() { + return mHistoryStore; + } + public void dumpAllState(Integer sessionId) { dumpAllState(getSession(sessionId)); } @@ -1045,6 +1056,11 @@ public void onCanGoForward(@NonNull GeckoSession aSession, boolean aCanGoForward @Override public @Nullable GeckoResult onLoadRequest(@NonNull GeckoSession aSession, @NonNull LoadRequest aRequest) { + if (aRequest.isRedirect) + mHistoryStore.addHistory(aRequest.uri, VisitType.EMBED); + else if (aRequest.triggerUri != null) + mHistoryStore.addHistory(aRequest.uri, VisitType.LINK); + final GeckoResult result = new GeckoResult<>(); String uri = aRequest.uri; diff --git a/app/src/common/shared/org/mozilla/vrbrowser/search/SearchEngineWrapper.java b/app/src/common/shared/org/mozilla/vrbrowser/search/SearchEngineWrapper.java index ae5f9cdb9..21cce6f10 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/search/SearchEngineWrapper.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/search/SearchEngineWrapper.java @@ -6,8 +6,11 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Handler; import android.preference.PreferenceManager; +import androidx.annotation.NonNull; + import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.geolocation.GeolocationData; @@ -19,8 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - -import androidx.annotation.NonNull; +import java.util.concurrent.CompletableFuture; import kotlinx.coroutines.Dispatchers; import mozilla.components.browser.search.SearchEngine; @@ -102,13 +104,14 @@ private String getSuggestionURL(String aQuery) { return mSearchEngine.buildSuggestionsURL(aQuery); } - public void getSuggestions(String aQuery, SuggestionsDelegate delegate) { + public CompletableFuture> getSuggestions(String aQuery) { + CompletableFuture> future = new CompletableFuture<>(); // TODO: Use mSuggestionsClient.getSuggestions when fixed in browser-search. String query = getSuggestionURL(aQuery); - SuggestionsClient.getSuggestions(mSearchEngine, query, result -> { - delegate.OnSuggestions(result); - return null; - }); + new Handler(mContext.getMainLooper()).post(() -> + SuggestionsClient.getSuggestions(mSearchEngine, query).thenAcceptAsync((result) -> future.complete(result))); + + return future; } public String getResourceURL() { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/search/suggestions/SuggestionsClient.java b/app/src/common/shared/org/mozilla/vrbrowser/search/suggestions/SuggestionsClient.java index 81db33543..f68a63772 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/search/suggestions/SuggestionsClient.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/search/suggestions/SuggestionsClient.java @@ -4,7 +4,7 @@ import com.loopj.android.http.TextHttpResponseHandler; import java.util.List; -import java.util.function.Function; +import java.util.concurrent.CompletableFuture; import cz.msebera.android.httpclient.Header; import mozilla.components.browser.search.SearchEngine; @@ -13,17 +13,21 @@ public class SuggestionsClient { private static AsyncHttpClient client = new AsyncHttpClient(); - public static void getSuggestions(SearchEngine mEngine, String aQuery, Function, Void> callback) { + public static CompletableFuture> getSuggestions(SearchEngine mEngine, String aQuery) { + final CompletableFuture future = new CompletableFuture(); client.cancelAllRequests(true); client.get(aQuery, null, new TextHttpResponseHandler("ISO-8859-1") { @Override public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + future.completeExceptionally(throwable); } @Override public void onSuccess(int statusCode, Header[] headers, String responseString) { - callback.apply(SuggestionParser.selectResponseParser(mEngine).apply(responseString)); + future.complete(SuggestionParser.selectResponseParser(mEngine).apply(responseString)); } }); + + return future; } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/search/suggestions/SuggestionsProvider.java b/app/src/common/shared/org/mozilla/vrbrowser/search/suggestions/SuggestionsProvider.java new file mode 100644 index 000000000..4267e5c90 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/search/suggestions/SuggestionsProvider.java @@ -0,0 +1,163 @@ +package org.mozilla.vrbrowser.search.suggestions; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import org.mozilla.vrbrowser.browser.SessionStore; +import org.mozilla.vrbrowser.search.SearchEngineWrapper; +import org.mozilla.vrbrowser.ui.widgets.SuggestionsWidget.SuggestionItem; +import org.mozilla.vrbrowser.ui.widgets.SuggestionsWidget.SuggestionItem.Type; +import org.mozilla.vrbrowser.utils.UrlUtils; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class SuggestionsProvider { + + public class DefaultSuggestionsComparator implements Comparator { + + public int compare(Object obj1, Object obj2) { + SuggestionItem suggestion1 = (SuggestionItem)obj1; + SuggestionItem suggestion2 = (SuggestionItem)obj2; + if (suggestion1.type == Type.SUGGESTION && suggestion2.type == Type.SUGGESTION) { + return 0; + + } else if (suggestion1.type == suggestion2.type) { + if (mFilterText != null) { + if (suggestion1.title != null && suggestion2.title != null) + return suggestion1.title.toLowerCase().indexOf(mFilterText) - suggestion2.title.toLowerCase().indexOf(mFilterText); + return suggestion1.url.toLowerCase().indexOf(mFilterText) - suggestion2.url.indexOf(mFilterText); + + } else { + return suggestion1.url.compareTo(suggestion2.url); + } + + } else { + return suggestion1.type.ordinal() - suggestion2.type.ordinal(); + } + } + } + + private Context mContext; + private SearchEngineWrapper mSearchEngineWrapper; + private String mText; + private String mFilterText; + private Comparator mComparator; + + public SuggestionsProvider(Context context) { + mContext = context; + mSearchEngineWrapper = SearchEngineWrapper.get(mContext); + mFilterText = ""; + mComparator = new DefaultSuggestionsComparator(); + } + + private String getSearchURLOrDomain(String text) { + if (UrlUtils.isDomain(text)) { + return text; + + } else { + return mSearchEngineWrapper.getSearchURL(text); + } + } + + public void setFilterText(String text) { + mFilterText = text.toLowerCase(); + } + + public void setText(String text) { mText = text; } + + public void setComparator(Comparator comparator) { + mComparator = comparator; + } + + public CompletableFuture> getBookmarkSuggestions(@NonNull List items) { + CompletableFuture future = new CompletableFuture(); + SessionStore.get().getBookmarkStore().getBookmarks().thenAcceptAsync((bookmarks) -> { + bookmarks.stream(). + filter(b -> b.getUrl().toLowerCase().contains(mFilterText) || + b.getTitle().toLowerCase().contains(mFilterText)) + .forEach(b -> items.add(SuggestionItem.create( + b.getTitle(), + b.getUrl(), + null, + Type.BOOKMARK + ))); + if (mComparator != null) + items.sort(mComparator); + future.complete(items); + }); + + return future; + } + + public CompletableFuture> getHistorySuggestions(@NonNull final List items) { + CompletableFuture future = new CompletableFuture(); + SessionStore.get().getHistoryStore().getHistory().thenAcceptAsync((history) -> { + history.stream() + .filter(h -> + h.toLowerCase().contains(mFilterText)) + .forEach(h -> items.add(SuggestionItem.create( + h, + h, + null, + Type.HISTORY + ))); + if (mComparator != null) + items.sort(mComparator); + future.complete(items); + }); + + return future; + } + + public CompletableFuture> getSearchEngineSuggestions(@NonNull final List items) { + CompletableFuture future = new CompletableFuture(); + + // Completion from browser-domains + if (!mText.equals(mFilterText)) { + items.add(SuggestionItem.create( + mText, + getSearchURLOrDomain(mText), + null, + Type.COMPLETION + )); + } + + // Original text + items.add(SuggestionItem.create( + mFilterText, + getSearchURLOrDomain(mFilterText), + null, + Type.SUGGESTION + )); + + // Suggestions + mSearchEngineWrapper.getSuggestions(mFilterText).thenAcceptAsync((suggestions) -> { + suggestions.forEach(s -> { + String url = mSearchEngineWrapper.getSearchURL(s); + items.add(SuggestionItem.create( + s, + url, + null, + Type.SUGGESTION + )); + }); + if (mComparator != null) + items.sort(mComparator); + future.complete(items); + }); + + return future; + } + + public CompletableFuture> getSuggestions() { + return CompletableFuture.supplyAsync(() -> new ArrayList()) + .thenComposeAsync(this::getSearchEngineSuggestions) + .thenComposeAsync(this::getBookmarkSuggestions) + .thenComposeAsync(this::getHistorySuggestions); + } + +} 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 507278bd3..0e02ebdc2 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 @@ -34,6 +34,7 @@ import androidx.databinding.DataBindingUtil; import mozilla.components.concept.storage.BookmarkNode; +import mozilla.components.concept.storage.VisitType; public class BookmarksView extends FrameLayout implements GeckoSession.NavigationDelegate, BookmarksStore.BookmarkListener { @@ -103,6 +104,7 @@ public void onClick(BookmarkNode bookmark) { mAudio.playSound(AudioEngine.Sound.CLICK); } + SessionStore.get().getHistoryStore().addHistory(bookmark.getUrl(), VisitType.BOOKMARK); SessionStore.get().loadUri(bookmark.getUrl()); } 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 621fe962a..194ed624d 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 @@ -45,6 +45,7 @@ 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 { @@ -412,6 +413,7 @@ public void handleURLEdit(String text) { } if (SessionStore.get().getCurrentUri() != url) { + SessionStore.get().getHistoryStore().addHistory(url, VisitType.TYPED); SessionStore.get().loadUri(url); if (mDelegate != null) { 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 c98a8c62f..ffd66924d 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 @@ -15,6 +15,9 @@ import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.mozilla.geckoview.AllowOrDeny; import org.mozilla.geckoview.GeckoResult; import org.mozilla.geckoview.GeckoSession; @@ -24,22 +27,21 @@ import org.mozilla.vrbrowser.browser.Media; import org.mozilla.vrbrowser.browser.SessionStore; import org.mozilla.vrbrowser.browser.SettingsStore; -import org.mozilla.vrbrowser.search.SearchEngineWrapper; +import org.mozilla.vrbrowser.search.suggestions.SuggestionsProvider; import org.mozilla.vrbrowser.ui.views.CustomUIButton; import org.mozilla.vrbrowser.ui.views.NavigationURLBar; import org.mozilla.vrbrowser.ui.views.UIButton; import org.mozilla.vrbrowser.ui.views.UITextButton; import org.mozilla.vrbrowser.ui.widgets.dialogs.VoiceSearchWidget; import org.mozilla.vrbrowser.utils.AnimationHelper; -import org.mozilla.vrbrowser.utils.UrlUtils; import org.mozilla.vrbrowser.utils.ServoUtils; +import org.mozilla.vrbrowser.utils.UIThreadExecutor; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import mozilla.components.concept.storage.VisitType; public class NavigationBarWidget extends UIWidget implements GeckoSession.NavigationDelegate, GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, WidgetManagerDelegate.WorldClickListener, @@ -85,7 +87,7 @@ public class NavigationBarWidget extends UIWidget implements GeckoSession.Naviga private Context mAppContext; private SharedPreferences mPrefs; private SuggestionsWidget mPopup; - private SearchEngineWrapper mSearchEngineWrapper; + private SuggestionsProvider mSuggestionsProvider; private VideoProjectionMenuWidget mProjectionMenu; private WidgetPlacement mProjectionMenuPlacement; private BrightnessMenuWidget mBrightnessWidget; @@ -164,6 +166,9 @@ else if (SessionStore.get().canUnstackSession()) mReloadButton.setOnClickListener(v -> { v.requestFocusFromTouch(); + SessionStore.get().getHistoryStore().addHistory( + SessionStore.get().getCurrentUri(), + VisitType.RELOAD); if (mIsLoading) { SessionStore.get().stop(); } else { @@ -310,7 +315,7 @@ else if (SessionStore.get().canUnstackSession()) mVoiceSearchWidget = createChild(VoiceSearchWidget.class, false); mVoiceSearchWidget.setDelegate(this); - mSearchEngineWrapper = SearchEngineWrapper.get(getContext()); + mSuggestionsProvider = new SuggestionsProvider(getContext()); SessionStore.get().addSessionChangeListener(this); @@ -854,48 +859,19 @@ public void OnShowSearchPopup() { return; } - mSearchEngineWrapper.getSuggestions( - originalText, - (suggestions) -> { - ArrayList items = new ArrayList<>(); - - if (!text.equals(originalText)) { - // Completion from browser-domains - items.add(SuggestionsWidget.SuggestionItem.create( - text, - getSearchURLOrDomain(text), - null, - SuggestionsWidget.SuggestionItem.Type.COMPLETION - )); - } - - // Original text - items.add(SuggestionsWidget.SuggestionItem.create( - originalText, - getSearchURLOrDomain(originalText), - null, - SuggestionsWidget.SuggestionItem.Type.SUGGESTION - )); - - // Suggestions - for (String suggestion : suggestions) { - String url = mSearchEngineWrapper.getSearchURL(suggestion); - items.add(SuggestionsWidget.SuggestionItem.create( - suggestion, - url, - null, - SuggestionsWidget.SuggestionItem.Type.SUGGESTION - )); - } - mPopup.setItems(items); + mSuggestionsProvider.setText(text); + mSuggestionsProvider.setFilterText(originalText); + mSuggestionsProvider.getSuggestions() + .whenCompleteAsync((items, ex) -> { + mPopup.updateItems(items); mPopup.setHighlightedText(originalText); if (!mPopup.isVisible()) { mPopup.updatePlacement((int)WidgetPlacement.convertPixelsToDp(getContext(), mURLBar.getWidth())); mPopup.show(CLEAR_FOCUS); } - } - ); + + }, new UIThreadExecutor()); } @Override @@ -905,15 +881,6 @@ public void onHideSearchPopup() { } } - private String getSearchURLOrDomain(String text) { - if (UrlUtils.isDomain(text)) { - return text; - - } else { - return mSearchEngineWrapper.getSearchURL(text); - } - } - // VoiceSearch Delegate @Override diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/SuggestionsWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/SuggestionsWidget.java index 5bdc35561..861b9bf48 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/SuggestionsWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/SuggestionsWidget.java @@ -18,6 +18,8 @@ import android.widget.ListView; import android.widget.TextView; +import androidx.annotation.NonNull; + import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; @@ -25,13 +27,10 @@ import java.util.ArrayList; import java.util.List; -import androidx.annotation.NonNull; - public class SuggestionsWidget extends UIWidget implements WidgetManagerDelegate.FocusChangeListener { private ListView mList; private SuggestionsAdapter mAdapter; - private List mListItems; private Animation mScaleUpAnimation; private Animation mScaleDownAnimation; private URLBarPopupDelegate mURLBarDelegate; @@ -84,7 +83,8 @@ public void onAnimationRepeat(Animation animation) { } }); - mListItems = new ArrayList<>(); + mAdapter = new SuggestionsAdapter(getContext(), R.layout.list_popup_window_item, new ArrayList<>()); + mList.setAdapter(mAdapter); mAudio = AudioEngine.fromContext(aContext); @@ -140,10 +140,10 @@ public void setHighlightedText(String text) { mHighlightedText = text; } - public void setItems(List items) { - mListItems = items; - mAdapter = new SuggestionsAdapter(getContext(), R.layout.list_popup_window_item, mListItems); - mList.setAdapter(mAdapter); + public void updateItems(List items) { + mAdapter.clear(); + mAdapter.addAll(items); + mAdapter.notifyDataSetChanged(); } public void updatePlacement(int aWidth) { @@ -161,21 +161,20 @@ public void updatePlacement(int aWidth) { public static class SuggestionItem { public enum Type { + COMPLETION, BOOKMARK, - FAVORITE, HISTORY, - SUGGESTION, - COMPLETION + SUGGESTION } public String faviconURL; - public String text; + public String title; public String url; public Type type = Type.SUGGESTION; - public static SuggestionItem create(@NonNull String text, String url, String faviconURL, Type type) { + public static SuggestionItem create(@NonNull String title, String url, String faviconURL, Type type) { SuggestionItem item = new SuggestionItem(); - item.text = text; + item.title = title; item.url = url; item.faviconURL = faviconURL; item.type = type; @@ -195,12 +194,8 @@ private class ItemViewHolder { View divider; } - private LayoutInflater mInflater; - public SuggestionsAdapter(@NonNull Context context, int resource, @NonNull List objects) { super(context, resource, objects); - - mInflater = LayoutInflater.from(getContext()); } public View getView(int position, View convertView, ViewGroup parent) { @@ -208,7 +203,7 @@ public View getView(int position, View convertView, ViewGroup parent) { ItemViewHolder itemViewHolder; if(listItem == null) { - listItem = mInflater.inflate(R.layout.list_popup_window_item, parent, false); + listItem = LayoutInflater.from(getContext()).inflate(R.layout.list_popup_window_item, parent, false); itemViewHolder = new ItemViewHolder(); @@ -237,17 +232,7 @@ public View getView(int position, View convertView, ViewGroup parent) { SuggestionItem selectedItem = getItem(position); // Make search substring as bold - final SpannableStringBuilder sb = new SpannableStringBuilder(selectedItem.text); - final StyleSpan bold = new StyleSpan(Typeface.BOLD); - final StyleSpan normal = new StyleSpan(Typeface.NORMAL); - int start = selectedItem.text.indexOf(mHighlightedText); - if (start >= 0) { - int end = start + mHighlightedText.length(); - sb.setSpan(normal, 0, start, Spannable.SPAN_INCLUSIVE_INCLUSIVE); - sb.setSpan(bold, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE); - sb.setSpan(normal, end, selectedItem.text.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); - } - itemViewHolder.title.setText(sb); + itemViewHolder.title.setText(createHighlightedText(selectedItem.title)); // Set the URL text if (selectedItem.url == null) { @@ -255,7 +240,7 @@ public View getView(int position, View convertView, ViewGroup parent) { } else { itemViewHolder.url.setVisibility(VISIBLE); - itemViewHolder.url.setText(selectedItem.url); + itemViewHolder.url.setText(createHighlightedText(selectedItem.url)); } // Set the description @@ -268,18 +253,22 @@ public View getView(int position, View convertView, ViewGroup parent) { } // Type related - if (selectedItem.type == SuggestionItem.Type.SUGGESTION) { - itemViewHolder.delete.setVisibility(GONE); - itemViewHolder.divider.setVisibility(GONE); - itemViewHolder.favicon.setVisibility(VISIBLE); + if (selectedItem.type == SuggestionItem.Type.SUGGESTION) itemViewHolder.favicon.setImageResource(R.drawable.ic_icon_search); + else if (selectedItem.type == SuggestionItem.Type.COMPLETION) + itemViewHolder.favicon.setImageResource(R.drawable.ic_icon_browser); + else if (selectedItem.type == SuggestionItem.Type.HISTORY) + itemViewHolder.favicon.setImageResource(R.drawable.ic_icon_history); + else if (selectedItem.type == SuggestionItem.Type.BOOKMARK) + itemViewHolder.favicon.setImageResource(R.drawable.ic_icon_bookmark_active); + + itemViewHolder.delete.setVisibility(GONE); + itemViewHolder.favicon.setVisibility(VISIBLE); - } else if (selectedItem.type == SuggestionItem.Type.COMPLETION) { - itemViewHolder.delete.setVisibility(GONE); + if (position == 0) itemViewHolder.divider.setVisibility(VISIBLE); - itemViewHolder.favicon.setVisibility(VISIBLE); - itemViewHolder.favicon.setImageResource(R.drawable.ic_icon_browser); - } + else + itemViewHolder.divider.setVisibility(GONE); return listItem; } @@ -369,4 +358,19 @@ public View getView(int position, View convertView, ViewGroup parent) { return false; }; } + + private SpannableStringBuilder createHighlightedText(@NonNull String text) { + final SpannableStringBuilder sb = new SpannableStringBuilder(text); + final StyleSpan bold = new StyleSpan(Typeface.BOLD); + final StyleSpan normal = new StyleSpan(Typeface.NORMAL); + int start = text.toLowerCase().indexOf(mHighlightedText.toLowerCase()); + if (start >= 0) { + int end = start + mHighlightedText.length(); + sb.setSpan(normal, 0, start, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + sb.setSpan(bold, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + sb.setSpan(normal, end, text.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + + return sb; + } } diff --git a/app/src/main/res/drawable/ic_icon_browser.xml b/app/src/main/res/drawable/ic_icon_browser.xml index efa59e09e..eb6a7cbda 100644 --- a/app/src/main/res/drawable/ic_icon_browser.xml +++ b/app/src/main/res/drawable/ic_icon_browser.xml @@ -1,47 +1,11 @@ - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="200" + android:viewportHeight="200"> - \ No newline at end of file + android:fillColor="@color/white" + android:pathData="M103.076,14.715L96.924,14.715C50.369,16.839 13.884,53.372 13.884,100c0,46.628 36.485,83.144 83.04,85.285h6.151c46.555,-2.141 83.04,-38.657 83.04,-85.285 0,-46.628 -36.485,-83.161 -83.04,-85.285zM24.429,99.774c0,-9.434 1.793,-18.449 5.026,-26.769 3.568,1.653 9.631,3.081 19.684,4.299 -1.213,7.258 -1.863,14.794 -1.863,22.453 0,7.78 0.668,15.299 1.88,22.47 -10.053,1.236 -16.133,2.646 -19.701,4.299 -3.234,-8.302 -5.026,-17.318 -5.026,-26.752zM32.777,133.905c2.197,-1.201 8.418,-2.559 17.996,-3.69 3.585,14.986 9.719,27.952 17.557,37.473 -15.29,-7.014 -27.838,-18.954 -35.553,-33.783zM96.924,176.147c-2.812,-0.157 -6.608,-2.106 -9.332,-2.559 -12.97,-6.509 -25.483,-24.907 -30.65,-46.176 9.982,-0.87 25.466,0.104 39.982,0zM96.924,121.321c-14.095,0.104 -28.418,-0.957 -39.648,0.07 -1.16,-6.875 -1.81,-14.115 -1.81,-21.617 0,-7.397 0.633,-14.655 1.828,-21.617 11.23,1.009 25.553,0.418 39.613,0.522L96.907,121.321ZM100,54.468c-2.267,0 -3.076,0.644 -3.076,2.889v15.229c-14.481,-0.104 -27.979,-1.566 -37.961,-2.437 1.195,-4.873 2.671,-9.59 4.464,-14.028 0.844,-2.089 -0.176,-4.438 -2.285,-5.291 -2.109,-0.835 -4.482,0.174 -5.343,2.263 -2.074,5.117 -3.779,10.547 -5.114,16.204 -9.543,-1.131 -15.747,-2.489 -17.926,-3.69 7.663,-14.725 20.088,-26.612 35.272,-33.661 -0.896,1.097 -1.757,2.193 -2.601,3.377 -1.318,1.828 -0.879,4.369 0.967,5.674 1.845,1.288 4.411,0.87 5.729,-0.957 4.675,-6.527 10.035,-11.296 15.712,-14.15 2.636,-0.418 5.325,-0.714 8.049,-0.87v15.073c0,2.245 1.828,4.055 4.095,4.055 2.267,0 4.095,-1.828 4.095,-4.055v-15.055c2.724,0.157 5.413,0.453 8.049,0.87 5.677,2.837 11.054,7.623 15.729,14.15 0.808,1.114 2.074,1.706 3.357,1.706 0.826,0 1.652,-0.244 2.373,-0.748 1.845,-1.305 2.285,-3.847 0.967,-5.657 -0.844,-1.184 -1.722,-2.297 -2.601,-3.377 15.167,7.049 27.61,18.937 35.255,33.661 -2.179,1.201 -8.383,2.559 -17.944,3.69 -1.336,-5.657 -3.023,-11.087 -5.114,-16.204 -0.844,-2.089 -3.216,-3.098 -5.343,-2.263 -2.109,0.835 -3.128,3.203 -2.285,5.291 1.793,4.421 3.269,9.138 4.464,14.028 -9.982,0.87 -23.48,2.332 -37.961,2.437v-15.264c0.053,-2.245 -0.756,-2.889 -3.023,-2.889zM144.534,99.774c0,7.502 -0.65,14.759 -1.81,21.617 -11.23,-1.027 -25.553,0.035 -39.648,-0.07L103.076,78.679c14.077,-0.087 28.401,0.505 39.631,-0.522 1.195,6.962 1.828,14.22 1.828,21.617zM112.408,173.589c-2.724,0.453 -6.52,2.419 -9.332,2.559v-48.734c14.499,0.104 28.031,1.114 38.014,1.984 -5.167,21.252 -15.712,37.682 -28.682,44.191zM131.652,167.706c7.838,-9.521 13.989,-22.487 17.557,-37.473 9.578,1.131 15.8,2.489 17.996,3.69 -7.698,14.812 -20.228,26.752 -35.553,33.783zM150.861,122.244c1.213,-7.171 1.88,-14.69 1.88,-22.47 0,-7.658 -0.65,-15.195 -1.863,-22.452 10.053,-1.236 16.133,-2.663 19.701,-4.299 3.216,8.32 5.009,17.335 5.009,26.769 0,9.434 -1.793,18.449 -5.009,26.769 -3.603,-1.671 -9.666,-3.098 -19.719,-4.316z" + android:strokeWidth="0.17489612" + android:strokeColor="@color/white" /> + diff --git a/app/src/main/res/drawable/ic_icon_history.xml b/app/src/main/res/drawable/ic_icon_history.xml new file mode 100644 index 000000000..59bf99a98 --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_history.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_icon_search.xml b/app/src/main/res/drawable/ic_icon_search.xml index 8439630d9..28f276a73 100644 --- a/app/src/main/res/drawable/ic_icon_search.xml +++ b/app/src/main/res/drawable/ic_icon_search.xml @@ -1,21 +1,16 @@ - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="200" + android:viewportHeight="200"> + android:fillColor="@color/white" + android:pathData="m129.553,151.892 l29.596,29.596c0,0 12.375,8.377 20.977,-1.904 0,0 9.658,-8.585 1.263,-19.921l-29.596,-30.427c0,0 28.316,-36.294 -2.717,-84.566l-20.994,8.394c0,0 15.317,15.733 14.054,37.35 0,0 1.038,47.406 -52.668,52.46 0,0 -51.196,-0.848 -53.1,-54.554 0,0 0.848,-49.293 52.443,-51.612L91.753,15.316c0,0 -71.758,-3.358 -76.57,74.7 0,0 -1.038,67.777 74.475,73.645 0.035,0 21.237,1.869 39.894,-11.769z" + android:strokeWidth="0.17307779" + android:strokeColor="@color/white" /> - \ No newline at end of file + android:fillColor="@color/white" + android:pathData="m82.355,36.709c0,0 28.887,-3.358 47.839,18.19l20.458,-7.685c0,0 -20.579,-32.954 -65.077,-31.725z" + android:strokeWidth="0.17307779" + android:strokeColor="@color/white" /> + diff --git a/app/src/main/res/layout/list_popup_window_item.xml b/app/src/main/res/layout/list_popup_window_item.xml index 3f661f612..b89f52b0a 100644 --- a/app/src/main/res/layout/list_popup_window_item.xml +++ b/app/src/main/res/layout/list_popup_window_item.xml @@ -19,8 +19,8 @@ \ No newline at end of file