Merge pull request #482 from maskaravivek/refactorNearby

Refactored NearbyActivity to extract out reusable code outside of the activity
This commit is contained in:
Josephine Lim 2017-04-04 18:37:00 +10:00 committed by GitHub
commit 397f7dbc85
11 changed files with 368 additions and 270 deletions

View file

@ -0,0 +1,7 @@
package fr.free.nrw.commons;
import android.content.Context;
public interface ViewHolder<T> {
void bindModel(Context context, T model);
}

View file

@ -1,11 +1,15 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.location;
public class LatLng { public class LatLng {
public final double latitude; public final double latitude;
public final double longitude; public final double longitude;
LatLng(double latitude, double longitude) { /** Accepts latitude and longitude.
* @param latitude double value
* @param longitude double value
*/
public LatLng(double latitude, double longitude) {
if(-180.0D <= longitude && longitude < 180.0D) { if(-180.0D <= longitude && longitude < 180.0D) {
this.longitude = longitude; this.longitude = longitude;
} else { } else {

View file

@ -0,0 +1,79 @@
package fr.free.nrw.commons.location;
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.util.Log;
public class LocationServiceManager implements LocationListener {
public static final String TAG = "LocationServiceManager";
private String provider;
private LocationManager locationManager;
private LatLng latestLocation;
public LocationServiceManager(Context context) {
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
provider = locationManager.getBestProvider(new Criteria(), true);
}
public LatLng getLatestLocation() {
return latestLocation;
}
/** Registers a LocationManager to listen for current location.
*/
public void registerLocationManager() {
try {
locationManager.requestLocationUpdates(provider, 400, 1, this);
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) {
this.onLocationChanged(location);
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "Illegal argument exception", e);
} catch (SecurityException e) {
Log.e(TAG, "Security exception", e);
}
}
/** Unregisters location manager.
*/
public void unregisterLocationManager() {
try {
locationManager.removeUpdates(this);
} catch (SecurityException e) {
Log.e(TAG, "Security exception", e);
}
}
@Override
public void onLocationChanged(Location location) {
double currentLatitude = location.getLatitude();
double currentLongitude = location.getLongitude();
Log.d(TAG, "Latitude: " + String.valueOf(currentLatitude)
+ " Longitude: " + String.valueOf(currentLongitude));
latestLocation = 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");
}
}

View file

@ -1,30 +1,18 @@
package fr.free.nrw.commons.nearby; 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.os.Bundle;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.theme.BaseActivity;
public class NearbyActivity extends BaseActivity { public class NearbyActivity extends BaseActivity {
private MyLocationListener myLocationListener; private LocationServiceManager locationManager;
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.class.getName(); private static final String TAG = NearbyActivity.class.getName();
@ -35,7 +23,8 @@ public class NearbyActivity extends BaseActivity {
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
registerLocationManager(); locationManager = new LocationServiceManager(this);
locationManager.registerLocationManager();
// Begin the transaction // Begin the transaction
FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
@ -70,75 +59,10 @@ public class NearbyActivity extends BaseActivity {
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new NearbyListFragment()).commit(); .replace(R.id.container, new NearbyListFragment()).commit();
} }
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 @Override
protected void onDestroy(){ protected void onDestroy(){
super.onDestroy(); super.onDestroy();
locationManager.unregisterLocationManager();
unregisterLocationManager();
} }
} }

View file

@ -0,0 +1,54 @@
package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import fr.free.nrw.commons.R;
import java.util.List;
public class NearbyAdapter extends ArrayAdapter<Place> {
private List<Place> placesList;
private Context context;
public List<Place> getPlacesList() {
return placesList;
}
/** Accepts activity context and list of places.
* @param context activity context
* @param places list of places
*/
public NearbyAdapter(Context context, List<Place> places) {
super(context, R.layout.item_place, places);
this.context = context;
placesList = places;
}
@Override
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);
// 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);
}
NearbyViewHolder viewHolder = new NearbyViewHolder(convertView);
viewHolder.bindModel(context, place);
// Return the completed view to render on screen
return convertView;
}
@Override
public long getItemId(int position) {
return position;
}
}

View file

@ -1,6 +1,8 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.content.Context; 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.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -10,27 +12,28 @@ import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView;
import java.text.NumberFormat; import butterknife.BindView;
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.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import fr.free.nrw.commons.R;
public class NearbyListFragment extends ListFragment implements TaskListener { public class NearbyListFragment extends ListFragment implements TaskListener {
private NearbyAsyncTask nearbyAsyncTask; private NearbyAsyncTask nearbyAsyncTask;
private NearbyAdapter mAdapter; private NearbyAdapter mAdapter;
private ListView listview;
private ProgressBar progressBar; @BindView(R.id.listview) ListView listview;
@BindView(R.id.progressBar) ProgressBar progressBar;
private boolean isTaskRunning = false; private boolean isTaskRunning = false;
private List<Place> places; private List<Place> places;
@ -53,7 +56,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
Log.d(TAG, "NearbyListFragment created"); Log.d(TAG, "NearbyListFragment created");
View view = inflater.inflate(R.layout.fragment_nearby, container, false); View view = inflater.inflate(R.layout.fragment_nearby, container, false);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar); ButterKnife.bind(this, view);
return view; return view;
} }
@ -62,8 +65,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
//Check that this is the first time view is created, to avoid double list when screen orientation changed //Check that this is the first time view is created, to avoid double list when screen orientation changed
if(savedInstanceState == null) { if(savedInstanceState == null) {
mLatestLocation = ((NearbyActivity) getActivity()).getmLatestLocation(); mLatestLocation = new LocationServiceManager(getActivity()).getLatestLocation();
listview = (ListView) getView().findViewById(R.id.listview);
nearbyAsyncTask = new NearbyAsyncTask(this); nearbyAsyncTask = new NearbyAsyncTask(this);
nearbyAsyncTask.execute(); nearbyAsyncTask.execute();
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
@ -159,145 +161,30 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
listview.setAdapter(mAdapter); listview.setAdapter(mAdapter);
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
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);
}
}
});
listener.onTaskFinished(result); listener.onTaskFinished(result);
mAdapter.notifyDataSetChanged(); mAdapter.notifyDataSetChanged();
} }
} }
private class NearbyAdapter extends ArrayAdapter<Place> { @OnItemClick(R.id.listview)
void onItemClicked(int position) {
Place place = places.get(position);
LatLng placeLatLng = place.location;
public List<Place> placesList; double latitude = placeLatLng.latitude;
private Context mContext; double longitude = placeLatLng.longitude;
public NearbyAdapter(Context context, List<Place> places) { Log.d(TAG, "Item at position "
super(context, R.layout.item_place, places); + position + " has coords: Lat: "
mContext = context; + latitude + " Long: "
placesList = places; + longitude);
}
@Override //Open map app at given position
public View getView(int position, View convertView, ViewGroup parent) { Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + latitude + "," + longitude);
// Get the data item for this position Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
Place place = getItem(position);
Log.d(TAG, "Place " + place.name);
// Check if an existing view is being reused, otherwise inflate the view if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
if (convertView == null) { startActivity(mapIntent);
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);
// See https://github.com/commons-app/apps-android-commons/issues/250
// Most common types of desc: building, house, cottage, farmhouse, village, civil parish, church, railway station,
// gatehouse, milestone, inn, secondary school, hotel
switch(place.description) {
case "building":
icon.setImageResource(R.drawable.round_icon_generic_building);
break;
case "house":
icon.setImageResource(R.drawable.round_icon_house);
break;
case "cottage":
icon.setImageResource(R.drawable.round_icon_house);
break;
case "farmhouse":
icon.setImageResource(R.drawable.round_icon_house);
break;
case "church":
icon.setImageResource(R.drawable.round_icon_church);
break;
case "railway station":
icon.setImageResource(R.drawable.round_icon_railway_station);
break;
case "gatehouse":
icon.setImageResource(R.drawable.round_icon_gatehouse);
break;
case "milestone":
icon.setImageResource(R.drawable.round_icon_milestone);
break;
case "inn":
icon.setImageResource(R.drawable.round_icon_house);
break;
case "city":
icon.setImageResource(R.drawable.round_icon_city);
break;
case "secondary school":
icon.setImageResource(R.drawable.round_icon_school);
break;
case "edu":
icon.setImageResource(R.drawable.round_icon_school);
break;
case "isle":
icon.setImageResource(R.drawable.round_icon_island);
break;
case "mountain":
icon.setImageResource(R.drawable.round_icon_mountain);
break;
case "airport":
icon.setImageResource(R.drawable.round_icon_airport);
break;
case "bridge":
icon.setImageResource(R.drawable.round_icon_bridge);
break;
case "road":
icon.setImageResource(R.drawable.round_icon_road);
break;
case "forest":
icon.setImageResource(R.drawable.round_icon_forest);
break;
case "park":
icon.setImageResource(R.drawable.round_icon_park);
break;
case "river":
icon.setImageResource(R.drawable.round_icon_river);
break;
case "waterfall":
icon.setImageResource(R.drawable.round_icon_waterfall);
break;
default:
icon.setImageResource(R.drawable.round_icon_unknown);
}
// Return the completed view to render on screen
return convertView;
}
@Override
public long getItemId(int position) {
return position;
} }
} }
@ -329,47 +216,4 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
} }
return places; return places;
} }
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;
}
} }

