Merge remote-tracking branch 'refs/remotes/commons-app/master'

This commit is contained in:
Josephine Lim 2017-05-14 13:41:38 +02:00
commit 66c5266677
36 changed files with 1030 additions and 297 deletions

View file

@ -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

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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<String> categories; // as loaded at runtime?
protected Map<String, String> descriptions; // multilingual descriptions as loaded

View file

@ -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);
}

View file

@ -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);
}

View file

@ -32,7 +32,7 @@ public class CacheController {
public void cacheCategory() {
List<String> pointCatList = new ArrayList<>();
if (MwVolleyApi.GpsCatExists.getGpsCatExists() == true) {
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
pointCatList.addAll(MwVolleyApi.getGpsCat());
Timber.d("Categories being cached: %s", pointCatList);
} else {

View file

@ -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. :)

View file

@ -108,7 +108,6 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
}
Timber.d("Found categories from Method A search, waiting for filter");
ArrayList<String> filteredItems = new ArrayList<>(filterYears(categories));
return filteredItems;
return new ArrayList<>(filterYears(categories));
}
}

View file

@ -83,16 +83,14 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
if(TextUtils.isEmpty(filter)) {
ArrayList<String> mergedItems = new ArrayList<>(catFragment.mergeItems());
Timber.d("Merged items, waiting for filter");
ArrayList<String> 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<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter));
Timber.d("Found cache items, waiting for filter");
ArrayList<String> 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<Void, Void, ArrayList<String>> {
}
Timber.d("Found categories from Prefix search, waiting for filter");
ArrayList<String> filteredItems = new ArrayList<>(filterYears(categories));
return filteredItems;
return new ArrayList<>(filterYears(categories));
}
}

View file

@ -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());

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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<String> 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;
}
}

View file

@ -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<Void, Integer, List<Place>> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected List<Place> doInBackground(Void... params) {
return NearbyController
.loadAttractionsFromLocation(curLatLang, getApplicationContext()
);
}
@Override
protected void onPostExecute(List<Place> 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();
}
}

View file

@ -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<NearbyMarker, NearbyBaseMarker> {
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<NearbyBaseMarker> CREATOR
= new Parcelable.Creator<NearbyBaseMarker>() {
public NearbyBaseMarker createFromParcel(Parcel in) {
return new NearbyBaseMarker(in);
}
public NearbyBaseMarker[] newArray(int size) {
return new NearbyBaseMarker[size];
}
};
}

View file

@ -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<Place> 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<Place> 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<Place> 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<Place> loadAttractionsFromLocationToPlaces(LatLng curLatLng,
Context context) {
List<Place> places = loadAttractionsFromLocation(curLatLng,context);
places = places.subList(0, Math.min(places.size(), MAX_RESULTS));
for (Place place: places) {
public static List<Place> loadAttractionsFromLocationToPlaces(
LatLng curLatLng,
List<Place> 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<BaseMarkerOptions> loadAttractionsFromLocationToBaseMarkerOptions(
public static List<NearbyBaseMarker> loadAttractionsFromLocationToBaseMarkerOptions(
LatLng curLatLng,
Context context) {
List<BaseMarkerOptions> baseMarkerOptionses = new ArrayList<>();
List<Place> places = loadAttractionsFromLocation(curLatLng,context);
places = places.subList(0, Math.min(places.size(), MAX_RESULTS));
for (Place place: places) {
List<Place> placeList) {
List<NearbyBaseMarker> 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;
}

View file

@ -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);
}
}

View file

@ -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<Place> 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<List<Place>>() {}.getType();
placeList = gson.fromJson(gsonPlaceList, listType);
Type curLatLngType = new TypeToken<LatLng>() {}.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<Void, Integer, List<Place>> {
@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<Place> doInBackground(Void... params) {
return NearbyController.loadAttractionsFromLocationToPlaces(
((NearbyActivity)getActivity())
.getLocationManager()
.getLatestLocation(), getActivity()
);
}
@Override
protected void onPostExecute(List<Place> 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);
}
}

View file

@ -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<Place> placeList;
private List<NearbyBaseMarker> 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<List<Place>>() {}.getType();
placeList = gson.fromJson(gsonPlaceList, listType);
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {}.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<Void, Integer, List<BaseMarkerOptions>> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected List<BaseMarkerOptions> doInBackground(Void... params) {
return NearbyController
.loadAttractionsFromLocationToBaseMarkerOptions(currentLocation, getActivity()
);
}
@Override
protected void onPostExecute(final List<BaseMarkerOptions> baseMarkerOptionses) {
super.onPostExecute(baseMarkerOptionses);
if (isCancelled()) {
return;
}
mapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(MapboxMap mapboxMap) {
mapboxMap.addMarkers(baseMarkerOptionses);
}
});
}
}
}

