Generate Nearby list using Wikidata Query

Plus,
* remove duplicate location service manager
* add option to switch data sources of Nearby
* faster sorting of places
* minor coding style improvents
This commit is contained in:
Yusuke Matsubara 2017-04-13 18:40:36 +09:00
parent eb99350caf
commit 8731987385
11 changed files with 302 additions and 82 deletions

View file

@ -8,12 +8,7 @@ import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.net.URLCodec;
import org.w3c.dom.Node;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import fr.free.nrw.commons.settings.Prefs;
import java.io.BufferedInputStream;
import java.io.IOException;
@ -28,6 +23,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.transform.Transformer;
@ -38,7 +34,13 @@ import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import fr.free.nrw.commons.settings.Prefs;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.net.URLCodec;
import org.w3c.dom.Node;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
public class Utils {
@ -72,7 +74,7 @@ public class Utils {
} catch (IOException e) {
Log.e(TAG, "IO Exception", e);
return "";
} finally {
} finally {
try {
is.close();
} catch (IOException e) {
@ -81,6 +83,22 @@ public class Utils {
}
}
/**
* Strips localization symbols from a string.
* Removes the suffix after "@" and quotes.
*
* @param s string possibly containing localization symbols
* @return stripped string
*/
public static String stripLocalizedString(String s) {
Matcher matcher = Pattern.compile("\\\"(.*)\\\"(@\\w+)?").matcher(s);
if (matcher.find()) {
return matcher.group(1);
} else {
return s;
}
}
public static Date parseMWDate(String mwDate) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
try {
@ -128,8 +146,9 @@ public class Utils {
}
private static DisplayImageOptions.Builder defaultImageOptionsBuilder;
public static DisplayImageOptions.Builder getGenericDisplayOptions() {
if(defaultImageOptionsBuilder == null) {
if (defaultImageOptionsBuilder == null) {
defaultImageOptionsBuilder = new DisplayImageOptions.Builder().cacheInMemory()
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
@ -159,7 +178,7 @@ public class Utils {
public static long countBytes(InputStream stream) throws IOException {
long count = 0;
BufferedInputStream bis = new BufferedInputStream(stream);
while(bis.read() != -1) {
while (bis.read() != -1) {
count++;
}
return count;
@ -168,11 +187,11 @@ public class Utils {
public static String makeThumbUrl(String imageUrl, String filename, int width) {
// Ugly Hack!
// Update: OH DEAR GOD WHAT A HORRIBLE HACK I AM SO SORRY
if(imageUrl.endsWith("webm")) {
if (imageUrl.endsWith("webm")) {
return imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px--" + filename.replaceAll("File:", "").replaceAll(" ", "_") + ".jpg";
} else {
String thumbUrl = imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px-" + filename.replaceAll("File:", "").replaceAll(" ", "_");
if(thumbUrl.endsWith("jpg") || thumbUrl.endsWith("png") || thumbUrl.endsWith("jpeg")) {
if (thumbUrl.endsWith("jpg") || thumbUrl.endsWith("png") || thumbUrl.endsWith("jpeg")) {
return thumbUrl;
} else {
return thumbUrl + ".png";
@ -181,50 +200,49 @@ public class Utils {
}
public static String capitalize(String string) {
return string.substring(0,1).toUpperCase(Locale.getDefault()) + string.substring(1);
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
}
public static String licenseTemplateFor(String license) {
if(license.equals(Prefs.Licenses.CC_BY_3)) {
if (license.equals(Prefs.Licenses.CC_BY_3)) {
return "{{self|cc-by-3.0}}";
} else if(license.equals(Prefs.Licenses.CC_BY_4)) {
} 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)) {
} 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)) {
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
return "{{self|cc-by-sa-4.0}}";
} else if(license.equals(Prefs.Licenses.CC0)) {
} else if (license.equals(Prefs.Licenses.CC0)) {
return "{{self|cc-zero}}";
}
throw new RuntimeException("Unrecognized license value");
}
public static int licenseNameFor(String license) {
if(license.equals(Prefs.Licenses.CC_BY_3)) {
if (license.equals(Prefs.Licenses.CC_BY_3)) {
return R.string.license_name_cc_by;
} else if(license.equals(Prefs.Licenses.CC_BY_4)) {
} 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)) {
} 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)) {
} 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)) {
} else if (license.equals(Prefs.Licenses.CC0)) {
return R.string.license_name_cc0;
}
throw new RuntimeException("Unrecognized license value");
}
public static String licenseUrlFor(String license) {
if(license.equals(Prefs.Licenses.CC_BY_3)) {
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)) {
} 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)) {
} 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)) {
} 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)) {
} else if (license.equals(Prefs.Licenses.CC0)) {
return "https://creativecommons.org/publicdomain/zero/1.0/";
}
throw new RuntimeException("Unrecognized license value");

View file

@ -32,12 +32,14 @@ public class NearbyActivity extends BaseActivity {
ft.add(R.id.container, fragment);
ft.commit();
}
@Override
public boolean onCreateOptionsMenu (Menu menu) {
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_nearby, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
@ -49,19 +51,23 @@ public class NearbyActivity extends BaseActivity {
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onResume(){
protected void onResume() {
super.onResume();
}
protected void refreshView()
{
protected void refreshView() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new NearbyListFragment()).commit();
}
public LocationServiceManager getLocationManager() {
return locationManager;
}
@Override
protected void onDestroy(){
protected void onDestroy() {
super.onDestroy();
locationManager.unregisterLocationManager();
}

View file

@ -33,7 +33,7 @@ public class NearbyAdapter extends ArrayAdapter<Place> {
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
Place place = getItem(position);
Log.d("NearbyAdapter", "Place " + place.name);
Log.v("NearbyAdapter", "" + place);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {

View file

@ -4,9 +4,11 @@ import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.LayoutInflater;
@ -20,16 +22,18 @@ import butterknife.ButterKnife;
import butterknife.OnItemClick;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class NearbyListFragment extends ListFragment implements TaskListener {
private static final int MAX_RESULTS = 1000;
private NearbyAsyncTask nearbyAsyncTask;
private NearbyAdapter mAdapter;
@BindView(R.id.listview) ListView listview;
@BindView(R.id.progressBar) ProgressBar progressBar;
@ -37,7 +41,6 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
private boolean isTaskRunning = false;
private List<Place> places;
private LatLng mLatestLocation;
private static final String TAG = NearbyListFragment.class.getName();
@ -64,8 +67,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
public void onViewCreated(View view, Bundle savedInstanceState) {
//Check that this is the first time view is created, to avoid double list when screen orientation changed
if(savedInstanceState == null) {
mLatestLocation = new LocationServiceManager(getActivity()).getLatestLocation();
if (savedInstanceState == null) {
nearbyAsyncTask = new NearbyAsyncTask(this);
nearbyAsyncTask.execute();
progressBar.setVisibility(View.VISIBLE);
@ -116,7 +118,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
super.onDestroy();
// See http://stackoverflow.com/questions/18264408/incomplete-asynctask-crashes-my-app
if(nearbyAsyncTask != null && nearbyAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
if (nearbyAsyncTask != null && nearbyAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
nearbyAsyncTask.cancel(true);
}
}
@ -125,7 +127,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
private final TaskListener listener;
public NearbyAsyncTask (TaskListener listener) {
public NearbyAsyncTask(TaskListener listener) {
this.listener = listener;
}
@ -143,7 +145,9 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
@Override
protected List<Place> doInBackground(Void... params) {
places = loadAttractionsFromLocation(mLatestLocation);
places = loadAttractionsFromLocation(
((NearbyActivity)getActivity()).getLocationManager().getLatestLocation()
);
return places;
}
@ -151,18 +155,17 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
protected void onPostExecute(List<Place> result) {
super.onPostExecute(result);
if(isCancelled()) {
if (isCancelled()) {
return;
}
progressBar.setVisibility(View.GONE);
NearbyAdapter adapter = new NearbyAdapter(getActivity(), places);
mAdapter = new NearbyAdapter(getActivity(), places);
listview.setAdapter(mAdapter);
listview.setAdapter(adapter);
listener.onTaskFinished(result);
mAdapter.notifyDataSetChanged();
adapter.notifyDataSetChanged();
}
}
@ -188,31 +191,38 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
}
}
private List<Place> loadAttractionsFromLocation(final LatLng curLatLng) {
List<Place> places = NearbyPlaces.get();
private List<Place> loadAttractionsFromLocation(LatLng curLatLng) {
Log.d(TAG, "Loading attractions near " + curLatLng);
if (curLatLng == null) {
return Collections.emptyList();
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
List<Place> places = prefs.getBoolean("useWikidata", true)
? NearbyPlaces.getInstance().getFromWikidataQuery(
curLatLng, Locale.getDefault().getLanguage())
: NearbyPlaces.getInstance().getFromWikiNeedsPictures();
if (curLatLng != null) {
Log.d(TAG, "Sorting places by distance...");
final Map<Place, Double> distances = new HashMap<>();
for (Place place: places) {
distances.put(place, computeDistanceBetween(place.location, curLatLng));
}
Collections.sort(places,
new Comparator<Place>() {
@Override
public int compare(Place lhs, Place rhs) {
double lhsDistance = computeDistanceBetween(
lhs.location, curLatLng);
double rhsDistance = computeDistanceBetween(
rhs.location, curLatLng);
double lhsDistance = distances.get(lhs);
double rhsDistance = distances.get(rhs);
return (int) (lhsDistance - rhsDistance);
}
}
);
}
if (places.size() > 0) {
for (int i = 0; i < 100; i++) {
Place place = places.get(i);
String distance = formatDistanceBetween(mLatestLocation, place.location);
place.setDistance(distance);
}
places = places.subList(0, Math.min(places.size(), MAX_RESULTS));
for (Place place: places) {
String distance = formatDistanceBetween(curLatLng, place.location);
place.setDistance(distance);
}
return places;
}

View file

@ -1,31 +1,166 @@
package fr.free.nrw.commons.nearby;
import android.net.Uri;
import android.os.StrictMode;
import android.util.Log;
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;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NearbyPlaces {
private static final String TAG = NearbyPlaces.class.getName();
static List<Place> places = null;
private static final int MIN_RESULTS = 40;
private static final double INITIAL_RADIUS = 1.0;
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;
public static List<Place> get() {
if(places != null) {
return places;
private NearbyPlaces(){
}
List<Place> getFromWikidataQuery(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);
Log.d(TAG, places.size() + " results at radius: " + radius);
if (places.size() >= MIN_RESULTS) {
break;
} else {
radius *= RADIUS_MULTIPLIER;
}
}
} catch (IOException e) {
Log.d(TAG, "" + e.toString());
// errors tend to be caused by too many results (and time out)
// try a small radius next time
Log.d(TAG, "back to initial radius: " + radius);
radius = INITIAL_RADIUS;
}
else {
return places;
}
private List<Place> getFromWikidataQuery(LatLng cur, String lang, double radius)
throws IOException {
List<Place> places = new ArrayList<>();
String query = WIKIDATA_QUERY_TEMPLATE.replace("${RADIUS}", "" + radius)
.replace("${LAT}", "" + String.format("%.3f", cur.latitude))
.replace("${LONG}", "" + String.format("%.3f", cur.longitude))
.replace("${LANG}", "" + lang);
query = URLEncoder.encode(query, "utf-8").replace("+", "%20");
String url = WIKIDATA_QUERY_URL.replace("${QUERY}", query);
Log.d(TAG, url.toString());
URLConnection conn = new URL(url).openConnection();
conn.setRequestProperty("Accept", "text/tab-separated-values");
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
Log.d(TAG, "Reading from query result...");
while ((line = in.readLine()) != null) {
Log.v(TAG, line);
line = line + "\n"; // to pad columns and make fields a fixed size
if (!line.startsWith("\"Point")) {
continue;
}
String[] fields = line.split("\t");
String point = fields[0];
String name = Utils.stripLocalizedString(fields[2]);
String type = Utils.stripLocalizedString(fields[4]);
String icon = fields[5];
double latitude = 0;
double longitude = 0;
Matcher matcher =
Pattern.compile("Point\\(([^ ]+) ([^ ]+)\\)").matcher(point);
if (!matcher.find()) {
continue;
}
try {
longitude = Double.parseDouble(matcher.group(1));
latitude = Double.parseDouble(matcher.group(2));
} catch (NumberFormatException e) {
throw new RuntimeException("LatLng parse error: " + point);
}
places.add(new Place(
name,
type, // list
type, // details
Uri.parse(icon),
new LatLng(latitude, longitude)
));
}
in.close();
return places;
}
List<Place> getFromWikiNeedsPictures() {
if (places != null) {
return places;
} else {
try {
places = new ArrayList<>();
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.ThreadPolicy policy
= new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
URL file = new URL("https://tools.wmflabs.org/wiki-needs-pictures/data/data.csv");
@ -45,7 +180,7 @@ public class NearbyPlaces {
}
String[] fields = line.split(",");
String name = fields[0];
String name = Utils.stripLocalizedString(fields[0]);
double latitude;
double longitude;
@ -73,10 +208,22 @@ public class NearbyPlaces {
in.close();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, e.toString());
}
}
return places;
}
/**
* Get the singleton instance of this class.
* The instance is created upon the first invocation of this method, and then reused.
*
* @return The singleton instance
*/
public static synchronized NearbyPlaces getInstance() {
if (singleton == null) {
singleton = new NearbyPlaces();
}
return singleton;
}
}

View file

@ -23,10 +23,8 @@ public class NearbyViewHolder implements ViewHolder<Place> {
@Override
public void bindModel(Context context, Place place) {
String quotelessName = place.name.replaceAll("^\"|\"$", "");
// Populate the data into the template view using the data object
tvName.setText(quotelessName);
tvName.setText(place.name);
tvDesc.setText(place.description);
distance.setText(place.distance);

View file

@ -7,18 +7,16 @@ import fr.free.nrw.commons.location.LatLng;
public class Place {
public String name;
public String description;
public String longDescription;
public Uri secondaryImageUrl;
public LatLng location;
public final String name;
public final String description;
public final String longDescription;
public final Uri secondaryImageUrl;
public final LatLng location;
public Bitmap image;
public Bitmap secondaryImage;
public String distance;
public Place() {}
public Place(String name, String description, String longDescription,
Uri secondaryImageUrl, LatLng location) {
this.name = name;
@ -32,4 +30,24 @@ public class Place {
this.distance = distance;
}
@Override
public boolean equals(Object o) {
if (o instanceof Place) {
Place that = (Place)o;
return this.name.equals(that.name) && this.location.equals(that.location);
} else {
return false;
}
}
@Override
public int hashCode() {
return this.name.hashCode() * 31 + this.location.hashCode();
}
@Override
public String toString() {
return String.format("Place(%s@%s)", name, location);
}
}

View file

@ -27,8 +27,14 @@ public class LengthUtils {
return numberFormat.format(distance) + "m";
}
/**
* Computes the distance between two points.
* @param from one of the two end points
* @param to one of the two end points
* @return distance between the points in meter
*/
public static double computeDistanceBetween(LatLng from, LatLng to) {
return computeAngleBetween(from, to) * 6371009.0D;
return computeAngleBetween(from, to) * 6371009.0D; // Earth's radius in meter
}
private static double computeAngleBetween(LatLng from, LatLng to) {

View file

@ -165,4 +165,6 @@ Tap this message (or hit back) to skip this step.</string>
<string name="become_a_tester_title">Become a Beta Tester</string>
<string name="become_a_tester_description">Opt-in to our beta channel on Google Play and get early access to new features and bug fixes</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>
</resources>

View file

@ -31,4 +31,11 @@
android:key="theme"
/>
<CheckBoxPreference
android:key="useWikidata"
android:title="@string/use_wikidata"
android:defaultValue="true"
android:summary="@string/use_wikidata_summary"
/>
</PreferenceScreen>

View file

@ -1,8 +1,8 @@
package fr.free.nrw.commons;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
public class UtilsTest {
@ -11,7 +11,7 @@ public class UtilsTest {
assertThat(Utils.fixExtension("SampleFile.jpeg", "jpeg"), is("SampleFile.jpg"));
}
@Test public void fixExtensionJPEGToJpg() {
@Test public void fixExtensionJpegToJpg() {
assertThat(Utils.fixExtension("SampleFile.JPEG", null), is("SampleFile.jpg"));
}
@ -46,4 +46,12 @@ public class UtilsTest {
@Test public void fixExtensionJpegNotExtension() {
assertThat(Utils.fixExtension("SAMPLE.jpeg.SAMPLE", "jpg"), is("SAMPLE.jpeg.SAMPLE.jpg"));
}
@Test public void stripLocalizedStringPass() {
assertThat(Utils.stripLocalizedString("Hello"), is("Hello"));
}
@Test public void stripLocalizedStringJa() {
assertThat(Utils.stripLocalizedString("\"こんにちは\"@ja"), is("こんにちは"));
}
}