mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-11-04 16:53:55 +01:00
Merge branch 'master' into featuredImages
This commit is contained in:
commit
463673f942
343 changed files with 11434 additions and 3062 deletions
|
|
@ -0,0 +1,59 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Created by bluesir9 on 16/9/17.
|
||||
*
|
||||
* <p>Responsible for checking if the picture that the user is trying to upload is useful or not. Will attempt to filter
|
||||
* away completely black,fuzzy/blurry pictures(for now).
|
||||
*
|
||||
* <p>todo: Detect selfies?
|
||||
*/
|
||||
|
||||
public class DetectUnwantedPicturesAsync extends AsyncTask<Void, Void, ImageUtils.Result> {
|
||||
|
||||
interface Callback {
|
||||
void onResult(ImageUtils.Result result);
|
||||
}
|
||||
|
||||
private final Callback callback;
|
||||
private final String imageMediaFilePath;
|
||||
|
||||
DetectUnwantedPicturesAsync(String imageMediaFilePath, Callback callback) {
|
||||
this.callback = callback;
|
||||
this.imageMediaFilePath = imageMediaFilePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageUtils.Result doInBackground(Void... voids) {
|
||||
try {
|
||||
Timber.d("FilePath: " + imageMediaFilePath);
|
||||
if (imageMediaFilePath == null) {
|
||||
return ImageUtils.Result.IMAGE_OK;
|
||||
}
|
||||
|
||||
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(imageMediaFilePath,false);
|
||||
|
||||
return ImageUtils.checkIfImageIsTooDark(decoder);
|
||||
} catch (IOException ioe) {
|
||||
Timber.e(ioe, "IO Exception");
|
||||
return ImageUtils.Result.IMAGE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ImageUtils.Result result) {
|
||||
super.onPostExecute(result);
|
||||
//callback to UI so that it can take necessary decision based on the result obtained
|
||||
callback.onResult(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
|
|
@ -28,12 +30,14 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
|||
DUPLICATE_CANCELLED
|
||||
}
|
||||
|
||||
private final WeakReference<Activity> activity;
|
||||
private final MediaWikiApi api;
|
||||
private final String fileSha1;
|
||||
private final Context context;
|
||||
private final WeakReference<Context> context;
|
||||
private final Callback callback;
|
||||
|
||||
public ExistingFileAsync(String fileSha1, Context context, Callback callback, MediaWikiApi mwApi) {
|
||||
public ExistingFileAsync(WeakReference<Activity> activity, String fileSha1, WeakReference<Context> context, Callback callback, MediaWikiApi mwApi) {
|
||||
this.activity = activity;
|
||||
this.fileSha1 = fileSha1;
|
||||
this.context = context;
|
||||
this.callback = callback;
|
||||
|
|
@ -69,19 +73,21 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
|||
// If file exists, display warning to user.
|
||||
// Use soft warning for now (user able to choose to proceed) until have determined that implementation works without bugs
|
||||
if (fileExists) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context.get());
|
||||
builder.setMessage(R.string.file_exists)
|
||||
.setTitle(R.string.warning);
|
||||
builder.setPositiveButton(R.string.no, (dialog, id) -> {
|
||||
//Go back to ContributionsActivity
|
||||
Intent intent = new Intent(context, ContributionsActivity.class);
|
||||
context.startActivity(intent);
|
||||
Intent intent = new Intent(context.get(), ContributionsActivity.class);
|
||||
context.get().startActivity(intent);
|
||||
callback.onResult(Result.DUPLICATE_CANCELLED);
|
||||
});
|
||||
builder.setNegativeButton(R.string.yes, (dialog, id) -> callback.onResult(Result.DUPLICATE_PROCEED));
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
if (!activity.get().isFinishing()) {
|
||||
dialog.show();
|
||||
}
|
||||
} else {
|
||||
callback.onResult(Result.NO_DUPLICATE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,25 @@ package fr.free.nrw.commons.upload;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Date;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
@ -28,7 +33,7 @@ public class FileUtils {
|
|||
* other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @param uri The Uri to query.
|
||||
* @author paulburke
|
||||
*/
|
||||
// Can be safely suppressed, checks for isKitKat before running isDocumentUri
|
||||
|
|
@ -36,6 +41,7 @@ public class FileUtils {
|
|||
@Nullable
|
||||
public static String getPath(Context context, Uri uri) {
|
||||
|
||||
String returnPath = null;
|
||||
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
|
||||
// DocumentProvider
|
||||
|
|
@ -47,31 +53,34 @@ public class FileUtils {
|
|||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
returnPath = Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
}
|
||||
}
|
||||
// DownloadsProvider
|
||||
else if (isDownloadsDocument(uri)) {
|
||||
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
|
||||
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
}
|
||||
// MediaProvider
|
||||
else if (isMediaDocument(uri)) {
|
||||
returnPath = getDataColumn(context, contentUri, null, null);
|
||||
} else if (isMediaDocument(uri)) { // MediaProvider
|
||||
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
if ("image".equals(type)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
switch (type) {
|
||||
case "image":
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
break;
|
||||
case "video":
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
break;
|
||||
case "audio":
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
|
|
@ -79,16 +88,55 @@ public class FileUtils {
|
|||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
returnPath = getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
}
|
||||
// MediaStore (and general)
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
return getDataColumn(context, uri, null, null);
|
||||
returnPath = getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
returnPath = uri.getPath();
|
||||
}
|
||||
|
||||
if(returnPath == null) {
|
||||
//fetching path may fail depending on the source URI and all hope is lost
|
||||
//so we will create and use a copy of the file, which seems to work
|
||||
String copyPath = null;
|
||||
try {
|
||||
ParcelFileDescriptor descriptor
|
||||
= context.getContentResolver().openFileDescriptor(uri, "r");
|
||||
if (descriptor != null) {
|
||||
|
||||
SharedPreferences sharedPref = PreferenceManager
|
||||
.getDefaultSharedPreferences(context);
|
||||
boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
|
||||
if (useExtStorage) {
|
||||
copyPath = Environment.getExternalStorageDirectory().toString()
|
||||
+ "/CommonsApp/" + new Date().getTime() + ".jpg";
|
||||
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
||||
newFile.mkdir();
|
||||
FileUtils.copy(
|
||||
descriptor.getFileDescriptor(),
|
||||
copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
copyPath = context.getCacheDir().getAbsolutePath()
|
||||
+ "/" + new Date().getTime() + ".jpg";
|
||||
FileUtils.copy(
|
||||
descriptor.getFileDescriptor(),
|
||||
copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.w(e, "Error in file " + copyPath);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return returnPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -109,7 +157,7 @@ public class FileUtils {
|
|||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
final String column = "_data";
|
||||
final String column = MediaStore.Images.ImageColumns.DATA;
|
||||
final String[] projection = {
|
||||
column
|
||||
};
|
||||
|
|
@ -163,7 +211,8 @@ public class FileUtils {
|
|||
|
||||
/**
|
||||
* Copy content from source file to destination file.
|
||||
* @param source stream copied from
|
||||
*
|
||||
* @param source stream copied from
|
||||
* @param destination stream copied to
|
||||
* @throws IOException thrown when failing to read source or opening destination file
|
||||
*/
|
||||
|
|
@ -176,7 +225,8 @@ public class FileUtils {
|
|||
|
||||
/**
|
||||
* Copy content from source file to destination file.
|
||||
* @param source file descriptor copied from
|
||||
*
|
||||
* @param source file descriptor copied from
|
||||
* @param destination file path copied to
|
||||
* @throws IOException thrown when failing to read source or opening destination file
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -113,11 +113,11 @@ public class GPSExtractor {
|
|||
*/
|
||||
@Nullable
|
||||
public String getCoords(boolean useGPS) {
|
||||
String latitude = "";
|
||||
String longitude = "";
|
||||
String latitude_ref = "";
|
||||
String longitude_ref = "";
|
||||
String decimalCoords = "";
|
||||
String latitude;
|
||||
String longitude;
|
||||
String latitudeRef;
|
||||
String longitudeRef;
|
||||
String decimalCoords;
|
||||
|
||||
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
||||
if (exif == null || exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
|
||||
|
|
@ -150,15 +150,15 @@ public class GPSExtractor {
|
|||
Timber.d("EXIF data has location info");
|
||||
|
||||
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
||||
latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||
latitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||
longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
||||
longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||
longitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||
|
||||
if (latitude!=null && latitude_ref!=null && longitude!=null && longitude_ref!=null) {
|
||||
Timber.d("Latitude: %s %s", latitude, latitude_ref);
|
||||
Timber.d("Longitude: %s %s", longitude, longitude_ref);
|
||||
if (latitude!=null && latitudeRef!=null && longitude!=null && longitudeRef!=null) {
|
||||
Timber.d("Latitude: %s %s", latitude, latitudeRef);
|
||||
Timber.d("Longitude: %s %s", longitude, longitudeRef);
|
||||
|
||||
decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref);
|
||||
decimalCoords = getDecimalCoords(latitude, latitudeRef, longitude, longitudeRef);
|
||||
return decimalCoords;
|
||||
} else {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.upload;
|
|||
|
||||
import android.Manifest;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -12,7 +11,9 @@ import android.database.DataSetObserver;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
|
@ -22,6 +23,7 @@ import android.view.inputmethod.InputMethodManager;
|
|||
import android.widget.AdapterView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -52,10 +54,17 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
|
||||
OnCategoriesSaveHandler {
|
||||
|
||||
@Inject MediaWikiApi mwApi;
|
||||
@Inject SessionManager sessionManager;
|
||||
@Inject UploadController uploadController;
|
||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||
@Inject
|
||||
MediaWikiApi mwApi;
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject
|
||||
UploadController uploadController;
|
||||
@Inject
|
||||
ModifierSequenceDao modifierSequenceDao;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
SharedPreferences prefs;
|
||||
|
||||
private ArrayList<Contribution> photosList = null;
|
||||
|
||||
|
|
@ -63,6 +72,8 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
private MediaDetailPagerFragment mediaDetails;
|
||||
private CategorizationFragment categorizationFragment;
|
||||
|
||||
private boolean locationPermitted = false;
|
||||
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return photosList.get(i);
|
||||
|
|
@ -166,19 +177,18 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
@Override
|
||||
public void onCategoriesSave(List<String> categories) {
|
||||
if (categories.size() > 0) {
|
||||
ModifierSequenceDao dao = new ModifierSequenceDao(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY));
|
||||
for (Contribution contribution : photosList) {
|
||||
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
|
||||
|
||||
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
|
||||
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
|
||||
|
||||
dao.save(categoriesSequence);
|
||||
modifierSequenceDao.save(categoriesSequence);
|
||||
}
|
||||
}
|
||||
// FIXME: Make sure that the content provider is up
|
||||
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
|
||||
ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
|
||||
ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, true); // Enable sync by default!
|
||||
finish();
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +218,14 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
requestAuthToken();
|
||||
|
||||
//TODO: 15/10/17 should location permission be explicitly requested if not provided?
|
||||
//check if location permission is enabled
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
locationPermitted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -241,7 +259,7 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
mwApi.setAuthCookie(authCookie);
|
||||
Intent intent = getIntent();
|
||||
|
||||
if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
|
||||
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
|
||||
if (photosList == null) {
|
||||
photosList = new ArrayList<>();
|
||||
ArrayList<Uri> urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
|
|
@ -253,6 +271,11 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
up.setTag("sequence", i);
|
||||
up.setSource(Contribution.SOURCE_EXTERNAL);
|
||||
up.setMultiple(true);
|
||||
String imageGpsCoordinates = extractImageGpsData(uri);
|
||||
if (imageGpsCoordinates != null) {
|
||||
Timber.d("GPS data for image found!");
|
||||
up.setDecimalCoords(imageGpsCoordinates);
|
||||
}
|
||||
photosList.add(up);
|
||||
}
|
||||
}
|
||||
|
|
@ -279,7 +302,49 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible()) ;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to extract the gps coordinates using exif data or by using the current
|
||||
* location if available for the image who's imageUri has been provided.
|
||||
* @param imageUri The uri of the image who's GPS coordinates data we wish to extract
|
||||
* @return GPS coordinates as a String as is returned by {@link GPSExtractor}
|
||||
*/
|
||||
@Nullable
|
||||
private String extractImageGpsData(Uri imageUri) {
|
||||
Timber.d("Entering extractImagesGpsData");
|
||||
|
||||
if (imageUri == null) {
|
||||
//now why would you do that???
|
||||
return null;
|
||||
}
|
||||
|
||||
GPSExtractor gpsExtractor = null;
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(imageUri,"r");
|
||||
if (fd != null) {
|
||||
gpsExtractor = new GPSExtractor(fd.getFileDescriptor(),this,prefs);
|
||||
}
|
||||
} else {
|
||||
String filePath = FileUtils.getPath(this,imageUri);
|
||||
if (filePath != null) {
|
||||
gpsExtractor = new GPSExtractor(filePath,this,prefs);
|
||||
}
|
||||
}
|
||||
|
||||
if (gpsExtractor != null) {
|
||||
//get image coordinates from exif data or user location
|
||||
return gpsExtractor.getCoords(locationPermitted);
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
Timber.w(fnfe);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
|
@ -27,12 +30,12 @@ import android.widget.TextView;
|
|||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import dagger.android.support.DaggerFragment;
|
||||
import dagger.android.support.AndroidSupportInjection;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
|
||||
public class MultipleUploadListFragment extends DaggerFragment {
|
||||
public class MultipleUploadListFragment extends Fragment {
|
||||
|
||||
public interface OnMultipleUploadInitiatedHandler {
|
||||
void OnMultipleUploadInitiated();
|
||||
|
|
@ -56,6 +59,12 @@ public class MultipleUploadListFragment extends DaggerFragment {
|
|||
private RelativeLayout overlay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
AndroidSupportInjection.inject(this);
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
private class PhotoDisplayAdapter extends BaseAdapter {
|
||||
|
||||
@Override
|
||||
|
|
@ -170,9 +179,21 @@ public class MultipleUploadListFragment extends DaggerFragment {
|
|||
photosGrid.setColumnWidth(photoSize.x);
|
||||
|
||||
baseTitle.addTextChangedListener(textWatcher);
|
||||
|
||||
baseTitle.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
hideKeyboard(v);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public void hideKeyboard(View view) {
|
||||
InputMethodManager inputMethodManager =(InputMethodManager)getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
baseTitle.removeTextChangedListener(textWatcher);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
|
|
@ -16,7 +19,9 @@ import android.support.annotation.RequiresApi;
|
|||
import android.support.design.widget.Snackbar;
|
||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
|
@ -29,6 +34,7 @@ import java.io.File;
|
|||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
|
@ -46,11 +52,14 @@ import fr.free.nrw.commons.caching.CacheController;
|
|||
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
@ -61,10 +70,10 @@ import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
|
|||
* Activity for the title/desc screen after image is selected. Also starts processing image
|
||||
* GPS coordinates or user location (if enabled in Settings) for category suggestions.
|
||||
*/
|
||||
public class ShareActivity
|
||||
extends AuthenticatedActivity
|
||||
public class ShareActivity
|
||||
extends AuthenticatedActivity
|
||||
implements SingleUploadFragment.OnUploadActionInitiated,
|
||||
OnCategoriesSaveHandler {
|
||||
OnCategoriesSaveHandler,SimilarImageDialogFragment.onResponse {
|
||||
|
||||
private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1;
|
||||
private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2;
|
||||
|
|
@ -72,11 +81,19 @@ public class ShareActivity
|
|||
private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
|
||||
private CategorizationFragment categorizationFragment;
|
||||
|
||||
@Inject MediaWikiApi mwApi;
|
||||
@Inject CacheController cacheController;
|
||||
@Inject SessionManager sessionManager;
|
||||
@Inject UploadController uploadController;
|
||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||
@Inject
|
||||
MediaWikiApi mwApi;
|
||||
@Inject
|
||||
CacheController cacheController;
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject
|
||||
UploadController uploadController;
|
||||
@Inject
|
||||
ModifierSequenceDao modifierSequenceDao;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
SharedPreferences prefs;
|
||||
|
||||
private String source;
|
||||
private String mimeType;
|
||||
|
|
@ -88,6 +105,7 @@ public class ShareActivity
|
|||
private boolean cacheFound;
|
||||
|
||||
private GPSExtractor imageObj;
|
||||
private GPSExtractor tempImageObj;
|
||||
private String decimalCoords;
|
||||
|
||||
private boolean useNewPermissions = false;
|
||||
|
|
@ -99,6 +117,9 @@ public class ShareActivity
|
|||
private Snackbar snackbar;
|
||||
private boolean duplicateCheckPassed = false;
|
||||
|
||||
private boolean haveCheckedForOtherImages = false;
|
||||
private boolean isNearbyUpload = false;
|
||||
|
||||
/**
|
||||
* Called when user taps the submit button.
|
||||
*/
|
||||
|
|
@ -166,13 +187,12 @@ public class ShareActivity
|
|||
|
||||
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
|
||||
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
|
||||
ModifierSequenceDao dao = new ModifierSequenceDao(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY));
|
||||
dao.save(categoriesSequence);
|
||||
modifierSequenceDao.save(categoriesSequence);
|
||||
}
|
||||
|
||||
// FIXME: Make sure that the content provider is up
|
||||
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
|
||||
ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
|
||||
ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, true); // Enable sync by default!
|
||||
|
||||
finish();
|
||||
}
|
||||
|
|
@ -197,6 +217,10 @@ public class ShareActivity
|
|||
finish();
|
||||
}
|
||||
|
||||
protected boolean isNearbyUpload() {
|
||||
return isNearbyUpload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
@ -216,13 +240,17 @@ public class ShareActivity
|
|||
//Receive intent from ContributionController.java when user selects picture to upload
|
||||
Intent intent = getIntent();
|
||||
|
||||
if (intent.getAction().equals(Intent.ACTION_SEND)) {
|
||||
if (Intent.ACTION_SEND.equals(intent.getAction())) {
|
||||
mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
||||
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
|
||||
} else {
|
||||
source = Contribution.SOURCE_EXTERNAL;
|
||||
}
|
||||
if (intent.hasExtra("isDirectUpload")) {
|
||||
Timber.d("This was initiated by a direct upload from Nearby");
|
||||
isNearbyUpload = true;
|
||||
}
|
||||
mimeType = intent.getType();
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +306,7 @@ public class ShareActivity
|
|||
REQUEST_PERM_ON_CREATE_LOCATION);
|
||||
}
|
||||
}
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
|
||||
|
||||
SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
||||
|
|
@ -302,7 +330,7 @@ public class ShareActivity
|
|||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
backgroundImageView.setImageURI(mediaUri);
|
||||
storagePermitted = true;
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -310,7 +338,7 @@ public class ShareActivity
|
|||
if (grantResults.length >= 1
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
locationPermitted = true;
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -319,12 +347,12 @@ public class ShareActivity
|
|||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
backgroundImageView.setImageURI(mediaUri);
|
||||
storagePermitted = true;
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
}
|
||||
if (grantResults.length >= 2
|
||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
||||
locationPermitted = true;
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -335,7 +363,7 @@ public class ShareActivity
|
|||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
//It is OK to call this at both (1) and (4) because if perm had been granted at
|
||||
//snackbar, user should not be prompted at submit button
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
|
||||
//Uploading only begins if storage permission granted from arrow icon
|
||||
uploadBegins();
|
||||
|
|
@ -346,7 +374,7 @@ public class ShareActivity
|
|||
}
|
||||
}
|
||||
|
||||
private void performPreuploadProcessingOfFile() {
|
||||
private void performPreUploadProcessingOfFile() {
|
||||
if (!useNewPermissions || storagePermitted) {
|
||||
if (!duplicateCheckPassed) {
|
||||
//Test SHA1 of image to see if it matches SHA1 of a file on Commons
|
||||
|
|
@ -357,11 +385,21 @@ public class ShareActivity
|
|||
Timber.d("File SHA1 is: %s", fileSHA1);
|
||||
|
||||
ExistingFileAsync fileAsyncTask =
|
||||
new ExistingFileAsync(fileSHA1, this, result -> {
|
||||
new ExistingFileAsync(new WeakReference<Activity>(this), fileSHA1, new WeakReference<Context>(this), result -> {
|
||||
Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
|
||||
duplicateCheckPassed = (result == DUPLICATE_PROCEED
|
||||
|| result == NO_DUPLICATE);
|
||||
}, mwApi);
|
||||
/*
|
||||
TODO: 16/9/17 should we run DetectUnwantedPicturesAsync if DUPLICATE_PROCEED is returned? Since that means
|
||||
we are processing images that are already on server???...
|
||||
*/
|
||||
|
||||
if (duplicateCheckPassed) {
|
||||
//image can be uploaded, so now check if its a useless picture or not
|
||||
performUnwantedPictureDetectionProcess();
|
||||
}
|
||||
|
||||
},mwApi);
|
||||
fileAsyncTask.execute();
|
||||
} catch (IOException e) {
|
||||
Timber.d(e, "IO Exception: ");
|
||||
|
|
@ -375,6 +413,37 @@ public class ShareActivity
|
|||
}
|
||||
}
|
||||
|
||||
private void performUnwantedPictureDetectionProcess() {
|
||||
String imageMediaFilePath = FileUtils.getPath(this,mediaUri);
|
||||
DetectUnwantedPicturesAsync detectUnwantedPicturesAsync = new DetectUnwantedPicturesAsync(imageMediaFilePath, result -> {
|
||||
|
||||
if (result != ImageUtils.Result.IMAGE_OK) {
|
||||
//show appropriate error message
|
||||
String errorMessage = result == ImageUtils.Result.IMAGE_DARK ? getString(R.string.upload_image_too_dark) : getString(R.string.upload_image_blurry);
|
||||
AlertDialog.Builder errorDialogBuilder = new AlertDialog.Builder(this);
|
||||
errorDialogBuilder.setMessage(errorMessage);
|
||||
errorDialogBuilder.setTitle(getString(R.string.warning));
|
||||
errorDialogBuilder.setPositiveButton(getString(R.string.no), (dialogInterface, i) -> {
|
||||
//user does not wish to upload the picture, take them back to ContributionsActivity
|
||||
Intent intent = new Intent(ShareActivity.this, ContributionsActivity.class);
|
||||
dialogInterface.dismiss();
|
||||
startActivity(intent);
|
||||
});
|
||||
errorDialogBuilder.setNegativeButton(getString(R.string.yes), (dialogInterface, i) -> {
|
||||
//user wishes to go ahead with the upload of this picture, just dismiss this dialog
|
||||
dialogInterface.dismiss();
|
||||
});
|
||||
|
||||
AlertDialog errorDialog = errorDialogBuilder.create();
|
||||
if (!isFinishing()) {
|
||||
errorDialog.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
detectUnwantedPicturesAsync.execute();
|
||||
}
|
||||
|
||||
private Snackbar requestPermissionUsingSnackBar(String rationale,
|
||||
final String[] perms,
|
||||
final int code) {
|
||||
|
|
@ -452,13 +521,93 @@ public class ShareActivity
|
|||
if (imageObj != null) {
|
||||
// Gets image coords from exif data or user location
|
||||
decimalCoords = imageObj.getCoords(gpsEnabled);
|
||||
useImageCoords();
|
||||
if(decimalCoords==null || !imageObj.imageCoordsExists){
|
||||
// Check if the location is from GPS or EXIF
|
||||
// Find other photos taken around the same time which has gps coordinates
|
||||
Timber.d("EXIF:false");
|
||||
Timber.d("EXIF call"+(imageObj==tempImageObj));
|
||||
if(!haveCheckedForOtherImages)
|
||||
findOtherImages(gpsEnabled);// Do not do repeat the process
|
||||
}
|
||||
else {
|
||||
// As the selected image has GPS data in EXIF go ahead with the same.
|
||||
useImageCoords();
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Timber.w("File not found: " + mediaUri, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void findOtherImages(boolean gpsEnabled) {
|
||||
Timber.d("filePath"+getPathOfMediaOrCopy());
|
||||
String filePath = getPathOfMediaOrCopy();
|
||||
long timeOfCreation = new File(filePath).lastModified();//Time when the original image was created
|
||||
File folder = new File(filePath.substring(0,filePath.lastIndexOf('/')));
|
||||
File[] files = folder.listFiles();
|
||||
Timber.d("folderTime Number:"+files.length);
|
||||
|
||||
for(File file : files){
|
||||
if(file.lastModified()-timeOfCreation<=(120*1000) && file.lastModified()-timeOfCreation>=-(120*1000)){
|
||||
//Make sure the photos were taken within 20seconds
|
||||
Timber.d("fild date:"+file.lastModified()+ " time of creation"+timeOfCreation);
|
||||
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
|
||||
ParcelFileDescriptor descriptor
|
||||
= null;
|
||||
try {
|
||||
descriptor = getContentResolver().openFileDescriptor(Uri.parse(file.getAbsolutePath()), "r");
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (descriptor != null) {
|
||||
tempImageObj = new GPSExtractor(descriptor.getFileDescriptor(),this, prefs);
|
||||
}
|
||||
} else {
|
||||
if (filePath != null) {
|
||||
tempImageObj = new GPSExtractor(file.getAbsolutePath(), this, prefs);
|
||||
}
|
||||
}
|
||||
|
||||
if(tempImageObj!=null){
|
||||
Timber.d("not null fild EXIF"+tempImageObj.imageCoordsExists +" coords"+tempImageObj.getCoords(gpsEnabled));
|
||||
if(tempImageObj.getCoords(gpsEnabled)!=null && tempImageObj.imageCoordsExists){
|
||||
// Current image has gps coordinates and it's not current gps locaiton
|
||||
Timber.d("This fild has image coords:"+ file.getAbsolutePath());
|
||||
// Create a dialog fragment for the suggestion
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString("originalImagePath",filePath);
|
||||
args.putString("possibleImagePath",file.getAbsolutePath());
|
||||
newFragment.setArguments(args);
|
||||
newFragment.show(fragmentManager, "dialog");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
haveCheckedForOtherImages = true; //Finished checking for other images
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostiveResponse() {
|
||||
imageObj = tempImageObj;
|
||||
decimalCoords = imageObj.getCoords(false);// Not necessary to use gps as image already ha EXIF data
|
||||
Timber.d("EXIF from tempImageObj");
|
||||
useImageCoords();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeResponse() {
|
||||
Timber.d("EXIF from imageObj");
|
||||
useImageCoords();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
|
||||
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
|
||||
|
|
@ -466,6 +615,7 @@ public class ShareActivity
|
|||
public void useImageCoords() {
|
||||
if (decimalCoords != null) {
|
||||
Timber.d("Decimal coords of image: %s", decimalCoords);
|
||||
Timber.d("is EXIF data present:"+imageObj.imageCoordsExists+" from findOther image:"+(imageObj==tempImageObj));
|
||||
|
||||
// Only set cache for this point if image has coords
|
||||
if (imageObj.imageCoordsExists) {
|
||||
|
|
@ -489,7 +639,10 @@ public class ShareActivity
|
|||
Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList);
|
||||
MwVolleyApi.setGpsCat(displayCatList);
|
||||
}
|
||||
}else{
|
||||
Timber.d("EXIF: no coords");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.facebook.imagepipeline.listener.RequestListener;
|
||||
import com.facebook.imagepipeline.listener.RequestLoggingListener;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
|
||||
/**
|
||||
* Created by harisanker on 14/2/18.
|
||||
*/
|
||||
|
||||
public class SimilarImageDialogFragment extends DialogFragment {
|
||||
SimpleDraweeView originalImage;
|
||||
SimpleDraweeView possibleImage;
|
||||
Button positiveButton;
|
||||
Button negativeButton;
|
||||
onResponse mOnResponse;//Implemented interface from shareActivity
|
||||
Boolean gotResponse = false;
|
||||
public SimilarImageDialogFragment() {
|
||||
}
|
||||
public interface onResponse{
|
||||
public void onPostiveResponse();
|
||||
public void onNegativeResponse();
|
||||
}
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false);
|
||||
Set<RequestListener> requestListeners = new HashSet<>();
|
||||
requestListeners.add(new RequestLoggingListener());
|
||||
|
||||
originalImage =(SimpleDraweeView) view.findViewById(R.id.orginalImage);
|
||||
possibleImage =(SimpleDraweeView) view.findViewById(R.id.possibleImage);
|
||||
positiveButton = (Button) view.findViewById(R.id.postive_button);
|
||||
negativeButton = (Button) view.findViewById(R.id.negative_button);
|
||||
|
||||
originalImage.setHierarchy(GenericDraweeHierarchyBuilder
|
||||
.newInstance(getResources())
|
||||
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_image_black_24dp,getContext().getTheme()))
|
||||
.setFailureImage(VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_error_outline_black_24dp, getContext().getTheme()))
|
||||
.build());
|
||||
possibleImage.setHierarchy(GenericDraweeHierarchyBuilder
|
||||
.newInstance(getResources())
|
||||
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_image_black_24dp,getContext().getTheme()))
|
||||
.setFailureImage(VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_error_outline_black_24dp, getContext().getTheme()))
|
||||
.build());
|
||||
|
||||
originalImage.setImageURI(Uri.fromFile(new File(getArguments().getString("originalImagePath"))));
|
||||
possibleImage.setImageURI(Uri.fromFile(new File(getArguments().getString("possibleImagePath"))));
|
||||
|
||||
negativeButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mOnResponse.onNegativeResponse();
|
||||
gotResponse = true;
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
positiveButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mOnResponse.onPostiveResponse();
|
||||
gotResponse = true;
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mOnResponse = (onResponse) getActivity();//Interface Implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
// I user dismisses dialog by pressing outside the dialog.
|
||||
if(!gotResponse)
|
||||
mOnResponse.onNegativeResponse();
|
||||
super.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
|
@ -8,9 +11,11 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
|
@ -25,6 +30,7 @@ import android.widget.Button;
|
|||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
|
@ -36,16 +42,16 @@ import butterknife.ButterKnife;
|
|||
import butterknife.OnClick;
|
||||
import butterknife.OnItemSelected;
|
||||
import butterknife.OnTouch;
|
||||
import dagger.android.support.DaggerFragment;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
public class SingleUploadFragment extends DaggerFragment {
|
||||
public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
@BindView(R.id.titleEdit) EditText titleEdit;
|
||||
@BindView(R.id.descEdit) EditText descEdit;
|
||||
|
|
@ -54,6 +60,7 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
|
||||
|
||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
||||
|
||||
private String license;
|
||||
private OnUploadActionInitiated uploadActionInitiatedHandler;
|
||||
|
|
@ -62,9 +69,6 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.activity_share, menu);
|
||||
if (titleEdit != null) {
|
||||
menu.findItem(R.id.menu_upload_single).setEnabled(titleEdit.getText().length() != 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -73,6 +77,11 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
//What happens when the 'submit' icon is tapped
|
||||
case R.id.menu_upload_single:
|
||||
|
||||
if (titleEdit.getText().toString().isEmpty()) {
|
||||
Toast.makeText(getContext(), R.string.add_title_toast, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
String title = titleEdit.getText().toString();
|
||||
String desc = descEdit.getText().toString();
|
||||
|
||||
|
|
@ -84,7 +93,6 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
|
||||
uploadActionInitiatedHandler.uploadActionInitiated(title, desc);
|
||||
return true;
|
||||
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
|
@ -95,6 +103,14 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
|
||||
Intent activityIntent = getActivity().getIntent();
|
||||
if (activityIntent.hasExtra("title")) {
|
||||
titleEdit.setText(activityIntent.getStringExtra("title"));
|
||||
}
|
||||
if (activityIntent.hasExtra("description")) {
|
||||
descEdit.setText(activityIntent.getStringExtra("description"));
|
||||
}
|
||||
|
||||
ArrayList<String> licenseItems = new ArrayList<>();
|
||||
licenseItems.add(getString(R.string.license_name_cc0));
|
||||
licenseItems.add(getString(R.string.license_name_cc_by));
|
||||
|
|
@ -104,6 +120,18 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
|
||||
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||
|
||||
// If this is a direct upload from Nearby, autofill title and desc fields with the Place's values
|
||||
boolean isNearbyUpload = ((ShareActivity) getActivity()).isNearbyUpload();
|
||||
|
||||
if (isNearbyUpload) {
|
||||
String imageTitle = directPrefs.getString("Title", "");
|
||||
String imageDesc = directPrefs.getString("Desc", "");
|
||||
String imageCats = directPrefs.getString("Category", "");
|
||||
Timber.d("Image title: " + imageTitle + ", image desc: " + imageDesc + ", image categories: " + imageCats);
|
||||
titleEdit.setText(imageTitle);
|
||||
descEdit.setText(imageDesc);
|
||||
}
|
||||
|
||||
// check if this is the first time we have uploaded
|
||||
if (prefs.getString("Title", "").trim().length() == 0
|
||||
&& prefs.getString("Desc", "").trim().length() == 0) {
|
||||
|
|
@ -136,11 +164,29 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
|
||||
titleEdit.addTextChangedListener(textWatcher);
|
||||
|
||||
titleEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
hideKeyboard(v);
|
||||
}
|
||||
});
|
||||
|
||||
descEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if(!hasFocus){
|
||||
hideKeyboard(v);
|
||||
}
|
||||
});
|
||||
|
||||
setLicenseSummary(license);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
public void hideKeyboard(View view) {
|
||||
Log.i("hide", "hideKeyboard: ");
|
||||
InputMethodManager inputMethodManager =(InputMethodManager)getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
titleEdit.removeTextChangedListener(textWatcher);
|
||||
|
|
@ -208,39 +254,45 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
*/
|
||||
@OnTouch(R.id.titleEdit)
|
||||
boolean titleInfo(View view, MotionEvent motionEvent) {
|
||||
//Should replace right with end to support different right-to-left languages as well
|
||||
final int value = titleEdit.getRight() - titleEdit.getCompoundDrawables()[2].getBounds().width();
|
||||
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.media_detail_title)
|
||||
.setMessage(R.string.title_info)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
final int value;
|
||||
if (ViewCompat.getLayoutDirection(getView()) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
value = titleEdit.getRight() - titleEdit.getCompoundDrawables()[2].getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
|
||||
showInfoAlert(R.string.media_detail_title, R.string.title_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
value = titleEdit.getLeft() + titleEdit.getCompoundDrawables()[0].getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
|
||||
showInfoAlert(R.string.media_detail_title, R.string.title_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@OnTouch(R.id.descEdit)
|
||||
boolean descriptionInfo(View view, MotionEvent motionEvent) {
|
||||
final int value = descEdit.getRight() - descEdit.getCompoundDrawables()[2].getBounds().width();
|
||||
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.media_detail_description)
|
||||
.setMessage(R.string.description_info)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
final int value;
|
||||
if (ViewCompat.getLayoutDirection(getView()) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
value = descEdit.getRight() - descEdit.getCompoundDrawables()[2].getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
|
||||
showInfoAlert(R.string.media_detail_description,R.string.description_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else{
|
||||
value = descEdit.getLeft() + descEdit.getCompoundDrawables()[0].getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
|
||||
showInfoAlert(R.string.media_detail_description,R.string.description_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private void setLicenseSummary(String license) {
|
||||
licenseSummaryView.setText(getString(R.string.share_license_summary, getString(Utils.licenseNameFor(license))));
|
||||
}
|
||||
|
|
@ -301,4 +353,14 @@ public class SingleUploadFragment extends DaggerFragment {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showInfoAlert (int titleStringID, int messageStringID){
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setTitle(titleStringID)
|
||||
.setMessage(messageStringID)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
|
@ -49,7 +50,7 @@ public class UploadController {
|
|||
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService();
|
||||
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService();
|
||||
isUploadServiceConnected = true;
|
||||
}
|
||||
|
||||
|
|
@ -81,13 +82,14 @@ public class UploadController {
|
|||
|
||||
/**
|
||||
* Starts a new upload task.
|
||||
* @param title the title of the contribution
|
||||
* @param mediaUri the media URI of the contribution
|
||||
* @param description the description of the contribution
|
||||
* @param mimeType the MIME type of the contribution
|
||||
* @param source the source of the contribution
|
||||
*
|
||||
* @param title the title of the contribution
|
||||
* @param mediaUri the media URI of the contribution
|
||||
* @param description the description of the contribution
|
||||
* @param mimeType the MIME type of the contribution
|
||||
* @param source the source of the contribution
|
||||
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
|
||||
* @param onComplete the progress tracker
|
||||
* @param onComplete the progress tracker
|
||||
*/
|
||||
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, ContributionUploadProgress onComplete) {
|
||||
Contribution contribution;
|
||||
|
|
@ -106,8 +108,9 @@ public class UploadController {
|
|||
|
||||
/**
|
||||
* Starts a new upload task.
|
||||
*
|
||||
* @param contribution the contribution object
|
||||
* @param onComplete the progress tracker
|
||||
* @param onComplete the progress tracker
|
||||
*/
|
||||
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
|
||||
//Set creator, desc, and license
|
||||
|
|
@ -134,15 +137,17 @@ public class UploadController {
|
|||
ContentResolver contentResolver = context.getContentResolver();
|
||||
try {
|
||||
if (contribution.getDataLength() <= 0) {
|
||||
length = contentResolver
|
||||
.openAssetFileDescriptor(contribution.getLocalUri(), "r")
|
||||
.getLength();
|
||||
if (length == -1) {
|
||||
// Let us find out the long way!
|
||||
length = countBytes(contentResolver
|
||||
.openInputStream(contribution.getLocalUri()));
|
||||
AssetFileDescriptor assetFileDescriptor = contentResolver
|
||||
.openAssetFileDescriptor(contribution.getLocalUri(), "r");
|
||||
if (assetFileDescriptor != null) {
|
||||
length = assetFileDescriptor.getLength();
|
||||
if (length == -1) {
|
||||
// Let us find out the long way!
|
||||
length = countBytes(contentResolver
|
||||
.openInputStream(contribution.getLocalUri()));
|
||||
}
|
||||
contribution.setDataLength(length);
|
||||
}
|
||||
contribution.setDataLength(length);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "IO Exception: ");
|
||||
|
|
@ -152,7 +157,7 @@ public class UploadController {
|
|||
Timber.e(e, "Security Exception: ");
|
||||
}
|
||||
|
||||
String mimeType = (String)contribution.getTag("mimeType");
|
||||
String mimeType = (String) contribution.getTag("mimeType");
|
||||
Boolean imagePrefix = false;
|
||||
|
||||
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
|
||||
|
|
@ -199,6 +204,7 @@ public class UploadController {
|
|||
|
||||
/**
|
||||
* Counts the number of bytes in {@code stream}.
|
||||
*
|
||||
* @param stream the stream
|
||||
* @return the number of bytes in {@code stream}
|
||||
* @throws IOException if an I/O error occurs
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
|
|||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
|
|
@ -52,9 +51,9 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
@Inject MediaWikiApi mwApi;
|
||||
@Inject SessionManager sessionManager;
|
||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||
@Inject ContributionDao contributionDao;
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private ContentProviderClient contributionsProviderClient;
|
||||
private NotificationCompat.Builder curProgressNotification;
|
||||
private int toUpload;
|
||||
|
||||
|
|
@ -67,7 +66,6 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1;
|
||||
public static final int NOTIFICATION_UPLOAD_COMPLETE = 2;
|
||||
public static final int NOTIFICATION_UPLOAD_FAILED = 3;
|
||||
private ContributionDao dao;
|
||||
|
||||
public UploadService() {
|
||||
super("UploadService");
|
||||
|
|
@ -107,7 +105,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
||||
|
||||
contribution.setTransferred(transferred);
|
||||
dao.save(contribution);
|
||||
contributionDao.save(contribution);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -115,7 +113,6 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
contributionsProviderClient.release();
|
||||
Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads);
|
||||
}
|
||||
|
||||
|
|
@ -124,8 +121,6 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
super.onCreate();
|
||||
|
||||
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
|
||||
dao = new ContributionDao(contributionsProviderClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -147,7 +142,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
|
||||
contribution.setState(Contribution.STATE_QUEUED);
|
||||
contribution.setTransferred(0);
|
||||
dao.save(contribution);
|
||||
contributionDao.save(contribution);
|
||||
toUpload++;
|
||||
if (curProgressNotification != null && toUpload != 1) {
|
||||
curProgressNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
|
||||
|
|
@ -166,7 +161,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent.getAction().equals(ACTION_START_SERVICE) && freshStart) {
|
||||
if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) {
|
||||
ContentValues failedValues = new ContentValues();
|
||||
failedValues.put(ContributionDao.Table.COLUMN_STATE, Contribution.STATE_FAILED);
|
||||
|
||||
|
|
@ -262,7 +257,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
contribution.setImageUrl(uploadResult.getImageUrl());
|
||||
contribution.setState(Contribution.STATE_COMPLETED);
|
||||
contribution.setDateUploaded(uploadResult.getDateUploaded());
|
||||
dao.save(contribution);
|
||||
contributionDao.save(contribution);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.d("I have a network fuckup");
|
||||
|
|
@ -274,7 +269,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
toUpload--;
|
||||
if (toUpload == 0) {
|
||||
// Sync modifications right after all uplaods are processed
|
||||
ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
|
||||
ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, new Bundle());
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -293,7 +288,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
notificationManager.notify(NOTIFICATION_UPLOAD_FAILED, failureNotification);
|
||||
|
||||
contribution.setState(Contribution.STATE_FAILED);
|
||||
dao.save(contribution);
|
||||
contributionDao.save(contribution);
|
||||
}
|
||||
|
||||
private String findUniqueFilename(String fileName) throws IOException {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue