diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java index f0b26d09e8..7486576b61 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java @@ -1727,13 +1727,12 @@ public class BinaryMapIndexReader { double half16t = MapUtils.getDistance(lat, MapUtils.getLongitudeFromTile(16, ((int) dx) + 0.5), lat, MapUtils.getLongitudeFromTile(16, (int) dx)); double cf31 = ((double) radiusMeters / (half16t * 2)) * (1 << 15); - int y31 = MapUtils.get31TileNumberY(lat); - int x31 = MapUtils.get31TileNumberX(lon); - left = (int) (x31 - cf31); - right = (int) (x31 + cf31); - top = (int) (y31 - cf31); - bottom = (int) (y31 + cf31); - + y = MapUtils.get31TileNumberY(lat); + x = MapUtils.get31TileNumberX(lon); + left = (int) (x - cf31); + right = (int) (x + cf31); + top = (int) (y - cf31); + bottom = (int) (y + cf31); } public boolean publish(T obj) { diff --git a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java index 1759e79034..e51f04df16 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java @@ -44,6 +44,7 @@ public class Amenity extends MapObject { public static final String IS_AGGR_PART = "is_aggr_part"; public static final String CONTENT_JSON = "content_json"; public static final String ROUTE_ID = "route_id"; + public static final String ROUTE_SOURCE = "route_source"; private String subType; diff --git a/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java b/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java index 5fb13d62ac..c3205431cd 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java @@ -16,11 +16,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.zip.GZIPInputStream; diff --git a/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java b/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java index 2c0c056ab3..a9d85ce118 100644 --- a/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java +++ b/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java @@ -78,6 +78,10 @@ public class Algorithms { return map == null || map.size() == 0; } + public static String emptyIfNull(String s) { + return s == null ? "" : s; + } + public static boolean isEmpty(CharSequence s) { return s == null || s.length() == 0; } @@ -86,6 +90,10 @@ public class Algorithms { return s == null || s.trim().length() == 0; } + public static int hash(Object... values) { + return Arrays.hashCode(values); + } + public static boolean stringsEqual(String s1, String s2) { if (s1 == null && s2 == null) { return true; diff --git a/OsmAnd/src/net/osmand/plus/mapmarkers/adapters/MapMarkersGroupsAdapter.java b/OsmAnd/src/net/osmand/plus/mapmarkers/adapters/MapMarkersGroupsAdapter.java index 2277dded7c..fb74956b16 100644 --- a/OsmAnd/src/net/osmand/plus/mapmarkers/adapters/MapMarkersGroupsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/mapmarkers/adapters/MapMarkersGroupsAdapter.java @@ -427,7 +427,7 @@ public class MapMarkersGroupsAdapter extends RecyclerView.Adapter extensions = metadata.getExtensionsToRead(); @@ -466,7 +467,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } @Nullable - private String getMetadataImageLink(@NonNull GPXUtilities.Metadata metadata) { + private String getMetadataImageLink(@NonNull Metadata metadata) { String link = metadata.link; if (!TextUtils.isEmpty(link)) { String lowerCaseLink = link.toLowerCase(); @@ -482,11 +483,12 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } @Nullable - private TravelArticle getTravelArticle(@NonNull GPXUtilities.Metadata metadata) { + private TravelArticle getTravelArticle(@NonNull GPXFile gpx) { + Metadata metadata = gpx.metadata; String title = metadata.getArticleTitle(); String lang = metadata.getArticleLang(); if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(lang)) { - return app.getTravelHelper().getArticleByTitle(title, lang); + return app.getTravelHelper().getArticleByTitle(title, gpx.getRect(), lang); } return null; } @@ -506,8 +508,8 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { public void onClick(View v) { TrackActivity activity = getTrackActivity(); if (activity != null) { - WikivoyageArticleDialogFragment.showInstance(app, - activity.getSupportFragmentManager(), article.getRouteId(), article.getLang()); + WikivoyageArticleDialogFragment.showInstance(app, activity.getSupportFragmentManager(), + article.generateIdentifier(), article.getLang()); } } }; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/WikivoyageWebViewClient.java b/OsmAnd/src/net/osmand/plus/wikivoyage/WikivoyageWebViewClient.java index d5e678c61d..c2c42551e2 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/WikivoyageWebViewClient.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/WikivoyageWebViewClient.java @@ -14,6 +14,7 @@ import androidx.fragment.app.FragmentManager; import net.osmand.AndroidUtils; import net.osmand.GPXUtilities; +import net.osmand.GPXUtilities.WptPt; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.plus.OsmandApplication; @@ -22,6 +23,7 @@ import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.wikipedia.WikiArticleHelper; import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.plus.wikivoyage.explore.WikivoyageExploreActivity; import java.io.File; @@ -67,8 +69,8 @@ public class WikivoyageWebViewClient extends WebViewClient { if (url.contains(WIKIVOYAGE_DOMAIN) && isWebPage) { String lang = WikiArticleHelper.getLang(url); String articleName = WikiArticleHelper.getArticleNameFromUrl(url, lang); - String articleId = app.getTravelHelper().getArticleId(articleName, lang); - if (!articleId.isEmpty()) { + TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(articleName, lang); + if (articleId != null) { WikivoyageArticleDialogFragment.showInstance(app, fragmentManager, articleId, lang); } else { WikiArticleHelper.warnAboutExternalLoad(url, activity, nightMode); @@ -83,8 +85,8 @@ public class WikivoyageWebViewClient extends WebViewClient { WikiArticleHelper.warnAboutExternalLoad(url, activity, nightMode); } else if (url.startsWith(PREFIX_GEO)) { if (article != null) { - List points = article.getGpxFile().getPoints(); - GPXUtilities.WptPt gpxPoint = null; + List points = article.getGpxFile().getPoints(); + WptPt gpxPoint = null; String coordinates = url.replace(PREFIX_GEO, ""); double lat; double lon; @@ -96,7 +98,7 @@ public class WikivoyageWebViewClient extends WebViewClient { Log.w(TAG, e.getMessage(), e); return true; } - for (GPXUtilities.WptPt point : points) { + for (WptPt point : points) { if (point.getLatitude() == lat && point.getLongitude() == lon) { gpxPoint = point; break; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java index 784c1ee8d4..9303fb7b84 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java @@ -30,17 +30,18 @@ import net.osmand.AndroidUtils; import net.osmand.IndexConstants; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.TrackActivity; import net.osmand.plus.development.OsmandDevelopmentPlugin; import net.osmand.plus.helpers.FileNameTranslationHelper; +import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.wikipedia.WikiArticleBaseDialogFragment; import net.osmand.plus.wikipedia.WikiArticleHelper; import net.osmand.plus.wikivoyage.WikivoyageShowPicturesDialogFragment; import net.osmand.plus.wikivoyage.WikivoyageWebViewClient; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.plus.wikivoyage.data.TravelHelper; import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper; import net.osmand.util.Algorithms; @@ -58,15 +59,15 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme public static final String TAG = "WikivoyageArticleDialogFragment"; - private static final String ROUTE_ID_KEY = "route_id_key"; - private static final String LANGS_KEY = "langs_key"; - private static final String SELECTED_LANG_KEY = "selected_lang_key"; + private static final String ARTICLE_ID_KEY = "article_id"; + private static final String LANGS_KEY = "langs"; + private static final String SELECTED_LANG_KEY = "selected_lang"; private static final String EMPTY_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d4//"; private static final int MENU_ITEM_SHARE = 0; - private String routeId = ""; + private TravelArticleIdentifier articleId; private ArrayList langs; private String selectedLang; private TravelArticle article; @@ -192,15 +193,17 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme if (requestCode == WikivoyageArticleContentsFragment.SHOW_CONTENT_ITEM_REQUEST_CODE) { String link = data.getStringExtra(WikivoyageArticleContentsFragment.CONTENT_ITEM_LINK_KEY); String title = data.getStringExtra(WikivoyageArticleContentsFragment.CONTENT_ITEM_TITLE_KEY); - moveToAnchor(link, title); + if (title != null) { + moveToAnchor(link, title); + } } else if (requestCode == WikivoyageShowPicturesDialogFragment.SHOW_PICTURES_CHANGED_REQUEST_CODE) { updateWebSettings(); populateArticle(); } else if (requestCode == WikivoyageArticleNavigationFragment.OPEN_ARTICLE_REQUEST_CODE) { - String tripId = data.getStringExtra(WikivoyageArticleNavigationFragment.ROUTE_ID_KEY); + TravelArticleIdentifier articleId = data.getParcelableExtra(WikivoyageArticleNavigationFragment.ARTICLE_ID_KEY); String selectedLang = data.getStringExtra(WikivoyageArticleNavigationFragment.SELECTED_LANG_KEY); - if (!Algorithms.isEmpty(tripId) && !TextUtils.isEmpty(selectedLang)) { - this.routeId = tripId; + if (articleId != null && !TextUtils.isEmpty(selectedLang)) { + this.articleId = articleId; this.selectedLang = selectedLang; populateArticle(); } @@ -286,21 +289,21 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme @Override protected void populateArticle() { - if (Algorithms.isEmpty(routeId) || langs == null) { + if (articleId == null || langs == null) { Bundle args = getArguments(); if (args != null) { - routeId = args.getString(ROUTE_ID_KEY); + articleId = args.getParcelable(ARTICLE_ID_KEY); langs = args.getStringArrayList(LANGS_KEY); } } - if (Algorithms.isEmpty(routeId) || langs == null || langs.isEmpty()) { + if (articleId == null || langs == null || langs.isEmpty()) { return; } if (selectedLang == null) { selectedLang = langs.get(0); } articleToolbarText.setText(""); - article = getMyApplication().getTravelHelper().getArticleById(routeId, selectedLang); + article = getMyApplication().getTravelHelper().getArticleById(articleId, selectedLang); if (article == null) { return; } @@ -366,34 +369,34 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme } public static boolean showInstanceByTitle(@NonNull OsmandApplication app, - @NonNull FragmentManager fm, - @NonNull String title, - @NonNull String lang) { - String articleId = app.getTravelHelper().getArticleId(title, lang); + @NonNull FragmentManager fm, + @NonNull String title, + @NonNull String lang) { + TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(title, lang); return showInstance(app, fm, articleId, lang); } public static boolean showInstance(@NonNull OsmandApplication app, @NonNull FragmentManager fm, - @NonNull String routeId, + @NonNull TravelArticleIdentifier articleId, @Nullable String selectedLang) { - ArrayList langs = app.getTravelHelper().getArticleLangs(routeId); - return showInstance(fm, routeId, langs, selectedLang); + ArrayList langs = app.getTravelHelper().getArticleLangs(articleId); + return showInstance(fm, articleId, langs, selectedLang); } public static boolean showInstance(@NonNull FragmentManager fm, - String routeId, + @NonNull TravelArticleIdentifier articleId, @NonNull ArrayList langs) { - return showInstance(fm, routeId, langs, null); + return showInstance(fm, articleId, langs, null); } - public static boolean showInstance(@NonNull FragmentManager fm, - String routeId, - @NonNull ArrayList langs, - @Nullable String selectedLang) { + private static boolean showInstance(@NonNull FragmentManager fm, + @NonNull TravelArticleIdentifier articleId, + @NonNull ArrayList langs, + @Nullable String selectedLang) { try { Bundle args = new Bundle(); - args.putString(ROUTE_ID_KEY, routeId); + args.putParcelable(ARTICLE_ID_KEY, articleId); args.putStringArrayList(LANGS_KEY, langs); if (langs.contains(selectedLang)) { args.putString(SELECTED_LANG_KEY, selectedLang); @@ -416,7 +419,7 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme return; } WikivoyageArticleNavigationFragment.showInstance(fm, - WikivoyageArticleDialogFragment.this, routeId, selectedLang); + WikivoyageArticleDialogFragment.this, articleId, selectedLang); } } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java index 7f0d9e8c0e..960608a784 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java @@ -26,6 +26,7 @@ import net.osmand.plus.base.MenuBottomSheetDialogFragment; import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem; import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.plus.wikivoyage.data.WikivoyageSearchResult; import net.osmand.util.Algorithms; @@ -38,14 +39,14 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr public static final String TAG = WikivoyageArticleNavigationFragment.class.getSimpleName(); - public static final String ROUTE_ID_KEY = "route_id_key"; - public static final String SELECTED_LANG_KEY = "selected_lang_key"; + public static final String ARTICLE_ID_KEY = "article_id"; + public static final String SELECTED_LANG_KEY = "selected_lang"; public static final int OPEN_ARTICLE_REQUEST_CODE = 2; private static final long UNDEFINED = -1; - private String routeId = ""; + private TravelArticleIdentifier articleId; private String selectedLang; private TravelArticle article; private List parentsList; @@ -61,26 +62,27 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr if (savedInstanceState != null) { selectedLang = savedInstanceState.getString(SELECTED_LANG_KEY); - routeId = savedInstanceState.getString(ROUTE_ID_KEY); + articleId = savedInstanceState.getParcelable(ARTICLE_ID_KEY); } else { Bundle args = getArguments(); if (args != null) { selectedLang = args.getString(SELECTED_LANG_KEY); - routeId = args.getString(ROUTE_ID_KEY); + articleId = args.getParcelable(ARTICLE_ID_KEY); } } - if (Algorithms.isEmpty(routeId) || TextUtils.isEmpty(selectedLang)) { + if (articleId == null || TextUtils.isEmpty(selectedLang)) { return; } - article = getMyApplication().getTravelHelper().getArticleById(routeId, selectedLang); + article = getMyApplication().getTravelHelper().getArticleById(articleId, selectedLang); if (article == null) { return; } parentsList = new ArrayList<>(Arrays.asList(article.getAggregatedPartOf().split(","))); - Map> navigationMap = getMyApplication().getTravelHelper().getNavigationMap(article); + Map> navigationMap + = getMyApplication().getTravelHelper().getNavigationMap(article); items.add(new TitleItem(getString(R.string.shared_string_navigation))); @@ -102,7 +104,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { WikivoyageSearchResult articleItem = listAdapter.getArticleItem(groupPosition, childPosition); - sendResults(articleItem.getRouteId()); + sendResults(articleItem.getArticleId()); dismiss(); return true; } @@ -111,10 +113,10 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { WikivoyageSearchResult articleItem = (WikivoyageSearchResult) listAdapter.getGroup(groupPosition); - if (Algorithms.isEmpty(articleItem.getRouteId())) { + if (Algorithms.isEmpty(articleItem.getArticleRouteId())) { Toast.makeText(getContext(), R.string.wiki_article_not_found, Toast.LENGTH_LONG).show(); } else { - sendResults(articleItem.getRouteId()); + sendResults(articleItem.getArticleId()); dismiss(); } return true; @@ -134,7 +136,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putString(ROUTE_ID_KEY, routeId); + outState.putParcelable(ARTICLE_ID_KEY, articleId); outState.putString(SELECTED_LANG_KEY, selectedLang); } @@ -148,17 +150,17 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr return nightMode ? R.color.wikivoyage_bottom_bar_bg_dark : R.color.list_background_color_light; } - private void sendResults(String routeId) { - WikivoyageArticleDialogFragment.showInstance(getMyApplication(), getFragmentManager(), routeId, selectedLang); + private void sendResults(TravelArticleIdentifier articleId) { + WikivoyageArticleDialogFragment.showInstance(getMyApplication(), getFragmentManager(), articleId, selectedLang); } public static boolean showInstance(@NonNull FragmentManager fm, @Nullable Fragment targetFragment, - String routeId, + @NonNull TravelArticleIdentifier articleId, @NonNull String selectedLang) { try { Bundle args = new Bundle(); - args.putString(ROUTE_ID_KEY, routeId); + args.putParcelable(ARTICLE_ID_KEY, articleId); args.putString(SELECTED_LANG_KEY, selectedLang); WikivoyageArticleNavigationFragment fragment = new WikivoyageArticleNavigationFragment(); if (targetFragment != null) { @@ -204,7 +206,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public Object getChild(int groupPosition, int childPosititon) { - return getArticleItem(groupPosition, childPosititon).getArticleTitles().get(0); + return getArticleItem(groupPosition, childPosititon).getArticleTitle(); } @Override @@ -236,8 +238,8 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { WikivoyageSearchResult articleItem = getArticleItem(groupPosition, childPosition); - String childTitle = articleItem.getArticleTitles().get(0); - boolean selected = Algorithms.stringsEqual(routeId, articleItem.getRouteId()) || parentsList.contains(childTitle); + String childTitle = articleItem.getArticleTitle(); + boolean selected = articleItem.getArticleId().equals(articleId) || parentsList.contains(childTitle); if (convertView == null) { convertView = LayoutInflater.from(context) @@ -263,7 +265,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public View getGroupView(final int groupPosition, final boolean isExpanded, View convertView, ViewGroup parent) { - String groupTitle = ((WikivoyageSearchResult) getGroup(groupPosition)).getArticleTitles().get(0); + String groupTitle = ((WikivoyageSearchResult) getGroup(groupPosition)).getArticleTitle(); boolean selected = parentsList.contains(groupTitle) || article.getTitle().equals(groupTitle); if (convertView == null) { convertView = LayoutInflater.from(context) diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java index a559f3cf0f..ed62fac337 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java @@ -1,5 +1,7 @@ package net.osmand.plus.wikivoyage.data; +import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -7,13 +9,21 @@ import androidx.annotation.Nullable; import androidx.annotation.Size; import net.osmand.GPXUtilities.GPXFile; +import net.osmand.Location; +import net.osmand.aidl.search.SearchResult; +import net.osmand.data.LatLon; +import net.osmand.util.Algorithms; +import net.osmand.util.MapUtils; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; +import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Objects; public class TravelArticle { @@ -21,19 +31,29 @@ public class TravelArticle { private static final String THUMB_PREFIX = "320px-"; private static final String REGULAR_PREFIX = "1280px-";//1280, 1024, 800 + File file; String title; String content; String isPartOf; - double lat; - double lon; + double lat = Double.NaN; + double lon = Double.NaN; String imageTitle; GPXFile gpxFile; String routeId; + String routeSource; long originalId; String lang; String contentsJson; String aggregatedPartOf; + @NonNull + public TravelArticleIdentifier generateIdentifier() { + return new TravelArticleIdentifier(this); + } + + public File getFile() { + return file; + } public String getTitle() { return title; @@ -67,6 +87,10 @@ public class TravelArticle { return routeId; } + public String getRouteSource() { + return routeSource; + } + public long getOriginalId() { return originalId; } @@ -126,4 +150,92 @@ public class TravelArticle { String md5 = new String(Hex.encodeHex(DigestUtils.md5(s))); return new String[]{md5.substring(0, 1), md5.substring(0, 2)}; } + + public static class TravelArticleIdentifier implements Parcelable { + @Nullable File file; + double lat; + double lon; + @Nullable String title; + @Nullable String routeId; + @Nullable String routeSource; + + public static final Creator CREATOR = new Creator() { + @Override + public TravelArticleIdentifier createFromParcel(Parcel in) { + return new TravelArticleIdentifier(in); + } + + @Override + public TravelArticleIdentifier[] newArray(int size) { + return new TravelArticleIdentifier[size]; + } + }; + + private TravelArticleIdentifier(@NonNull Parcel in) { + readFromParcel(in); + } + + private TravelArticleIdentifier(@NonNull TravelArticle article) { + file = article.file; + lat = article.lat; + lon = article.lon; + title = article.title; + routeId = article.routeId; + routeSource = article.routeSource; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeDouble(lat); + out.writeDouble(lon); + out.writeString(title); + out.writeString(routeId); + out.writeString(routeSource); + out.writeString(file != null ? file.getAbsolutePath() : null); + } + + private void readFromParcel(Parcel in) { + lat = in.readDouble(); + lon = in.readDouble(); + title = in.readString(); + routeId = in.readString(); + routeSource = in.readString(); + String filePath = in.readString(); + if (!Algorithms.isEmpty(filePath)) { + file = new File(filePath); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TravelArticleIdentifier that = (TravelArticleIdentifier) o; + return areLatLonEqual(that.lat, that.lon, lat, lon) && + Algorithms.objectEquals(file, that.file) && + Algorithms.stringsEqual(title, that.title) && + Algorithms.stringsEqual(routeId, that.routeId) && + Algorithms.stringsEqual(routeSource, that.routeSource); + } + + @Override + public int hashCode() { + return Algorithms.hash(file, lat, lon, title, routeId, routeSource); + } + + private static boolean areLatLonEqual(double lat1, double lon1, double lat2, double lon2) { + boolean latEqual = (Double.isNaN(lat1) && Double.isNaN(lat2)) || Math.abs(lat1 - lat2) < 0.00001; + boolean lonEqual = (Double.isNaN(lon1) && Double.isNaN(lon2)) || Math.abs(lon1 - lon2) < 0.00001; + return latEqual && lonEqual; + } + } } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java index d50d05e42b..e9e392a9e3 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java @@ -16,10 +16,12 @@ import net.osmand.OsmAndCollator; import net.osmand.PlatformUtil; import net.osmand.data.Amenity; import net.osmand.data.LatLon; +import net.osmand.data.QuadRect; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.api.SQLiteAPI.SQLiteConnection; import net.osmand.plus.api.SQLiteAPI.SQLiteCursor; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; @@ -245,12 +247,15 @@ public class TravelDbHelper implements TravelHelper { if (cursor != null) { if (cursor.moveToFirst()) { do { - WikivoyageSearchResult rs = new WikivoyageSearchResult(); - rs.routeId = cursor.getLong(0) + ""; - rs.articleTitles.add(cursor.getString(1)); - rs.langs.add(cursor.getString(2)); - rs.isPartOf.add(cursor.getString(3)); - rs.imageTitle = cursor.getString(4); + String routeId = cursor.getLong(0) + ""; + String articleTitle = cursor.getString(1); + String lang = cursor.getString(2); + String isPartOf = cursor.getString(3); + String imageTitle = cursor.getString(4); + List langs = new ArrayList<>(); + langs.add(lang); + WikivoyageSearchResult rs = new WikivoyageSearchResult(routeId, articleTitle, + isPartOf, imageTitle, langs); res.add(rs); } while (cursor.moveToNext()); } @@ -386,12 +391,12 @@ public class TravelDbHelper implements TravelHelper { Collections.sort(list, new Comparator() { @Override public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) { - boolean c1 = CollatorStringMatcher.cmatches(collator, searchQuery, o1.articleTitles.get(0), + boolean c1 = CollatorStringMatcher.cmatches(collator, searchQuery, o1.getArticleTitle(), StringMatcherMode.CHECK_ONLY_STARTS_WITH); - boolean c2 = CollatorStringMatcher.cmatches(collator, searchQuery, o2.articleTitles.get(0), + boolean c2 = CollatorStringMatcher.cmatches(collator, searchQuery, o2.getArticleTitle(), StringMatcherMode.CHECK_ONLY_STARTS_WITH); if (c1 == c2) { - return collator.compare(o1.articleTitles.get(0), o2.articleTitles.get(0)); + return collator.compare(o1.getArticleTitle(), o2.getArticleTitle()); } else if (c1) { return -1; } else if (c2) { @@ -421,29 +426,28 @@ public class TravelDbHelper implements TravelHelper { }); } } - private Collection groupSearchResultsByRouteId(List res) { String baseLng = application.getLanguage(); Map wikivoyage = new HashMap<>(); for (WikivoyageSearchResult rs : res) { - WikivoyageSearchResult prev = wikivoyage.get(rs.routeId); + WikivoyageSearchResult prev = wikivoyage.get(rs.getArticleRouteId()); if (prev != null) { - int insInd = prev.langs.size(); + boolean matchLang = false; if (rs.langs.get(0).equals(baseLng)) { - insInd = 0; + matchLang = true; } else if (rs.langs.get(0).equals("en")) { if (!prev.langs.get(0).equals(baseLng)) { - insInd = 0; - } else { - insInd = 1; + matchLang = true; } } - prev.articleTitles.add(insInd, rs.articleTitles.get(0)); - prev.langs.add(insInd, rs.langs.get(0)); - prev.isPartOf.add(insInd, rs.isPartOf.get(0)); + if (matchLang) { + prev.articleId.title = rs.getArticleTitle(); + prev.isPartOf = rs.getIsPartOf(); + } + prev.langs.add(matchLang ? 0 : 1, rs.langs.get(0)); } else { - wikivoyage.put(rs.routeId, rs); + wikivoyage.put(rs.getArticleRouteId(), rs); } } return wikivoyage.values(); @@ -451,12 +455,11 @@ public class TravelDbHelper implements TravelHelper { @NonNull @Override - public LinkedHashMap> getNavigationMap( - @NonNull final TravelArticle article) { + public Map> getNavigationMap(@NonNull final TravelArticle article) { String lang = article.getLang(); String title = article.getTitle(); if (TextUtils.isEmpty(lang) || TextUtils.isEmpty(title)) { - return new LinkedHashMap<>(); + return Collections.emptyMap(); } String[] parts = null; if (!TextUtils.isEmpty(article.getAggregatedPartOf())) { @@ -498,20 +501,20 @@ public class TravelDbHelper implements TravelHelper { SQLiteCursor cursor = conn.rawQuery(query.toString(), params.toArray(new String[0])); if (cursor != null && cursor.moveToFirst()) { do { - WikivoyageSearchResult rs = new WikivoyageSearchResult(); - rs.routeId = cursor.getLong(0) + ""; - rs.articleTitles.add(cursor.getString(1)); - rs.langs.add(cursor.getString(2)); - rs.isPartOf.add(cursor.getString(3)); - List l = navMap.get(rs.isPartOf.get(0)); + String routeId = cursor.getLong(0) + ""; + String articleTitle = cursor.getString(1); + String articleLang = cursor.getString(2); + String isPartOf = cursor.getString(3); + WikivoyageSearchResult rs = new WikivoyageSearchResult(routeId, articleTitle, + isPartOf, null, Collections.singletonList(articleLang)); + List l = navMap.get(rs.isPartOf); if (l == null) { l = new ArrayList<>(); - navMap.put(rs.isPartOf.get(0), l); + navMap.put(rs.isPartOf, l); } l.add(rs); - String key = rs.getArticleTitles().get(0); - if (headers != null && headers.contains(key)) { - headerObjs.put(key, rs); + if (headers != null && headers.contains(articleTitle)) { + headerObjs.put(articleTitle, rs); } } while (cursor.moveToNext()); } @@ -527,12 +530,10 @@ public class TravelDbHelper implements TravelHelper { Collections.sort(results, new Comparator() { @Override public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) { - return collator.compare(o1.articleTitles.get(0), o2.articleTitles.get(0)); + return collator.compare(o1.getArticleTitle(), o2.getArticleTitle()); } }); - WikivoyageSearchResult emptyResult = new WikivoyageSearchResult(); - emptyResult.articleTitles.add(header); - emptyResult.routeId = ""; + WikivoyageSearchResult emptyResult = new WikivoyageSearchResult("", header, null, null, null); searchResult = searchResult != null ? searchResult : emptyResult; res.put(searchResult, results); } @@ -542,9 +543,10 @@ public class TravelDbHelper implements TravelHelper { @Override @Nullable - public TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang) { + public TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) { TravelArticle res = null; SQLiteConnection conn = openConnection(); + String routeId = articleId.routeId; if (conn != null && !Algorithms.isEmpty(routeId)) { SQLiteCursor cursor = conn.rawQuery(ARTICLES_TABLE_SELECT + " WHERE " + ARTICLES_COL_TRIP_ID + " = ? AND " + ARTICLES_COL_LANG + " = ?", new String[] { routeId, lang }); @@ -558,9 +560,21 @@ public class TravelDbHelper implements TravelHelper { return res; } - @Override @Nullable - public TravelArticle getArticleByTitle(@NonNull String title, @NonNull String lang) { + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) { + return getArticleByTitle(title, new QuadRect(), lang); + } + + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang) { + return getArticleByTitle(title, new QuadRect(), lang); + } + + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect, @NonNull final String lang) { TravelArticle res = null; SQLiteConnection conn = openConnection(); if (conn != null) { @@ -576,33 +590,32 @@ public class TravelDbHelper implements TravelHelper { return res; } - @NonNull + @Nullable @Override - public String getArticleId(@NonNull String title, @NonNull String lang) { - String res = ""; + public TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang) { + TravelArticle article = null; SQLiteConnection conn = openConnection(); if (conn != null) { - SQLiteCursor cursor = conn.rawQuery("SELECT " + ARTICLES_COL_TRIP_ID + " FROM " - + ARTICLES_TABLE_NAME + " WHERE " + ARTICLES_COL_TITLE + " = ? AND " + SQLiteCursor cursor = conn.rawQuery(ARTICLES_TABLE_SELECT + " WHERE " + ARTICLES_COL_TITLE + " = ? AND " + ARTICLES_COL_LANG + " = ?", new String[]{title, lang}); if (cursor != null) { if (cursor.moveToFirst()) { - res = cursor.getLong(0) + ""; + article = readArticle(cursor); } cursor.close(); } } - return res; + return article != null ? article.generateIdentifier() : null; } @NonNull @Override - public ArrayList getArticleLangs(@NonNull String routeId) { + public ArrayList getArticleLangs(@NonNull TravelArticleIdentifier articleId) { ArrayList res = new ArrayList<>(); SQLiteConnection conn = openConnection(); if (conn != null) { SQLiteCursor cursor = conn.rawQuery("SELECT " + ARTICLES_COL_LANG + " FROM " + ARTICLES_TABLE_NAME - + " WHERE " + ARTICLES_COL_TRIP_ID + " = ?", new String[]{routeId}); + + " WHERE " + ARTICLES_COL_TRIP_ID + " = ?", new String[]{articleId.routeId}); if (cursor != null) { if (cursor.moveToFirst()) { String baseLang = application.getLanguage(); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java index b55c398817..af60514e73 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java @@ -3,6 +3,10 @@ package net.osmand.plus.wikivoyage.data; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import net.osmand.data.LatLon; +import net.osmand.data.QuadRect; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; + import java.io.File; import java.util.ArrayList; import java.util.List; @@ -28,16 +32,22 @@ public interface TravelHelper { Map> getNavigationMap(@NonNull final TravelArticle article); @Nullable - TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang); + TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang); @Nullable - TravelArticle getArticleByTitle(@NonNull String title, @NonNull String lang); + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang); + + @Nullable + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang); + + @Nullable + TravelArticle getArticleByTitle(@NonNull String title, @NonNull QuadRect rect, @NonNull String lang); + + @Nullable + TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang); @NonNull - String getArticleId(@NonNull String title, @NonNull String lang); - - @NonNull - ArrayList getArticleLangs(@NonNull String routeId); + ArrayList getArticleLangs(@NonNull TravelArticleIdentifier articleId); @NonNull String getGPXName(@NonNull final TravelArticle article); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java index e1dc65ebfd..842c2058e3 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java @@ -1,10 +1,11 @@ package net.osmand.plus.wikivoyage.data; +import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import net.osmand.Collator; -import net.osmand.CollatorStringMatcher; import net.osmand.GPXUtilities; import net.osmand.GPXUtilities.GPXFile; import net.osmand.IndexConstants; @@ -13,10 +14,13 @@ import net.osmand.PlatformUtil; import net.osmand.ResultMatcher; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter; +import net.osmand.binary.BinaryMapIndexReader.SearchRequest; import net.osmand.data.Amenity; import net.osmand.data.LatLon; +import net.osmand.data.QuadRect; import net.osmand.osm.PoiCategory; import net.osmand.plus.OsmandApplication; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; @@ -25,33 +29,39 @@ import org.apache.commons.logging.Log; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; - -import static net.osmand.CollatorStringMatcher.StringMatcherMode.CHECK_EQUALS_FROM_SPACE; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; public class TravelObfHelper implements TravelHelper { private static final Log LOG = PlatformUtil.getLog(TravelObfHelper.class); public static final String ROUTE_ARTICLE = "route_article"; - public static final int SEARCH_RADIUS = 100000; + public static final int POPULAR_ARTICLES_SEARCH_RADIUS = 100000; + public static final int ARTICLE_SEARCH_RADIUS = 50000; + public static final int MAX_POPULAR_ARTICLES_COUNT = 100; private final OsmandApplication app; private final Collator collator; private List popularArticles = new ArrayList<>(); - private final Map cachedArticles; + private Map> cachedArticles = new ConcurrentHashMap<>(); private final TravelLocalDataHelper localDataHelper; public TravelObfHelper(OsmandApplication app) { this.app = app; collator = OsmAndCollator.primaryCollator(); localDataHelper = new TravelLocalDataHelper(app); - cachedArticles = new HashMap<>(); } @Override @@ -71,22 +81,23 @@ public class TravelObfHelper implements TravelHelper { @NonNull public List loadPopularArticles() { - String language = app.getLanguage(); + String lang = app.getLanguage(); List popularArticles = new ArrayList<>(); - for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) { + for (BinaryMapIndexReader reader : getReaders()) { try { final LatLon location = app.getMapViewTrackingUtilities().getMapLocation(); - BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( - location, SEARCH_RADIUS, -1, getSearchRouteArticleFilter(), null); - List amenities = travelBookReader.searchPoi(req); + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( + location, POPULAR_ARTICLES_SEARCH_RADIUS, -1, getSearchRouteArticleFilter(), null); + List amenities = reader.searchPoi(req); if (amenities.size() > 0) { - for (Amenity a : amenities) { - if (!Algorithms.isEmpty(a.getName(language))) { - TravelArticle article = readArticle(a, language); - popularArticles.add(article); - cachedArticles.put(article.routeId, article); - if (popularArticles.size() >= 100) { - break; + for (Amenity amenity : amenities) { + if (!Algorithms.isEmpty(amenity.getName(lang))) { + TravelArticle article = cacheTravelArticles(reader.getFile(), amenity, lang); + if (article != null) { + popularArticles.add(article); + if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) { + break; + } } } } @@ -102,13 +113,25 @@ public class TravelObfHelper implements TravelHelper { }); } } catch (Exception e) { - LOG.error(e.getMessage()); + LOG.error(e.getMessage(), e); } } this.popularArticles = popularArticles; return popularArticles; } + @Nullable + private TravelArticle cacheTravelArticles(File file, Amenity amenity, String lang) { + TravelArticle article = null; + Map articles = readArticles(file, amenity); + if (!Algorithms.isEmpty(articles)) { + TravelArticleIdentifier newArticleId = articles.values().iterator().next().generateIdentifier(); + cachedArticles.put(newArticleId, articles); + article = getCachedArticle(newArticleId, lang); + } + return article; + } + SearchPoiTypeFilter getSearchRouteArticleFilter() { return new SearchPoiTypeFilter() { @Override @@ -123,23 +146,29 @@ public class TravelObfHelper implements TravelHelper { }; } - private TravelArticle readArticle(@NonNull Amenity amenity, @Nullable String lang) { - TravelArticle res = new TravelArticle(); - String title = Algorithms.isEmpty(amenity.getName(lang)) ? amenity.getName() : amenity.getName(lang); - if (Algorithms.isEmpty(title)) { - Map namesMap = amenity.getNamesMap(true); - if (!namesMap.isEmpty()) { - lang = namesMap.keySet().iterator().next(); - title = amenity.getName(lang); - } + @NonNull + private Map readArticles(@NonNull File file, @NonNull Amenity amenity) { + Map articles = new HashMap<>(); + Set langs = getLanguages(amenity); + for (String lang : langs) { + articles.put(lang, readArticle(file, amenity, lang)); } - res.title = title; + return articles; + } + + @NonNull + private TravelArticle readArticle(@NonNull File file, @NonNull Amenity amenity, @Nullable String lang) { + TravelArticle res = new TravelArticle(); + res.file = file; + String title = amenity.getName(lang); + res.title = Algorithms.isEmpty(title) ? amenity.getName() : title; res.content = amenity.getDescription(lang); res.isPartOf = emptyIfNull(amenity.getTagContent(Amenity.IS_PART, lang)); res.lat = amenity.getLocation().getLatitude(); res.lon = amenity.getLocation().getLongitude(); - res.imageTitle = emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, lang)); - res.routeId = getRouteId(amenity); + res.imageTitle = emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, null)); + res.routeId = emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null)); + res.routeSource = emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null)); res.originalId = 0; res.lang = lang; res.contentsJson = emptyIfNull(amenity.getTagContent(Amenity.CONTENT_JSON, lang)); @@ -151,85 +180,72 @@ public class TravelObfHelper implements TravelHelper { return text == null ? "" : text; } - private String getRouteId(Amenity amenity) { - return amenity.getTagContent(Amenity.ROUTE_ID, null); - } - @Override public boolean isAnyTravelBookPresent() { - return !Algorithms.isEmpty(getTravelBookReaders()); + return !Algorithms.isEmpty(getReaders()); } @NonNull @Override public List search(@NonNull String searchQuery) { List res = new ArrayList<>(); - List searchObjects = null; - for (BinaryMapIndexReader reader : app.getResourceManager().getTravelRepositories()) { + Map> amenityMap = new HashMap<>(); + for (BinaryMapIndexReader reader : getReaders()) { try { - BinaryMapIndexReader.SearchRequest searchRequest = BinaryMapIndexReader. - buildSearchPoiRequest(0, 0, searchQuery, + SearchRequest searchRequest = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, searchQuery, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchRouteArticleFilter(), null, null); - searchObjects = reader.searchPoiByName(searchRequest); + List amenities = reader.searchPoiByName(searchRequest); + if (!Algorithms.isEmpty(amenities)) { + amenityMap.put(reader.getFile(), amenities); + } } catch (IOException e) { - LOG.error(e); + LOG.error(e.getMessage(), e); } } - if (!Algorithms.isEmpty(searchObjects)) { - String baseLng = app.getLanguage(); - for (Amenity obj : searchObjects) { - WikivoyageSearchResult r = new WikivoyageSearchResult(); - TravelArticle article = readArticle(obj, baseLng); - r.articleTitles = new ArrayList<>(Collections.singletonList(article.title)); - r.imageTitle = article.imageTitle; - r.routeId = article.routeId; - r.isPartOf = new ArrayList<>(Collections.singletonList(article.isPartOf)); - r.langs = new ArrayList<>(Collections.singletonList(baseLng)); - res.add(r); - cachedArticles.put(article.routeId, article); + if (!Algorithms.isEmpty(amenityMap)) { + String appLang = app.getLanguage(); + for (Entry> entry : amenityMap.entrySet()) { + File file = entry.getKey(); + for (Amenity amenity : entry.getValue()) { + Set nameLangs = getLanguages(amenity); + if (nameLangs.contains(appLang)) { + TravelArticle article = readArticle(file, amenity, appLang); + WikivoyageSearchResult r = new WikivoyageSearchResult(article, new ArrayList<>(nameLangs)); + res.add(r); + } + } } - res = new ArrayList<>(groupSearchResultsByRouteId(res)); sortSearchResults(res); } return res; } - private void sortSearchResults(@NonNull List list) { - Collections.sort(list, new Comparator() { - - @Override - public int compare(WikivoyageSearchResult res1, WikivoyageSearchResult res2) { - return collator.compare(res1.articleTitles.get(0), res2.articleTitles.get(0)); - } - }); - } - - @NonNull - private Collection groupSearchResultsByRouteId(@NonNull List res) { - String baseLng = app.getLanguage(); - Map wikivoyage = new HashMap<>(); - for (WikivoyageSearchResult rs : res) { - WikivoyageSearchResult prev = wikivoyage.get(rs.routeId); - if (prev != null) { - int insInd = prev.langs.size(); - if (rs.langs.get(0).equals(baseLng)) { - insInd = 0; - } else if (rs.langs.get(0).equals("en")) { - if (!prev.langs.get(0).equals(baseLng)) { - insInd = 0; - } else { - insInd = 1; - } + private Set getLanguages(@NonNull Amenity amenity) { + Set langs = new HashSet<>(); + String descrStart = Amenity.DESCRIPTION + ":"; + String partStart = Amenity.IS_PART + ":"; + for (String infoTag : amenity.getAdditionalInfoKeys()) { + if (infoTag.startsWith(descrStart)) { + if (infoTag.length() > descrStart.length()) { + langs.add(infoTag.substring(descrStart.length())); + } + } else if (infoTag.startsWith(partStart)) { + if (infoTag.length() > partStart.length()) { + langs.add(infoTag.substring(partStart.length())); } - prev.articleTitles.add(insInd, rs.articleTitles.get(0)); - prev.langs.add(insInd, rs.langs.get(0)); - prev.isPartOf.add(insInd, rs.isPartOf.get(0)); - } else { - wikivoyage.put(rs.routeId, rs); } } - return wikivoyage.values(); + return langs; + } + + private void sortSearchResults(@NonNull List list) { + Collections.sort(list, new Comparator() { + @Override + public int compare(WikivoyageSearchResult res1, WikivoyageSearchResult res2) { + return collator.compare(res1.articleId.title, res2.articleId.title); + } + }); } @NonNull @@ -241,71 +257,153 @@ public class TravelObfHelper implements TravelHelper { @NonNull @Override public Map> getNavigationMap(@NonNull final TravelArticle article) { - return Collections.emptyMap(); - } - - @Override - public TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang) { - TravelArticle article = cachedArticles.get(routeId); - if (article != null) { - return article; - } else { - return getArticleByIdFromTravelBooks(routeId, lang); + final String lang = article.getLang(); + final String title = article.getTitle(); + if (TextUtils.isEmpty(lang) || TextUtils.isEmpty(title)) { + return Collections.emptyMap(); } - } - - private TravelArticle getArticleByIdFromTravelBooks(final String routeId, final String lang) { - TravelArticle article = null; - final List amenities = new ArrayList<>(); - for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) { + final String[] parts; + if (!TextUtils.isEmpty(article.getAggregatedPartOf())) { + String[] originalParts = article.getAggregatedPartOf().split(","); + if (originalParts.length > 1) { + parts = new String[originalParts.length]; + for (int i = 0; i < originalParts.length; i++) { + parts[i] = originalParts[originalParts.length - i - 1]; + } + } else { + parts = originalParts; + } + } else { + parts = null; + } + Map> navMap = new HashMap<>(); + Set headers = new LinkedHashSet(); + Map headerObjs = new HashMap<>(); + Map> amenityMap = new HashMap<>(); + for (BinaryMapIndexReader reader : getReaders()) { try { - BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( - 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, -1, getSearchRouteArticleFilter(), - new ResultMatcher() { - boolean done = false; + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, + Integer.MAX_VALUE, 0, Integer.MAX_VALUE, -1, getSearchRouteArticleFilter(), new ResultMatcher() { @Override public boolean publish(Amenity amenity) { - if (getRouteId(amenity).equals(routeId)) { - amenities.add(amenity); - done = true; + String isPartOf = amenity.getTagContent(Amenity.IS_PART, lang); + if (Algorithms.stringsEqual(title, isPartOf)) { + return true; + } else if (parts != null && parts.length > 0) { + String title = amenity.getName(lang); + title = Algorithms.isEmpty(title) ? amenity.getName() : title; + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if (i == 0 && Algorithms.stringsEqual(part, title) || Algorithms.stringsEqual(part, isPartOf)) { + return true; + } + } } return false; } @Override public boolean isCancelled() { - return done; + return false; } }); - - travelBookReader.searchPoi(req); - } catch (IOException e) { - LOG.error(e.getMessage()); - } - if (!amenities.isEmpty()) { - article = readArticle(amenities.get(0), lang); - cachedArticles.put(article.routeId, article); + List amenities = reader.searchPoi(req); + if (!Algorithms.isEmpty(amenities)) { + amenityMap.put(reader.getFile(), amenities); + } + } catch (Exception e) { + LOG.error(e.getMessage(), e); } } - return article; + if (parts != null && parts.length > 0) { + headers.addAll(Arrays.asList(parts)); + headers.add(title); + } + if (!Algorithms.isEmpty(amenityMap)) { + for (Entry> entry : amenityMap.entrySet()) { + File file = entry.getKey(); + for (Amenity amenity : entry.getValue()) { + Set nameLangs = getLanguages(amenity); + if (nameLangs.contains(lang)) { + TravelArticle a = readArticle(file, amenity, lang); + WikivoyageSearchResult rs = new WikivoyageSearchResult(a, new ArrayList<>(nameLangs)); + List l = navMap.get(rs.isPartOf); + if (l == null) { + l = new ArrayList<>(); + navMap.put(rs.isPartOf, l); + } + l.add(rs); + if (headers != null && headers.contains(a.getTitle())) { + headerObjs.put(a.getTitle(), rs); + } + } + } + } + } + + LinkedHashMap> res = new LinkedHashMap<>(); + for (String header : headers) { + WikivoyageSearchResult searchResult = headerObjs.get(header); + List results = navMap.get(header); + if (results != null) { + Collections.sort(results, new Comparator() { + @Override + public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) { + return collator.compare(o1.getArticleTitle(), o2.getArticleTitle()); + } + }); + WikivoyageSearchResult emptyResult = new WikivoyageSearchResult("", header, null, null, null); + searchResult = searchResult != null ? searchResult : emptyResult; + res.put(searchResult, results); + } + } + return res; + } + + @Override + public TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) { + TravelArticle article = getCachedArticle(articleId, lang); + return article == null ? findArticleById(articleId, lang) : article; } @Nullable - @Override - public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) { + private TravelArticle getCachedArticle(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) { + TravelArticle article = null; + Map articles = cachedArticles.get(articleId); + if (articles != null) { + if (Algorithms.isEmpty(lang)) { + Collection ac = articles.values(); + if (!ac.isEmpty()) { + article = ac.iterator().next(); + } + } else { + article = articles.get(lang); + if (article == null) { + article = articles.get(""); + } + } + } + return article == null ? findArticleById(articleId, lang) : article; + } + + private TravelArticle findArticleById(@NonNull final TravelArticleIdentifier articleId, final String lang) { TravelArticle article = null; final List amenities = new ArrayList<>(); - for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) { + for (BinaryMapIndexReader reader : getReaders()) { try { - BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( - 0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchRouteArticleFilter(), - new ResultMatcher() { + if (articleId.file != null && !articleId.file.equals(reader.getFile())) { + continue; + } + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, + Algorithms.emptyIfNull(articleId.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, + getSearchRouteArticleFilter(), new ResultMatcher() { boolean done = false; @Override public boolean publish(Amenity amenity) { - if (CollatorStringMatcher.cmatches(collator, title, amenity.getName(lang), CHECK_EQUALS_FROM_SPACE)) { + if (Algorithms.stringsEqual(articleId.routeId, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null))) + && Algorithms.stringsEqual(articleId.routeSource, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null)))) { amenities.add(amenity); done = true; } @@ -318,19 +416,74 @@ public class TravelObfHelper implements TravelHelper { } }, null); - travelBookReader.searchPoiByName(req); + if (!Double.isNaN(articleId.lat)) { + req.setBBoxRadius(articleId.lat, articleId.lon, ARTICLE_SEARCH_RADIUS); + if (!Algorithms.isEmpty(articleId.title)) { + reader.searchPoiByName(req); + } else { + reader.searchPoi(req); + } + } else { + reader.searchPoi(req); + } } catch (IOException e) { LOG.error(e.getMessage()); } if (!amenities.isEmpty()) { - article = readArticle(amenities.get(0), lang); - cachedArticles.put(article.routeId, article); + article = cacheTravelArticles(reader.getFile(), amenities.get(0), lang); } } return article; } - private List getTravelBookReaders() { + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) { + return getArticleByTitle(title, new QuadRect(), lang); + } + + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang) { + QuadRect rect = latLon != null ? MapUtils.calculateLatLonBbox(latLon.getLatitude(), latLon.getLongitude(), ARTICLE_SEARCH_RADIUS) : new QuadRect(); + return getArticleByTitle(title, rect, lang); + } + + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect, @NonNull final String lang) { + TravelArticle article = null; + List amenities = null; + int x = 0; + int y = 0; + int left = 0; + int right = Integer.MAX_VALUE; + int top = 0; + int bottom = Integer.MAX_VALUE; + if (rect.height() > 0 && rect.width() > 0) { + x = (int) rect.centerX(); + y = (int) rect.centerY(); + left = (int) rect.left; + right = (int) rect.right; + top = (int) rect.top; + bottom = (int) rect.bottom; + } + for (BinaryMapIndexReader reader : getReaders()) { + try { + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( + x, y, title, left, right, top, bottom, getSearchRouteArticleFilter(), null, null); + amenities = reader.searchPoiByName(req); + } catch (IOException e) { + LOG.error(e.getMessage()); + } + if (!Algorithms.isEmpty(amenities)) { + article = cacheTravelArticles(reader.getFile(), amenities.get(0), lang); + } + } + return article; + } + + private List getReaders() { if (!app.isApplicationInitializing()) { return app.getResourceManager().getTravelRepositories(); } else { @@ -338,14 +491,16 @@ public class TravelObfHelper implements TravelHelper { } } - @NonNull + @Nullable @Override - public String getArticleId(@NonNull String title, @NonNull String lang) { + public TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang) { TravelArticle a = null; - for (TravelArticle article : cachedArticles.values()) { - if (article.getTitle().equals(title)) { - a = article; - break; + for (Map articles : cachedArticles.values()) { + for (TravelArticle article : articles.values()) { + if (article.getTitle().equals(title)) { + a = article; + break; + } } } if (a == null) { @@ -354,17 +509,18 @@ public class TravelObfHelper implements TravelHelper { a = article; } } - return a != null && a.getRouteId() != null ? a.getRouteId() : ""; + return a != null ? a.generateIdentifier() : null; } @NonNull @Override - public ArrayList getArticleLangs(@NonNull String routeId) { + public ArrayList getArticleLangs(@NonNull TravelArticleIdentifier articleId) { ArrayList res = new ArrayList<>(); - res.add("en"); - for (TravelArticle article : popularArticles) { - if (article.getRouteId().equals(routeId)) { - res.add(article.getLang()); + TravelArticle article = getArticleById(articleId, ""); + if (article != null) { + Map articles = cachedArticles.get(articleId); + if (articles != null) { + res.addAll(articles.keySet()); } } return res; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchResult.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchResult.java index 7f3b7bfd49..5482bfee6c 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchResult.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchResult.java @@ -1,5 +1,9 @@ package net.osmand.plus.wikivoyage.data; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.util.Algorithms; import java.util.ArrayList; @@ -9,25 +13,52 @@ public class WikivoyageSearchResult { private static final int SHOW_LANGS = 3; - String routeId; - List articleTitles = new ArrayList<>(); - List langs = new ArrayList<>(); - List isPartOf = new ArrayList<>(); - String imageTitle; + TravelArticleIdentifier articleId; - public String getRouteId() { - return routeId; + String imageTitle; + String isPartOf; + + List langs = new ArrayList<>(); + + public WikivoyageSearchResult(@NonNull TravelArticle article, @Nullable List langs) { + articleId = article.generateIdentifier(); + imageTitle = article.imageTitle; + isPartOf = article.isPartOf; + if (langs != null) { + this.langs = langs; + } } - public List getArticleTitles() { - return articleTitles; + public WikivoyageSearchResult(String routeId, String articleTitle, String isPartOf, String imageTitle, @Nullable List langs) { + TravelArticle article = new TravelArticle(); + article.routeId = routeId; + article.title = articleTitle; + + this.articleId = article.generateIdentifier(); + this.imageTitle = imageTitle; + this.isPartOf = isPartOf; + if (langs != null) { + this.langs = langs; + } + } + + public TravelArticleIdentifier getArticleId() { + return articleId; + } + + public String getArticleTitle() { + return articleId.title; + } + + public String getArticleRouteId() { + return articleId.routeId; } public List getLangs() { return langs; } - public List getIsPartOf() { + public String getIsPartOf() { return isPartOf; } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesTabFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesTabFragment.java index 3bcb969f47..617ecf2d46 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesTabFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesTabFragment.java @@ -50,7 +50,7 @@ public class SavedArticlesTabFragment extends BaseOsmAndFragment implements Trav public void openArticle(TravelArticle article) { FragmentManager fm = getFragmentManager(); if (fm != null) { - WikivoyageArticleDialogFragment.showInstanceByTitle(app, fm, article.getTitle(), article.getLang()); + WikivoyageArticleDialogFragment.showInstance(app, fm, article.generateIdentifier(), article.getLang()); } } }); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/WikivoyageExploreActivity.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/WikivoyageExploreActivity.java index 45047e2e1c..202c834d8d 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/WikivoyageExploreActivity.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/WikivoyageExploreActivity.java @@ -37,6 +37,7 @@ import net.osmand.plus.download.DownloadIndexesThread.DownloadEvents; import net.osmand.plus.wikipedia.WikiArticleHelper; import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.plus.wikivoyage.data.TravelHelper; import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper; import net.osmand.plus.wikivoyage.search.WikivoyageSearchDialogFragment; @@ -50,8 +51,8 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv TravelLocalDataHelper.Listener { private static final String TAB_SELECTED = "tab_selected"; - private static final String ROUTE_ID_KEY = "route_id_key"; - private static final String SELECTED_LANG_KEY = "selected_lang_key"; + private static final String ARTICLE_ID_KEY = "article_id"; + private static final String SELECTED_LANG_KEY = "selected_lang"; private static final int EXPLORE_POSITION = 0; private static final int SAVED_ARTICLES_POSITION = 1; @@ -182,9 +183,9 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv BottomNavigationView bottomNav = (BottomNavigationView) findViewById(R.id.bottom_navigation); bottomNav.setSelectedItemId(R.id.action_saved_articles); } - String articleId = intent.getStringExtra(ROUTE_ID_KEY); + TravelArticleIdentifier articleId = intent.getParcelableExtra(ARTICLE_ID_KEY); String selectedLang = intent.getStringExtra(SELECTED_LANG_KEY); - if (!Algorithms.isEmpty(articleId)) { + if (articleId != null) { WikivoyageArticleDialogFragment.showInstance(app, getSupportFragmentManager(), articleId, selectedLang); } } @@ -201,8 +202,8 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv String title = WikiArticleHelper.decodeTitleFromTravelUrl(data.getQueryParameter("title")); String selectedLang = data.getQueryParameter("lang"); if (!Algorithms.isEmpty(title) && !Algorithms.isEmpty(selectedLang)) { - String articleId = app.getTravelHelper().getArticleId(title, selectedLang); - if (!articleId.isEmpty()) { + TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(title, selectedLang); + if (articleId != null) { WikivoyageArticleDialogFragment.showInstance(app, getSupportFragmentManager(), articleId, selectedLang); } } @@ -279,7 +280,7 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv private void applyIntentParameters(Intent intent, TravelArticle article) { intent.putExtra(TAB_SELECTED, viewPager.getCurrentItem()); - intent.putExtra(ROUTE_ID_KEY, article.getRouteId()); + intent.putExtra(ARTICLE_ID_KEY, article.generateIdentifier()); intent.putExtra(SELECTED_LANG_KEY, article.getLang()); } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/ArticleTravelCard.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/ArticleTravelCard.java index b9c5dbeb11..b955273d01 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/ArticleTravelCard.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/ArticleTravelCard.java @@ -76,7 +76,8 @@ public class ArticleTravelCard extends BaseTravelCard { @Override public void onClick(View v) { if (fragmentManager != null) { - WikivoyageArticleDialogFragment.showInstance(app, fragmentManager, article.getRouteId(), article.getLang()); + WikivoyageArticleDialogFragment.showInstance(app, fragmentManager, + article.generateIdentifier(), article.getLang()); } } }; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/menu/WikivoyageWptPtMenuController.java b/OsmAnd/src/net/osmand/plus/wikivoyage/menu/WikivoyageWptPtMenuController.java index 947af0897a..6f3ee2fd43 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/menu/WikivoyageWptPtMenuController.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/menu/WikivoyageWptPtMenuController.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.Metadata; import net.osmand.GPXUtilities.WptPt; +import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; import net.osmand.plus.R; @@ -14,12 +15,13 @@ import net.osmand.plus.activities.MapActivity; import net.osmand.plus.mapcontextmenu.controllers.WptPtMenuController; import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; public class WikivoyageWptPtMenuController extends WptPtMenuController { private WikivoyageWptPtMenuController(@NonNull MapActivity mapActivity, @NonNull PointDescription pointDescription, @NonNull WptPt wpt, @NonNull TravelArticle article) { super(new WikivoyageWptPtMenuBuilder(mapActivity, wpt), mapActivity, pointDescription, wpt); - final String tripId = article.getRouteId(); + final TravelArticleIdentifier articleId = article.generateIdentifier(); final String lang = article.getLang(); leftTitleButtonController = new TitleButtonController() { @Override @@ -27,7 +29,7 @@ public class WikivoyageWptPtMenuController extends WptPtMenuController { MapActivity mapActivity = getMapActivity(); if (mapActivity != null) { WikivoyageArticleDialogFragment.showInstance(mapActivity.getMyApplication(), - mapActivity.getSupportFragmentManager(), tripId, lang); + mapActivity.getSupportFragmentManager(), articleId, lang); } } }; @@ -42,7 +44,7 @@ public class WikivoyageWptPtMenuController extends WptPtMenuController { String title = metadata != null ? metadata.getArticleTitle() : null; String lang = metadata != null ? metadata.getArticleLang() : null; if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(lang)) { - return mapActivity.getMyApplication().getTravelHelper().getArticleByTitle(title, lang); + return mapActivity.getMyApplication().getTravelHelper().getArticleByTitle(title, new LatLon(wpt.lat, wpt.lon), lang); } return null; } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/search/SearchRecyclerViewAdapter.java b/OsmAnd/src/net/osmand/plus/wikivoyage/search/SearchRecyclerViewAdapter.java index de9793d267..34e369a3f2 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/search/SearchRecyclerViewAdapter.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/search/SearchRecyclerViewAdapter.java @@ -83,8 +83,8 @@ public class SearchRecyclerViewAdapter extends RecyclerView.Adapter(res.getLangs())); + WikivoyageArticleDialogFragment.showInstance(fm, res.getArticleId(), new ArrayList<>(res.getLangs())); } else if (item instanceof WikivoyageSearchHistoryItem) { WikivoyageSearchHistoryItem historyItem = (WikivoyageSearchHistoryItem) item; WikivoyageArticleDialogFragment