package com.jbirdvegas.mgerrit.views;
/*
* Copyright (C) 2013 Android Open Kang Project (AOKP)
* Author: Evan Conway (P4R4N01D), 2013
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.SearchView;
import com.google.analytics.tracking.android.EasyTracker;
import com.google.analytics.tracking.android.MapBuilder;
import com.jbirdvegas.mgerrit.Prefs;
import com.jbirdvegas.mgerrit.message.SearchQueryChanged;
import com.jbirdvegas.mgerrit.search.OwnerSearch;
import com.jbirdvegas.mgerrit.search.ProjectSearch;
import com.jbirdvegas.mgerrit.search.SearchKeyword;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import de.greenrobot.event.EventBus;
public class GerritSearchView extends SearchView
implements SearchView.OnQueryTextListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "GerrritSearchView";
private final SharedPreferences mPrefs;
Context mContext;
Set<SearchKeyword> mAdditionalKeywords;
// The list of keyword tokens for the last processed query
Set<SearchKeyword> mCurrentKeywords;
public GerritSearchView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setOnQueryTextListener(this);
setupCancelButton();
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mCurrentKeywords = new HashSet<>();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPrefs.registerOnSharedPreferenceChangeListener(this);
Integer user = Prefs.getTrackingUser(mContext);
if (user != null) {
replaceKeyword(new OwnerSearch(user.toString()), true);
}
String project = Prefs.getCurrentProject(mContext);
if (project != null) {
replaceKeyword(new ProjectSearch(project), true);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public boolean onQueryTextSubmit(String query) {
Set<SearchKeyword> tokens = constructTokens(query);
if (tokens != null) {
// If there is no project keyword in the query, it should be cleared
if (SearchKeyword.findKeyword(tokens, ProjectSearch.class) < 0 &&
!Prefs.getCurrentProject(mContext).isEmpty()) {
Prefs.setCurrentProject(mContext, null);
}
// If there is no owner keyword in the query, it should be cleared
if (SearchKeyword.findKeyword(tokens, OwnerSearch.class) < 0 &&
Prefs.getTrackingUser(mContext) != null) {
Prefs.clearTrackingUser(mContext);
}
}
// Pass this on to the current CardsFragment instance
if (!processTokens(tokens)) {
Log.w(TAG, "Could not process query: " + query);
}
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// Handled when the search is submitted instead.
if (newText.isEmpty()) {
onQueryTextSubmit(null);
} else setVisibility(VISIBLE);
return false;
}
/**
* Set the search query. This will construct the SQL query and restart
* the loader to perform the query
* @param query The search query text
*/
private Set<SearchKeyword> constructTokens(@Nullable String query) {
// An empty query will result in an empty set
if (query == null || query.isEmpty()) {
return new HashSet<>();
}
return SearchKeyword.constructTokens(query);
}
@Contract("null -> true")
private boolean processTokens(final Set<SearchKeyword> tokens) {
Set<SearchKeyword> newTokens = safeMerge(tokens, mAdditionalKeywords);
mCurrentKeywords = newTokens;
String where = "";
ArrayList<String> bindArgs = new ArrayList<>();
if (newTokens != null && !newTokens.isEmpty()) {
where = SearchKeyword.constructDbSearchQuery(newTokens);
if (where != null && !where.isEmpty()) {
for (SearchKeyword token : newTokens) {
bindArgs.addAll(Arrays.asList(token.getEscapeArgument()));
}
} else {
return false;
}
}
EventBus.getDefault().postSticky(new SearchQueryChanged(where, bindArgs,
getContext().getClass().getSimpleName(), tokens));
return true;
}
/**
* Always show the cancel button and set its onClick listener. The button
* has private visibility so we need reflection to access it.
*/
private void setupCancelButton() {
try {
Field searchField = SearchView.class.getDeclaredField("mCloseButton");
searchField.setAccessible(true);
ImageView closeBtn = (ImageView) searchField.get(this);
closeBtn.setVisibility(VISIBLE);
closeBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
toggleVisibility();
}
});
} catch (Exception e) {
EasyTracker.getInstance(mContext).send(MapBuilder.createEvent(
"GerritSearchView",
"setupCancelButton",
"search_button_reflection_visibility",
null).build());
e.printStackTrace();
}
}
public void toggleVisibility() {
int visibility = getVisibility();
if (visibility == View.GONE) {
setVisibility(View.VISIBLE);
requestFocus();
}
else setVisibility(View.GONE);
}
@Override
public void setVisibility(int visibility) {
String query = getQuery().toString();
if (!query.isEmpty() && visibility == GONE) setQuery("", true);
super.setVisibility(visibility);
setIconified(visibility == GONE);
}
/**
* Modifies future searches for this fragment by appending additional
* keywords to search for that will not be present in the original
* search query. This clears all old keywords that were previously injected.
*
* Used for the changelog
* @param keywords
*/
public void injectKeywords(Set<SearchKeyword> keywords) {
mAdditionalKeywords = new HashSet<>(keywords);
onQueryTextSubmit(getQuery().toString()); // Force search refresh
}
/**
* Add the elements of otherSet to oldSet and return a new set.
*/
private Set<SearchKeyword> safeMerge(Set<SearchKeyword> oldSet, Set<SearchKeyword> otherSet) {
HashSet<SearchKeyword> newSet = new HashSet<>();
if (oldSet != null && !oldSet.isEmpty()) {
newSet.addAll(oldSet);
}
if (otherSet != null && !otherSet.isEmpty()) {
newSet.addAll(otherSet);
}
return newSet;
}
private void replaceKeyword(SearchKeyword keyword, boolean submit) {
String currentQuery = getQuery().toString();
String query = SearchKeyword.replaceKeyword(currentQuery, keyword);
if (!query.equals(currentQuery)) this.setQuery(query, submit);
}
/**
* @return The list of search keywords that were included in the query plus any additional
* keywords that were set via injectKeywords(Set<SearchKeyword>)
*/
public Set<SearchKeyword> getLastQuery() {
return mCurrentKeywords;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case Prefs.CURRENT_PROJECT:
replaceKeyword(new ProjectSearch(Prefs.getCurrentProject(mContext)), true);
break;
case Prefs.TRACKING_USER:
replaceKeyword(new OwnerSearch(Prefs.getTrackingUser(mContext)), true);
break;
}
}
}