You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
12 KiB
411 lines
12 KiB
package org.purplei2p.lightning.search; |
|
|
|
import android.app.Application; |
|
import android.content.Context; |
|
import android.graphics.Color; |
|
import android.graphics.drawable.Drawable; |
|
import android.support.annotation.NonNull; |
|
import android.support.annotation.Nullable; |
|
import android.view.LayoutInflater; |
|
import android.view.View; |
|
import android.view.ViewGroup; |
|
import android.widget.BaseAdapter; |
|
import android.widget.Filter; |
|
import android.widget.Filterable; |
|
import android.widget.ImageView; |
|
import android.widget.TextView; |
|
|
|
import com.anthonycr.bonsai.Completable; |
|
import com.anthonycr.bonsai.CompletableAction; |
|
import com.anthonycr.bonsai.CompletableSubscriber; |
|
import com.anthonycr.bonsai.Scheduler; |
|
import com.anthonycr.bonsai.Schedulers; |
|
import com.anthonycr.bonsai.Single; |
|
import com.anthonycr.bonsai.SingleAction; |
|
import com.anthonycr.bonsai.SingleOnSubscribe; |
|
import com.anthonycr.bonsai.SingleSubscriber; |
|
|
|
import java.io.File; |
|
import java.io.FilenameFilter; |
|
import java.util.ArrayList; |
|
import java.util.Collections; |
|
import java.util.Comparator; |
|
import java.util.Iterator; |
|
import java.util.List; |
|
import java.util.Locale; |
|
|
|
import javax.inject.Inject; |
|
|
|
import org.purplei2p.lightning.R; |
|
import org.purplei2p.lightning.BrowserApp; |
|
import org.purplei2p.lightning.database.HistoryItem; |
|
import org.purplei2p.lightning.database.bookmark.BookmarkModel; |
|
import org.purplei2p.lightning.database.history.HistoryModel; |
|
import org.purplei2p.lightning.preference.PreferenceManager; |
|
import org.purplei2p.lightning.utils.Preconditions; |
|
import org.purplei2p.lightning.utils.ThemeUtils; |
|
|
|
public class SuggestionsAdapter extends BaseAdapter implements Filterable { |
|
|
|
private static final Scheduler FILTER_SCHEDULER = Schedulers.newSingleThreadedScheduler(); |
|
|
|
private static final String CACHE_FILE_TYPE = ".sgg"; |
|
|
|
private final List<HistoryItem> mFilteredList = new ArrayList<>(5); |
|
|
|
private final List<HistoryItem> mHistory = new ArrayList<>(5); |
|
private final List<HistoryItem> mBookmarks = new ArrayList<>(5); |
|
private final List<HistoryItem> mSuggestions = new ArrayList<>(5); |
|
|
|
private static final int MAX_SUGGESTIONS = 5; |
|
|
|
@NonNull private final Drawable mSearchDrawable; |
|
@NonNull private final Drawable mHistoryDrawable; |
|
@NonNull private final Drawable mBookmarkDrawable; |
|
|
|
private final Comparator<HistoryItem> mFilterComparator = new SuggestionsComparator(); |
|
|
|
@Inject BookmarkModel mBookmarkManager; |
|
@Inject PreferenceManager mPreferenceManager; |
|
@Inject HistoryModel mHistoryModel; |
|
@Inject Application mApplication; |
|
|
|
private final List<HistoryItem> mAllBookmarks = new ArrayList<>(5); |
|
|
|
private final boolean mDarkTheme; |
|
private boolean mIsIncognito = true; |
|
@NonNull private final Context mContext; |
|
|
|
public SuggestionsAdapter(@NonNull Context context, boolean dark, boolean incognito) { |
|
super(); |
|
BrowserApp.getAppComponent().inject(this); |
|
mContext = context; |
|
mDarkTheme = dark || incognito; |
|
mIsIncognito = incognito; |
|
|
|
refreshBookmarks(); |
|
|
|
mSearchDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_search, mDarkTheme); |
|
mBookmarkDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_bookmark, mDarkTheme); |
|
mHistoryDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_history, mDarkTheme); |
|
} |
|
|
|
public void clearCache() { |
|
// We don't need these cache files anymore |
|
Schedulers.io().execute(new ClearCacheRunnable(mApplication)); |
|
} |
|
|
|
public void refreshBookmarks() { |
|
mBookmarkManager.getAllBookmarks() |
|
.subscribeOn(Schedulers.io()) |
|
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { |
|
@Override |
|
public void onItem(@Nullable List<HistoryItem> item) { |
|
Preconditions.checkNonNull(item); |
|
mAllBookmarks.clear(); |
|
mAllBookmarks.addAll(item); |
|
} |
|
}); |
|
} |
|
|
|
@Override |
|
public int getCount() { |
|
return mFilteredList.size(); |
|
} |
|
|
|
@Nullable |
|
@Override |
|
public Object getItem(int position) { |
|
if (position > mFilteredList.size() || position < 0) { |
|
return null; |
|
} |
|
return mFilteredList.get(position); |
|
} |
|
|
|
@Override |
|
public long getItemId(int position) { |
|
return 0; |
|
} |
|
|
|
private static class SuggestionHolder { |
|
|
|
SuggestionHolder(@NonNull View view) { |
|
mTitle = view.findViewById(R.id.title); |
|
mUrl = view.findViewById(R.id.url); |
|
mImage = view.findViewById(R.id.suggestionIcon); |
|
} |
|
|
|
@NonNull final ImageView mImage; |
|
@NonNull final TextView mTitle; |
|
@NonNull final TextView mUrl; |
|
|
|
} |
|
|
|
@Nullable |
|
@Override |
|
public View getView(int position, @Nullable View convertView, ViewGroup parent) { |
|
SuggestionHolder holder; |
|
|
|
if (convertView == null) { |
|
LayoutInflater inflater = LayoutInflater.from(mContext); |
|
convertView = inflater.inflate(R.layout.two_line_autocomplete, parent, false); |
|
|
|
holder = new SuggestionHolder(convertView); |
|
convertView.setTag(holder); |
|
} else { |
|
holder = (SuggestionHolder) convertView.getTag(); |
|
} |
|
HistoryItem web; |
|
web = mFilteredList.get(position); |
|
holder.mTitle.setText(web.getTitle()); |
|
holder.mUrl.setText(web.getUrl()); |
|
|
|
if (mDarkTheme) { |
|
holder.mTitle.setTextColor(Color.WHITE); |
|
} |
|
|
|
Drawable image; |
|
switch (web.getImageId()) { |
|
case R.drawable.ic_bookmark: { |
|
image = mBookmarkDrawable; |
|
break; |
|
} |
|
case R.drawable.ic_search: { |
|
image = mSearchDrawable; |
|
break; |
|
} |
|
case R.drawable.ic_history: { |
|
image = mHistoryDrawable; |
|
break; |
|
} |
|
default: |
|
image = mSearchDrawable; |
|
break; |
|
} |
|
|
|
holder.mImage.setImageDrawable(image); |
|
|
|
return convertView; |
|
} |
|
|
|
@NonNull |
|
@Override |
|
public Filter getFilter() { |
|
return new SearchFilter(this, mHistoryModel); |
|
} |
|
|
|
private synchronized void publishResults(@NonNull List<HistoryItem> list) { |
|
mFilteredList.clear(); |
|
mFilteredList.addAll(list); |
|
notifyDataSetChanged(); |
|
} |
|
|
|
private void clearSuggestions() { |
|
Completable.create(new CompletableAction() { |
|
@Override |
|
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { |
|
mBookmarks.clear(); |
|
mHistory.clear(); |
|
mSuggestions.clear(); |
|
subscriber.onComplete(); |
|
} |
|
}).subscribeOn(FILTER_SCHEDULER) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(); |
|
} |
|
|
|
private void combineResults(final @Nullable List<HistoryItem> bookmarkList, |
|
final @Nullable List<HistoryItem> historyList, |
|
final @Nullable List<HistoryItem> suggestionList) { |
|
Single.create(new SingleAction<List<HistoryItem>>() { |
|
@Override |
|
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) { |
|
List<HistoryItem> list = new ArrayList<>(5); |
|
if (bookmarkList != null) { |
|
mBookmarks.clear(); |
|
mBookmarks.addAll(bookmarkList); |
|
} |
|
if (historyList != null) { |
|
mHistory.clear(); |
|
mHistory.addAll(historyList); |
|
} |
|
if (suggestionList != null) { |
|
mSuggestions.clear(); |
|
mSuggestions.addAll(suggestionList); |
|
} |
|
Iterator<HistoryItem> bookmark = mBookmarks.iterator(); |
|
Iterator<HistoryItem> history = mHistory.iterator(); |
|
Iterator<HistoryItem> suggestion = mSuggestions.listIterator(); |
|
while (list.size() < MAX_SUGGESTIONS) { |
|
if (!bookmark.hasNext() && !suggestion.hasNext() && !history.hasNext()) { |
|
break; |
|
} |
|
if (bookmark.hasNext()) { |
|
list.add(bookmark.next()); |
|
} |
|
if (suggestion.hasNext() && list.size() < MAX_SUGGESTIONS) { |
|
list.add(suggestion.next()); |
|
} |
|
if (history.hasNext() && list.size() < MAX_SUGGESTIONS) { |
|
list.add(history.next()); |
|
} |
|
} |
|
|
|
Collections.sort(list, mFilterComparator); |
|
subscriber.onItem(list); |
|
subscriber.onComplete(); |
|
} |
|
}).subscribeOn(FILTER_SCHEDULER) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { |
|
@Override |
|
public void onItem(@Nullable List<HistoryItem> item) { |
|
Preconditions.checkNonNull(item); |
|
publishResults(item); |
|
} |
|
}); |
|
} |
|
|
|
@NonNull |
|
private Single<List<HistoryItem>> getBookmarksForQuery(@NonNull final String query) { |
|
return Single.create(new SingleAction<List<HistoryItem>>() { |
|
@Override |
|
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) { |
|
List<HistoryItem> bookmarks = new ArrayList<>(5); |
|
int counter = 0; |
|
for (int n = 0; n < mAllBookmarks.size(); n++) { |
|
if (counter >= 5) { |
|
break; |
|
} |
|
if (mAllBookmarks.get(n).getTitle().toLowerCase(Locale.getDefault()) |
|
.startsWith(query)) { |
|
bookmarks.add(mAllBookmarks.get(n)); |
|
counter++; |
|
} else if (mAllBookmarks.get(n).getUrl().contains(query)) { |
|
bookmarks.add(mAllBookmarks.get(n)); |
|
counter++; |
|
} |
|
} |
|
subscriber.onItem(bookmarks); |
|
subscriber.onComplete(); |
|
} |
|
}); |
|
} |
|
|
|
@NonNull |
|
private Single<List<HistoryItem>> getSuggestionsForQuery(@NonNull final String query) { |
|
return Single.empty(); |
|
} |
|
|
|
private boolean shouldRequestNetwork() { |
|
return !mIsIncognito; |
|
} |
|
|
|
private static class SearchFilter extends Filter { |
|
|
|
@NonNull private final SuggestionsAdapter mSuggestionsAdapter; |
|
@NonNull private final HistoryModel mHistoryModel; |
|
|
|
SearchFilter(@NonNull SuggestionsAdapter suggestionsAdapter, |
|
@NonNull HistoryModel historyModel) { |
|
mSuggestionsAdapter = suggestionsAdapter; |
|
mHistoryModel = historyModel; |
|
} |
|
|
|
@NonNull |
|
@Override |
|
protected FilterResults performFiltering(@Nullable CharSequence constraint) { |
|
FilterResults results = new FilterResults(); |
|
if (constraint == null || constraint.length() == 0) { |
|
mSuggestionsAdapter.clearSuggestions(); |
|
return results; |
|
} |
|
String query = constraint.toString().toLowerCase(Locale.getDefault()).trim(); |
|
|
|
if (mSuggestionsAdapter.shouldRequestNetwork()) { |
|
mSuggestionsAdapter.getSuggestionsForQuery(query) |
|
.subscribeOn(Schedulers.worker()) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { |
|
@Override |
|
public void onItem(@Nullable List<HistoryItem> item) { |
|
mSuggestionsAdapter.combineResults(null, null, item); |
|
} |
|
}); |
|
} |
|
|
|
mSuggestionsAdapter.getBookmarksForQuery(query) |
|
.subscribeOn(Schedulers.io()) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { |
|
@Override |
|
public void onItem(@Nullable List<HistoryItem> item) { |
|
mSuggestionsAdapter.combineResults(item, null, null); |
|
} |
|
}); |
|
|
|
mHistoryModel.findHistoryItemsContaining(query) |
|
.subscribeOn(Schedulers.io()) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { |
|
@Override |
|
public void onItem(@Nullable List<HistoryItem> item) { |
|
mSuggestionsAdapter.combineResults(null, item, null); |
|
} |
|
}); |
|
results.count = 1; |
|
return results; |
|
} |
|
|
|
@NonNull |
|
@Override |
|
public CharSequence convertResultToString(@NonNull Object resultValue) { |
|
return ((HistoryItem) resultValue).getUrl(); |
|
} |
|
|
|
@Override |
|
protected void publishResults(CharSequence constraint, FilterResults results) { |
|
mSuggestionsAdapter.combineResults(null, null, null); |
|
} |
|
} |
|
|
|
private static class ClearCacheRunnable implements Runnable { |
|
|
|
@NonNull |
|
private final Application app; |
|
|
|
ClearCacheRunnable(@NonNull Application app) { |
|
this.app = app; |
|
} |
|
|
|
@Override |
|
public void run() { |
|
File dir = new File(app.getCacheDir().toString()); |
|
String[] fileList = dir.list(new NameFilter()); |
|
for (String fileName : fileList) { |
|
File file = new File(dir.getPath() + fileName); |
|
file.delete(); |
|
} |
|
} |
|
|
|
private static class NameFilter implements FilenameFilter { |
|
|
|
@Override |
|
public boolean accept(File dir, @NonNull String filename) { |
|
return filename.endsWith(CACHE_FILE_TYPE); |
|
} |
|
} |
|
} |
|
|
|
private static class SuggestionsComparator implements Comparator<HistoryItem> { |
|
|
|
@Override |
|
public int compare(@NonNull HistoryItem lhs, @NonNull HistoryItem rhs) { |
|
if (lhs.getImageId() == rhs.getImageId()) return 0; |
|
if (lhs.getImageId() == R.drawable.ic_bookmark) return -1; |
|
if (rhs.getImageId() == R.drawable.ic_bookmark) return 1; |
|
if (lhs.getImageId() == R.drawable.ic_history) return -1; |
|
return 1; |
|
} |
|
} |
|
} |