View file

@ -3,6 +3,8 @@ package fr.free.nrw.commons.nearby;
import android.os.StrictMode; import android.os.StrictMode;
import android.util.Log; import android.util.Log;
import fr.free.nrw.commons.location.LatLng;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -10,6 +12,7 @@ import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class NearbyPlaces { public class NearbyPlaces {
private static final String TAG = NearbyPlaces.class.getName(); private static final String TAG = NearbyPlaces.class.getName();

View file

@ -0,0 +1,35 @@
package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.ViewHolder;
import fr.free.nrw.commons.utils.ResourceUtils;
public class NearbyViewHolder implements ViewHolder<Place> {
@BindView(R.id.tvName) TextView tvName;
@BindView(R.id.tvDesc) TextView tvDesc;
@BindView(R.id.distance) TextView distance;
@BindView(R.id.icon) ImageView icon;
public NearbyViewHolder(View view) {
ButterKnife.bind(this, view);
}
@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);
tvDesc.setText(place.description);
distance.setText(place.distance);
icon.setImageResource(ResourceUtils.getDescriptionIcon(place.description));
}
}

View file

@ -3,6 +3,8 @@ package fr.free.nrw.commons.nearby;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import fr.free.nrw.commons.location.LatLng;
public class Place { public class Place {
public String name; public String name;

View file

@ -0,0 +1,57 @@
package fr.free.nrw.commons.utils;
import fr.free.nrw.commons.location.LatLng;
import java.text.NumberFormat;
public class LengthUtils {
/** Returns a formatted distance string between two points.
* @param point1 LatLng type point1
* @param point2 LatLng type point2
* @return string distance
*/
public static 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";
}
public 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 longitude) {
return hav(lat1 - lat2) + hav(longitude) * Math.cos(lat1) * Math.cos(lat2);
}
private static double hav(double x) {
double sinHalf = Math.sin(x * 0.5D);
return sinHalf * sinHalf;
}
}

