Merge branch '2.10-release' into 2.10-release-fork

This commit is contained in:
Josephine Lim 2019-02-21 19:21:09 +10:00
commit 0a894dc415
11 changed files with 237 additions and 82 deletions

View file

@ -27,7 +27,8 @@ dependencies {
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.1.1'
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.1.1'
implementation 'com.facebook.fresco:fresco:1.10.0'
implementation 'com.drewnoakes:metadata-extractor:2.11.0'
// UI
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'

View file

@ -3,25 +3,33 @@ package fr.free.nrw.commons.contributions;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.StringDef;
import java.lang.annotation.Retention;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.StringUtils;
import static java.lang.annotation.RetentionPolicy.SOURCE;
public class Contribution extends Media {
//{{According to EXIF data|2009-01-09}}
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "|date={{According to EXIF data|%s}}";
//{{date|2009|1|9}} 9 January 2009
private static final String TEMPLATE_DATA_OTHER_SOURCE = "{{date|%d|%d|%d}}";
public static Creator<Contribution> CREATOR = new Creator<Contribution>() {
@Override
public Contribution createFromParcel(Parcel parcel) {
@ -57,6 +65,7 @@ public class Contribution extends Media {
private boolean isMultiple;
private String wikiDataEntityId;
private Uri contentProviderUri;
private String dateCreatedSource;
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date dateCreated,
int state, long dataLength, Date dateUploaded, long transferred,
@ -71,6 +80,7 @@ public class Contribution extends Media {
this.width = width;
this.height = height;
this.license = license;
this.dateCreatedSource = "";
}
public Contribution(Uri localUri, String imageUrl, String filename, String description, long dataLength,
@ -78,6 +88,7 @@ public class Contribution extends Media {
super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
this.decimalCoords = decimalCoords;
this.editSummary = editSummary;
this.dateCreatedSource = "";
}
public Contribution(Parcel in) {
@ -99,7 +110,13 @@ public class Contribution extends Media {
parcel.writeInt(isMultiple ? 1 : 0);
}
public String getDateCreatedSource() {
return dateCreatedSource;
}
public void setDateCreatedSource(String dateCreatedSource) {
this.dateCreatedSource = dateCreatedSource;
}
public boolean getMultiple() {
return isMultiple;
@ -143,20 +160,19 @@ public class Contribution extends Media {
public String getPageContents(Context applicationContext) {
StringBuilder buffer = new StringBuilder();
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
buffer
.append("== {{int:filedesc}} ==\n")
.append("{{Information\n")
.append("|description=").append(getDescription()).append("\n")
.append("|source=").append("{{own}}\n")
.append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
if (dateCreated != null) {
buffer
.append("|date={{According to EXIF data|").append(isoFormat.format(dateCreated)).append("}}\n");
String templatizedCreatedDate = getTemplatizedCreatedDate();
if (!StringUtils.isNullOrWhiteSpace(templatizedCreatedDate)) {
buffer.append("|date=").append(templatizedCreatedDate);
}
buffer
.append("}}").append("\n");
buffer.append("}}").append("\n");
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
if (decimalCoords != null) {
@ -178,6 +194,28 @@ public class Contribution extends Media {
return buffer.toString();
}
/**
* Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
* @return
*/
private String getTemplatizedCreatedDate() {
if (dateCreated != null) {
if (UploadableFile.DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource)) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
return String.format(Locale.ENGLISH, TEMPLATE_DATE_ACC_TO_EXIF, isoFormat.format(dateCreated)) + "\n";
} else {
Calendar calendar = Calendar.getInstance();
calendar.setTime(dateCreated);
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
return String.format(Locale.ENGLISH, TEMPLATE_DATA_OTHER_SOURCE,
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)) + "\n";
}
}
return "";
}
@Override
public void setFilename(String filename) {
this.filename = filename;

View file

@ -300,7 +300,7 @@ public class ContributionDao {
onUpdate(db, from, to);
return;
}
if (from == 8) {
if (from > 5) {
// Added place field
db.execSQL(ADD_WIKI_DATA_ENTITY_ID_FIELD);
from++;

View file

@ -6,7 +6,16 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifSubIFDDirectory;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import javax.annotation.Nullable;
import fr.free.nrw.commons.upload.FileUtils;
@ -62,16 +71,32 @@ public class UploadableFile implements Parcelable {
return 0;
}
/**
* First try to get the file creation date from EXIF else fall back to CP
* @param context
* @return
*/
@Nullable
public DateTimeWithSource getFileCreatedDate(Context context) {
DateTimeWithSource dateTimeFromExif = getDateTimeFromExif();
if (dateTimeFromExif == null) {
return getFileCreatedDateFromCP(context);
} else {
return dateTimeFromExif;
}
}
/**
* Get filePath creation date from uri from all possible content providers
*
* @return
*/
public long getFileCreatedDate(Context context) {
private DateTimeWithSource getFileCreatedDateFromCP(Context context) {
try {
Cursor cursor = context.getContentResolver().query(contentUri, null, null, null, null);
if (cursor == null) {
return -1;//Could not fetch last_modified
return null;//Could not fetch last_modified
}
//Content provider contracts for opening gallery from the app and that by sharing from gallery from outside are different and we need to handle both the cases
int lastModifiedColumnIndex = cursor.getColumnIndex("last_modified");//If gallery is opened from in app
@ -80,18 +105,69 @@ public class UploadableFile implements Parcelable {
}
//If both the content providers do not give the data, lets leave it to Jesus
if (lastModifiedColumnIndex == -1) {
return -1l;
return null;
}
cursor.moveToFirst();
return cursor.getLong(lastModifiedColumnIndex);
return new DateTimeWithSource(cursor.getLong(lastModifiedColumnIndex), DateTimeWithSource.CP_SOURCE);
} catch (Exception e) {
return -1;////Could not fetch last_modified
return null;////Could not fetch last_modified
}
}
/**
* Get filePath creation date from uri from EXIF
*
* @return
*/
private DateTimeWithSource getDateTimeFromExif() {
Metadata metadata;
try {
metadata = ImageMetadataReader.readMetadata(file);
ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
if (directory!=null && directory.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) {
Date date = directory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
return new DateTimeWithSource(date, DateTimeWithSource.EXIF_SOURCE);
}
} catch (ImageProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeParcelable(contentUri, 0);
parcel.writeSerializable(file);
}
/**
* This class contains the epochDate along with the source from which it was extracted
*/
public class DateTimeWithSource {
public static final String CP_SOURCE = "contentProvider";
public static final String EXIF_SOURCE = "exif";
private final long epochDate;
private final String source;
public DateTimeWithSource(long epochDate, String source) {
this.epochDate = epochDate;
this.source = source;
}
public DateTimeWithSource(Date date, String source) {
this.epochDate = date.getTime();
this.source = source;
}
public long getEpochDate() {
return epochDate;
}
public String getSource() {
return source;
}
}
}

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.mwapi;
import org.jetbrains.annotations.NotNull;
import java.util.Date;
public class UploadResult {
@ -34,12 +36,13 @@ public class UploadResult {
this.imageUrl = imageUrl;
}
@NotNull
@Override
public String toString() {
return "UploadResult{" +
"errorCode='" + errorCode + '\'' +
", resultStatus='" + resultStatus + '\'' +
", dateUploaded='" + dateUploaded.toString() + '\'' +
", dateUploaded='" + (dateUploaded == null ? "" : dateUploaded.toString()) + '\'' +
", imageUrl='" + imageUrl + '\'' +
", canonicalFilename='" + canonicalFilename + '\'' +
'}';

View file

@ -17,7 +17,6 @@ import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.support.v7.widget.Toolbar;
import com.pedrogomez.renderers.RVRendererAdapter;
@ -52,10 +51,9 @@ public class NotificationActivity extends NavigationBaseActivity {
RelativeLayout relativeLayout;
@BindView(R.id.no_notification_background)
RelativeLayout no_notification;
@BindView(R.id.toolbar)
Toolbar toolbar;
/* @BindView(R.id.swipe_bg)
TextView swipe_bg;*/
@BindView(R.id.no_notification_text)
TextView noNotificationText;
@Inject
NotificationController controller;
@ -63,8 +61,7 @@ public class NotificationActivity extends NavigationBaseActivity {
private NotificationWorkerFragment mNotificationWorkerFragment;
private RVRendererAdapter<Notification> adapter;
private List<Notification> notificationList;
MenuItem notificationmenuitem;
TextView nonotificationtext;
MenuItem notificationMenuItem;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -75,7 +72,6 @@ public class NotificationActivity extends NavigationBaseActivity {
.findFragmentByTag(TAG_NOTIFICATION_WORKER_FRAGMENT);
initListView();
initDrawer();
nonotificationtext = (TextView)this.findViewById(R.id.no_notification_text);
setPageTitle();
}
@ -160,7 +156,7 @@ public class NotificationActivity extends NavigationBaseActivity {
no_notification.setVisibility(View.VISIBLE);
} else {
setAdapter(notificationList);
} if (notificationmenuitem != null) {
} if (notificationMenuItem != null) {
}
progressBar.setVisibility(View.GONE);
}, throwable -> {
@ -178,7 +174,7 @@ public class NotificationActivity extends NavigationBaseActivity {
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_notifications, menu);
notificationmenuitem = menu.findItem(R.id.archived);
notificationMenuItem = menu.findItem(R.id.archived);
setMenuItemTitle();
return true;
}
@ -262,18 +258,18 @@ public class NotificationActivity extends NavigationBaseActivity {
private void setEmptyView() {
if (getIntent().getStringExtra("title").equals("read")) {
nonotificationtext.setText(R.string.no_archived_notification);
noNotificationText.setText(R.string.no_archived_notification);
}else {
nonotificationtext.setText(R.string.no_notification);
noNotificationText.setText(R.string.no_notification);
}
}
private void setMenuItemTitle() {
if (getIntent().getStringExtra("title").equals("read")) {
notificationmenuitem.setTitle(R.string.menu_option_unread);
notificationMenuItem.setTitle(R.string.menu_option_unread);
}else {
notificationmenuitem.setTitle(R.string.menu_option_archived);
notificationMenuItem.setTitle(R.string.menu_option_archived);
}
}

View file

@ -7,11 +7,13 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.daimajia.swipe.SwipeLayout;
import com.nineoldandroids.view.ViewHelper;
import com.pedrogomez.renderers.Renderer;
import com.daimajia.swipe.SwipeLayout;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
@ -33,6 +35,8 @@ public class NotificationRenderer extends Renderer<Notification> {
SwipeLayout swipeLayout;
@BindView(R.id.bottom)
LinearLayout bottomLayout;
@BindView(R.id.notification_view)
RelativeLayout notificationView;
private NotificationClicked listener;
private boolean isarchivedvisible = false;
@ -42,19 +46,27 @@ public class NotificationRenderer extends Renderer<Notification> {
this.listener = listener;
this.isarchivedvisible = isarchivedvisible;
}
@OnClick(R.id.notification_view)
void onNotificationClicked() {
listener.notificationClicked(getContent());
}
@OnClick(R.id.bottom)
void onBottomLayoutClicked(){
Notification notification = getContent();
Timber.d("NotificationID: %s", notification.notificationId);
listener.markNotificationAsRead(notification);
}
@Override
protected void setUpView(View rootView) {
}
@Override
protected void hookListeners(View rootView) {
rootView.setOnClickListener(v -> listener.notificationClicked(getContent()));
}
@Override

View file

@ -117,6 +117,7 @@ public class ImageProcessingService {
/**
* Checks for image geolocation
* returns IMAGE_OK if the place is null or if the file doesn't contain a geolocation
* @param filePath file to be checked
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
*/
@ -127,6 +128,11 @@ public class ImageProcessingService {
}
return Single.fromCallable(() -> filePath)
.map(fileUtilsWrapper::getGeolocationOfFile)
.flatMap(geoLocation -> imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation()));
.flatMap(geoLocation -> {
if (StringUtils.isNullOrWhiteSpace(geoLocation)) {
return Single.just(ImageUtils.IMAGE_OK);
}
return imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
});
}
}

View file

@ -43,7 +43,7 @@ public class UploadModel {
"",
GPSExtractor.DUMMY,
null,
-1L) {
-1L, "") {
};
private final BasicKvStore basicKvStore;
private final List<String> licenses;
@ -99,10 +99,16 @@ public class UploadModel {
String source,
SimilarImageInterface similarImageInterface) {
fileProcessor.initFileDetails(Objects.requireNonNull(uploadableFile.getFilePath()), context.getContentResolver());
long fileCreatedDate = uploadableFile.getFileCreatedDate(context);
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile.getFileCreatedDate(context);
long fileCreatedDate = -1;
String createdTimestampSource = "";
if (dateTimeWithSource != null) {
fileCreatedDate = dateTimeWithSource.getEpochDate();
createdTimestampSource = dateTimeWithSource.getSource();
}
Timber.d("File created date is %d", fileCreatedDate);
GPSExtractor gpsExtractor = fileProcessor.processFileCoordinates(similarImageInterface);
return new UploadItem(Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate);
return new UploadItem(Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate, createdTimestampSource);
}
void onItemsProcessed(Place place, List<UploadItem> uploadItems) {
@ -284,11 +290,13 @@ public class UploadModel {
contribution.setTag("mimeType", item.mimeType);
contribution.setSource(item.source);
contribution.setContentProviderUri(item.mediaUri);
Timber.d("Created timestamp while building contribution is %s, %s",
item.getCreatedTimestamp(),
new Date(item.getCreatedTimestamp()));
if (item.createdTimestamp != -1L) {
contribution.setDateCreated(new Date(item.getCreatedTimestamp()));
contribution.setDateCreatedSource(item.getCreatedTimestampSource());
//Set the date only if you have it, else the upload service is gonna try it the other way
}
return contribution;
@ -332,10 +340,15 @@ public class UploadModel {
private boolean visited;
private boolean error;
private long createdTimestamp;
private String createdTimestampSource;
private BehaviorSubject<Integer> imageQuality;
@SuppressLint("CheckResult")
UploadItem(Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords, @Nullable Place place, long createdTimestamp) {
UploadItem(Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords,
@Nullable Place place,
long createdTimestamp,
String createdTimestampSource) {
this.createdTimestampSource = createdTimestampSource;
title = new Title();
descriptions = new ArrayList<>();
descriptions.add(new Description());
@ -348,6 +361,10 @@ public class UploadModel {
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
}
public String getCreatedTimestampSource() {
return createdTimestampSource;
}
public String getMimeType() {
return mimeType;
}

View file

@ -2,6 +2,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:foreground="?selectableItemBackground"
android:minHeight="72dp">
<com.daimajia.swipe.SwipeLayout android:layout_height="match_parent"
@ -27,52 +28,53 @@
android:layout_height="20dp" />
</RelativeLayout>
</LinearLayout>
<RelativeLayout
android:id="@+id/notification_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:background="@android:color/white"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_message_black_24dp"
<android.support.v7.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="@android:color/white"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_message_black_24dp"
/>
/>
<!--app:tint="@color/primaryDarkColor"-->
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
/>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="2 June" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignTop="@id/time"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_toEndOf="@id/icon"
android:layout_toLeftOf="@id/time"
android:layout_toRightOf="@id/icon"
android:layout_toStartOf="@id/time"
android:ellipsize="end"
android:layout_alignParentTop="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:padding="12dp"
/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignTop="@id/time"
android:layout_alignParentTop="true"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_toStartOf="@id/time"
android:layout_toLeftOf="@id/time"
android:layout_toEndOf="@id/icon"
android:layout_toRightOf="@id/icon"
android:ellipsize="end"
android:padding="12dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
tools:text="You just made your tenth edit" />
</RelativeLayout>
</com.daimajia.swipe.SwipeLayout>
</RelativeLayout>

View file

@ -108,15 +108,19 @@ class ContributionDaoTest {
@Test
fun migrateTableVersionFrom_v6_to_v7() {
Table.onUpdate(database, 6, 7)
// Table didn't change in version 7
verifyZeroInteractions(database)
// Table has changed in version 7
inOrder(database) {
verify<SQLiteDatabase>(database).execSQL(Table.ADD_WIKI_DATA_ENTITY_ID_FIELD)
}
}
@Test
fun migrateTableVersionFrom_v7_to_v8() {
Table.onUpdate(database, 7, 8)
// Table didn't change in version 8
verifyZeroInteractions(database)
// Table has changed in version 8
inOrder(database) {
verify<SQLiteDatabase>(database).execSQL(Table.ADD_WIKI_DATA_ENTITY_ID_FIELD)
}
}
@Test
@ -355,4 +359,4 @@ class ContributionDaoTest {
contribution.wikiDataEntityId = "Q1"
return contribution
}
}
}