diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a72b3cc65..31d3e41f2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -82,6 +82,10 @@
+
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index 7f93599f3..1c62a48bb 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -30,6 +30,7 @@ import fr.free.nrw.commons.AboutActivity;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.SettingsActivity;
+import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.upload.UploadService;
public class ContributionsListFragment extends Fragment {
@@ -142,15 +143,28 @@ public class ContributionsListFragment extends Fragment {
feedbackIntent.setType("message/rfc822");
feedbackIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { CommonsApplication.FEEDBACK_EMAIL });
feedbackIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(CommonsApplication.FEEDBACK_EMAIL_SUBJECT, CommonsApplication.APPLICATION_VERSION));
-
try {
startActivity(feedbackIntent);
}
catch (ActivityNotFoundException e) {
Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
}
-
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:
((SourceRefresher)getActivity()).refreshSource();
return true;
@@ -169,6 +183,14 @@ public class ContributionsListFragment extends Fragment {
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);
+ }
+ }
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/LatLng.java b/app/src/main/java/fr/free/nrw/commons/nearby/LatLng.java
new file mode 100644
index 000000000..96b36cfba
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/LatLng.java
@@ -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 + ")";
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
new file mode 100644
index 000000000..a06d98eea
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
@@ -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();
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
new file mode 100644
index 000000000..78a5d01af
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
@@ -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 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 loadAttractionsFromLocation(final LatLng curLatLng) {
+
+ List places = NearbyPlaces.get();
+ if (curLatLng != null) {
+ Log.d(TAG, "Sorting places by distance...");
+ Collections.sort(places,
+ new Comparator() {
+ @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> {
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ super.onProgressUpdate(values);
+ progressBar.setProgress(values[0]);
+ }
+
+ @Override
+ protected List doInBackground(Void... params) {
+ places = loadAttractionsFromLocation(mLatestLocation);
+ return places;
+ }
+
+ @Override
+ protected void onPostExecute(List 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 {
+
+ public List placesList;
+ private Context mContext;
+
+ public NearbyAdapter(Context context, List 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;
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
new file mode 100644
index 000000000..0e7c3e1ed
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
@@ -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 places = null;
+
+ public static List get() {
+ if(places != null) {
+ return places;
+ }
+ else {
+ try {
+ places = new ArrayList();
+ 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;
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
new file mode 100644
index 000000000..beadd0f85
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
@@ -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;
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java b/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
index 8445092ff..4313e106c 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
@@ -80,8 +80,8 @@ public class GPSExtractor {
}
/**
- * Extracts geolocation of image from EXIF data.
- * @return coordinates of image as string (needs to be passed as a String in API query)
+ * Extracts geolocation (either of image from EXIF data, or of user)
+ * @return coordinates as string (needs to be passed as a String in API query)
*/
@Nullable
public String getCoords(boolean useGPS) {
@@ -103,6 +103,7 @@ public class GPSExtractor {
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) {
registerLocationManager();
@@ -122,6 +123,7 @@ public class GPSExtractor {
} else if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
return null;
} else {
+ //If image has EXIF data, extract image coords
imageCoordsExists = true;
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 {
@Override
diff --git a/app/src/main/res/drawable/empty_photo.png b/app/src/main/res/drawable/empty_photo.png
new file mode 100644
index 000000000..da1478a51
Binary files /dev/null and b/app/src/main/res/drawable/empty_photo.png differ
diff --git a/app/src/main/res/drawable/icon_city.png b/app/src/main/res/drawable/icon_city.png
new file mode 100644
index 000000000..22004c3ec
Binary files /dev/null and b/app/src/main/res/drawable/icon_city.png differ
diff --git a/app/src/main/res/drawable/icon_edu.png b/app/src/main/res/drawable/icon_edu.png
new file mode 100644
index 000000000..f94c9403a
Binary files /dev/null and b/app/src/main/res/drawable/icon_edu.png differ
diff --git a/app/src/main/res/drawable/icon_event.png b/app/src/main/res/drawable/icon_event.png
new file mode 100644
index 000000000..03d0e1f4c
Binary files /dev/null and b/app/src/main/res/drawable/icon_event.png differ
diff --git a/app/src/main/res/drawable/icon_isle.png b/app/src/main/res/drawable/icon_isle.png
new file mode 100644
index 000000000..6fdf75baa
Binary files /dev/null and b/app/src/main/res/drawable/icon_isle.png differ
diff --git a/app/src/main/res/drawable/icon_landmark.png b/app/src/main/res/drawable/icon_landmark.png
new file mode 100644
index 000000000..9024b3fda
Binary files /dev/null and b/app/src/main/res/drawable/icon_landmark.png differ
diff --git a/app/src/main/res/drawable/icon_mountain.png b/app/src/main/res/drawable/icon_mountain.png
new file mode 100644
index 000000000..ded8e2970
Binary files /dev/null and b/app/src/main/res/drawable/icon_mountain.png differ
diff --git a/app/src/main/res/drawable/icon_war.jpg b/app/src/main/res/drawable/icon_war.jpg
new file mode 100644
index 000000000..bf74779e7
Binary files /dev/null and b/app/src/main/res/drawable/icon_war.jpg differ
diff --git a/app/src/main/res/layout/activity_campaigns.xml b/app/src/main/res/layout/activity_campaigns.xml
new file mode 100644
index 000000000..cfac7c79b
--- /dev/null
+++ b/app/src/main/res/layout/activity_campaigns.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_nearby.xml b/app/src/main/res/layout/activity_nearby.xml
new file mode 100644
index 000000000..daa243281
--- /dev/null
+++ b/app/src/main/res/layout/activity_nearby.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_campaigns.xml b/app/src/main/res/layout/fragment_campaigns.xml
new file mode 100644
index 000000000..3ff614f0e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_campaigns.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_nearby.xml b/app/src/main/res/layout/fragment_nearby.xml
new file mode 100644
index 000000000..ef9ff565c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_nearby.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_place.xml b/app/src/main/res/layout/item_place.xml
new file mode 100644
index 000000000..9f052786e
--- /dev/null
+++ b/app/src/main/res/layout/item_place.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_campaign_item.xml b/app/src/main/res/layout/layout_campaign_item.xml
new file mode 100644
index 000000000..79c0355dd
--- /dev/null
+++ b/app/src/main/res/layout/layout_campaign_item.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/fragment_contributions_list.xml b/app/src/main/res/menu/fragment_contributions_list.xml
index c4ff771b7..3805a7730 100644
--- a/app/src/main/res/menu/fragment_contributions_list.xml
+++ b/app/src/main/res/menu/fragment_contributions_list.xml
@@ -27,6 +27,11 @@
app:showAsAction="never"
android:icon="@android:drawable/ic_menu_send"
/>
+
-
+
+
+ #90000000
+ #22000000
+ #ddd
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 47c822467..bd62460f7 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -2,4 +2,7 @@
16dp
16dp
+ 120dp
+ 4dp
+ 8dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0d2edb1bd..d64578557 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -33,6 +33,7 @@
Uploading
From Gallery
Take photo
+ Nearby
My uploads
Share
@@ -154,4 +155,6 @@
Optional permission: Get current location for category suggestions
OK
Back
+
+ Nearby Places
diff --git a/app/src/main/res/xml/campaigns_sync_adapter.xml b/app/src/main/res/xml/campaigns_sync_adapter.xml
new file mode 100644
index 000000000..1da828c50
--- /dev/null
+++ b/app/src/main/res/xml/campaigns_sync_adapter.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/disable-campaigns.patch b/disable-campaigns.patch
new file mode 100644
index 000000000..586abe1d5
--- /dev/null
+++ b/disable-campaigns.patch
@@ -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"
+ />
+
+-
+-
+-
+-
+-
+-
+
+
+
+@@ -106,17 +98,6 @@
+ android:resource="@xml/authenticator" />
+
+
+-
+-
+-
+-
+-
+-
+
+@@ -148,13 +129,6 @@
+ android:exported="false">
+
+
+-
+-