package net.osmand.plus.download.ui;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import net.osmand.Collator;
import net.osmand.CollatorStringMatcher;
import net.osmand.OsmAndCollator;
import net.osmand.ResultMatcher;
import net.osmand.binary.BinaryMapDataObject;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
import net.osmand.data.Amenity;
import net.osmand.map.OsmandRegions;
import net.osmand.map.WorldRegion;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.download.CityItem;
import net.osmand.plus.download.DownloadActivity;
import net.osmand.plus.download.DownloadActivity.BannerAndDownloadFreeVersion;
import net.osmand.plus.download.DownloadActivityType;
import net.osmand.plus.download.DownloadIndexesThread.DownloadEvents;
import net.osmand.plus.download.DownloadResourceGroup;
import net.osmand.plus.download.DownloadResourceGroup.DownloadResourceGroupType;
import net.osmand.plus.download.DownloadResources;
import net.osmand.plus.download.IndexItem;
import net.osmand.search.core.SearchPhrase;
import net.osmand.util.Algorithms;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
public class SearchDialogFragment extends DialogFragment implements DownloadEvents, OnItemClickListener {
public static final String TAG = "SearchDialogFragment";
private static final String SEARCH_TEXT_DLG_KEY = "search_text_dlg_key";
private ListView listView;
private SearchListAdapter listAdapter;
private BannerAndDownloadFreeVersion banner;
private String searchText;
private View searchView;
private EditText searchEditText;
private ProgressBar progressBar;
private ImageButton clearButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
boolean isLightTheme = getMyApplication().getSettings().OSMAND_THEME.get() == OsmandSettings.OSMAND_LIGHT_THEME;
int themeId = isLightTheme ? R.style.OsmandLightTheme : R.style.OsmandDarkTheme;
setStyle(STYLE_NO_FRAME, themeId);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.maps_in_category_fragment, container, false);
if (savedInstanceState != null) {
searchText = savedInstanceState.getString(SEARCH_TEXT_DLG_KEY);
}
if (searchText == null) {
searchText = getArguments().getString(SEARCH_TEXT_DLG_KEY);
}
if (searchText == null)
searchText = "";
Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
toolbar.setNavigationContentDescription(R.string.access_shared_string_navigate_up);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
banner = new BannerAndDownloadFreeVersion(view, (DownloadActivity) getActivity(), false);
LinearLayout ll = (LinearLayout) view;
ExpandableListView expandablelistView = (ExpandableListView) view.findViewById(android.R.id.list);
ll.removeView(expandablelistView);
listView = new ListView(getActivity());
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
layoutParams.weight = 1;
layoutParams.setMargins(0, 0, 0, 0);
listView.setLayoutParams(layoutParams);
ll.addView(listView);
listView.setOnItemClickListener(this);
listAdapter = new SearchListAdapter(getDownloadActivity());
listView.setOnItemClickListener(this);
listView.setAdapter(listAdapter);
TypedValue typedValue = new TypedValue();
getActivity().getTheme().resolveAttribute(R.attr.toolbar_theme, typedValue, true);
searchView = inflater.inflate(R.layout.search_text_layout, toolbar, false);
toolbar.addView(searchView);
searchEditText = (EditText) view.findViewById(R.id.searchEditText);
searchEditText.setHint(R.string.search_map_hint);
searchEditText.setTextColor(Color.WHITE);
boolean isLight = getMyApplication().getSettings().isLightContent();
searchEditText.setHintTextColor(isLight ?
getMyApplication().getResources().getColor(R.color.inactive_item_orange) : getMyApplication().getResources().getColor(R.color.searchbar_tab_inactive_dark));
progressBar = (ProgressBar) view.findViewById(R.id.searchProgressBar);
clearButton = (ImageButton) view.findViewById(R.id.clearButton);
clearButton.setVisibility(View.GONE);
searchEditText.addTextChangedListener(new 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) {
updateSearchText(s.toString());
}
});
clearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (searchEditText.getText().length() == 0) {
dismiss();
} else {
searchEditText.setText("");
}
}
});
searchEditText.requestFocus();
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setShowsDialog(true);
final boolean isLightContent = getMyApplication().getSettings().isLightContent();
final int colorId = isLightContent ? R.color.bg_color_light : R.color.bg_color_dark;
listView.setBackgroundColor(ContextCompat.getColor(getActivity(), colorId));
}
@Override
public void newDownloadIndexes() {
if(banner != null) {
banner.updateBannerInProgress();
}
updateSearchText(searchText);
}
@Override
public void downloadHasFinished() {
if(banner != null) {
banner.updateBannerInProgress();
}
listAdapter.notifyDataSetChanged();
}
@Override
public void downloadInProgress() {
if(banner != null) {
banner.updateBannerInProgress();
}
listAdapter.notifyDataSetChanged();
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString(SEARCH_TEXT_DLG_KEY, searchText);
super.onSaveInstanceState(outState);
}
@Override
public void onResume() {
super.onResume();
if (!Algorithms.isEmpty(searchText)) {
searchEditText.setText(searchText);
}
}
public void updateSearchText(String searchText) {
this.searchText = searchText;
SearchListAdapter.SearchIndexFilter filter = (SearchListAdapter.SearchIndexFilter) listAdapter.getFilter();
filter.cancelFilter();
filter.filter(searchText);
}
private OsmandApplication getMyApplication() {
return (OsmandApplication) getActivity().getApplication();
}
private DownloadActivity getDownloadActivity() {
return (DownloadActivity) getActivity();
}
public static SearchDialogFragment createInstance(String searchText) {
Bundle bundle = new Bundle();
bundle.putString(SEARCH_TEXT_DLG_KEY, searchText);
SearchDialogFragment fragment = new SearchDialogFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
Object obj = listAdapter.getItem(position);
if (obj instanceof DownloadResourceGroup) {
String uniqueId = ((DownloadResourceGroup) obj).getUniqueId();
final DownloadResourceGroupFragment regionDialogFragment = DownloadResourceGroupFragment
.createInstance(uniqueId);
((DownloadActivity) getActivity()).showDialog(getActivity(), regionDialogFragment);
} else if (obj instanceof IndexItem) {
IndexItem indexItem = (IndexItem) obj;
ItemViewHolder vh = (ItemViewHolder) v.getTag();
View.OnClickListener ls = vh.getRightButtonAction(indexItem, vh.getClickAction(indexItem));
ls.onClick(v);
}
}
private void showProgressBar() {
updateClearButtonVisibility(false);
progressBar.setVisibility(View.VISIBLE);
}
private void hideProgressBar() {
updateClearButtonVisibility(true);
progressBar.setVisibility(View.GONE);
}
private void updateClearButtonVisibility(boolean show) {
if (show) {
clearButton.setVisibility(searchEditText.length() > 0 ? View.VISIBLE : View.GONE);
} else {
clearButton.setVisibility(View.GONE);
}
}
private class SearchListAdapter extends BaseAdapter implements Filterable {
private SearchIndexFilter mFilter;
private OsmandRegions osmandRegions;
private List<Object> items = new LinkedList<>();
private DownloadActivity ctx;
public SearchListAdapter(DownloadActivity ctx) {
this.ctx = ctx;
this.osmandRegions = ctx.getMyApplication().getRegions();
TypedArray ta = ctx.getTheme().obtainStyledAttributes(new int[]{android.R.attr.textColorPrimary});
ta.recycle();
}
public void clear() {
items.clear();
notifyDataSetChanged();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public int getCount() {
return items.size();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemViewType(int position) {
Object obj = items.get(position);
if (obj instanceof IndexItem || obj instanceof CityItem) {
return 0;
} else {
return 1;
}
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Object obj = items.get(position);
if (obj instanceof IndexItem || obj instanceof CityItem) {
ItemViewHolder viewHolder;
if (convertView != null && convertView.getTag() instanceof ItemViewHolder) {
viewHolder = (ItemViewHolder) convertView.getTag();
} else {
convertView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.two_line_with_images_list_item, parent, false);
viewHolder = new ItemViewHolder(convertView, getDownloadActivity());
viewHolder.setShowRemoteDate(true);
convertView.setTag(viewHolder);
}
if (obj instanceof IndexItem) {
IndexItem item = (IndexItem) obj;
viewHolder.setShowTypeInDesc(true);
viewHolder.bindIndexItem(item);
} else {
CityItem item = (CityItem) obj;
viewHolder.bindIndexItem(item);
if (item.getIndexItem() == null) {
new IndexItemResolverTask(viewHolder, item).execute();
}
}
} else {
DownloadResourceGroup group = (DownloadResourceGroup) obj;
DownloadGroupViewHolder viewHolder;
if (convertView != null && convertView.getTag() instanceof DownloadGroupViewHolder) {
viewHolder = (DownloadGroupViewHolder) convertView.getTag();
} else {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.simple_list_menu_item,
parent, false);
viewHolder = new DownloadGroupViewHolder(getDownloadActivity(), convertView);
convertView.setTag(viewHolder);
}
viewHolder.bindItem(group);
}
return convertView;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new SearchIndexFilter();
}
return mFilter;
}
class IndexItemResolverTask extends AsyncTask<Void, Void, IndexItem> {
private final WeakReference<ItemViewHolder> viewHolderReference;
private final CityItem cityItem;
public IndexItemResolverTask(ItemViewHolder viewHolder, CityItem cityItem) {
this.viewHolderReference = new WeakReference<>(viewHolder);
this.cityItem = cityItem;
}
@Override
protected IndexItem doInBackground(Void... params) {
Amenity amenity = cityItem.getAmenity();
BinaryMapDataObject o = null;
try {
o = osmandRegions.findBinaryMapDataObject(amenity.getLocation());
} catch (IOException e) {
// ignore
}
if (o != null) {
String selectedFullName = osmandRegions.getFullName(o);
WorldRegion downloadRegion = osmandRegions.getRegionData(selectedFullName);
List<IndexItem> indexItems = ctx.getDownloadThread().getIndexes().getIndexItems(downloadRegion);
for (IndexItem item : indexItems) {
if (item.getType() == DownloadActivityType.NORMAL_FILE) {
return item;
}
}
}
return null;
}
@Override
protected void onPostExecute(IndexItem indexItem) {
if (isCancelled()) {
return;
}
ItemViewHolder viewHolder = viewHolderReference.get();
if (viewHolder != null) {
if (indexItem != null) {
cityItem.setIndexItem(indexItem);
viewHolder.bindIndexItem(indexItem, cityItem.getName());
}
}
}
}
private final class SearchIndexFilter extends Filter {
private OsmandRegions osmandRegions;
private final int searchCityLimit = 10000;
private final List<String> citySubTypes = Arrays.asList("city", "town");
private SearchRequest<Amenity> searchCityRequest;
public SearchIndexFilter() {
this.osmandRegions = ctx.getMyApplication().getRegions();
}
public void cancelFilter() {
if (searchCityRequest != null) {
searchCityRequest.setInterrupted(true);
}
}
private void processGroup(DownloadResourceGroup group, List<Object> filter, List<List<String>> conds) {
String name = null;
if (group.getRegion() != null && group.getRegion().getRegionSearchText() != null) {
name = group.getRegion().getRegionSearchText().toLowerCase();
}
if (name == null) {
name = group.getName(ctx).toLowerCase();
}
if (group.getType().isScreen() && group.getParentGroup() != null
&& group.getParentGroup().getParentGroup() != null
&& group.getParentGroup().getParentGroup().getType() != DownloadResourceGroupType.WORLD
&& isMatch(conds, false, name)) {
filter.add(group);
for (DownloadResourceGroup g : group.getGroups()) {
if (g.getType() == DownloadResourceGroupType.REGION_MAPS) {
if (g.getIndividualResources() != null) {
for (IndexItem item : g.getIndividualResources()) {
if (item.getType() == DownloadActivityType.NORMAL_FILE) {
filter.add(item);
break;
}
}
}
break;
}
}
}
// process other maps & voice prompts
if (group.getType() == DownloadResourceGroupType.OTHER_MAPS_HEADER
|| group.getType() == DownloadResourceGroupType.VOICE_HEADER_REC
|| group.getType() == DownloadResourceGroupType.VOICE_HEADER_TTS
|| group.getType() == DownloadResourceGroupType.FONTS_HEADER) {
if (group.getIndividualResources() != null) {
for (IndexItem item : group.getIndividualResources()) {
name = item.getVisibleName(ctx, osmandRegions, false).toLowerCase();
if (isMatch(conds, false, name)) {
filter.add(item);
break;
}
}
}
}
if (group.getGroups() != null) {
for (DownloadResourceGroup g : group.getGroups()) {
processGroup(g, filter, conds);
}
}
}
public List<CityItem> searchCities(final OsmandApplication app, final String text) throws IOException {
IndexItem worldBaseMapItem = app.getDownloadThread().getIndexes().getWorldBaseMapItem();
if (worldBaseMapItem == null || !worldBaseMapItem.isDownloaded()) {
return new ArrayList<>();
}
File obf = worldBaseMapItem.getTargetFile(app);
final BinaryMapIndexReader baseMapReader = new BinaryMapIndexReader(new RandomAccessFile(obf, "r"), obf);
final SearchPhrase.NameStringMatcher nm = new SearchPhrase.NameStringMatcher(
text, CollatorStringMatcher.StringMatcherMode.CHECK_STARTS_FROM_SPACE);
final String lang = app.getSettings().MAP_PREFERRED_LOCALE.get();
final boolean translit = app.getSettings().MAP_TRANSLITERATE_NAMES.get();
final List<Amenity> amenities = new ArrayList<>();
SearchRequest<Amenity> request = BinaryMapIndexReader.buildSearchPoiRequest(
0, 0,
text,
Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE,
new ResultMatcher<Amenity>() {
int count = 0;
@Override
public boolean publish(Amenity amenity) {
if (count++ > searchCityLimit) {
return false;
}
List<String> otherNames = amenity.getAllNames(true);
String localeName = amenity.getName(lang, translit);
String subType = amenity.getSubType();
if (!citySubTypes.contains(subType)
|| (!nm.matches(localeName) && !nm.matches(otherNames))) {
return false;
}
amenities.add(amenity);
return false;
}
@Override
public boolean isCancelled() {
return count > searchCityLimit;
}
});
searchCityRequest = request;
baseMapReader.searchPoiByName(request);
try {
baseMapReader.close();
} catch (Exception e) {
e.printStackTrace();
}
List<CityItem> items = new ArrayList<>();
for (Amenity amenity : amenities) {
items.add(new CityItem(amenity.getName(), amenity, null));
}
return items;
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
getMyApplication().runInUIThread(new Runnable() {
@Override
public void run() {
showProgressBar();
}
});
DownloadResources root = ctx.getDownloadThread().getIndexes();
FilterResults results = new FilterResults();
if (constraint == null || constraint.length() < 2) {
results.values = new ArrayList<>();
results.count = 0;
} else {
List<Object> filter = new ArrayList<>();
if (constraint.length() > 2) {
try {
filter.addAll(searchCities(getMyApplication(), constraint.toString()));
} catch (IOException e) {
e.printStackTrace();
}
}
String[] ors = constraint.toString().split(",");
List<List<String>> conds = new ArrayList<>();
for (String or : ors) {
final ArrayList<String> cond = new ArrayList<>();
for (String term : or.split("\\s")) {
final String t = term.trim().toLowerCase();
if (t.length() > 0) {
cond.add(t);
}
}
if (cond.size() > 0) {
conds.add(cond);
}
}
processGroup(root, filter, conds);
final Collator collator = OsmAndCollator.primaryCollator();
Collections.sort(filter, new Comparator<Object>() {
@Override
public int compare(Object obj1, Object obj2) {
String str1;
String str2;
if (obj1 instanceof DownloadResourceGroup) {
str1 = ((DownloadResourceGroup) obj1).getName(ctx);
} else if (obj1 instanceof IndexItem) {
str1 = ((IndexItem) obj1).getVisibleName(getMyApplication(), osmandRegions, false);
} else {
Amenity a = ((CityItem) obj1).getAmenity();
if ("city".equals(a.getSubType())) {
str1 = "!" + ((CityItem) obj1).getName();
} else {
str1 = ((CityItem) obj1).getName();
}
}
if (obj2 instanceof DownloadResourceGroup) {
str2 = ((DownloadResourceGroup) obj2).getName(ctx);
} else if (obj2 instanceof IndexItem) {
str2 = ((IndexItem) obj2).getVisibleName(getMyApplication(), osmandRegions, false);
} else {
Amenity a = ((CityItem) obj2).getAmenity();
if ("city".equals(a.getSubType())) {
str2 = "!" + ((CityItem) obj2).getName();
} else {
str2 = ((CityItem) obj2).getName();
}
}
return collator.compare(str1, str2);
}
});
results.values = filter;
results.count = filter.size();
}
getMyApplication().runInUIThread(new Runnable() {
@Override
public void run() {
hideProgressBar();
}
});
return results;
}
private boolean isMatch(List<List<String>> conditions, boolean matchByDefault, String text) {
boolean res = matchByDefault;
for (List<String> or : conditions) {
boolean tadd = true;
for (String var : or) {
if (!text.contains(var)) {
tadd = false;
break;
}
}
if (!tadd) {
res = false;
} else {
res = true;
break;
}
}
return res;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
items.clear();
List<Object> values = (List<Object>) results.values;
if (values != null && !values.isEmpty()) {
items.addAll(values);
}
notifyDataSetChanged();
}
}
}
}