Merge pull request #244 from misaochan/nearby-listview
Add list of nearby places without photos
|
|
@ -82,6 +82,10 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".auth.SignupActivity"
|
android:name=".auth.SignupActivity"
|
||||||
android:label="@string/title_activity_signup"/>
|
android:label="@string/title_activity_signup"/>
|
||||||
|
<activity
|
||||||
|
android:name=".nearby.NearbyActivity"
|
||||||
|
android:label="@string/title_activity_nearby"
|
||||||
|
android:parentActivityName=".contributions.ContributionsActivity"/>
|
||||||
|
|
||||||
<service android:name=".upload.UploadService" >
|
<service android:name=".upload.UploadService" >
|
||||||
</service>
|
</service>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import fr.free.nrw.commons.AboutActivity;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.SettingsActivity;
|
import fr.free.nrw.commons.SettingsActivity;
|
||||||
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
import fr.free.nrw.commons.upload.UploadService;
|
import fr.free.nrw.commons.upload.UploadService;
|
||||||
|
|
||||||
public class ContributionsListFragment extends Fragment {
|
public class ContributionsListFragment extends Fragment {
|
||||||
|
|
@ -142,15 +143,28 @@ public class ContributionsListFragment extends Fragment {
|
||||||
feedbackIntent.setType("message/rfc822");
|
feedbackIntent.setType("message/rfc822");
|
||||||
feedbackIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { CommonsApplication.FEEDBACK_EMAIL });
|
feedbackIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { CommonsApplication.FEEDBACK_EMAIL });
|
||||||
feedbackIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(CommonsApplication.FEEDBACK_EMAIL_SUBJECT, CommonsApplication.APPLICATION_VERSION));
|
feedbackIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(CommonsApplication.FEEDBACK_EMAIL_SUBJECT, CommonsApplication.APPLICATION_VERSION));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivity(feedbackIntent);
|
startActivity(feedbackIntent);
|
||||||
}
|
}
|
||||||
catch (ActivityNotFoundException e) {
|
catch (ActivityNotFoundException e) {
|
||||||
Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.menu_nearby:
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (ContextCompat.checkSelfPermission(this.getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
//See http://stackoverflow.com/questions/33169455/onrequestpermissionsresult-not-being-called-in-dialog-fragment
|
||||||
|
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 2);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
|
||||||
|
startActivity(nearbyIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
|
||||||
|
startActivity(nearbyIntent);
|
||||||
|
}
|
||||||
case R.id.menu_refresh:
|
case R.id.menu_refresh:
|
||||||
((SourceRefresher)getActivity()).refreshSource();
|
((SourceRefresher)getActivity()).refreshSource();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -169,6 +183,14 @@ public class ContributionsListFragment extends Fragment {
|
||||||
controller.startGalleryPick();
|
controller.startGalleryPick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 2 = Location allowed when 'nearby places' selected
|
||||||
|
case 2: {
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Log.d("ContributionsList", "Location permission granted");
|
||||||
|
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
|
||||||
|
startActivity(nearbyIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
43
app/src/main/java/fr/free/nrw/commons/nearby/LatLng.java
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
public class LatLng {
|
||||||
|
|
||||||
|
public final double latitude;
|
||||||
|
public final double longitude;
|
||||||
|
|
||||||
|
LatLng(double latitude, double longitude) {
|
||||||
|
if(-180.0D <= longitude && longitude < 180.0D) {
|
||||||
|
this.longitude = longitude;
|
||||||
|
} else {
|
||||||
|
this.longitude = ((longitude - 180.0D) % 360.0D + 360.0D) % 360.0D - 180.0D;
|
||||||
|
}
|
||||||
|
this.latitude = Math.max(-90.0D, Math.min(90.0D, latitude));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
boolean var1 = true;
|
||||||
|
byte var2 = 1;
|
||||||
|
long var3 = Double.doubleToLongBits(this.latitude);
|
||||||
|
int var5 = 31 * var2 + (int)(var3 ^ var3 >>> 32);
|
||||||
|
var3 = Double.doubleToLongBits(this.longitude);
|
||||||
|
var5 = 31 * var5 + (int)(var3 ^ var3 >>> 32);
|
||||||
|
return var5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if(this == o) {
|
||||||
|
return true;
|
||||||
|
} else if(!(o instanceof LatLng)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
LatLng var2 = (LatLng)o;
|
||||||
|
return Double.doubleToLongBits(this.latitude) == Double.doubleToLongBits(var2.latitude) && Double.doubleToLongBits(this.longitude) == Double.doubleToLongBits(var2.longitude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "lat/lng: (" + this.latitude + "," + this.longitude + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
121
app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Criteria;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationListener;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
public class NearbyActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private MyLocationListener myLocationListener;
|
||||||
|
private LocationManager locationManager;
|
||||||
|
private String provider;
|
||||||
|
private Criteria criteria;
|
||||||
|
private LatLng mLatestLocation;
|
||||||
|
|
||||||
|
private double currentLatitude, currentLongitude;
|
||||||
|
//private String gpsCoords;
|
||||||
|
|
||||||
|
private static final String TAG = "NearbyActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_nearby);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
registerLocationManager();
|
||||||
|
|
||||||
|
// Begin the transaction
|
||||||
|
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||||
|
NearbyListFragment fragment = new NearbyListFragment();
|
||||||
|
ft.add(R.id.container, fragment);
|
||||||
|
ft.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume(){
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LatLng getmLatestLocation() {
|
||||||
|
return mLatestLocation;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Registers a LocationManager to listen for current location
|
||||||
|
*/
|
||||||
|
protected void registerLocationManager() {
|
||||||
|
locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
criteria = new Criteria();
|
||||||
|
provider = locationManager.getBestProvider(criteria, true);
|
||||||
|
myLocationListener = new MyLocationListener();
|
||||||
|
|
||||||
|
try {
|
||||||
|
locationManager.requestLocationUpdates(provider, 400, 1, myLocationListener);
|
||||||
|
Location location = locationManager.getLastKnownLocation(provider);
|
||||||
|
//Location works, just need to 'send' GPS coords via emulator extended controls if testing on emulator
|
||||||
|
Log.d(TAG, "Checking for location...");
|
||||||
|
if (location != null) {
|
||||||
|
myLocationListener.onLocationChanged(location);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.e(TAG, "Illegal argument exception", e);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.e(TAG, "Security exception", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void unregisterLocationManager() {
|
||||||
|
try {
|
||||||
|
locationManager.removeUpdates(myLocationListener);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.e(TAG, "Security exception", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for user's location when it changes
|
||||||
|
*/
|
||||||
|
private class MyLocationListener implements LocationListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationChanged(Location location) {
|
||||||
|
currentLatitude = location.getLatitude();
|
||||||
|
currentLongitude = location.getLongitude();
|
||||||
|
Log.d(TAG, "Latitude: " + String.valueOf(currentLatitude) + " Longitude: " + String.valueOf(currentLongitude));
|
||||||
|
|
||||||
|
mLatestLocation = new LatLng(currentLatitude, currentLongitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||||
|
Log.d(TAG, provider + "'s status changed to " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderEnabled(String provider) {
|
||||||
|
Log.d(TAG, "Provider " + provider + " enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderDisabled(String provider) {
|
||||||
|
Log.d(TAG, "Provider " + provider + " disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy(){
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
unregisterLocationManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Adapter;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ListAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
public class NearbyListFragment extends ListFragment {
|
||||||
|
|
||||||
|
private int mImageSize;
|
||||||
|
private boolean mItemClicked;
|
||||||
|
private NearbyAdapter mAdapter;
|
||||||
|
|
||||||
|
private List<Place> places;
|
||||||
|
private LatLng mLatestLocation;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
|
private static final String TAG = "NearbyListFragment";
|
||||||
|
|
||||||
|
public NearbyListFragment() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
Log.d(TAG, "NearbyListFragment created");
|
||||||
|
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||||
|
progressBar.setMax(10);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setProgress(0);
|
||||||
|
|
||||||
|
mLatestLocation = ((NearbyActivity) getActivity()).getmLatestLocation();
|
||||||
|
|
||||||
|
getNearbyPlaces nearbyList = new getNearbyPlaces();
|
||||||
|
nearbyList.execute();
|
||||||
|
|
||||||
|
Log.d(TAG, "Adapter set to ListView");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Place> loadAttractionsFromLocation(final LatLng curLatLng) {
|
||||||
|
|
||||||
|
List<Place> places = NearbyPlaces.get();
|
||||||
|
if (curLatLng != null) {
|
||||||
|
Log.d(TAG, "Sorting places by distance...");
|
||||||
|
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);
|
||||||
|
return (int) (lhsDistance - rhsDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < 500; i++) {
|
||||||
|
Place place = places.get(i);
|
||||||
|
String distance = formatDistanceBetween(mLatestLocation, place.location);
|
||||||
|
System.out.println("Sorted " + place.name + " at " + distance + " away.");
|
||||||
|
place.setDistance(distance);
|
||||||
|
}
|
||||||
|
return places;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class getNearbyPlaces extends AsyncTask<Void, Integer, List<Place>> {
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Integer... values) {
|
||||||
|
super.onProgressUpdate(values);
|
||||||
|
progressBar.setProgress(values[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Place> doInBackground(Void... params) {
|
||||||
|
places = loadAttractionsFromLocation(mLatestLocation);
|
||||||
|
return places;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(List<Place> result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
|
||||||
|
mAdapter = new NearbyAdapter(getActivity(), places);
|
||||||
|
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
ListView listview = (ListView) getView().findViewById(R.id.listview);
|
||||||
|
listview.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
|
||||||
|
|
||||||
|
Place place = places.get(position);
|
||||||
|
LatLng placeLatLng = place.location;
|
||||||
|
|
||||||
|
double latitude = placeLatLng.latitude;
|
||||||
|
double longitude = placeLatLng.longitude;
|
||||||
|
|
||||||
|
Log.d(TAG, "Item at position " + position + " has coords: Lat: " + latitude + " Long: " + 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NearbyAdapter extends ArrayAdapter<Place> {
|
||||||
|
|
||||||
|
public List<Place> placesList;
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
public NearbyAdapter(Context context, List<Place> places) {
|
||||||
|
super(context, R.layout.item_place, places);
|
||||||
|
mContext = context;
|
||||||
|
placesList = places;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
// Get the data item for this position
|
||||||
|
Place place = (Place) getItem(position);
|
||||||
|
Log.d(TAG, "Place " + place.name);
|
||||||
|
|
||||||
|
// Check if an existing view is being reused, otherwise inflate the view
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_place, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup view for data population
|
||||||
|
TextView tvName = (TextView) convertView.findViewById(R.id.tvName);
|
||||||
|
TextView tvDesc = (TextView) convertView.findViewById(R.id.tvDesc);
|
||||||
|
TextView distance = (TextView) convertView.findViewById(R.id.distance);
|
||||||
|
ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
|
||||||
|
|
||||||
|
String quotelessName = place.name.replaceAll("^\"|\"$", "");
|
||||||
|
|
||||||
|
// Populate the data into the template view using the data object
|
||||||
|
tvName.setText(quotelessName);
|
||||||
|
tvDesc.setText(place.description);
|
||||||
|
distance.setText(place.distance);
|
||||||
|
|
||||||
|
//Types of desc: landmark, city, edu, event, mountain, isle
|
||||||
|
switch(place.description) {
|
||||||
|
case "landmark":
|
||||||
|
icon.setImageResource(R.drawable.icon_landmark);
|
||||||
|
break;
|
||||||
|
case "city":
|
||||||
|
icon.setImageResource(R.drawable.icon_city);
|
||||||
|
break;
|
||||||
|
case "edu":
|
||||||
|
icon.setImageResource(R.drawable.icon_edu);
|
||||||
|
break;
|
||||||
|
case "event":
|
||||||
|
icon.setImageResource(R.drawable.icon_event);
|
||||||
|
break;
|
||||||
|
case "mountain":
|
||||||
|
icon.setImageResource(R.drawable.icon_mountain);
|
||||||
|
break;
|
||||||
|
case "isle":
|
||||||
|
icon.setImageResource(R.drawable.icon_isle);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
icon.setImageResource(R.drawable.empty_photo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the completed view to render on screen
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatDistanceBetween(LatLng point1, LatLng point2) {
|
||||||
|
if (point1 == null || point2 == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberFormat numberFormat = NumberFormat.getNumberInstance();
|
||||||
|
double distance = Math.round(computeDistanceBetween(point1, point2));
|
||||||
|
|
||||||
|
// Adjust to KM if M goes over 1000 (see javadoc of method for note
|
||||||
|
// on only supporting metric)
|
||||||
|
if (distance >= 1000) {
|
||||||
|
numberFormat.setMaximumFractionDigits(1);
|
||||||
|
return numberFormat.format(distance / 1000) + "km";
|
||||||
|
}
|
||||||
|
return numberFormat.format(distance) + "m";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double computeDistanceBetween(LatLng from, LatLng to) {
|
||||||
|
return computeAngleBetween(from, to) * 6371009.0D;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double computeAngleBetween(LatLng from, LatLng to) {
|
||||||
|
return distanceRadians(Math.toRadians(from.latitude), Math.toRadians(from.longitude), Math.toRadians(to.latitude), Math.toRadians(to.longitude));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static double distanceRadians(double lat1, double lng1, double lat2, double lng2) {
|
||||||
|
return arcHav(havDistance(lat1, lat2, lng1 - lng2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double arcHav(double x) {
|
||||||
|
return 2.0D * Math.asin(Math.sqrt(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double havDistance(double lat1, double lat2, double dLng) {
|
||||||
|
return hav(lat1 - lat2) + hav(dLng) * Math.cos(lat1) * Math.cos(lat2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double hav(double x) {
|
||||||
|
double sinHalf = Math.sin(x * 0.5D);
|
||||||
|
return sinHalf * sinHalf;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.StrictMode;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class NearbyPlaces {
|
||||||
|
|
||||||
|
static List<Place> places = null;
|
||||||
|
|
||||||
|
public static List<Place> get() {
|
||||||
|
if(places != null) {
|
||||||
|
return places;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
places = new ArrayList<Place>();
|
||||||
|
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");
|
||||||
|
|
||||||
|
BufferedReader in = new BufferedReader(new InputStreamReader(file.openStream()));
|
||||||
|
|
||||||
|
boolean firstLine = true;
|
||||||
|
String line;
|
||||||
|
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
line = in.readLine();
|
||||||
|
|
||||||
|
// Skip CSV header.
|
||||||
|
if (firstLine) {
|
||||||
|
firstLine = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(line);
|
||||||
|
String[] fields = line.split(",");
|
||||||
|
String name = fields[0];
|
||||||
|
|
||||||
|
double latitude;
|
||||||
|
double longitude;
|
||||||
|
try {
|
||||||
|
latitude = Double.parseDouble(fields[1]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
latitude = 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
longitude = Double.parseDouble(fields[2]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
longitude = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String type = fields[3];
|
||||||
|
String image;
|
||||||
|
|
||||||
|
places.add(new Place(
|
||||||
|
name,
|
||||||
|
type, // list
|
||||||
|
type, // details
|
||||||
|
null,
|
||||||
|
new LatLng(latitude, longitude)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return places;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
app/src/main/java/fr/free/nrw/commons/nearby/Place.java
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
public class Place {
|
||||||
|
|
||||||
|
public String name;
|
||||||
|
public String description;
|
||||||
|
public String longDescription;
|
||||||
|
public Uri secondaryImageUrl;
|
||||||
|
public 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;
|
||||||
|
this.description = description;
|
||||||
|
this.longDescription = longDescription;
|
||||||
|
this.secondaryImageUrl = secondaryImageUrl;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDistance(String distance) {
|
||||||
|
this.distance = distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -80,8 +80,8 @@ public class GPSExtractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts geolocation of image from EXIF data.
|
* Extracts geolocation (either of image from EXIF data, or of user)
|
||||||
* @return coordinates of image as string (needs to be passed as a String in API query)
|
* @return coordinates as string (needs to be passed as a String in API query)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getCoords(boolean useGPS) {
|
public String getCoords(boolean useGPS) {
|
||||||
|
|
@ -103,6 +103,7 @@ public class GPSExtractor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
||||||
if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null && useGPS) {
|
if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null && useGPS) {
|
||||||
registerLocationManager();
|
registerLocationManager();
|
||||||
|
|
||||||
|
|
@ -122,6 +123,7 @@ public class GPSExtractor {
|
||||||
} else if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
|
} else if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
//If image has EXIF data, extract image coords
|
||||||
imageCoordsExists = true;
|
imageCoordsExists = true;
|
||||||
Log.d(TAG, "EXIF data has location info");
|
Log.d(TAG, "EXIF data has location info");
|
||||||
|
|
||||||
|
|
@ -142,6 +144,9 @@ public class GPSExtractor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for user's location when it changes
|
||||||
|
*/
|
||||||
private class MyLocationListener implements LocationListener {
|
private class MyLocationListener implements LocationListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
BIN
app/src/main/res/drawable/empty_photo.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/drawable/icon_city.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
app/src/main/res/drawable/icon_edu.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app/src/main/res/drawable/icon_event.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
app/src/main/res/drawable/icon_isle.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
app/src/main/res/drawable/icon_landmark.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
app/src/main/res/drawable/icon_mountain.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/drawable/icon_war.jpg
Normal file
|
After Width: | Height: | Size: 203 KiB |
13
app/src/main/res/layout/activity_campaigns.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/campaignsList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
/>
|
||||||
|
</FrameLayout>
|
||||||
13
app/src/main/res/layout/activity_nearby.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
18
app/src/main/res/layout/fragment_campaigns.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<GridView android:id="@+id/campaignsList"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:stretchMode="columnWidth"
|
||||||
|
android:columnWidth="240dp"
|
||||||
|
android:numColumns="auto_fit"
|
||||||
|
android:listSelector="@null"
|
||||||
|
android:fadingEdge="none"
|
||||||
|
android:fastScrollEnabled="false"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
18
app/src/main/res/layout/fragment_nearby.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true" />
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/listview"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
59
app/src/main/res/layout/item_place.xml
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:layout_height="@dimen/icon_size">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="@dimen/icon_size"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:src="@drawable/empty_photo"
|
||||||
|
android:background="#ffffff"
|
||||||
|
android:scaleType="centerCrop" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/distance"
|
||||||
|
android:layout_width="@dimen/icon_size"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignBottom="@+id/icon"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="@dimen/tiny_margin"
|
||||||
|
style="?android:textAppearanceSmallInverse"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:background="@color/text_background"
|
||||||
|
tools:text="Overlay"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toEndOf="@+id/icon"
|
||||||
|
android:layout_toRightOf="@+id/icon"
|
||||||
|
android:paddingTop="@dimen/small_margin"
|
||||||
|
android:paddingLeft="@dimen/small_margin"
|
||||||
|
android:paddingRight="@dimen/small_margin"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="none"
|
||||||
|
style="?android:textAppearanceMedium"
|
||||||
|
tools:text="Name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvDesc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toEndOf="@+id/icon"
|
||||||
|
android:layout_toRightOf="@+id/icon"
|
||||||
|
android:layout_below="@+id/tvName"
|
||||||
|
android:padding="@dimen/small_margin"
|
||||||
|
android:ellipsize="none"
|
||||||
|
android:maxLines="4"
|
||||||
|
style="?android:textAppearanceSmall"
|
||||||
|
tools:text="Description" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</FrameLayout>
|
||||||
12
app/src/main/res/layout/layout_campaign_item.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/campaignItemName"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -27,6 +27,11 @@
|
||||||
app:showAsAction="never"
|
app:showAsAction="never"
|
||||||
android:icon="@android:drawable/ic_menu_send"
|
android:icon="@android:drawable/ic_menu_send"
|
||||||
/>
|
/>
|
||||||
|
<item android:id="@+id/menu_nearby"
|
||||||
|
android:title="@string/menu_nearby"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:icon="@android:drawable/ic_menu_myplaces"
|
||||||
|
/>
|
||||||
<item android:id="@+id/menu_refresh"
|
<item android:id="@+id/menu_refresh"
|
||||||
android:title="@string/menu_refresh"
|
android:title="@string/menu_refresh"
|
||||||
app:showAsAction="never"
|
app:showAsAction="never"
|
||||||
|
|
|
||||||
8
app/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<color name="text_background">#90000000</color>
|
||||||
|
<color name="transparent_actionbar_background">#22000000</color>
|
||||||
|
<color name="lighter_gray">#ddd</color>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -2,4 +2,7 @@
|
||||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
<dimen name="icon_size">120dp</dimen>
|
||||||
|
<dimen name="tiny_margin">4dp</dimen>
|
||||||
|
<dimen name="small_margin">8dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
<string name="contribution_state_starting">Uploading</string>
|
<string name="contribution_state_starting">Uploading</string>
|
||||||
<string name="menu_from_gallery">From Gallery</string>
|
<string name="menu_from_gallery">From Gallery</string>
|
||||||
<string name="menu_from_camera">Take photo</string>
|
<string name="menu_from_camera">Take photo</string>
|
||||||
|
<string name="menu_nearby">Nearby</string>
|
||||||
|
|
||||||
<string name="provider_contributions">My uploads</string>
|
<string name="provider_contributions">My uploads</string>
|
||||||
<string name="menu_share">Share</string>
|
<string name="menu_share">Share</string>
|
||||||
|
|
@ -154,4 +155,6 @@
|
||||||
<string name="location_permission_rationale">Optional permission: Get current location for category suggestions</string>
|
<string name="location_permission_rationale">Optional permission: Get current location for category suggestions</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
|
|
||||||
|
<string name="title_activity_nearby">Nearby Places</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
9
app/src/main/res/xml/campaigns_sync_adapter.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:contentAuthority="fr.free.nrw.commons.campaigns.contentprovider"
|
||||||
|
android:accountType="fr.free.nrw.commons"
|
||||||
|
android:supportsUploading="false"
|
||||||
|
android:userVisible="true"
|
||||||
|
android:isAlwaysSyncable="true"
|
||||||
|
/>
|
||||||
51
disable-campaigns.patch
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
|
||||||
|
index 5ad1c77..50b16ff 100644
|
||||||
|
--- a/app/src/main/AndroidManifest.xml
|
||||||
|
+++ b/app/src/main/AndroidManifest.xml
|
||||||
|
@@ -82,14 +82,6 @@
|
||||||
|
android:label="@string/title_activity_settings"
|
||||||
|
/>
|
||||||
|
<activity android:name=".AboutActivity" android:label="@string/title_activity_about"/>
|
||||||
|
- <activity android:name=".campaigns.CampaignActivity" android:label="Campaigns"
|
||||||
|
- android:icon="@drawable/ic_launcher"
|
||||||
|
- >
|
||||||
|
- <intent-filter>
|
||||||
|
- <action android:name="android.intent.action.MAIN"/>
|
||||||
|
- <category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
- </intent-filter>
|
||||||
|
- </activity>
|
||||||
|
|
||||||
|
<service android:name=".upload.UploadService" >
|
||||||
|
</service>
|
||||||
|
@@ -106,17 +98,6 @@
|
||||||
|
android:resource="@xml/authenticator" />
|
||||||
|
</service>
|
||||||
|
<service
|
||||||
|
- android:name=".campaigns.CampaignsSyncService"
|
||||||
|
- android:exported="true">
|
||||||
|
- <intent-filter>
|
||||||
|
- <action
|
||||||
|
- android:name="android.content.SyncAdapter" />
|
||||||
|
- </intent-filter>
|
||||||
|
- <meta-data
|
||||||
|
- android:name="android.content.SyncAdapter"
|
||||||
|
- android:resource="@xml/campaigns_sync_adapter" />
|
||||||
|
- </service>
|
||||||
|
- <service
|
||||||
|
android:name=".contributions.ContributionsSyncService"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
@@ -148,13 +129,6 @@
|
||||||
|
android:exported="false">
|
||||||
|
</provider>
|
||||||
|
<provider
|
||||||
|
- android:name=".campaigns.CampaignsContentProvider"
|
||||||
|
- android:label="@string/provider_campaigns"
|
||||||
|
- android:syncable="true"
|
||||||
|
- android:authorities="fr.free.nrw.commons.campaigns.contentprovider"
|
||||||
|
- android:exported="false">
|
||||||
|
- </provider>
|
||||||
|
- <provider
|
||||||
|
android:name=".modifications.ModificationsContentProvider"
|
||||||
|
android:label="@string/provider_modifications"
|
||||||
|
android:syncable="true"
|
||||||