Merge branch 'master' into dependency-injection

This commit is contained in:
Paul Hawke 2017-11-24 22:12:43 -06:00
commit 02b5b9b680
148 changed files with 1169 additions and 364 deletions

View file

@ -1,7 +1,5 @@
package fr.free.nrw.commons;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
@ -27,9 +25,4 @@ public class AboutActivity extends NavigationBaseActivity {
versionText.setText(BuildConfig.VERSION_NAME);
initDrawer();
}
public static void startYourself(Context context) {
Intent settingsIntent = new Intent(context, AboutActivity.class);
context.startActivity(settingsIntent);
}
}

View file

@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.stetho.Stetho;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
@ -60,17 +61,15 @@ public class CommonsApplication extends DaggerApplication {
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
private CommonsApplicationComponent component;
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
if (setupLeakCanary() == RefWatcher.DISABLED) {
return;
}
LeakCanary.install(this);
Timber.plant(new Timber.DebugTree());
@ -86,6 +85,18 @@ public class CommonsApplication extends DaggerApplication {
Fresco.initialize(this);
}
protected RefWatcher setupLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
CommonsApplication application = (CommonsApplication) context.getApplicationContext();
return application.refWatcher;
}
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return injector();

View file

@ -120,8 +120,9 @@ public class Media implements Parcelable {
return localUri;
}
@Nullable
public String getImageUrl() {
if (imageUrl == null) {
if (imageUrl == null && this.getFilename() != null) {
imageUrl = Utils.makeThumbBaseUrl(this.getFilename());
}
return imageUrl;

View file

@ -11,7 +11,6 @@ import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
@ -39,7 +38,6 @@ public class MediaDataExtractor {
private String filename;
private ArrayList<String> categories;
private Map<String, String> descriptions;
private Date date;
private String license;
private @Nullable LatLng coordinates;
private LicenseList licenseList;
@ -155,7 +153,7 @@ public class MediaDataExtractor {
}
private Node findTemplate(Element parentNode, String title_) throws IOException {
String title= new PageTitle(title_).getDisplayText();
String title = new PageTitle(title_).getDisplayText();
NodeList nodes = parentNode.getChildNodes();
for (int i = 0, length = nodes.getLength(); i < length; i++) {
Node node = nodes.item(i);
@ -181,7 +179,7 @@ public class MediaDataExtractor {
}
private static abstract class TemplateChildNodeComparator {
abstract public boolean match(Node node);
public abstract boolean match(Node node);
}
private Node findTemplateParameter(Node templateNode, String name) throws IOException {

View file

@ -42,7 +42,7 @@ public class MediaWikiImageView extends SimpleDraweeView {
if (currentThumbnailTask != null) {
currentThumbnailTask.cancel(true);
}
if(media == null) {
if (media == null) {
return;
}

View file

@ -2,18 +2,22 @@ package fr.free.nrw.commons;
import android.content.Context;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
public class Utils {
@ -33,7 +37,7 @@ public class Utils {
}
}
public static String makeThumbBaseUrl(String filename) {
public static String makeThumbBaseUrl(@NonNull String filename) {
String name = new PageTitle(filename).getPrefixedText();
String sha = new String(Hex.encodeHex(DigestUtils.md5(name)));
return String.format("%s/%s/%s/%s", BuildConfig.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), urlEncode(name));
@ -89,4 +93,37 @@ public class Utils {
public static boolean isDarkTheme(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false);
}
/**
* Will be used to fetch the logs generated by the app ever since the beginning of times....
* i.e. since the time the app started.
*
* @return String containing all the logs since the time the app started
*/
public static String getAppLogs() {
final String processId = Integer.toString(android.os.Process.myPid());
StringBuilder stringBuilder = new StringBuilder();
try {
String[] command = new String[] {"logcat","-d","-v","threadtime"};
Process process = Runtime.getRuntime().exec(command);
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.contains(processId)) {
stringBuilder.append(line);
}
}
} catch (IOException ioe) {
Timber.e("getAppLogs failed", ioe);
}
return stringBuilder.toString();
}
}

View file

@ -18,7 +18,7 @@ import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
public class AccountUtil {
static final String ACCOUNT_TYPE = "fr.free.nrw.commons";
public static final String ACCOUNT_TYPE = "fr.free.nrw.commons";
private final Context context;
public AccountUtil(Context context) {

View file

@ -34,6 +34,7 @@ import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import timber.log.Timber;
import static android.view.KeyEvent.KEYCODE_ENTER;
@ -199,7 +200,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
}
public void startMainActivity() {
ContributionsActivity.startYourself(this);
NavigationBaseActivity.startActivityWithFlags(this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
finish();
}
@ -253,8 +254,8 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@Override
public void afterTextChanged(Editable editable) {
boolean enabled = usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0 &&
(BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE);
boolean enabled = usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0
&& (BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE);
loginButton.setEnabled(enabled);
}
}

View file

@ -149,10 +149,13 @@ public class ContributionsActivity
if (savedInstanceState != null) {
mediaDetails = (MediaDetailPagerFragment)supportFragmentManager
.findFragmentById(R.id.contributionsFragmentContainer);
getSupportLoaderManager().initLoader(0, null, this);
}
requestAuthToken();
initDrawer();
setTitle(getString(R.string.title_activity_contributions));
setUploadCount();
}
@Override
@ -242,6 +245,8 @@ public class ContributionsActivity
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
contributionsList.changeProgressBarVisibility(false);
if (contributionsList.getAdapter() == null) {
contributionsList.setAdapter(new ContributionsListAdapter(getApplicationContext(),
cursor, 0));
@ -249,8 +254,6 @@ public class ContributionsActivity
((CursorAdapter) contributionsList.getAdapter()).swapCursor(cursor);
}
setUploadCount();
contributionsList.clearSyncMessage();
notifyAndMigrateDataSetObservers();
}
@ -281,18 +284,16 @@ public class ContributionsActivity
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
compositeDisposable.add(
mediaWikiApi
.getUploadCount(sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
uploadCount -> getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount)),
t -> Timber.e(t, "Fetching upload count failed")
)
);
compositeDisposable.add(mediaWikiApi
.getUploadCount(sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
uploadCount -> getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount)),
t -> Timber.e(t, "Fetching upload count failed")
));
}
@Override
@ -344,9 +345,4 @@ public class ContributionsActivity
public void refreshSource() {
getSupportLoaderManager().restartLoader(0, null, this);
}
public static void startYourself(Context context) {
context.startActivity(new Intent(context, ContributionsActivity.class));
}
}

