package org.mtransit.android.ui.fragment;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import org.mtransit.android.R;
import org.mtransit.android.commons.BundleUtils;
import org.mtransit.android.commons.KeyboardUtils;
import org.mtransit.android.commons.LocationUtils;
import org.mtransit.android.commons.MTLog;
import org.mtransit.android.commons.StringUtils;
import org.mtransit.android.commons.TaskUtils;
import org.mtransit.android.commons.task.MTAsyncTask;
import org.mtransit.android.commons.ui.widget.MTArrayAdapter;
import org.mtransit.android.data.DataSourceProvider;
import org.mtransit.android.data.DataSourceType;
import org.mtransit.android.data.POIArrayAdapter;
import org.mtransit.android.data.POIManager;
import org.mtransit.android.task.POISearchLoader;
import org.mtransit.android.ui.MTActivityWithLocation;
import org.mtransit.android.ui.MainActivity;
import org.mtransit.android.ui.view.MTSearchView;
import org.mtransit.android.util.LoaderUtils;
import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.TextView;
public class SearchFragment extends ABFragment implements LoaderManager.LoaderCallbacks<ArrayList<POIManager>>, MTActivityWithLocation.UserLocationListener,
POIArrayAdapter.TypeHeaderButtonsClickListener, AdapterView.OnItemSelectedListener {
private static final String TAG = SearchFragment.class.getSimpleName();
@Override
public String getLogTag() {
return TAG;
}
private static final String TRACKING_SCREEN_NAME = "Search";
@Override
public String getScreenName() {
return TRACKING_SCREEN_NAME;
}
private static final String EXTRA_QUERY = "extra_query";
private static final String EXTRA_TYPE_FILTER = "extra_type_filter";
private static final String EXTRA_SEARCH_HAS_FOCUS = "extra_search_has_focus";
public static SearchFragment newInstance(String optQuery, Integer optTypeIdFilter, TypeFilter optTypeFilter) {
SearchFragment f = new SearchFragment();
Bundle args = new Bundle();
if (!TextUtils.isEmpty(optQuery)) {
args.putString(EXTRA_QUERY, optQuery);
f.query = optQuery;
}
if (optTypeIdFilter != null) {
args.putInt(EXTRA_TYPE_FILTER, optTypeIdFilter);
f.typeIdFilter = optTypeIdFilter;
}
f.typeFilter = optTypeFilter;
f.setArguments(args);
return f;
}
private POIArrayAdapter adapter;
private CharSequence emptyText = null;
private Location userLocation;
private String query = null;
private Integer typeIdFilter = null;
private TypeFilter typeFilter = null;
private boolean searchHasFocus = true;
private void resetTypeFilter() {
this.typeFilter = null;
}
private boolean hasTypeFilter() {
if (this.typeFilter == null) {
initTypeFilterAsync();
return false;
}
return true;
}
private void initTypeFilterAsync() {
if (this.loadTypeFilterTask != null && this.loadTypeFilterTask.getStatus() == MTAsyncTask.Status.RUNNING) {
return;
}
if (this.typeIdFilter == null) {
this.typeIdFilter = TypeFilter.ALL.getDataSourceTypeId(); // default
}
TaskUtils.cancelQuietly(this.loadTypeFilterTask, true);
this.loadTypeFilterTask = new LoadTypeFilterTask();
TaskUtils.execute(this.loadTypeFilterTask);
}
private LoadTypeFilterTask loadTypeFilterTask;
private class LoadTypeFilterTask extends MTAsyncTask<Void, Void, Boolean> {
@Override
public String getLogTag() {
return SearchFragment.this.getLogTag() + ">" + LoadTypeFilterTask.class.getSimpleName();
}
@Override
protected Boolean doInBackgroundMT(Void... params) {
return initTypeFilterSync();
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (result) {
applyNewTypeFilter();
}
}
}
private boolean initTypeFilterSync() {
if (this.typeFilter != null) {
return false;
}
if (this.typeIdFilter != null) {
if (this.typeIdFilter.equals(TypeFilter.ALL.getDataSourceTypeId())) {
this.typeFilter = TypeFilter.ALL;
} else {
DataSourceType parseId = DataSourceType.parseId(this.typeIdFilter);
if (parseId != null) {
this.typeFilter = TypeFilter.fromDataSourceType(parseId);
}
}
}
return this.typeFilter != null;
}
private void setTypeFilterFromType(Integer newTypeId) {
if (newTypeId == null) {
return;
}
if (this.typeIdFilter != null && this.typeIdFilter.equals(newTypeId)) {
return;
}
this.typeIdFilter = newTypeId;
resetTypeFilter();
initTypeFilterSync();
applyNewTypeFilter();
}
private void applyNewTypeFilter() {
if (this.typeFilter == null) {
return;
}
getLoaderManager().destroyLoader(POI_SEARCH_LOADER); // cancel now
cancelRestartSearchLater();
View view = getView();
if (this.typeFilter.getDataSourceTypeId() == TypeFilter.ALL.getDataSourceTypeId()) {
this.adapter.setShowTypeHeader(POIArrayAdapter.TYPE_HEADER_MORE);
} else {
this.adapter.setShowTypeHeader(POIArrayAdapter.TYPE_HEADER_NONE);
}
int position = this.typeFiltersAdapter.getPosition(this.typeFilter);
if (view != null) {
((Spinner) view.findViewById(R.id.typeFilters)).setSelection(position, true);
}
this.adapter.clear();
switchView(view);
restartSearchLater();
}
private TypeFilter getTypeFilterOrNull() {
if (!hasTypeFilter()) {
return null;
}
return this.typeFilter;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
initAdapters(activity);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
restoreInstanceState(savedInstanceState, getArguments());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_search, container, false);
setupView(view);
return view;
}
private void initAdapters(Activity activity) {
this.adapter = new POIArrayAdapter(activity);
this.adapter.setTag(getLogTag());
this.adapter.setOnTypeHeaderButtonsClickListener(this);
this.typeFiltersAdapter = new TypeFiltersAdapter(activity);
}
@Override
public boolean onTypeHeaderButtonClick(int buttonId, DataSourceType type) {
if (buttonId == POIArrayAdapter.TypeHeaderButtonsClickListener.BUTTON_MORE) {
KeyboardUtils.hideKeyboard(getActivity(), getView());
setTypeFilterFromType(type.getId());
return true; // handled
}
return false; // not handled
}
private void setupView(View view) {
if (view == null) {
return;
}
Spinner typeFiltersSpinner = (Spinner) view.findViewById(R.id.typeFilters);
TypeFilter typeFilter = getTypeFilterOrNull();
int position = this.typeFiltersAdapter.getPosition(typeFilter);
typeFiltersSpinner.setSelection(position);
if (typeFiltersSpinner.getOnItemSelectedListener() == null) {
typeFiltersSpinner.setOnItemSelectedListener(this);
}
setupAdapters(view);
}
private void setupAdapters(View view) {
if (view == null) {
return;
}
inflateList(view);
switchView(view);
linkAdapterWithListView(view);
Spinner typeFiltersSpinner = (Spinner) view.findViewById(R.id.typeFilters);
typeFiltersSpinner.setAdapter(this.typeFiltersAdapter);
}
private void linkAdapterWithListView(View view) {
if (view == null || this.adapter == null) {
return;
}
View listView = view.findViewById(R.id.list);
if (listView != null) {
this.adapter.setListView((AbsListView) listView);
}
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setTypeFilterFromType(this.typeFiltersAdapter.getItem(position).getDataSourceTypeId());
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
private TypeFiltersAdapter typeFiltersAdapter = null;
private void restoreInstanceState(Bundle... bundles) {
Integer newTypeIdFilter = BundleUtils.getInt(EXTRA_TYPE_FILTER, bundles);
if (newTypeIdFilter != null && !newTypeIdFilter.equals(this.typeIdFilter)) {
this.typeIdFilter = newTypeIdFilter;
resetTypeFilter();
}
String newQuery = BundleUtils.getString(EXTRA_QUERY, bundles);
if (!TextUtils.isEmpty(newQuery) && !newQuery.equals(this.query)) {
this.query = newQuery;
}
Boolean newSearchHasFocus = BundleUtils.getBoolean(EXTRA_SEARCH_HAS_FOCUS, bundles);
if (newSearchHasFocus != null) {
this.searchHasFocus = newSearchHasFocus;
}
if (this.typeIdFilter == null) {
this.typeFilter = TypeFilter.ALL;
this.typeIdFilter = this.typeFilter.getDataSourceTypeId();
}
if (this.typeIdFilter == TypeFilter.ALL.getDataSourceTypeId()) {
this.adapter.setShowTypeHeader(POIArrayAdapter.TYPE_HEADER_MORE);
} else {
this.adapter.setShowTypeHeader(POIArrayAdapter.TYPE_HEADER_NONE);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (this.query != null) {
outState.putString(EXTRA_QUERY, this.query);
}
if (this.typeIdFilter != null) {
outState.putInt(EXTRA_TYPE_FILTER, this.typeIdFilter);
}
refreshSearchHasFocus();
outState.putBoolean(EXTRA_SEARCH_HAS_FOCUS, this.searchHasFocus);
super.onSaveInstanceState(outState);
}
@Override
public void onResume() {
super.onResume();
if (this.modulesUpdated) {
new Handler().post(new Runnable() {
@Override
public void run() {
if (SearchFragment.this.modulesUpdated) {
onModulesUpdated();
}
}
});
}
switchView(getView());
this.adapter.onResume(getActivity(), this.userLocation);
if (!this.adapter.isInitialized()) {
applyNewQuery();
}
onUserLocationChanged(((MTActivityWithLocation) getActivity()).getLastLocation());
}
@Override
public void onPause() {
super.onPause();
if (this.adapter != null) {
this.adapter.onPause();
}
}
private boolean modulesUpdated = false;
@Override
public void onModulesUpdated() {
this.modulesUpdated = true;
if (!isResumed()) {
return;
}
if (this.typeFiltersAdapter != null) {
this.typeFiltersAdapter.reset();
}
cancelRestartSearchLater();
restartSearchLater();
this.modulesUpdated = false; // processed
}
private static final int POI_SEARCH_LOADER = 0;
@Override
public Loader<ArrayList<POIManager>> onCreateLoader(int id, Bundle args) {
MTLog.v(this, "onCreateLoader(%s,%s)", id, args);
switch (id) {
case POI_SEARCH_LOADER:
TypeFilter typeFilter = getTypeFilterOrNull();
return new POISearchLoader(getActivity(), this.query, typeFilter, this.userLocation);
default:
MTLog.w(this, "Loader id '%s' unknown!", id);
return null;
}
}
@Override
public void onLoaderReset(Loader<ArrayList<POIManager>> loader) {
if (this.adapter != null) {
this.adapter.clear();
}
}
@Override
public void onLoadFinished(Loader<ArrayList<POIManager>> loader, ArrayList<POIManager> data) {
this.adapter.setPois(data);
this.adapter.updateDistanceNowAsync(this.userLocation);
switchView(getView());
}
@Override
public void onUserLocationChanged(Location newLocation) {
if (newLocation != null) {
if (this.userLocation == null || LocationUtils.isMoreRelevant(getLogTag(), this.userLocation, newLocation)) {
this.userLocation = newLocation;
if (this.adapter != null) {
this.adapter.setLocation(newLocation);
}
}
}
}
private void applyNewQuery() {
getLoaderManager().destroyLoader(POI_SEARCH_LOADER); // cancel now
cancelRestartSearchLater();
if (TextUtils.isEmpty(this.query)) {
this.emptyText = getString(R.string.search_hint);
this.adapter.setPois(new ArrayList<POIManager>()); // empty search = no result
switchView(getView());
return;
}
this.emptyText = getString(R.string.search_no_result_for_and_query, this.query);
this.adapter.clear();
switchView(getView());
restartSearchLater();
}
public void setSearchQuery(String query, boolean alreadyInSearchView) {
if (this.query == null || !StringUtils.equals(StringUtils.trim(this.query), StringUtils.trim(query))) {
this.query = query == null ? StringUtils.EMPTY : query;
applyNewQuery();
}
}
private Handler handler = new Handler();
private RestartSearchLater restartSearchLater = null;
private class RestartSearchLater implements Runnable {
@Override
public void run() {
if (getTypeFilterOrNull() != null) {
LoaderUtils.restartLoader(SearchFragment.this, POI_SEARCH_LOADER, null, SearchFragment.this);
cancelRestartSearchLater();
}
}
}
private void restartSearchLater() {
this.restartSearchLater = new RestartSearchLater();
this.handler.postDelayed(this.restartSearchLater, TimeUnit.SECONDS.toMillis(1));
}
private void cancelRestartSearchLater() {
if (this.restartSearchLater != null) {
this.handler.removeCallbacks(this.restartSearchLater);
this.restartSearchLater = null;
}
}
private void switchView(View view) {
if (view == null) {
return;
}
if (this.adapter == null || !this.adapter.isInitialized()) {
showLoading(view);
} else if (this.adapter.getPoisCount() == 0) {
showEmpty(view);
} else {
showList(view);
}
TypeFilter typeFilter = getTypeFilterOrNull();
if (typeFilter == null || typeFilter.getDataSourceTypeId() == TypeFilter.ALL.getDataSourceTypeId()) {
if (view.findViewById(R.id.typeFilters).getVisibility() != View.GONE) {
view.findViewById(R.id.typeFilters).setVisibility(View.GONE);
}
} else {
if (view.findViewById(R.id.typeFilters).getVisibility() != View.VISIBLE) {
view.findViewById(R.id.typeFilters).setVisibility(View.VISIBLE);
}
}
}
private void showList(View view) {
if (view.findViewById(R.id.loading) != null) { // IF inflated/present DO
view.findViewById(R.id.loading).setVisibility(View.GONE); // hide
}
if (view.findViewById(R.id.empty) != null) { // IF inflated/present DO
view.findViewById(R.id.empty).setVisibility(View.GONE); // hide
}
inflateList(view);
view.findViewById(R.id.list).setVisibility(View.VISIBLE); // show
}
private void inflateList(View view) {
if (view.findViewById(R.id.list) == null) { // IF NOT present/inflated DO
((ViewStub) view.findViewById(R.id.list_stub)).inflate(); // inflate
}
}
private void showLoading(View view) {
if (view.findViewById(R.id.list) != null) { // IF inflated/present DO
view.findViewById(R.id.list).setVisibility(View.GONE); // hide
}
if (view.findViewById(R.id.empty) != null) { // IF inflated/present DO
view.findViewById(R.id.empty).setVisibility(View.GONE); // hide
}
view.findViewById(R.id.loading).setVisibility(View.VISIBLE); // show
}
private void showEmpty(View view) {
if (view.findViewById(R.id.list) != null) { // IF inflated/present DO
view.findViewById(R.id.list).setVisibility(View.GONE); // hide
}
if (view.findViewById(R.id.loading) != null) { // IF inflated/present DO
view.findViewById(R.id.loading).setVisibility(View.GONE); // hide
}
if (view.findViewById(R.id.empty) == null) { // IF NOT present/inflated DO
((ViewStub) view.findViewById(R.id.empty_stub)).inflate(); // inflate
}
if (!TextUtils.isEmpty(this.emptyText)) {
((TextView) view.findViewById(R.id.empty_text)).setText(this.emptyText);
}
view.findViewById(R.id.empty).setVisibility(View.VISIBLE); // show
}
@Override
public boolean isABReady() {
return this.searchView != null;
}
@Override
public boolean isABShowSearchMenuItem() {
return false;
}
@Override
public boolean isABCustomViewFocusable() {
return true;
}
@Override
public boolean isABCustomViewRequestFocus() {
return searchHasFocus();
}
private boolean searchHasFocus() {
refreshSearchHasFocus();
return this.searchHasFocus;
}
private void refreshSearchHasFocus() {
if (this.searchView != null) {
this.searchHasFocus = this.searchView.hasFocus();
}
}
@Override
public View getABCustomView() {
return getSearchView();
}
private MTSearchView searchView;
private MTSearchView getSearchView() {
if (this.searchView == null) {
initSearchView();
}
return this.searchView;
}
private void initSearchView() {
Activity activity = getActivity();
if (activity == null) {
return;
}
MainActivity mainActivity = (MainActivity) activity;
ActionBar supportActionBar = mainActivity.getSupportActionBar();
Context context = supportActionBar == null ? mainActivity : supportActionBar.getThemedContext();
this.searchView = new MTSearchView(mainActivity, context);
this.searchView.setQuery(this.query, false);
if (!this.searchHasFocus) {
this.searchView.clearFocus();
}
}
@Override
public void onDestroy() {
super.onDestroy();
cancelRestartSearchLater();
this.searchView = null;
this.handler = null;
TaskUtils.cancelQuietly(this.loadTypeFilterTask, true);
}
public static class TypeFilter implements MTLog.Loggable {
private static final String TAG = SearchFragment.class.getSimpleName() + ">" + TypeFilter.class.getSimpleName();
@Override
public String getLogTag() {
return TAG;
}
public static final TypeFilter ALL = new TypeFilter(-1, R.string.all, -1);
private int dataSourceTypeId;
private int nameResId;
private int iconResId;
public TypeFilter(int dataSourceTypeId, int nameResId, int iconResId) {
this.dataSourceTypeId = dataSourceTypeId;
this.nameResId = nameResId;
this.iconResId = iconResId;
}
@Override
public String toString() {
return new StringBuilder(TypeFilter.class.getSimpleName()).append('[') //
.append("dataSourceTypeId:").append(this.dataSourceTypeId).append(',') //
.append("nameResId:").append(this.nameResId).append(',') //
.append("iconResId:").append(this.iconResId).append(',') //
.append(']').toString();
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof TypeFilter)) {
return false;
}
TypeFilter other = (TypeFilter) o;
if (this.dataSourceTypeId != other.dataSourceTypeId) {
return false;
}
if (this.nameResId != other.nameResId) {
return false;
}
if (this.iconResId != other.iconResId) {
return false;
}
return true;
}
public int getNameResId() {
return nameResId;
}
public int getIconResId() {
return iconResId;
}
public int getDataSourceTypeId() {
return dataSourceTypeId;
}
public static TypeFilter fromDataSourceType(DataSourceType dst) {
if (dst == null) {
return null;
}
return new TypeFilter(dst.getId(), dst.getPoiShortNameResId(), dst.getGrey600IconResId());
}
}
private static class TypeFiltersAdapter extends MTArrayAdapter<TypeFilter> {
private static final String TAG = SearchFragment.class.getSimpleName() + ">" + TypeFiltersAdapter.class.getSimpleName();
@Override
public String getLogTag() {
return TAG;
}
private LayoutInflater layoutInflater;
public TypeFiltersAdapter(Context context) {
super(context, -1);
this.layoutInflater = LayoutInflater.from(context);
init();
}
private void init() {
ArrayList<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
typeFilters.add(TypeFilter.ALL);
ArrayList<DataSourceType> availableTypes = DataSourceProvider.get(getContext()).getAvailableAgencyTypes();
if (availableTypes != null) {
for (DataSourceType dst : availableTypes) {
if (dst.isSearchable()) {
typeFilters.add(TypeFilter.fromDataSourceType(dst));
}
}
}
addAll(typeFilters);
}
public void reset() {
clear();
init();
notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getTheView(position, convertView, parent);
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getTheView(position, convertView, parent);
}
private View getTheView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = this.layoutInflater.inflate(R.layout.layout_poi_type_item, parent, false);
TypeViewHolder holder = new TypeViewHolder();
holder.nameTv = (TextView) convertView.findViewById(R.id.name);
convertView.setTag(holder);
}
TypeViewHolder holder = (TypeViewHolder) convertView.getTag();
TypeFilter type = getItem(position);
holder.nameTv.setText(type.getNameResId());
if (type.getIconResId() != -1) {
holder.nameTv.setCompoundDrawablesWithIntrinsicBounds(type.getIconResId(), 0, 0, 0);
} else {
holder.nameTv.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
return convertView;
}
private static class TypeViewHolder {
TextView nameTv;
}
}
}