View file

@ -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;
}
}

View file

@ -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<Place> places;
@ -73,13 +36,15 @@ public class NearbyPlaces {
private NearbyPlaces(){
}
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) {
List<Place> getFromWikidataQuery(Context context,
LatLng curLatLng,
String lang) {
List<Place> 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<Place> getFromWikidataQuery(LatLng cur, String lang, double radius)
private List<Place> getFromWikidataQuery(Context context,
LatLng cur,
String lang,
double radius)
throws IOException {
List<Place> 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();

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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");

View file

@ -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.");
}
}
}

View file

@ -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;
}
}

View file

@ -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<Uri> {
@Override
public Uri deserialize(final JsonElement src, final Type srcType,
final JsonDeserializationContext context) throws JsonParseException {
return Uri.parse(src.getAsString());
}
}

View file

@ -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<Uri> {
public JsonElement serialize(Uri src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}

View file

@ -5,6 +5,13 @@
android:layout_height="match_parent"
>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
/>
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"

View file

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:behavior_peekHeight="@dimen/bottom_peak_height">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:minHeight="@dimen/bottom_peak_height"
android:background="@android:color/white"
android:animateLayoutChanges="true">
<LinearLayout
android:id="@+id/link_preview_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:background="?attr/selectableItemBackground"
android:minHeight="64dp">
<TextView
android:id="@+id/link_preview_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:paddingBottom="4dp"
android:textSize="20sp"
android:fontFamily="serif"
android:lineSpacingMultiplier="0.9"
android:maxLines="3"
android:ellipsize="end"
android:layout_marginLeft="12dp"
android:textColor="@android:color/black"
tools:text="Lorem ipsum" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="@android:color/black"/>
<TextView
android:id="@+id/link_preview_extract"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textSize="16sp"
android:lineSpacingMultiplier="1.3"
android:textIsSelectable="true"
android:textColor="@android:color/black"
tools:text="Lorem ipsum"/>
<View
android:id="@+id/link_preview_bottom_padding"
android:layout_width="match_parent"
android:layout_height="70dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<TextView
android:id="@+id/link_preview_directions_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="16dp"
android:paddingBottom="16dp"
style="@style/Widget.AppCompat.Button.Borderless"
android:textColor="@android:color/black"
android:text="GET DIRECTIONS"/>
<TextView
android:id="@+id/link_preview_go_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="16dp"
android:paddingBottom="16dp"
style="@style/Widget.AppCompat.Button.Borderless"
android:textColor="@android:color/black"
android:text="READ ARTICLE"/>
</LinearLayout>
</FrameLayout>

View file

@ -159,6 +159,35 @@
/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:padding="16dp"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:text="Coordinates"
android:textSize="16sp"
android:textStyle="bold"
android:paddingBottom="6dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="License link"
android:id="@+id/mediaDetailCoordinates"
android:layout_gravity="left|start"
android:background="?attr/subBackground"
android:textColor="@android:color/white"
android:textSize="14sp"
android:padding="12dp"
/>
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
android:layout_width="match_parent"
android:layout_height="8dp"

View file

@ -4,13 +4,6 @@
android:orientation="vertical"
>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
/>
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"

View file

@ -2,4 +2,5 @@
<dimen name="icon_size">120dp</dimen>
<dimen name="tiny_margin">4dp</dimen>
<dimen name="small_margin">8dp</dimen>
<dimen name="bottom_peak_height">240dp</dimen>
</resources>

View file

@ -170,5 +170,7 @@ Tap this message (or hit back) to skip this step.</string>
<string name="beta_opt_in_link">https://play.google.com/apps/testing/fr.free.nrw.commons</string>
<string name="use_wikidata">Use Wikidata</string>
<string name="use_wikidata_summary">(Warning: disabling this may cause large mobile data consumption)</string>
<string name="map_theme_light">mapbox://styles/mapbox/traffic-day-v2</string>
<string name="map_theme_dark">mapbox://styles/mapbox/traffic-night-v2</string>
<string name="mapbox_commons_app_token">pk.eyJ1IjoibWFza2FyYXZpdmVrIiwiYSI6ImNqMmxvdzFjMTAwMHYzM283ZWM3eW5tcDAifQ.ib5SZ9EVjwJe6GSKve0bcg</string>
</resources>

View file

@ -45,4 +45,10 @@
<style name="ProgressBar" parent="Widget.AppCompat.ProgressBar.Horizontal" />
<style name="borderless_dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:background">@android:color/transparent</item>
</style>
</resources>