diff --git a/app/src/main/assets/queries/nearby_query.txt b/app/src/main/assets/queries/nearby_query.txt new file mode 100644 index 000000000..02f741228 --- /dev/null +++ b/app/src/main/assets/queries/nearby_query.txt @@ -0,0 +1,43 @@ +SELECT + (SAMPLE(?location) as ?location) + ?item + (SAMPLE(COALESCE(?item_label_preferred_language, ?item_label_any_language)) as ?label) + (SAMPLE(?classId) as ?class) + (SAMPLE(COALESCE(?class_label_preferred_language, ?class_label_any_language, "?")) as ?class_label) + (SAMPLE(COALESCE(?icon0, ?icon1)) as ?icon) + (SAMPLE(COALESCE(?emoji0, ?emoji1)) as ?emoji) + (SAMPLE(?sitelink) as ?sitelink) + WHERE { + # Around given location... + SERVICE wikibase:around { + ?item wdt:P625 ?location. + bd:serviceParam wikibase:center "Point(${LONG} ${LAT})"^^geo:wktLiteral. + bd:serviceParam wikibase:radius "${RADIUS}" . # Radius in kilometers. + } + + # ... and without an image. + MINUS {?item wdt:P18 []} + + # Get the label in the preferred language of the user, or any other language if no label is available in that language. + OPTIONAL {?item rdfs:label ?item_label_preferred_language. FILTER (lang(?item_label_preferred_language) = "${LANG}")} + OPTIONAL {?item rdfs:label ?item_label_any_language} + + # Get the class label in the preferred language of the user, or any other language if no label is available in that language. + OPTIONAL { + ?item p:P31/ps:P31 ?classId. + OPTIONAL {?classId rdfs:label ?class_label_preferred_language. FILTER (lang(?class_label_preferred_language) = "${LANG}")} + OPTIONAL {?classId rdfs:label ?class_label_any_language} + + # Get icon + OPTIONAL { ?classId wdt:P2910 ?icon0. } + OPTIONAL { ?classId wdt:P279*/wdt:P2910 ?icon1. } + # Get emoji + OPTIONAL { ?classId wdt:P487 ?emoji0. } + OPTIONAL { ?classId wdt:P279*/wdt:P487 ?emoji1. } + OPTIONAL { + ?sitelink schema:about ?item . + ?sitelink schema:inLanguage "en" + } + } + } + GROUP BY ?item \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/EventLog.java b/app/src/main/java/fr/free/nrw/commons/EventLog.java index d6c8d5059..694c03b6b 100644 --- a/app/src/main/java/fr/free/nrw/commons/EventLog.java +++ b/app/src/main/java/fr/free/nrw/commons/EventLog.java @@ -94,9 +94,7 @@ public class EventLog { data.put("appversion", "Android/" + BuildConfig.VERSION_NAME); fullData.put("event", data); return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";"); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } catch (JSONException e) { + } catch (MalformedURLException | JSONException e) { throw new RuntimeException(e); } } diff --git a/app/src/main/java/fr/free/nrw/commons/LicenseList.java b/app/src/main/java/fr/free/nrw/commons/LicenseList.java index 0ef067496..f3edf3be0 100644 --- a/app/src/main/java/fr/free/nrw/commons/LicenseList.java +++ b/app/src/main/java/fr/free/nrw/commons/LicenseList.java @@ -69,9 +69,8 @@ public class LicenseList { int nameId = stringIdByName(stringId); //Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId); if(nameId != 0) { - String name = res.getString(nameId); //Log.d("Commons", "LicenseList.nameForTemplate: name: " + name); - return name; + return res.getString(nameId); } return template; } diff --git a/app/src/main/java/fr/free/nrw/commons/Media.java b/app/src/main/java/fr/free/nrw/commons/Media.java index 924427cef..f8f90896a 100644 --- a/app/src/main/java/fr/free/nrw/commons/Media.java +++ b/app/src/main/java/fr/free/nrw/commons/Media.java @@ -144,6 +144,14 @@ public class Media implements Parcelable { this.license = license; } + public String getCoordinates() { + return coordinates; + } + + public void setCoordinates(String coordinates) { + this.coordinates = coordinates; + } + // Primary metadata fields protected Uri localUri; protected String imageUrl; @@ -155,6 +163,7 @@ public class Media implements Parcelable { protected int width; protected int height; protected String license; + private String coordinates; protected String creator; protected ArrayList categories; // as loaded at runtime? protected Map descriptions; // multilingual descriptions as loaded diff --git a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java index df9d5a4de..344546c7a 100644 --- a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java +++ b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java @@ -1,5 +1,7 @@ package fr.free.nrw.commons; +import fr.free.nrw.commons.location.LatLng; + import org.mediawiki.api.ApiResult; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -38,6 +40,7 @@ public class MediaDataExtractor { private String author; private Date date; private String license; + private String coordinates; private LicenseList licenseList; /** @@ -110,9 +113,7 @@ public class MediaDataExtractor { doc = docBuilder.parse(new ByteArrayInputStream(source.getBytes("UTF-8"))); } catch (ParserConfigurationException e) { throw new RuntimeException(e); - } catch (IllegalStateException e) { - throw new IOException(e); - } catch (SAXException e) { + } catch (IllegalStateException | SAXException e) { throw new IOException(e); } Node templateNode = findTemplate(doc.getDocumentElement(), "information"); @@ -124,6 +125,14 @@ public class MediaDataExtractor { author = getFlatText(authorNode); } + Node coordinateTemplateNode = findTemplate(doc.getDocumentElement(), "location"); + + if (coordinateTemplateNode != null) { + coordinates = getCoordinates(coordinateTemplateNode); + } else { + coordinates = "No coordinates found"; + } + /* Pull up the license data list... look for the templates in two ways: @@ -244,6 +253,25 @@ public class MediaDataExtractor { return parentNode.getTextContent(); } + /** + * Extracts the coordinates from the template and returns them as pretty formatted string. + * Loops over the children of the coordinate template: + * {{Location|47.50111007666667|19.055700301944444}} + * and extracts the latitude and longitude as a pretty string. + * + * @param parentNode The node of the coordinates template. + * @return Pretty formatted coordinates. + * @throws IOException Parsing failed. + */ + private String getCoordinates(Node parentNode) throws IOException { + NodeList childNodes = parentNode.getChildNodes(); + double latitudeText = Double.parseDouble(childNodes.item(1).getTextContent()); + double longitudeText = Double.parseDouble(childNodes.item(2).getTextContent()); + LatLng coordinates = new LatLng(latitudeText, longitudeText); + + return coordinates.getPrettyCoordinateString(); + } + // Extract a dictionary of multilingual texts from a subset of the parse tree. // Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}. // Text outside those wrappers is stuffed into a 'default' faux language key if present. @@ -289,6 +317,7 @@ public class MediaDataExtractor { media.setCategories(categories); media.setDescriptions(descriptions); + media.setCoordinates(coordinates); if (license != null) { media.setLicense(license); } diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java index d4a049f5d..0ece6e119 100644 --- a/app/src/main/java/fr/free/nrw/commons/Utils.java +++ b/app/src/main/java/fr/free/nrw/commons/Utils.java @@ -122,10 +122,7 @@ public class Utils { Transformer transformer = null; try { transformer = TransformerFactory.newInstance().newTransformer(); - } catch (TransformerConfigurationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (TransformerFactoryConfigurationError e) { + } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) { // TODO Auto-generated catch block e.printStackTrace(); } @@ -202,54 +199,57 @@ public class Utils { } public static String licenseTemplateFor(String license) { - if (license.equals(Prefs.Licenses.CC_BY_3)) { - return "{{self|cc-by-3.0}}"; - } else if (license.equals(Prefs.Licenses.CC_BY_4)) { - return "{{self|cc-by-4.0}}"; - } else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) { - return "{{self|cc-by-sa-3.0}}"; - } else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) { - return "{{self|cc-by-sa-4.0}}"; - } else if (license.equals(Prefs.Licenses.CC0)) { - return "{{self|cc-zero}}"; - } else if (license.equals(Prefs.Licenses.CC_BY)) { - return "{{self|cc-by-3.0}}"; - } else if (license.equals(Prefs.Licenses.CC_BY_SA)) { - return "{{self|cc-by-sa-3.0}}"; + switch (license) { + case Prefs.Licenses.CC_BY_3: + return "{{self|cc-by-3.0}}"; + case Prefs.Licenses.CC_BY_4: + return "{{self|cc-by-4.0}}"; + case Prefs.Licenses.CC_BY_SA_3: + return "{{self|cc-by-sa-3.0}}"; + case Prefs.Licenses.CC_BY_SA_4: + return "{{self|cc-by-sa-4.0}}"; + case Prefs.Licenses.CC0: + return "{{self|cc-zero}}"; + case Prefs.Licenses.CC_BY: + return "{{self|cc-by-3.0}}"; + case Prefs.Licenses.CC_BY_SA: + return "{{self|cc-by-sa-3.0}}"; } throw new RuntimeException("Unrecognized license value: " + license); } public static int licenseNameFor(String license) { - if (license.equals(Prefs.Licenses.CC_BY_3)) { - return R.string.license_name_cc_by; - } else if (license.equals(Prefs.Licenses.CC_BY_4)) { - return R.string.license_name_cc_by_four; - } else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) { - return R.string.license_name_cc_by_sa; - } else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) { - return R.string.license_name_cc_by_sa_four; - } else if (license.equals(Prefs.Licenses.CC0)) { - return R.string.license_name_cc0; - } else if (license.equals(Prefs.Licenses.CC_BY)) { // for backward compatibility to v2.1 - return R.string.license_name_cc_by_3_0; - } else if (license.equals(Prefs.Licenses.CC_BY_SA)) { // for backward compatibility to v2.1 - return R.string.license_name_cc_by_sa_3_0; + switch (license) { + case Prefs.Licenses.CC_BY_3: + return R.string.license_name_cc_by; + case Prefs.Licenses.CC_BY_4: + return R.string.license_name_cc_by_four; + case Prefs.Licenses.CC_BY_SA_3: + return R.string.license_name_cc_by_sa; + case Prefs.Licenses.CC_BY_SA_4: + return R.string.license_name_cc_by_sa_four; + case Prefs.Licenses.CC0: + return R.string.license_name_cc0; + case Prefs.Licenses.CC_BY: // for backward compatibility to v2.1 + return R.string.license_name_cc_by_3_0; + case Prefs.Licenses.CC_BY_SA: // for backward compatibility to v2.1 + return R.string.license_name_cc_by_sa_3_0; } throw new RuntimeException("Unrecognized license value: " + license); } public static String licenseUrlFor(String license) { - if (license.equals(Prefs.Licenses.CC_BY_3)) { - return "https://creativecommons.org/licenses/by/3.0/"; - } else if (license.equals(Prefs.Licenses.CC_BY_4)) { - return "https://creativecommons.org/licenses/by/4.0/"; - } else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) { - return "https://creativecommons.org/licenses/by-sa/3.0/"; - } else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) { - return "https://creativecommons.org/licenses/by-sa/4.0/"; - } else if (license.equals(Prefs.Licenses.CC0)) { - return "https://creativecommons.org/publicdomain/zero/1.0/"; + switch (license) { + case Prefs.Licenses.CC_BY_3: + return "https://creativecommons.org/licenses/by/3.0/"; + case Prefs.Licenses.CC_BY_4: + return "https://creativecommons.org/licenses/by/4.0/"; + case Prefs.Licenses.CC_BY_SA_3: + return "https://creativecommons.org/licenses/by-sa/3.0/"; + case Prefs.Licenses.CC_BY_SA_4: + return "https://creativecommons.org/licenses/by-sa/4.0/"; + case Prefs.Licenses.CC0: + return "https://creativecommons.org/publicdomain/zero/1.0/"; } throw new RuntimeException("Unrecognized license value: " + license); } diff --git a/app/src/main/java/fr/free/nrw/commons/caching/CacheController.java b/app/src/main/java/fr/free/nrw/commons/caching/CacheController.java index 2d8dafacb..3cf61385d 100644 --- a/app/src/main/java/fr/free/nrw/commons/caching/CacheController.java +++ b/app/src/main/java/fr/free/nrw/commons/caching/CacheController.java @@ -32,7 +32,7 @@ public class CacheController { public void cacheCategory() { List pointCatList = new ArrayList<>(); - if (MwVolleyApi.GpsCatExists.getGpsCatExists() == true) { + if (MwVolleyApi.GpsCatExists.getGpsCatExists()) { pointCatList.addAll(MwVolleyApi.getGpsCat()); Timber.d("Categories being cached: %s", pointCatList); } else { diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java index 0f71e329b..2d7b77024 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java @@ -357,8 +357,7 @@ public class CategorizationFragment extends Fragment { new String[] {name}, null); if (cursor.moveToFirst()) { - Category cat = Category.fromCursor(cursor); - return cat; + return Category.fromCursor(cursor); } } catch (RemoteException e) { // This feels lazy, but to hell with checked exceptions. :) diff --git a/app/src/main/java/fr/free/nrw/commons/category/MethodAUpdater.java b/app/src/main/java/fr/free/nrw/commons/category/MethodAUpdater.java index 35ab0ef17..33835119e 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/MethodAUpdater.java +++ b/app/src/main/java/fr/free/nrw/commons/category/MethodAUpdater.java @@ -108,7 +108,6 @@ public class MethodAUpdater extends AsyncTask> { } Timber.d("Found categories from Method A search, waiting for filter"); - ArrayList filteredItems = new ArrayList<>(filterYears(categories)); - return filteredItems; + return new ArrayList<>(filterYears(categories)); } } diff --git a/app/src/main/java/fr/free/nrw/commons/category/PrefixUpdater.java b/app/src/main/java/fr/free/nrw/commons/category/PrefixUpdater.java index 10499acbd..350c0b21f 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/PrefixUpdater.java +++ b/app/src/main/java/fr/free/nrw/commons/category/PrefixUpdater.java @@ -83,16 +83,14 @@ public class PrefixUpdater extends AsyncTask> { if(TextUtils.isEmpty(filter)) { ArrayList mergedItems = new ArrayList<>(catFragment.mergeItems()); Timber.d("Merged items, waiting for filter"); - ArrayList filteredItems = new ArrayList<>(filterYears(mergedItems)); - return filteredItems; + return new ArrayList<>(filterYears(mergedItems)); } //if user types in something that is in cache, return cached category if(catFragment.categoriesCache.containsKey(filter)) { ArrayList cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter)); Timber.d("Found cache items, waiting for filter"); - ArrayList filteredItems = new ArrayList<>(filterYears(cachedItems)); - return filteredItems; + return new ArrayList<>(filterYears(cachedItems)); } //otherwise if user has typed something in that isn't in cache, search API for matching categories @@ -119,7 +117,6 @@ public class PrefixUpdater extends AsyncTask> { } Timber.d("Found categories from Prefix search, waiting for filter"); - ArrayList filteredItems = new ArrayList<>(filterYears(categories)); - return filteredItems; + return new ArrayList<>(filterYears(categories)); } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java index bcdf31d1b..c39298810 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java @@ -195,7 +195,7 @@ public class Contribution extends Media { cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString()); } if(getImageUrl() != null) { - cv.put(Table.COLUMN_IMAGE_URL, getImageUrl().toString()); + cv.put(Table.COLUMN_IMAGE_URL, getImageUrl()); } if(getDateUploaded() != null) { cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime()); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java index deaf655a1..5ef34335a 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java @@ -38,7 +38,7 @@ public class ContributionController { String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg"; File _photoFile = new File(path); try { - if(_photoFile.exists() == false) { + if(!_photoFile.exists()) { _photoFile.getParentFile().mkdirs(); _photoFile.createNewFile(); } diff --git a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java index 839cba14e..47d112991 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java +++ b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java @@ -42,4 +42,52 @@ public class LatLng { public String toString() { return "lat/lng: (" + this.latitude + "," + this.longitude + ")"; } + + /** + * Rounds the float to 4 digits. + * + * @param coordinate A coordinate value as string. + * @return String of the rounded number. + */ + private String formatCoordinate(double coordinate) { + double roundedNumber = Math.round(coordinate * 10000d) / 10000d; + return String.valueOf(roundedNumber); + } + + /** + * Returns "N" or "S" depending on the latitude. + * + * @return "N" or "S". + */ + private String getNorthSouth() { + if (this.latitude < 0) { + return "S"; + } + + return "N"; + } + + /** + * Returns "E" or "W" depending on the longitude. + * + * @return "E" or "W". + */ + private String getEastWest() { + if (this.longitude < 180) { + return "E"; + } + + return "W"; + } + + /** + * Returns a nicely formatted coordinate string. Used e.g. in + * the detail view. + * + * @return The formatted string. + */ + public String getPrettyCoordinateString() { + return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", " + + formatCoordinate(this.longitude) + " " + this.getEastWest(); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index 91d45b982..b32d83a31 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -70,6 +70,7 @@ public class MediaDetailFragment extends Fragment { private TextView title; private TextView desc; private TextView license; + private TextView coordinates; private LinearLayout categoryContainer; private ScrollView scrollView; private ArrayList categoryNames; @@ -123,6 +124,7 @@ public class MediaDetailFragment extends Fragment { title = (TextView) view.findViewById(R.id.mediaDetailTitle); desc = (TextView) view.findViewById(R.id.mediaDetailDesc); license = (TextView) view.findViewById(R.id.mediaDetailLicense); + coordinates = (TextView) view.findViewById(R.id.mediaDetailCoordinates); categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer); licenseList = new LicenseList(getActivity()); @@ -220,12 +222,13 @@ public class MediaDetailFragment extends Fragment { protected void onPostExecute(Boolean success) { detailFetchTask = null; - if (success.booleanValue()) { + if (success) { extractor.fill(media); // Set text of desc, license, and categories desc.setText(prettyDescription(media)); license.setText(prettyLicense(media)); + coordinates.setText(prettyCoordinates(media)); categoryNames.removeAll(categoryNames); categoryNames.addAll(media.getCategories()); @@ -388,4 +391,15 @@ public class MediaDetailFragment extends Fragment { return licenseObj.getName(); } } + + /** + * Returns the coordinates nicely formatted. + * + * @return Coordinates as text. + */ + private String prettyCoordinates(Media media) { + String coordinates = media.getCoordinates(); + + return coordinates; + } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java index 53e01af86..0f8f176de 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java @@ -1,35 +1,58 @@ package fr.free.nrw.commons.nearby; +import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.FragmentTransaction; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; -import fr.free.nrw.commons.R; +import butterknife.BindView; +import butterknife.ButterKnife; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.theme.BaseActivity; +import fr.free.nrw.commons.utils.UriSerializer; + +import fr.free.nrw.commons.R; + +import java.util.List; public class NearbyActivity extends BaseActivity { + @BindView(R.id.progressBar) + ProgressBar progressBar; private boolean isMapViewActive = false; private LocationServiceManager locationManager; + private LatLng curLatLang; + private Gson gson; + private String gsonPlaceList; + private String gsonCurLatLng; + private Bundle bundle; + private NearbyAsyncTask nearbyAsyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_nearby); + ButterKnife.bind(this); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); } + bundle = new Bundle(); locationManager = new LocationServiceManager(this); locationManager.registerLocationManager(); + curLatLang = locationManager.getLatestLocation(); + nearbyAsyncTask = new NearbyAsyncTask(); + nearbyAsyncTask.execute(); - // Begin the transaction - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - NearbyListFragment fragment = new NearbyListFragment(); - ft.add(R.id.container, fragment); - ft.commit(); } @Override @@ -56,9 +79,10 @@ public class NearbyActivity extends BaseActivity { private void showMapView() { if (!isMapViewActive) { - getSupportFragmentManager().beginTransaction() - .replace(R.id.container, new NearbyMapFragment()).commit(); isMapViewActive = true; + if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) { + setMapFragment(); + } } } @@ -68,13 +92,8 @@ public class NearbyActivity extends BaseActivity { } protected void refreshView() { - if (isMapViewActive) { - getSupportFragmentManager().beginTransaction() - .replace(R.id.container, new NearbyMapFragment()).commit(); - } else { - getSupportFragmentManager().beginTransaction() - .replace(R.id.container, new NearbyListFragment()).commit(); - } + nearbyAsyncTask = new NearbyAsyncTask(); + nearbyAsyncTask.execute(); } public LocationServiceManager getLocationManager() { @@ -86,4 +105,77 @@ public class NearbyActivity extends BaseActivity { super.onDestroy(); locationManager.unregisterLocationManager(); } + + private class NearbyAsyncTask extends AsyncTask> { + + @Override + protected void onPreExecute() { + super.onPreExecute(); + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected List doInBackground(Void... params) { + return NearbyController + .loadAttractionsFromLocation(curLatLang, getApplicationContext() + ); + } + + @Override + protected void onPostExecute(List placeList) { + super.onPostExecute(placeList); + + if (isCancelled()) { + return; + } + + gson = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriSerializer()) + .create(); + gsonPlaceList = gson.toJson(placeList); + gsonCurLatLng = gson.toJson(curLatLang); + + bundle.clear(); + bundle.putString("PlaceList", gsonPlaceList); + bundle.putString("CurLatLng", gsonCurLatLng); + + // Begin the transaction + if (isMapViewActive) { + setMapFragment(); + } else { + setListFragment(); + } + + if (progressBar != null) { + progressBar.setVisibility(View.GONE); + } + } + } + + /** + * Calls fragment for map view. + */ + public void setMapFragment() { + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + NearbyMapFragment fragment = new NearbyMapFragment(); + fragment.setArguments(bundle); + ft.add(R.id.container, fragment); + ft.commit(); + } + + /** + * Calls fragment for list view. + */ + public void setListFragment() { + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + NearbyListFragment fragment = new NearbyListFragment(); + fragment.setArguments(bundle); + ft.add(R.id.container, fragment); + ft.commit(); + } + } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyBaseMarker.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyBaseMarker.java new file mode 100644 index 000000000..686b3e6ce --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyBaseMarker.java @@ -0,0 +1,88 @@ +package fr.free.nrw.commons.nearby; + +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; +import com.mapbox.mapboxsdk.annotations.Icon; +import com.mapbox.mapboxsdk.annotations.IconFactory; +import com.mapbox.mapboxsdk.geometry.LatLng; + +import fr.free.nrw.commons.utils.UriDeserializer; +import fr.free.nrw.commons.utils.UriSerializer; + +public class NearbyBaseMarker extends BaseMarkerOptions { + private Place place; + public NearbyBaseMarker() { + + } + + public NearbyBaseMarker place(Place place) { + this.place = place; + return getThis(); + } + + public NearbyBaseMarker(Parcel in) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriDeserializer()) + .create(); + + position((LatLng) in.readParcelable(LatLng.class.getClassLoader())); + snippet(in.readString()); + String iconId = in.readString(); + Bitmap iconBitmap = in.readParcelable(Bitmap.class.getClassLoader()); + Icon icon = IconFactory.recreate(iconId, iconBitmap); + icon(icon); + title(in.readString()); + String gsonString = in.readString(); + place(gson.fromJson(gsonString, Place.class)); + } + + @Override + public NearbyBaseMarker getThis() { + return this; + } + + @Override + public NearbyMarker getMarker() { + return new NearbyMarker(this, place); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriSerializer()) + .create(); + + dest.writeParcelable(position, flags); + dest.writeString(snippet); + dest.writeString(icon.getId()); + dest.writeParcelable(icon.getBitmap(), flags); + dest.writeString(title); + dest.writeString(gson.toJson(place)); + } + + public Place getPlace() { + return place; + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public NearbyBaseMarker createFromParcel(Parcel in) { + return new NearbyBaseMarker(in); + } + + public NearbyBaseMarker[] newArray(int size) { + return new NearbyBaseMarker[size]; + } + }; +} diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java index 0d0712e1b..ba2aec1d9 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java @@ -1,17 +1,10 @@ package fr.free.nrw.commons.nearby; -import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween; -import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; - import android.content.Context; import android.content.SharedPreferences; +import android.graphics.drawable.Icon; import android.preference.PreferenceManager; -import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; -import com.mapbox.mapboxsdk.annotations.MarkerOptions; - -import fr.free.nrw.commons.location.LatLng; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -20,13 +13,23 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import fr.free.nrw.commons.location.LatLng; import timber.log.Timber; +import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween; +import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; + public class NearbyController { private static final int MAX_RESULTS = 1000; - private static List loadAttractionsFromLocation(LatLng curLatLng, Context context) { + /** + * Prepares Place list to make their distance information update later. + * @param curLatLng current location for user + * @param context context + * @return Place list without distance information + */ + public static List loadAttractionsFromLocation(LatLng curLatLng, Context context) { Timber.d("Loading attractions near %s", curLatLng); if (curLatLng == null) { return Collections.emptyList(); @@ -34,7 +37,9 @@ public class NearbyController { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); List places = prefs.getBoolean("useWikidata", true) ? NearbyPlaces.getInstance().getFromWikidataQuery( - curLatLng, Locale.getDefault().getLanguage()) + context, + curLatLng, + Locale.getDefault().getLanguage()) : NearbyPlaces.getInstance().getFromWikiNeedsPictures(); if (curLatLng != null) { Timber.d("Sorting places by distance..."); @@ -59,42 +64,44 @@ public class NearbyController { /** * Loads attractions from location for list view, we need to return Place data type. * @param curLatLng users current location - * @param context current activity + * @param placeList list of nearby places in Place data type * @return Place list that holds nearby places */ - - public static List loadAttractionsFromLocationToPlaces(LatLng curLatLng, - Context context) { - - List places = loadAttractionsFromLocation(curLatLng,context); - places = places.subList(0, Math.min(places.size(), MAX_RESULTS)); - for (Place place: places) { + public static List loadAttractionsFromLocationToPlaces( + LatLng curLatLng, + List placeList) { + placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS)); + for (Place place: placeList) { String distance = formatDistanceBetween(curLatLng, place.location); place.setDistance(distance); } - return places; + return placeList; } /** *Loads attractions from location for map view, we need to return BaseMarkerOption data type. * @param curLatLng users current location - * @param context the activity + * @param placeList list of nearby places in Place data type * @return BaseMarkerOprions list that holds nearby places */ - public static List loadAttractionsFromLocationToBaseMarkerOptions( + public static List loadAttractionsFromLocationToBaseMarkerOptions( LatLng curLatLng, - Context context) { - List baseMarkerOptionses = new ArrayList<>(); - List places = loadAttractionsFromLocation(curLatLng,context); - places = places.subList(0, Math.min(places.size(), MAX_RESULTS)); - for (Place place: places) { + List placeList) { + List baseMarkerOptionses = new ArrayList<>(); + placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS)); + for (Place place: placeList) { String distance = formatDistanceBetween(curLatLng, place.location); place.setDistance(distance); - baseMarkerOptionses.add(new MarkerOptions() - .position(new com.mapbox.mapboxsdk.geometry - .LatLng(place.location.latitude,place.location.longitude)) - .title(place.name) - .snippet(place.description)); + + NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker(); + nearbyBaseMarker.title(place.name); + nearbyBaseMarker.position( + new com.mapbox.mapboxsdk.geometry.LatLng( + place.location.latitude, + place.location.longitude)); + nearbyBaseMarker.place(place); + + baseMarkerOptionses.add(nearbyBaseMarker); } return baseMarkerOptionses; } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java new file mode 100644 index 000000000..e777eb3ee --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java @@ -0,0 +1,110 @@ +package fr.free.nrw.commons.nearby; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import fr.free.nrw.commons.R; +import fr.free.nrw.commons.Utils; +import fr.free.nrw.commons.location.LatLng; +import fr.free.nrw.commons.ui.widget.OverlayDialog; +import fr.free.nrw.commons.utils.DialogUtil; + +public class NearbyInfoDialog extends OverlayDialog { + + private final static String ARG_TITLE = "placeTitle"; + private final static String ARG_DESC = "placeDesc"; + private final static String ARG_LATITUDE = "latitude"; + private final static String ARG_LONGITUDE = "longitude"; + private final static String ARG_ARTICLE_LINK = "articleLink"; + private final static String ARG_WIKI_DATA_LINK = "wikiDataLink"; + + @BindView(R.id.link_preview_title) + TextView placeTitle; + @BindView(R.id.link_preview_extract) + TextView placeDescription; + + @BindView(R.id.link_preview_go_button) + TextView goToButton; + + private Unbinder unbinder; + + private LatLng location; + private Uri articleLink; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_nearby_info, container, false); + unbinder = ButterKnife.bind(this, view); + initUi(); + return view; + } + + private void initUi() { + Bundle bundle = getArguments(); + placeTitle.setText(bundle.getString(ARG_TITLE)); + placeDescription.setText(bundle.getString(ARG_DESC)); + location = new LatLng(bundle.getDouble(ARG_LATITUDE), bundle.getDouble(ARG_LONGITUDE)); + getArticleLink(bundle); + } + + private void getArticleLink(Bundle bundle) { + String articleLink = bundle.getString(ARG_ARTICLE_LINK); + articleLink = articleLink.replace("<", "").replace(">", ""); + + if (Utils.isNullOrWhiteSpace(articleLink) || articleLink == "\n") { + articleLink = bundle.getString(ARG_WIKI_DATA_LINK).replace("<", "").replace(">", ""); + } + + if (!Utils.isNullOrWhiteSpace(articleLink) && articleLink != "\n") { + this.articleLink = Uri.parse(articleLink); + } else { + goToButton.setVisibility(View.GONE); + } + } + + public static void showYourself(FragmentActivity fragmentActivity, Place place) { + NearbyInfoDialog mDialog = new NearbyInfoDialog(); + Bundle bundle = new Bundle(); + bundle.putString(ARG_TITLE, place.name); + bundle.putString(ARG_DESC, place.description); + bundle.putDouble(ARG_LATITUDE, place.location.latitude); + bundle.putDouble(ARG_LONGITUDE, place.location.longitude); + bundle.putString(ARG_ARTICLE_LINK, place.siteLink.toString()); + bundle.putString(ARG_WIKI_DATA_LINK, place.wikiDataLink.toString()); + mDialog.setArguments(bundle); + DialogUtil.showSafely(fragmentActivity, mDialog); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @OnClick(R.id.link_preview_directions_button) + void onDirectionsClick() { + //Open map app at given position + Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + location.latitude + "," + location.longitude); + Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); + + if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) { + startActivity(mapIntent); + } + } + + @OnClick(R.id.link_preview_go_button) + void onReadArticleClick() { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, articleLink); + startActivity(browserIntent); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java index 5921c0e30..048f1d450 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java @@ -2,30 +2,38 @@ package fr.free.nrw.commons.nearby; import android.content.Intent; import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; -import android.widget.ProgressBar; - -import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnItemClick; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + import fr.free.nrw.commons.R; import fr.free.nrw.commons.location.LatLng; +import fr.free.nrw.commons.utils.UriDeserializer; + +import java.lang.reflect.Type; +import java.util.List; + + import timber.log.Timber; public class NearbyListFragment extends ListFragment { - - private NearbyAsyncTask nearbyAsyncTask; + private Gson gson; + private List placeList; + private LatLng curLatLng; @BindView(R.id.listView) ListView listview; - @BindView(R.id.progressBar) ProgressBar progressBar; + private NearbyAdapter adapter; @@ -55,85 +63,27 @@ public class NearbyListFragment extends ListFragment { // Check that this is the first time view is created, // to avoid double list when screen orientation changed + Bundle bundle = this.getArguments(); + gson = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriDeserializer()) + .create(); + if (bundle != null) { + String gsonPlaceList = bundle.getString("PlaceList"); + String gsonLatLng = bundle.getString("CurLatLng"); + Type listType = new TypeToken>() {}.getType(); + placeList = gson.fromJson(gsonPlaceList, listType); + Type curLatLngType = new TypeToken() {}.getType(); + curLatLng = gson.fromJson(gsonLatLng, curLatLngType); + placeList = NearbyController.loadAttractionsFromLocationToPlaces(curLatLng, placeList); + } if (savedInstanceState == null) { adapter.clear(); - nearbyAsyncTask = new NearbyAsyncTask(); - nearbyAsyncTask.execute(); - progressBar.setVisibility(View.VISIBLE); Timber.d("Saved instance state is null, populating ListView"); - } else { - progressBar.setVisibility(View.GONE); } - // If we are returning here from a screen orientation and the AsyncTask is still working, - // re-create and display the progress dialog. - if (isTaskRunning()) { - progressBar.setVisibility(View.VISIBLE); - } - } - - private boolean isTaskRunning() { - return nearbyAsyncTask != null && nearbyAsyncTask.getStatus() != AsyncTask.Status.FINISHED; - } - - @Override - public void onDetach() { - // All dialogs should be closed before leaving the activity in order to avoid - // the: Activity has leaked window com.android.internal.policy... exception - if (progressBar != null && progressBar.isShown()) { - progressBar.setVisibility(View.GONE); - } - super.onDetach(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - - // See http://stackoverflow.com/questions/18264408/incomplete-asynctask-crashes-my-app - if (isTaskRunning()) { - nearbyAsyncTask.cancel(true); - } - } - - private class NearbyAsyncTask extends AsyncTask> { - - @Override - protected void onPreExecute() { - super.onPreExecute(); - progressBar.setVisibility(View.VISIBLE); - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - progressBar.setProgress(values[0]); - } - - @Override - protected List doInBackground(Void... params) { - return NearbyController.loadAttractionsFromLocationToPlaces( - ((NearbyActivity)getActivity()) - .getLocationManager() - .getLatestLocation(), getActivity() - ); - } - - @Override - protected void onPostExecute(List places) { - super.onPostExecute(places); - - if (isCancelled()) { - return; - } - - if (progressBar != null) { - progressBar.setVisibility(View.GONE); - } - adapter.clear(); - adapter.addAll(places); - adapter.notifyDataSetChanged(); - } + adapter.clear(); + adapter.addAll(placeList); + adapter.notifyDataSetChanged(); } @OnItemClick(R.id.listView) @@ -146,12 +96,6 @@ public class NearbyListFragment extends ListFragment { Timber.d("Item at position %d has coords: Lat: %f Long: %f", position, latitude, longitude); - //Open map app at given position - Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + latitude + "," + longitude); - Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); - - if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) { - startActivity(mapIntent); - } + NearbyInfoDialog.showYourself(getActivity(), place); } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java index 7b627ecdf..c15a407c3 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java @@ -1,14 +1,19 @@ package fr.free.nrw.commons.nearby; -import android.os.AsyncTask; +import android.net.Uri; import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; import com.mapbox.mapboxsdk.Mapbox; -import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; +import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; @@ -18,14 +23,18 @@ import com.mapbox.mapboxsdk.maps.MapboxMapOptions; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.services.android.telemetry.MapboxTelemetry; +import java.lang.reflect.Type; import java.util.List; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.utils.UriDeserializer; public class NearbyMapFragment extends android.support.v4.app.Fragment { - private NearbyAsyncTask nearbyAsyncTask; - private fr.free.nrw.commons.location.LatLng currentLocation; private MapView mapView; + private Gson gson; + private List placeList; + private List baseMarkerOptionses; + private fr.free.nrw.commons.location.LatLng curLatLng; public NearbyMapFragment() { @@ -34,7 +43,21 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - currentLocation = ((NearbyActivity)getActivity()).getLocationManager().getLatestLocation(); + Bundle bundle = this.getArguments(); + gson = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriDeserializer()) + .create(); + if (bundle != null) { + String gsonPlaceList = bundle.getString("PlaceList"); + String gsonLatLng = bundle.getString("CurLatLng"); + Type listType = new TypeToken>() {}.getType(); + placeList = gson.fromJson(gsonPlaceList, listType); + Type curLatLngType = new TypeToken() {}.getType(); + curLatLng = gson.fromJson(gsonLatLng, curLatLngType); + baseMarkerOptionses = NearbyController + .loadAttractionsFromLocationToBaseMarkerOptions(curLatLng, placeList); + + } Mapbox.getInstance(getActivity(), getString(R.string.mapbox_commons_app_token)); MapboxTelemetry.getInstance().setTelemetryEnabled(false); @@ -46,22 +69,45 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment { MapboxMapOptions options = new MapboxMapOptions() .styleUrl(Style.OUTDOORS) .camera(new CameraPosition.Builder() - .target(new LatLng(currentLocation.latitude, currentLocation.longitude)) + .target(new LatLng(curLatLng.latitude, curLatLng.longitude)) .zoom(11) .build()); // create map mapView = new MapView(getActivity(), options); mapView.onCreate(savedInstanceState); + mapView.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(MapboxMap mapboxMap) { + mapboxMap.addMarkers(baseMarkerOptionses); + + mapboxMap.setOnMarkerClickListener(new MapboxMap.OnMarkerClickListener() { + @Override + public boolean onMarkerClick(@NonNull Marker marker) { + if (marker instanceof NearbyMarker) { + NearbyMarker nearbyMarker = (NearbyMarker) marker; + Place place = nearbyMarker.getNearbyBaseMarker().getPlace(); + NearbyInfoDialog.showYourself(getActivity(), place); + } + return false; + } + }); + } + }); + if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) { + mapView.setStyleUrl(getResources().getString(R.string.map_theme_dark)); + } else { + mapView.setStyleUrl(getResources().getString(R.string.map_theme_light)); + } + setHasOptionsMenu(false); + return mapView; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - nearbyAsyncTask = new NearbyAsyncTask(); - nearbyAsyncTask.execute(); } @Override @@ -93,39 +139,4 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment { mapView.onDestroy(); super.onDestroyView(); } - - private class NearbyAsyncTask extends AsyncTask> { - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - } - - @Override - protected List doInBackground(Void... params) { - return NearbyController - .loadAttractionsFromLocationToBaseMarkerOptions(currentLocation, getActivity() - ); - } - - @Override - protected void onPostExecute(final List baseMarkerOptionses) { - super.onPostExecute(baseMarkerOptionses); - - if (isCancelled()) { - return; - } - mapView.getMapAsync(new OnMapReadyCallback() { - @Override - public void onMapReady(MapboxMap mapboxMap) { - mapboxMap.addMarkers(baseMarkerOptionses); - } - }); - } - } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMarker.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMarker.java new file mode 100644 index 000000000..c65ede203 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMarker.java @@ -0,0 +1,31 @@ +package fr.free.nrw.commons.nearby; + +import com.mapbox.mapboxsdk.annotations.Marker; + +public class NearbyMarker extends Marker { + private Place place; + private NearbyBaseMarker nearbyBaseMarker; + + /** + * Creates a instance of {@link Marker} using the builder of Marker. + * + * @param baseMarkerOptions The builder used to construct the Marker. + */ + public NearbyMarker(NearbyBaseMarker baseMarkerOptions, Place place) { + super(baseMarkerOptions); + this.place = place; + this.nearbyBaseMarker = baseMarkerOptions; + } + + public NearbyBaseMarker getNearbyBaseMarker() { + return nearbyBaseMarker; + } + + public Place getPlace() { + return place; + } + + public void setNearbyBaseMarker(NearbyBaseMarker nearbyBaseMarker) { + this.nearbyBaseMarker = nearbyBaseMarker; + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java index 44d415505..a6d08be04 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java @@ -1,11 +1,9 @@ package fr.free.nrw.commons.nearby; +import android.content.Context; import android.net.Uri; import android.os.StrictMode; -import fr.free.nrw.commons.Utils; -import fr.free.nrw.commons.location.LatLng; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -19,6 +17,9 @@ import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; +import fr.free.nrw.commons.Utils; +import fr.free.nrw.commons.location.LatLng; +import fr.free.nrw.commons.utils.FileUtils; import timber.log.Timber; public class NearbyPlaces { @@ -28,44 +29,6 @@ public class NearbyPlaces { private static final double MAX_RADIUS = 300.0; private static final double RADIUS_MULTIPLIER = 1.618; private static final String WIKIDATA_QUERY_URL = "https://query.wikidata.org/sparql?query=${QUERY}"; - private static final String WIKIDATA_QUERY_TEMPLATE = "SELECT\n" + - " (SAMPLE(?location) as ?location)\n" + - " ?item\n" + - " (SAMPLE(COALESCE(?item_label_preferred_language, ?item_label_any_language)) as ?label)\n" + - " (SAMPLE(?classId) as ?class)\n" + - " (SAMPLE(COALESCE(?class_label_preferred_language, ?class_label_any_language, \"?\")) as ?class_label)\n" + - " (SAMPLE(COALESCE(?icon0, ?icon1)) as ?icon)\n" + - " (SAMPLE(COALESCE(?emoji0, ?emoji1)) as ?emoji)\n" + - "WHERE {\n" + - " # Around given location...\n" + - " SERVICE wikibase:around {\n" + - " ?item wdt:P625 ?location.\n" + - " bd:serviceParam wikibase:center \"Point(${LONG} ${LAT})\"^^geo:wktLiteral. \n" + - " bd:serviceParam wikibase:radius \"${RADIUS}\" . # Radius in kilometers.\n" + - " }\n" + - " \n" + - " # ... and without an image.\n" + - " MINUS {?item wdt:P18 []}\n" + - " \n" + - " # Get the label in the preferred language of the user, or any other language if no label is available in that language.\n" + - " OPTIONAL {?item rdfs:label ?item_label_preferred_language. FILTER (lang(?item_label_preferred_language) = \"${LANG}\")}\n" + - " OPTIONAL {?item rdfs:label ?item_label_any_language}\n" + - " \n" + - " # Get the class label in the preferred language of the user, or any other language if no label is available in that language.\n" + - " OPTIONAL {\n" + - " ?item p:P31/ps:P31 ?classId.\n" + - " OPTIONAL {?classId rdfs:label ?class_label_preferred_language. FILTER (lang(?class_label_preferred_language) = \"${LANG}\")}\n" + - " OPTIONAL {?classId rdfs:label ?class_label_any_language}\n" + - "\n" + - " # Get icon\n" + - " OPTIONAL { ?classId wdt:P2910 ?icon0. }\n" + - " OPTIONAL { ?classId wdt:P279*/wdt:P2910 ?icon1. }\n" + - " # Get emoji\n" + - " OPTIONAL { ?classId wdt:P487 ?emoji0. }\n" + - " OPTIONAL { ?classId wdt:P279*/wdt:P487 ?emoji1. }\n" + - " }\n" + - "}\n" + - "GROUP BY ?item\n"; private static NearbyPlaces singleton; private double radius = INITIAL_RADIUS; private List places; @@ -73,13 +36,15 @@ public class NearbyPlaces { private NearbyPlaces(){ } - List getFromWikidataQuery(LatLng curLatLng, String lang) { + List getFromWikidataQuery(Context context, + LatLng curLatLng, + String lang) { List places = Collections.emptyList(); try { // increase the radius gradually to find a satisfactory number of nearby places while (radius < MAX_RADIUS) { - places = getFromWikidataQuery(curLatLng, lang, radius); + places = getFromWikidataQuery(context, curLatLng, lang, radius); Timber.d("%d results at radius: %f", places.size(), radius); if (places.size() >= MIN_RESULTS) { break; @@ -97,10 +62,18 @@ public class NearbyPlaces { return places; } - private List getFromWikidataQuery(LatLng cur, String lang, double radius) + private List getFromWikidataQuery(Context context, + LatLng cur, + String lang, + double radius) throws IOException { List places = new ArrayList<>(); - String query = WIKIDATA_QUERY_TEMPLATE.replace("${RADIUS}", "" + radius) + + String query = FileUtils.readFromFile(context, "queries/nearby_query.txt"); + + Timber.d(query); + + query = query.replace("${RADIUS}", "" + radius) .replace("${LAT}", "" + String.format(Locale.ROOT, "%.3f", cur.latitude)) .replace("${LONG}", "" + String.format(Locale.ROOT, "%.3f", cur.longitude)) .replace("${LANG}", "" + lang); @@ -124,6 +97,8 @@ public class NearbyPlaces { String point = fields[0]; String name = Utils.stripLocalizedString(fields[2]); String type = Utils.stripLocalizedString(fields[4]); + String sitelink = Utils.stripLocalizedString(fields[7]); + String wikiDataLink = Utils.stripLocalizedString(fields[3]); String icon = fields[5]; double latitude = 0; @@ -145,7 +120,9 @@ public class NearbyPlaces { type, // list type, // details Uri.parse(icon), - new LatLng(latitude, longitude) + new LatLng(latitude, longitude), + Uri.parse(sitelink), + Uri.parse(wikiDataLink) )); } in.close(); @@ -202,7 +179,9 @@ public class NearbyPlaces { type, // list type, // details null, - new LatLng(latitude, longitude) + new LatLng(latitude, longitude), + null, + null )); } in.close(); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java index 222a992cc..a975282e9 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java @@ -16,14 +16,19 @@ public class Place { public Bitmap image; public Bitmap secondaryImage; public String distance; + public Uri siteLink; + public Uri wikiDataLink; + public Place(String name, String description, String longDescription, - Uri secondaryImageUrl, LatLng location) { + Uri secondaryImageUrl, LatLng location, Uri siteLink, Uri wikiDataLink) { this.name = name; this.description = description; this.longDescription = longDescription; this.secondaryImageUrl = secondaryImageUrl; this.location = location; + this.siteLink = siteLink; + this.wikiDataLink = wikiDataLink; } public void setDistance(String distance) { diff --git a/app/src/main/java/fr/free/nrw/commons/ui/widget/OverlayDialog.java b/app/src/main/java/fr/free/nrw/commons/ui/widget/OverlayDialog.java new file mode 100644 index 000000000..69bc8549f --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/ui/widget/OverlayDialog.java @@ -0,0 +1,48 @@ +package fr.free.nrw.commons.ui.widget; + +import android.app.Dialog; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.view.Gravity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import fr.free.nrw.commons.R; + +public abstract class OverlayDialog extends DialogFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(STYLE_NORMAL, R.style.borderless_dialog); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + setDialogLayoutToFullScreen(); + super.onViewCreated(view, savedInstanceState); + } + + private void setDialogLayoutToFullScreen() { + Window window = getDialog().getWindow(); + WindowManager.LayoutParams wlp = window.getAttributes(); + window.requestFeature(Window.FEATURE_NO_TITLE); + wlp.gravity = Gravity.BOTTOM; + wlp.width = WindowManager.LayoutParams.MATCH_PARENT; + wlp.height = WindowManager.LayoutParams.MATCH_PARENT; + window.setAttributes(wlp); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + Window window = dialog.getWindow(); + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + return dialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java index b5be52a9f..2ead068ac 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java @@ -113,7 +113,7 @@ public class ShareActivity Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG); startingToast.show(); - if (cacheFound == false) { + if (!cacheFound) { //Has to be called after apiCall.request() app.cacheData.cacheCategory(); Timber.d("Cache the categories found"); diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java b/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java new file mode 100644 index 000000000..3992324b5 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java @@ -0,0 +1,80 @@ +package fr.free.nrw.commons.utils; + +import android.app.Activity; +import android.app.Dialog; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; + +import timber.log.Timber; + +public class DialogUtil { + + public static void dismissSafely(@Nullable Activity activity, @Nullable DialogFragment dialog) { + boolean isActivityDestroyed = false; + + if (activity == null || dialog == null) { + Timber.d("dismiss called with null activity / dialog. Ignoring."); + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + isActivityDestroyed = activity.isDestroyed(); + } + if (activity.isFinishing() || isActivityDestroyed) { + return; + } + try { + dialog.dismiss(); + + } catch (IllegalStateException e) { + Timber.e(e, "Could not dismiss dialog."); + } + } + + public static void showSafely(Activity activity, Dialog dialog) { + if (activity == null || dialog == null) { + Timber.d("Show called with null activity / dialog. Ignoring."); + return; + } + + boolean isActivityDestroyed = false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + isActivityDestroyed = activity.isDestroyed(); + } + if (activity.isFinishing() || isActivityDestroyed) { + Timber.e("Activity is not running. Could not show dialog. "); + return; + } + try { + dialog.show(); + } catch (IllegalStateException e) { + Timber.e(e, "Could not show dialog."); + } + } + + public static void showSafely(FragmentActivity activity, DialogFragment dialog) { + boolean isActivityDestroyed = false; + + if (activity == null || dialog == null) { + Timber.d("show called with null activity / dialog. Ignoring."); + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + isActivityDestroyed = activity.isDestroyed(); + } + if (activity.isFinishing() || isActivityDestroyed) { + return; + } + + try { + if (dialog.getDialog() == null || !dialog.getDialog().isShowing()) { + dialog.show(activity.getSupportFragmentManager(), dialog.getClass().getSimpleName()); + } + } catch (IllegalStateException e) { + Timber.e(e, "Could not show dialog."); + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java new file mode 100644 index 000000000..4cb20ae0a --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java @@ -0,0 +1,33 @@ +package fr.free.nrw.commons.utils; + +import android.content.Context; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class FileUtils { + public static String readFromFile(Context context, String fileName) { + String stringBuilder = ""; + BufferedReader reader = null; + try { + reader = new BufferedReader( + new InputStreamReader(context.getAssets().open(fileName), "UTF-8")); + String mLine; + while ((mLine = reader.readLine()) != null) { + stringBuilder += mLine + "\n"; + } + } catch (IOException e) { + //log the exception + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + //log the exception + } + } + } + return stringBuilder; + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/utils/UriDeserializer.java b/app/src/main/java/fr/free/nrw/commons/utils/UriDeserializer.java new file mode 100644 index 000000000..ad37b27fc --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/UriDeserializer.java @@ -0,0 +1,18 @@ +package fr.free.nrw.commons.utils; + +import android.net.Uri; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import java.lang.reflect.Type; + +public class UriDeserializer implements JsonDeserializer { + @Override + public Uri deserialize(final JsonElement src, final Type srcType, + final JsonDeserializationContext context) throws JsonParseException { + return Uri.parse(src.getAsString()); + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/utils/UriSerializer.java b/app/src/main/java/fr/free/nrw/commons/utils/UriSerializer.java new file mode 100644 index 000000000..d5a4c28ec --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/UriSerializer.java @@ -0,0 +1,17 @@ +package fr.free.nrw.commons.utils; + +import android.net.Uri; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; + + +public class UriSerializer implements JsonSerializer { + public JsonElement serialize(Uri src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toString()); + } +} diff --git a/app/src/main/res/layout/activity_nearby.xml b/app/src/main/res/layout/activity_nearby.xml index d03ae8381..53f31056b 100644 --- a/app/src/main/res/layout/activity_nearby.xml +++ b/app/src/main/res/layout/activity_nearby.xml @@ -5,6 +5,13 @@ android:layout_height="match_parent" > + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_media_detail.xml b/app/src/main/res/layout/fragment_media_detail.xml index db03bccba..279957270 100644 --- a/app/src/main/res/layout/fragment_media_detail.xml +++ b/app/src/main/res/layout/fragment_media_detail.xml @@ -159,6 +159,35 @@ /> + + + + + - - 120dp 4dp 8dp + 240dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0414f98fa..536f66e41 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -170,5 +170,7 @@ Tap this message (or hit back) to skip this step. https://play.google.com/apps/testing/fr.free.nrw.commons Use Wikidata (Warning: disabling this may cause large mobile data consumption) + mapbox://styles/mapbox/traffic-day-v2 + mapbox://styles/mapbox/traffic-night-v2 pk.eyJ1IjoibWFza2FyYXZpdmVrIiwiYSI6ImNqMmxvdzFjMTAwMHYzM283ZWM3eW5tcDAifQ.ib5SZ9EVjwJe6GSKve0bcg diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a253fef6a..39fa1e4c4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -45,4 +45,10 @@ + \ No newline at end of file