View file

@ -0,0 +1,89 @@
package fr.free.nrw.commons.utils;
import android.support.annotation.DrawableRes;
import fr.free.nrw.commons.R;
public class ResourceUtils {
/**
* See https://github.com/commons-app/apps-android-commons/issues/250
* Most common types of desc: building, house, cottage, farmhouse,
* village, civil parish, church, railway station,
* gatehouse, milestone, inn, secondary school, hotel
* @param description Place description
* @return icon res id
*/
@DrawableRes
public static int getDescriptionIcon(String description) {
int resourceId;
switch (description) {
case "building":
resourceId = R.drawable.round_icon_generic_building;
break;
case "house":
resourceId = R.drawable.round_icon_house;
break;
case "cottage":
resourceId = R.drawable.round_icon_house;
break;
case "farmhouse":
resourceId = R.drawable.round_icon_house;
break;
case "church":
resourceId = R.drawable.round_icon_church;
break;
case "railway station":
resourceId = R.drawable.round_icon_railway_station;
break;
case "gatehouse":
resourceId = R.drawable.round_icon_gatehouse;
break;
case "milestone":
resourceId = R.drawable.round_icon_milestone;
break;
case "inn":
resourceId = R.drawable.round_icon_house;
break;
case "city":
resourceId = R.drawable.round_icon_city;
break;
case "secondary school":
resourceId = R.drawable.round_icon_school;
break;
case "edu":
resourceId = R.drawable.round_icon_school;
break;
case "isle":
resourceId = R.drawable.round_icon_island;
break;
case "mountain":
resourceId = R.drawable.round_icon_mountain;
break;
case "airport":
resourceId = R.drawable.round_icon_airport;
break;
case "bridge":
resourceId = R.drawable.round_icon_bridge;
break;
case "road":
resourceId = R.drawable.round_icon_road;
break;
case "forest":
resourceId = R.drawable.round_icon_forest;
break;
case "park":
resourceId = R.drawable.round_icon_park;
break;
case "river":
resourceId = R.drawable.round_icon_river;
break;
case "waterfall":
resourceId = R.drawable.round_icon_waterfall;
break;
default:
resourceId = R.drawable.round_icon_unknown;
}
return resourceId;
}
}