[android] Refactored search fragment & adapter.

This commit is contained in:
Dmitry Yunitsky 2015-06-26 13:12:25 +03:00 committed by Alex Zolotarev
parent 6f89512532
commit e087959d0c
7 changed files with 133 additions and 141 deletions

View file

@ -48,7 +48,7 @@ extern "C"
ActiveMapsLayout & layout = GetMapLayout();
ActiveMapsLayout::TGroup coreGroup = ToGroup(group);
int pos = static_cast<int>(position);
bool const local = (isLocal == JNI_TRUE) ? true : false;
bool const local = isLocal == JNI_TRUE;
TMapOptions opt = ToOptions(options);
if (options == -1 || local)

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/LinearLayout1"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -1,8 +1,11 @@
package com.mapswithme.maps.search;
import android.content.res.Resources;
import android.text.Html;
import android.graphics.Typeface;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -60,7 +63,10 @@ public class SearchAdapter extends BaseAdapter
private final SearchFragment mSearchFragment;
private final LayoutInflater mInflater;
private final Resources mResources;
private int mCount = -1;
private static final int COUNT_NO_RESULTS = -1;
private int mCount = COUNT_NO_RESULTS;
private int mResultId;
public SearchAdapter(SearchFragment fragment)
@ -98,7 +104,7 @@ public class SearchAdapter extends BaseAdapter
{
if (mSearchFragment.doShowCategories())
return mCategoriesIds.length;
else if (mCount < 0)
else if (mCount == COUNT_NO_RESULTS)
return 0;
else if (doShowSearchOnMapButton())
return mCount + 1;
@ -123,7 +129,6 @@ public class SearchAdapter extends BaseAdapter
return position;
}
@Override
public Object getItem(int position)
{
@ -151,7 +156,7 @@ public class SearchAdapter extends BaseAdapter
case RESULT_TYPE:
convertView = mInflater.inflate(R.layout.item_search, parent, false);
break;
case MESSAGE_TYPE:
default:
convertView = mInflater.inflate(R.layout.item_search_message, parent, false);
break;
}
@ -180,49 +185,39 @@ public class SearchAdapter extends BaseAdapter
private void bindResultView(ViewHolder holder, int position)
{
final SearchResult r = mSearchFragment.getResult(position, mResultId);
if (r != null)
final SearchResult result = mSearchFragment.getResult(position, mResultId);
if (result != null)
{
String country = null;
String dist = null;
Spanned s;
// TODO replace completely html text with spannable builders
if (r.mType == SearchResult.TYPE_FEATURE)
SpannableStringBuilder builder = new SpannableStringBuilder(result.mName);
if (result.mType == SearchResult.TYPE_FEATURE)
{
if (r.mHighlightRanges.length > 0)
if (result.mHighlightRanges.length > 0)
{
StringBuilder builder = new StringBuilder();
int pos = 0, j = 0, n = r.mHighlightRanges.length / 2;
int j = 0, n = result.mHighlightRanges.length / 2;
for (int i = 0; i < n; ++i)
{
int start = r.mHighlightRanges[j++];
int len = r.mHighlightRanges[j++];
int start = result.mHighlightRanges[j++];
int len = result.mHighlightRanges[j++];
builder.append(r.mName.substring(pos, start));
builder.append("<font color=\"#1F9952\">");
builder.append(r.mName.substring(start, start + len));
builder.append("</font>");
pos = start + len;
builder.setSpan(new StyleSpan(Typeface.BOLD), start, start + len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
builder.append(r.mName.substring(pos));
s = Html.fromHtml(builder.toString());
}
else
s = Html.fromHtml(r.mName);
builder.clearSpans();
country = r.mCountry;
dist = r.mDistance;
UiUtils.hide(holder.mImageLeft);
holder.mView.setBackgroundResource(0);
country = result.mCountry;
dist = result.mDistance;
}
else
s = Html.fromHtml("<font color=\"#1F9952\">" + r.mName + "</font>");
builder.setSpan(new ForegroundColorSpan(mResources.getColor(R.color.text_green)), 0, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
UiUtils.setTextAndShow(holder.mName, s);
UiUtils.hide(holder.mImageLeft);
UiUtils.setTextAndShow(holder.mName, builder);
UiUtils.setTextAndHideIfEmpty(holder.mCountry, country);
UiUtils.setTextAndHideIfEmpty(holder.mItemType, r.mAmenity);
UiUtils.setTextAndHideIfEmpty(holder.mItemType, result.mAmenity);
UiUtils.setTextAndHideIfEmpty(holder.mDistance, dist);
}
}
@ -244,7 +239,7 @@ public class SearchAdapter extends BaseAdapter
* @param count
* @param resultId
*/
public void updateData(int count, int resultId)
public void showData(int count, int resultId)
{
mCount = count;
mResultId = resultId;
@ -252,9 +247,9 @@ public class SearchAdapter extends BaseAdapter
notifyDataSetChanged();
}
public void updateCategories()
public void showCategories()
{
mCount = -1;
mCount = COUNT_NO_RESULTS;
notifyDataSetChanged();
}
@ -346,6 +341,7 @@ public class SearchAdapter extends BaseAdapter
public static final int TYPE_FEATURE = 1;
public int mType;
// consecutive pairs of numbers (each pair contains : start index, length), specifying highlighted substrings of original query
public int[] mHighlightRanges;

View file

@ -4,8 +4,6 @@ import android.app.Activity;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@ -20,7 +18,6 @@ import android.widget.TextView;
import com.mapswithme.maps.Framework;
import com.mapswithme.maps.MWMActivity;
import com.mapswithme.maps.MWMApplication;
import com.mapswithme.maps.R;
import com.mapswithme.maps.base.BaseMwmListFragment;
import com.mapswithme.maps.base.OnBackPressListener;
@ -28,6 +25,7 @@ import com.mapswithme.maps.data.RouterTypes;
import com.mapswithme.maps.location.LocationHelper;
import com.mapswithme.util.InputUtils;
import com.mapswithme.util.Language;
import com.mapswithme.util.StringUtils;
import com.mapswithme.util.UiUtils;
import com.mapswithme.util.Utils;
import com.mapswithme.util.statistics.Statistics;
@ -56,10 +54,10 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
// Current position.
private double mLat;
private double mLon;
private double mNorth = -1.0;
private static final double DUMMY_NORTH = -1;
//
private int mFlags = 0;
private int mQueryId = 0;
private int mFlags;
private int mQueryId;
private SearchAdapter mAdapter;
@Override
@ -90,44 +88,85 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
}
}
private void initAdapter()
@Override
public void onResume()
{
mAdapter = new SearchAdapter(this);
super.onResume();
LocationHelper.INSTANCE.addLocationListener(this);
setSearchQuery(getLastQuery());
mEtSearchQuery.requestFocus();
}
@Override
public void onPause()
{
LocationHelper.INSTANCE.removeLocationListener(this);
super.onPause();
}
@Override
public void onDestroy()
{
nativeDisconnect();
super.onDestroy();
}
@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
if (!isAdded())
return;
position -= l.getHeaderViewsCount();
final String suggestion = mAdapter.onItemClick(position);
if (suggestion == null)
showSearchResultOnMap(position);
else
// set suggestion string and run search (this call invokes runSearch)
setSearchQuery(suggestion);
}
/**
* If search string is empty - show search categories.
*/
protected boolean doShowCategories()
{
return getSearchString().length() == 0;
return getSearchQuery().isEmpty();
}
private String getSearchString()
private String getSearchQuery()
{
return mEtSearchQuery.getText().toString();
}
private void setSearchQuery(String query)
{
Utils.setTextAndCursorToEnd(mEtSearchQuery, query);
}
private void initAdapter()
{
mAdapter = new SearchAdapter(this);
}
private void setUpView(ViewGroup root)
{
mBtnVoice = root.findViewById(R.id.search_voice_input);
mBtnVoice.setOnClickListener(this);
mPbSearch = (ProgressBar) root.findViewById(R.id.search_progress);
mBtnClearQuery = root.findViewById(R.id.search_image_clear);
mBtnClearQuery.setOnClickListener(this);
// Initialize search edit box processor.
mEtSearchQuery = (EditText) root.findViewById(R.id.search_text_query);
mEtSearchQuery.addTextChangedListener(new TextWatcher()
mEtSearchQuery.addTextChangedListener(new StringUtils.SimpleTextWatcher()
{
@Override
public void afterTextChanged(Editable s)
public void onTextChanged(CharSequence s, int start, int before, int count)
{
if (!isAdded())
return;
@ -154,16 +193,6 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
UiUtils.hide(mBtnVoice);
}
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3)
{
}
@Override
public void onTextChanged(CharSequence s, int arg1, int arg2, int arg3)
{
}
});
mEtSearchQuery.setOnEditorActionListener(new TextView.OnEditorActionListener()
@ -186,22 +215,14 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
}
});
mBtnVoice.setOnClickListener(this);
final ListView listView = getListView();
listView.setOnScrollListener(new OnScrollListener()
{
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
{
// Hide keyboard when user starts scroll
InputUtils.hideKeyboard(mEtSearchQuery);
// Hacky way to remove focus from only edittext at activity
mEtSearchQuery.setFocusableInTouchMode(false);
mEtSearchQuery.setFocusable(false);
mEtSearchQuery.setFocusableInTouchMode(true);
mEtSearchQuery.setFocusable(true);
InputUtils.removeFocusEditTextHack(mEtSearchQuery);
}
@Override
@ -253,46 +274,6 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
}
// FIXME: This code only for demonstration purposes and will be removed soon
@Override
public void onResume()
{
super.onResume();
// Reset current mode flag - start first search.
mFlags = 0;
mNorth = -1.0;
LocationHelper.INSTANCE.addLocationListener(this);
setSearchQuery(getLastQuery());
mEtSearchQuery.requestFocus();
}
@Override
public void onPause()
{
LocationHelper.INSTANCE.removeLocationListener(this);
super.onPause();
}
@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
if (!isAdded())
return;
position -= l.getHeaderViewsCount();
final String suggestion = mAdapter.onItemClick(position);
if (suggestion == null)
showSearchResultOnMap(position);
else
// set suggestion string and run search (this call invokes runSearch)
setSearchQuery(suggestion);
}
private void showSearchResultOnMap(int position)
{
// If user searched for something, clear API layer
@ -300,12 +281,12 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
// Put query string for "View on map" or feature name for search result.
final boolean allResults = (position == 0);
final String query = getSearchString();
final String query = getSearchQuery();
SearchController.getInstance().setQuery(allResults ? query : "");
if (allResults)
{
nativeShowAllSearchResults();
runInteractiveSearch(query, Language.getKeyboardInput(getActivity()));
runInteractiveSearch(query, Language.getKeyboardLocale());
}
InputUtils.hideKeyboard(mEtSearchQuery);
@ -322,10 +303,9 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
private void showCategories()
{
// TODO clear edittexty?
clearLastQuery();
displaySearchProgress(false);
mAdapter.updateCategories();
mAdapter.showCategories();
}
@Override
@ -346,11 +326,6 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
@Override
public void onLocationError(int errorCode) {}
private boolean isCurrentResult(int id)
{
return (id >= mQueryId && id < mQueryId + QUERY_STEP);
}
// Called from native code
@SuppressWarnings("unused")
public void updateData(final int count, final int resultId)
@ -365,7 +340,7 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
if (!doShowCategories())
{
mAdapter.updateData(count, resultId);
mAdapter.showData(count, resultId);
// scroll list view to the top
setSelection(0);
}
@ -390,14 +365,9 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
});
}
private void setSearchQuery(String query)
{
Utils.setTextAndCursorToEnd(mEtSearchQuery, query);
}
private int runSearch()
{
final String query = getSearchString();
final String query = getSearchQuery();
if (query.isEmpty())
{
// do force search next time from empty list
@ -406,7 +376,7 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
}
final int id = mQueryId + QUERY_STEP;
if (nativeRunSearch(query, Language.getKeyboardInput(MWMApplication.get()), mLat, mLon, mFlags, id))
if (nativeRunSearch(query, Language.getKeyboardLocale(), mLat, mLon, mFlags, id))
{
mQueryId = id;
// mark that it's not the first query already - don't do force search
@ -446,7 +416,7 @@ public class SearchFragment extends BaseMwmListFragment implements View.OnClickL
public SearchAdapter.SearchResult getResult(int position, int queryID)
{
return nativeGetResult(position, queryID, mLat, mLon, (mFlags & HAS_POSITION) != 0, mNorth);
return nativeGetResult(position, queryID, mLat, mLon, (mFlags & HAS_POSITION) != 0, DUMMY_NORTH);
}
@Override

View file

@ -5,6 +5,7 @@ import android.content.Intent;
import android.speech.RecognizerIntent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import java.util.ArrayList;
@ -12,12 +13,13 @@ public class InputUtils
{
private static Boolean mVoiceInputSupported = null;
private InputUtils() { /* static class */ }
public static boolean isVoiceInputSupported(Context context)
{
if (mVoiceInputSupported == null)
{
mVoiceInputSupported = Utils.isIntentSupported(context, new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH));
}
return mVoiceInputSupported;
}
@ -25,7 +27,7 @@ public class InputUtils
{
final Intent vrIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
vrIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH)
.putExtra(RecognizerIntent.EXTRA_PROMPT, promptText);
.putExtra(RecognizerIntent.EXTRA_PROMPT, promptText);
return vrIntent;
}
@ -36,21 +38,30 @@ public class InputUtils
*/
public static String getMostConfidentResult(Intent vrIntentResult)
{
final ArrayList<String> recongnizedStrings
= vrIntentResult.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
final ArrayList<String> recognizedStrings
= vrIntentResult.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
if (recongnizedStrings == null)
if (recognizedStrings == null)
return null;
return recongnizedStrings.isEmpty() ? null : recongnizedStrings.get(0);
return recognizedStrings.isEmpty() ? null : recognizedStrings.get(0);
}
public static void hideKeyboard(View view)
{
final Context c = view.getContext();
final InputMethodManager imm = (InputMethodManager)c.getSystemService(Context.INPUT_METHOD_SERVICE);
final InputMethodManager imm = (InputMethodManager) c.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private InputUtils() { /* static class */ }
/*
Hacky method to remove focus from the only EditText at activity
*/
public static void removeFocusEditTextHack(EditText editText)
{
editText.setFocusableInTouchMode(false);
editText.setFocusable(false);
editText.setFocusableInTouchMode(true);
editText.setFocusable(true);
}
}

View file

@ -1,29 +1,30 @@
package com.mapswithme.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.mapswithme.maps.MWMApplication;
import java.util.Locale;
public class Language
{
// Locale.getLanguage() returns even 3-letter codes, not that we need in the C++ core,
// so we use locale itself, like zh_CN
static public String getDefault()
static public String getDefaultLocale()
{
return Locale.getDefault().toString();
}
// After some testing on Galaxy S4, looks like this method doesn't work on all devices:
// sometime it always returns the same value as getDefault()
static public String getKeyboardInput(Context context)
// sometime it always returns the same value as getDefaultLocale()
static public String getKeyboardLocale()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
final InputMethodManager imm = (InputMethodManager) MWMApplication.get().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null)
{
final InputMethodSubtype ims = imm.getCurrentInputMethodSubtype();
@ -32,6 +33,6 @@ public class Language
}
}
return getDefault();
return getDefaultLocale();
}
}

View file

@ -1,7 +1,9 @@
package com.mapswithme.util;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import android.text.style.CharacterStyle;
import com.mapswithme.maps.MWMApplication;
@ -62,6 +64,7 @@ public class StringUtils
* Removes html tags, generated from edittext content after it's transformed to html.
* In version 4.3.1 we converted descriptions, entered by users, to html automatically. Later html conversion was cancelled, but those converted descriptions should be converted back to
* plain text, that's why that ugly util is introduced.
*
* @param text source text
* @return result text
*/
@ -84,5 +87,17 @@ public class StringUtils
return (size + Constants.KB - 1) / Constants.KB + " " + MWMApplication.get().getString(R.string.kb);
}
public static class SimpleTextWatcher implements TextWatcher
{
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override
public void afterTextChanged(Editable s) { }
}
private StringUtils() {}
}