View file

@ -34,7 +34,7 @@ class ContributionsListAdapter extends CursorAdapter {
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));
views.seqNumView.setVisibility(View.VISIBLE);
switch(contribution.getState()) {
switch (contribution.getState()) {
case Contribution.STATE_COMPLETED:
views.stateView.setVisibility(View.GONE);
views.progressView.setVisibility(View.GONE);
@ -50,7 +50,7 @@ class ContributionsListAdapter extends CursorAdapter {
views.progressView.setVisibility(View.VISIBLE);
long total = contribution.getDataLength();
long transferred = contribution.getTransferred();
if(transferred == 0 || transferred >= total) {
if (transferred == 0 || transferred >= total) {
views.progressView.setIndeterminate(true);
} else {
views.progressView.setProgress((int)(((double)transferred / (double)total) * 100));

View file

@ -5,7 +5,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
@ -18,6 +17,7 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.ProgressBar;
import android.widget.TextView;
import javax.inject.Inject;
@ -42,10 +42,12 @@ public class ContributionsListFragment extends DaggerFragment {
GridView contributionsList;
@BindView(R.id.waitingMessage)
TextView waitingMessage;
@BindView(R.id.emptyMessage)
TextView emptyMessage;
@BindView(R.id.loadingContributionsProgressBar)
ProgressBar progressBar;
@Inject @Named("prefs") SharedPreferences prefs;
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
private ContributionController controller;
@Override
@ -69,6 +71,7 @@ public class ContributionsListFragment extends DaggerFragment {
waitingMessage.setVisibility(GONE);
}
changeProgressBarVisibility(true);
return v;
}
@ -80,6 +83,10 @@ public class ContributionsListFragment extends DaggerFragment {
this.contributionsList.setAdapter(adapter);
}
public void changeProgressBarVisibility(boolean isVisible) {
this.progressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (outState == null) {

View file

@ -57,6 +57,9 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
}
private boolean fileExists(ContentProviderClient client, String filename) {
if (filename == null) {
return false;
}
Cursor cursor = null;
try {
cursor = client.query(BASE_URI,

View file

@ -7,7 +7,7 @@ import android.database.sqlite.SQLiteOpenHelper;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.modifications.ModifierSequence;
public class DBOpenHelper extends SQLiteOpenHelper{
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "commons.db";
private static final int DATABASE_VERSION = 6;

View file

@ -15,6 +15,7 @@ import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.NearbyPlaces;
@ -71,6 +72,12 @@ public class CommonsApplicationModule {
return new ApacheHttpClientMediaWikiApi(BuildConfig.WIKIMEDIA_API_HOST);
}
@Provides
@Singleton
public LocationServiceManager provideLocationServiceManager() {
return new LocationServiceManager(application);
}
@Provides
@Singleton
public CacheController provideCacheController() {

View file

@ -13,7 +13,7 @@ public class LatLng {
* @param longitude double value
*/
public LatLng(double latitude, double longitude, float accuracy) {
if(-180.0D <= longitude && longitude < 180.0D) {
if (-180.0D <= longitude && longitude < 180.0D) {
this.longitude = longitude;
} else {
this.longitude = ((longitude - 180.0D) % 360.0D + 360.0D) % 360.0D - 180.0D;
@ -33,9 +33,9 @@ public class LatLng {
}
public boolean equals(Object o) {
if(this == o) {
if (this == o) {
return true;
} else if(!(o instanceof LatLng)) {
} else if (!(o instanceof LatLng)) {
return false;
} else {
LatLng var2 = (LatLng)o;

View file

@ -7,22 +7,33 @@ import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
import javax.inject.Singleton;
import timber.log.Timber;
public class LocationServiceManager implements LocationListener {
private String provider;
private LocationManager locationManager;
private LatLng latestLocation;
private LatLng lastLocation;
private Float latestLocationAccuracy;
private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>();
public LocationServiceManager(Context context) {
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
provider = locationManager.getBestProvider(new Criteria(), true);
}
public LatLng getLatestLocation() {
return latestLocation;
public boolean isProviderEnabled() {
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
public LatLng getLastLocation() {
return lastLocation;
}
/**
@ -64,6 +75,16 @@ public class LocationServiceManager implements LocationListener {
}
}
public void addLocationListener(LocationUpdateListener listener) {
if (!locationListeners.contains(listener)) {
locationListeners.add(listener);
}
}
public void removeLocationListener(LocationUpdateListener listener) {
locationListeners.remove(listener);
}
@Override
public void onLocationChanged(Location location) {
double currentLatitude = location.getLatitude();
@ -71,8 +92,11 @@ public class LocationServiceManager implements LocationListener {
latestLocationAccuracy = location.getAccuracy();
Timber.d("Latitude: %f Longitude: %f Accuracy %f",
currentLatitude, currentLongitude, latestLocationAccuracy);
lastLocation = new LatLng(currentLatitude, currentLongitude, latestLocationAccuracy);
latestLocation = new LatLng(currentLatitude, currentLongitude, latestLocationAccuracy);
for (LocationUpdateListener listener : locationListeners) {
listener.onLocationChanged(lastLocation);
}
}
@Override

View file

@ -0,0 +1,5 @@
package fr.free.nrw.commons.location;
public interface LocationUpdateListener {
void onLocationChanged(LatLng latLng);
}

View file

@ -152,8 +152,14 @@ public class MediaDetailPagerFragment extends DaggerFragment implements ViewPage
private void downloadMedia(Media m) {
String imageUrl = m.getImageUrl(),
fileName = m.getFilename();
if (imageUrl == null || fileName == null) {
return;
}
// Strip 'File:' from beginning of filename, we really shouldn't store it
fileName = fileName.replaceFirst("^File:", "");
Uri imageUri = Uri.parse(imageUrl);
DownloadManager.Request req = new DownloadManager.Request(imageUri);

View file

@ -13,7 +13,7 @@ public class CategoryModifier extends PageModifier {
public CategoryModifier(String... categories) {
super(MODIFIER_NAME);
JSONArray categoriesArray = new JSONArray();
for(String category: categories) {
for (String category: categories) {
categoriesArray.put(category);
}
try {
@ -34,7 +34,7 @@ public class CategoryModifier extends PageModifier {
categories = params.optJSONArray(PARAM_CATEGORIES);
StringBuilder categoriesString = new StringBuilder();
for(int i=0; i < categories.length(); i++) {
for (int i = 0; i < categories.length(); i++) {
String category = categories.optString(i);
categoriesString.append("\n[[Category:").append(category).append("]]");
}

View file

@ -16,7 +16,7 @@ import dagger.android.AndroidInjection;
import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
public class ModificationsContentProvider extends ContentProvider{
public class ModificationsContentProvider extends ContentProvider {
private static final int MODIFICATIONS = 1;
private static final int MODIFICATIONS_ID = 2;
@ -51,7 +51,7 @@ public class ModificationsContentProvider extends ContentProvider{
int uriType = uriMatcher.match(uri);
switch(uriType) {
switch (uriType) {
case MODIFICATIONS:
break;
default:
@ -112,7 +112,7 @@ public class ModificationsContentProvider extends ContentProvider{
sqlDB.beginTransaction();
switch (uriType) {
case MODIFICATIONS:
for(ContentValues value: values) {
for (ContentValues value: values) {
Timber.d("Inserting! %s", value);
sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, value);
}

View file

@ -27,7 +27,7 @@ public class ModifierSequence {
public ModifierSequence(Uri mediaUri, JSONObject data) {
this(mediaUri);
JSONArray modifiersJSON = data.optJSONArray("modifiers");
for (int i=0; i< modifiersJSON.length(); i++) {
for (int i = 0; i < modifiersJSON.length(); i++) {
modifiers.add(PageModifier.fromJSON(modifiersJSON.optJSONObject(i)));
}
}
@ -49,7 +49,7 @@ public class ModifierSequence {
public String getEditSummary() {
StringBuilder editSummary = new StringBuilder();
for(PageModifier modifier: modifiers) {
for (PageModifier modifier: modifiers) {
editSummary.append(modifier.getEditSumary()).append(" ");
}
editSummary.append("Via Commons Mobile App");
@ -93,12 +93,12 @@ public class ModifierSequence {
public void save() {
try {
if(contentUri == null) {
if (contentUri == null) {
contentUri = client.insert(ModificationsContentProvider.BASE_URI, this.toContentValues());
} else {
client.update(contentUri, toContentValues(), null, null);
}
} catch(RemoteException e) {
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}

View file

@ -7,9 +7,9 @@ public abstract class PageModifier {
public static PageModifier fromJSON(JSONObject data) {
String name = data.optString("name");
if(name.equals(CategoryModifier.MODIFIER_NAME)) {
if (name.equals(CategoryModifier.MODIFIER_NAME)) {
return new CategoryModifier(data.optJSONObject("data"));
} else if(name.equals(TemplateRemoveModifier.MODIFIER_NAME)) {
} else if (name.equals(TemplateRemoveModifier.MODIFIER_NAME)) {
return new TemplateRemoveModifier(data.optJSONObject("data"));
}

View file

@ -41,18 +41,18 @@ public class TemplateRemoveModifier extends PageModifier {
Pattern templateStartPattern = Pattern.compile("\\{\\{" + templateNormalized, Pattern.CASE_INSENSITIVE);
Matcher matcher = templateStartPattern.matcher(pageContents);
while(matcher.find()) {
while (matcher.find()) {
int braceCount = 1;
int startIndex = matcher.start();
int curIndex = matcher.end();
Matcher openMatch = PATTERN_TEMPLATE_OPEN.matcher(pageContents);
Matcher closeMatch = PATTERN_TEMPLATE_CLOSE.matcher(pageContents);
while(curIndex < pageContents.length()) {
while (curIndex < pageContents.length()) {
boolean openFound = openMatch.find(curIndex);
boolean closeFound = closeMatch.find(curIndex);
if(openFound && (!closeFound || openMatch.start() < closeMatch.start())) {
if (openFound && (!closeFound || openMatch.start() < closeMatch.start())) {
braceCount++;
curIndex = openMatch.end();
} else if (closeFound) {
@ -71,8 +71,8 @@ public class TemplateRemoveModifier extends PageModifier {
}
// Strip trailing whitespace
while(curIndex < pageContents.length()) {
if(pageContents.charAt(curIndex) == ' ' || pageContents.charAt(curIndex) == '\n') {
while (curIndex < pageContents.length()) {
if (pageContents.charAt(curIndex) == ' ' || pageContents.charAt(curIndex) == '\n') {
curIndex++;
} else {
break;

View file

@ -388,10 +388,15 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
@Override
@NonNull
public UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, final ProgressListener progressListener) throws IOException {
public UploadResult uploadFile(String filename,
@NonNull InputStream file,
long dataLength,
String pageContents,
String editSummary,
final ProgressListener progressListener) throws IOException {
ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, progressListener::onProgress);
Log.e("WTF", "Result: " +result.toString());
Log.e("WTF", "Result: " + result.toString());
String resultStatus = result.getString("/api/upload/@result");
if (!resultStatus.equals("Success")) {

View file

@ -5,9 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -30,34 +28,42 @@ import com.google.gson.GsonBuilder;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.UriSerializer;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class NearbyActivity extends NavigationBaseActivity {
@BindView(R.id.progressBar)
ProgressBar progressBar;
@Inject NearbyPlaces nearbyPlaces;
@Inject @Named("default_preferences") SharedPreferences prefs;
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
private boolean isMapViewActive = false;
private static final int LOCATION_REQUEST = 1;
private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed";
private LocationServiceManager locationManager;
@BindView(R.id.progressBar)
ProgressBar progressBar;
@Inject
LocationServiceManager locationManager;
@Inject
NearbyController nearbyController;
private LatLng curLatLang;
private Bundle bundle;
private NearbyAsyncTask nearbyAsyncTask;
private SharedPreferences sharedPreferences;
private NearbyActivityMode viewMode;
private Disposable placesDisposable;
private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -97,7 +103,8 @@ public class NearbyActivity extends NavigationBaseActivity {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_refresh:
refreshView();
lockNearbyView = false;
refreshView(true);
return true;
case R.id.action_toggle_view:
viewMode = viewMode.toggle();
@ -109,19 +116,11 @@ public class NearbyActivity extends NavigationBaseActivity {
}
}
private void startLookingForNearby() {
locationManager = new LocationServiceManager(this);
locationManager.registerLocationManager();
curLatLang = locationManager.getLatestLocation();
nearbyAsyncTask = new NearbyAsyncTask(this, new NearbyController(nearbyPlaces, prefs));
nearbyAsyncTask.execute();
}
private void checkLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
startLookingForNearby();
refreshView(false);
} else {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
@ -162,7 +161,7 @@ public class NearbyActivity extends NavigationBaseActivity {
}
}
} else {
startLookingForNearby();
refreshView(false);
}
}
@ -171,16 +170,10 @@ public class NearbyActivity extends NavigationBaseActivity {
switch (requestCode) {
case LOCATION_REQUEST: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startLookingForNearby();
refreshView(false);
} else {
//If permission not granted, go to page that says Nearby Places cannot be displayed
if (nearbyAsyncTask != null) {
nearbyAsyncTask.cancel(true);
}
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
hideProgressBar();
showLocationPermissionDeniedErrorDialog();
}
}
@ -205,8 +198,7 @@ public class NearbyActivity extends NavigationBaseActivity {
}
private void checkGps() {
LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
if (!locationManager.isProviderEnabled()) {
Timber.d("GPS is not enabled");
new AlertDialog.Builder(this)
.setMessage(R.string.gps_disabled)
@ -231,104 +223,106 @@ public class NearbyActivity extends NavigationBaseActivity {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
Timber.d("User is back from Settings page");
refreshView();
refreshView(false);
}
}
private void toggleView() {
if (nearbyAsyncTask != null) {
if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
if (viewMode.isMap()) {
setMapFragment();
} else {
setListFragment();
}
}
sharedPreferences.edit().putBoolean(MAP_LAST_USED_PREFERENCE, viewMode.isMap()).apply();
if (viewMode.isMap()) {
setMapFragment();
} else {
setListFragment();
}
sharedPreferences.edit().putBoolean(MAP_LAST_USED_PREFERENCE, viewMode.isMap()).apply();
}
@Override
protected void onStart() {
super.onStart();
locationManager.registerLocationManager();
locationManager.addLocationListener(this);
}
@Override
protected void onStop() {
super.onStop();
locationManager.removeLocationListener(this);
locationManager.unregisterLocationManager();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (placesDisposable != null) {
placesDisposable.dispose();
}
}
@Override
protected void onResume() {
super.onResume();
lockNearbyView = false;
checkGps();
refreshView(false);
}
@Override
protected void onPause() {
super.onPause();
if (nearbyAsyncTask != null) {
nearbyAsyncTask.cancel(true);
private void refreshView(boolean isHardRefresh) {
if (lockNearbyView) {
return;
}
LatLng lastLocation = locationManager.getLastLocation();
if (curLatLang != null && curLatLang.equals(lastLocation)) { //refresh view only if location has changed
if (isHardRefresh) {
ViewUtil.showLongToast(this, R.string.nearby_location_has_not_changed);
}
return;
}
curLatLang = lastLocation;
if (curLatLang == null) {
Timber.d("Skipping update of nearby places as location is unavailable");
return;
}
progressBar.setVisibility(View.VISIBLE);
placesDisposable = Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLang, this))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::populatePlaces);
}
private void refreshView() {
nearbyAsyncTask = new NearbyAsyncTask(this, new NearbyController(nearbyPlaces, prefs));
nearbyAsyncTask.execute();
private void populatePlaces(List<Place> placeList) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer())
.create();
String gsonPlaceList = gson.toJson(placeList);
String gsonCurLatLng = gson.toJson(curLatLang);
if (placeList.size() == 0) {
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(this, R.string.no_nearby, duration);
toast.show();
}
bundle.clear();
bundle.putString("PlaceList", gsonPlaceList);
bundle.putString("CurLatLng", gsonCurLatLng);
lockNearbyView = true;
// Begin the transaction
if (viewMode.isMap()) {
setMapFragment();
} else {
setListFragment();
}
hideProgressBar();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (locationManager != null) {
locationManager.unregisterLocationManager();
}
}
private class NearbyAsyncTask extends AsyncTask<Void, Integer, List<Place>> {
private final Context mContext;
private final NearbyController nearbyController;
private NearbyAsyncTask(Context context, NearbyController nearbyController) {
this.mContext = context;
this.nearbyController = nearbyController;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected List<Place> doInBackground(Void... params) {
return nearbyController.loadAttractionsFromLocation(curLatLang, NearbyActivity.this);
}
@Override
protected void onPostExecute(List<Place> placeList) {
super.onPostExecute(placeList);
if (isCancelled()) {
return;
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer())
.create();
String gsonPlaceList = gson.toJson(placeList);
String gsonCurLatLng = gson.toJson(curLatLang);
if (placeList.size() == 0) {
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(mContext, R.string.no_nearby, duration);
toast.show();
}
bundle.clear();
bundle.putString("PlaceList", gsonPlaceList);
bundle.putString("CurLatLng", gsonCurLatLng);
// Begin the transaction
if (viewMode.isMap()) {
setMapFragment();
} else {
setListFragment();
}
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
private void hideProgressBar() {
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
}
@ -354,8 +348,8 @@ public class NearbyActivity extends NavigationBaseActivity {
fragmentTransaction.commitAllowingStateLoss();
}
public static void startYourself(Context context) {
Intent settingsIntent = new Intent(context, NearbyActivity.class);
context.startActivity(settingsIntent);
@Override
public void onLocationChanged(LatLng latLng) {
refreshView(false);
}
}

View file

@ -14,6 +14,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UiUtils;
@ -22,13 +25,14 @@ import timber.log.Timber;
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
public class NearbyController {
private static final int MAX_RESULTS = 1000;
private final NearbyPlaces nearbyPlaces;
private final SharedPreferences prefs;
public NearbyController(NearbyPlaces nearbyPlaces, SharedPreferences prefs) {
@Inject
public NearbyController(NearbyPlaces nearbyPlaces,
@Named("default_preferences") SharedPreferences prefs) {
this.nearbyPlaces = nearbyPlaces;
this.prefs = prefs;
}

View file

@ -46,7 +46,7 @@ public class NearbyPlaces {
try {
// increase the radius gradually to find a satisfactory number of nearby places
while (radius < MAX_RADIUS) {
while (radius <= MAX_RADIUS) {
places = getFromWikidataQuery(curLatLng, lang, radius);
Timber.d("%d results at radius: %f", places.size(), radius);
if (places.size() >= MIN_RESULTS) {
@ -62,6 +62,11 @@ public class NearbyPlaces {
Timber.d("back to initial radius: %f", radius);
radius = INITIAL_RADIUS;
}
// make sure we will be able to send at least one request next time
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
return places;
}

View file

@ -1,7 +1,5 @@
package fr.free.nrw.commons.settings;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDelegate;
@ -54,9 +52,4 @@ public class SettingsActivity extends NavigationBaseActivity {
return super.onOptionsItemSelected(item);
}
}
public static void startYourself(Context context) {
Intent settingsIntent = new Intent(context, SettingsActivity.class);
context.startActivity(settingsIntent);
}
}

View file

@ -1,23 +1,41 @@
package fr.free.nrw.commons.settings;
import android.Manifest;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.widget.Toast;
import java.io.File;
import javax.inject.Inject;
import javax.inject.Named;
import dagger.android.AndroidInjection;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.utils.FileUtils;
public class SettingsFragment extends PreferenceFragment {
private static final int REQUEST_CODE_WRITE_EXTERNAL_STORAGE = 100;
@Inject @Named("default_preferences") SharedPreferences prefs;
@Override
@ -76,6 +94,63 @@ public class SettingsFragment extends PreferenceFragment {
return true;
});
Preference sendLogsPreference = findPreference("sendLogFile");
sendLogsPreference.setOnPreferenceClickListener(preference -> {
//first we need to check if we have the necessary permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(
getActivity(),
Manifest.permission.WRITE_EXTERNAL_STORAGE)
==
PackageManager.PERMISSION_GRANTED) {
sendAppLogsViaEmail();
} else {
//first get the necessary permission
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_WRITE_EXTERNAL_STORAGE);
}
} else {
sendAppLogsViaEmail();
}
return true;
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_WRITE_EXTERNAL_STORAGE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
sendAppLogsViaEmail();
}
}
}
private void sendAppLogsViaEmail() {
String appLogs = Utils.getAppLogs();
File appLogsFile = FileUtils.createAndGetAppLogsFile(appLogs);
Context applicationContext = getActivity().getApplicationContext();
Uri appLogsFilePath = FileProvider.getUriForFile(
getActivity(),
applicationContext.getPackageName() + ".provider",
appLogsFile
);
Intent feedbackIntent = new Intent(Intent.ACTION_SEND);
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,
BuildConfig.VERSION_NAME));
feedbackIntent.putExtra(Intent.EXTRA_STREAM,appLogsFilePath);
try {
startActivity(feedbackIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
}
}
}

View file

@ -13,7 +13,7 @@ public abstract class BaseActivity extends DaggerAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
boolean currentThemeIsDark = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme", false);
if (currentThemeIsDark) {
if (currentThemeIsDark){
currentTheme = true;
setTheme(R.style.DarkAppTheme);
} else {

View file

@ -1,6 +1,9 @@
package fr.free.nrw.commons.theme;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
@ -9,7 +12,9 @@ import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import butterknife.BindView;
@ -18,6 +23,7 @@ import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.nearby.NearbyActivity;
@ -47,6 +53,22 @@ public abstract class NavigationBaseActivity extends BaseActivity
toggle.setDrawerIndicatorEnabled(true);
toggle.syncState();
setDrawerPaneWidth();
setUserName();
}
/**
* Set the username in navigationHeader.
*/
private void setUserName() {
View navHeaderView = navigationView.getHeaderView(0);
TextView username = navHeaderView.findViewById(R.id.username);
AccountManager accountManager = AccountManager.get(this);
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE);
if (allAccounts.length != 0) {
username.setText(allAccounts[0].name);
}
}
public void initBackButton() {
@ -70,30 +92,25 @@ public abstract class NavigationBaseActivity extends BaseActivity
@Override
public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) {
final int itemId = item.getItemId();
switch (itemId) {
case R.id.action_home:
drawerLayout.closeDrawer(navigationView);
if (!(this instanceof ContributionsActivity)) {
ContributionsActivity.startYourself(this);
}
startActivityWithFlags(
this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
Intent.FLAG_ACTIVITY_SINGLE_TOP);
return true;
case R.id.action_nearby:
drawerLayout.closeDrawer(navigationView);
if (!(this instanceof NearbyActivity)) {
NearbyActivity.startYourself(this);
}
startActivityWithFlags(this, NearbyActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return true;
case R.id.action_about:
drawerLayout.closeDrawer(navigationView);
if (!(this instanceof AboutActivity)) {
AboutActivity.startYourself(this);
}
startActivityWithFlags(this, AboutActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return true;
case R.id.action_settings:
drawerLayout.closeDrawer(navigationView);
if (!(this instanceof SettingsActivity)) {
SettingsActivity.startYourself(this);
}
startActivityWithFlags(this, SettingsActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return true;
case R.id.action_introduction:
drawerLayout.closeDrawer(navigationView);
@ -127,6 +144,7 @@ public abstract class NavigationBaseActivity extends BaseActivity
.show();
return true;
default:
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
return false;
}
}
@ -143,4 +161,12 @@ public abstract class NavigationBaseActivity extends BaseActivity
finish();
}
}
public static <T> void startActivityWithFlags(Context context, Class<T> cls, int... flags) {
Intent intent = new Intent(context, cls);
for (int flag: flags) {
intent.addFlags(flag);
}
context.startActivity(intent);
}
}

View file

@ -123,8 +123,9 @@ public class FileUtils {
} catch (IllegalArgumentException e) {
Timber.d(e);
} finally {
if (cursor != null)
if (cursor != null) {
cursor.close();
}
}
return null;
}

View file

@ -224,7 +224,7 @@ public class GPSExtractor {
return decimalCoords;
}
private double convertToDegree(String stringDMS){
private double convertToDegree(String stringDMS) {
double result;
String[] DMS = stringDMS.split(",", 3);

View file

@ -69,7 +69,7 @@ public class MwVolleyApi {
* @param coords Coordinates to build query with
* @return URL for API query
*/
private String buildUrl (String coords){
private String buildUrl(String coords) {
Uri.Builder builder = Uri.parse(MWURL).buildUpon();

View file

@ -65,7 +65,7 @@ public class UploadController {
}
public void cleanup() {
if(isUploadServiceConnected) {
if (isUploadServiceConnected) {
context.unbindService(uploadServiceConnection);
}
}
@ -87,11 +87,11 @@ public class UploadController {
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
//Set creator, desc, and license
if(TextUtils.isEmpty(contribution.getCreator())) {
if (TextUtils.isEmpty(contribution.getCreator())) {
contribution.setCreator(sessionManager.getCurrentAccount().name);
}
if(contribution.getDescription() == null) {
if (contribution.getDescription() == null) {
contribution.setDescription("");
}
@ -109,11 +109,11 @@ public class UploadController {
long length;
ContentResolver contentResolver = context.getContentResolver();
try {
if(contribution.getDataLength() <= 0) {
if (contribution.getDataLength() <= 0) {
length = contentResolver
.openAssetFileDescriptor(contribution.getLocalUri(), "r")
.getLength();
if(length == -1) {
if (length == -1) {
// Let us find out the long way!
length = countBytes(contentResolver
.openInputStream(contribution.getLocalUri()));

View file

@ -185,7 +185,7 @@ public class UploadService extends HandlerService<Contribution> {
@SuppressLint("StringFormatInvalid")
private void uploadContribution(Contribution contribution) {
InputStream file = null;
InputStream file;
String notificationTag = contribution.getLocalUri().toString();
@ -196,6 +196,14 @@ public class UploadService extends HandlerService<Contribution> {
Timber.d("File not found");
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
fileNotFound.show();
return;
}
//As the file is null there's no point in continuing the upload process
//mwapi.upload accepts a NonNull input stream
if(file == null) {
Timber.d("File not found");
return;
}
Timber.d("Before execution!");

View file

@ -15,6 +15,6 @@ public class ExecutorUtils {
}
};
public static Executor uiExecutor () { return uiExecutor;}
public static Executor uiExecutor() { return uiExecutor; }
}

View file

@ -1,11 +1,16 @@
package fr.free.nrw.commons.utils;
import android.os.Environment;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
public class FileUtils {
/**
@ -53,5 +58,32 @@ public class FileUtils {
return deletedAll;
}
public static File createAndGetAppLogsFile(String logs) {
try {
File commonsAppDirectory = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
if (!commonsAppDirectory.exists()) {
commonsAppDirectory.mkdir();
}
File logsFile = new File(commonsAppDirectory,"logs.txt");
if (logsFile.exists()) {
//old logs file is useless
logsFile.delete();
}
logsFile.createNewFile();
FileOutputStream outputStream = new FileOutputStream(logsFile);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.append(logs);
outputStreamWriter.close();
outputStream.flush();
outputStream.close();
return logsFile;
} catch (IOException ioe) {
Timber.e(ioe);
return null;
}
}
}

View file

@ -23,8 +23,8 @@ public class FragmentUtils {
.commitNow();
return true;
} catch (IllegalStateException e) {
Timber.e(e, "Could not add & commit fragment. " +
"Did you mean to call commitAllowingStateLoss?");
Timber.e(e, "Could not add & commit fragment. "
+ "Did you mean to call commitAllowingStateLoss?");
}
return false;
}

View file

@ -0,0 +1,17 @@
package fr.free.nrw.commons.utils;
import android.content.Context;
import android.support.annotation.StringRes;
import android.widget.Toast;
public class ViewUtil {
public static void showLongToast(final Context context, @StringRes final int stringResId) {
ExecutorUtils.uiExecutor().execute(new Runnable() {
@Override
public void run() {
Toast.makeText(context, context.getString(stringResId), Toast.LENGTH_LONG).show();
}
});
}
}