From 3cdfdcffe1deb8de1b840b013c975c7911e4df67 Mon Sep 17 00:00:00 2001 From: Madhur Gupta <30932899+madhurgupta10@users.noreply.github.com> Date: Mon, 8 Aug 2022 11:21:07 +0530 Subject: [PATCH] Merge 4.0-release into master (#5028) * Fix string for custom selector * Fix bug #4950 back arrow still present on top-level activity (#4952) * Fix bug #4949 by correctly setting previous db version number (#4956) * Fix bug #4949 by correctly setting previous db version number * Fix failing tests * Fix bug #4959 by correctly setting previous db version number and updating the current db version (#4960) * Fix bug #4957 (#4961) * Update library to new version that handles older Java VMs Fixes #4972 I believe. * Versioning for v4.0.0 * Changelog for v4.0.0 * Fix bug #4984 Added queries for package name for Android API 30+ (#4987) * Update mapbox sdk version (#4989) * Versioning for v4.0.1 * Changelog for v4.0.1 * Remove network type information from NetworkUtils (#4996) * Remove network type information from NetworkUtils * Ignore dependent tests * Fix #4992 invert the equals condition to be null safe (#4995) * Fix java.lang.NullPointerException for username in ContributionBoundaryCallback (#5003) * Fix failing tests for PR #5003 (#5004) * Fix java.lang.NullPointerException for username in ContributionBoundaryCallback * Fix failing tests * Update Room DB Version (#5011) * Fix #5001 (#5010) * Fix DB update issue (#5016) * [WIP] Fix both timezone problem and saved date problem (#5019) * Fix both timezone problem and saved date problem * Fixaccidental test code and add comments * Add issue link to the comments * Fix format issue and null checks * Versioning for v4.0.2 * Changelog for v4.0.2 * Add "Report Violation" menu option (#5025) * Add "Report Violation" menu option * Update email template * Update email address * Fixed typo Co-authored-by: Josephine Lim * Versioning for v4.0.3 * Changelog for v4.0.3 Co-authored-by: Josephine Lim Co-authored-by: Nicolas Raoul Co-authored-by: neslihanturan --- CHANGELOG.md | 19 ++++ app/build.gradle | 12 +-- app/src/main/AndroidManifest.xml | 10 +++ .../free/nrw/commons/CommonsApplication.java | 4 + .../LocationPickerActivity.java | 2 +- .../bookmarks/items/BookmarkItemsDao.java | 12 +-- .../nrw/commons/category/CategoryDao.java | 15 +++- .../free/nrw/commons/category/CategoryItem.kt | 4 +- .../nrw/commons/contributions/Contribution.kt | 2 + .../ContributionBoundaryCallback.kt | 22 ++--- .../contributions/ContributionsFragment.java | 7 +- .../free/nrw/commons/data/DBOpenHelper.java | 4 +- .../fr/free/nrw/commons/db/AppDatabase.kt | 2 +- .../description/DescriptionEditActivity.kt | 3 +- .../commons/filepicker/UploadableFile.java | 38 +++++++- .../media/MediaDetailPagerFragment.java | 77 +++++++++++++++- .../recentlanguages/RecentLanguagesDao.java | 12 +-- .../commons/upload/PageContentsCreator.java | 6 +- .../free/nrw/commons/upload/UploadItem.java | 7 +- .../free/nrw/commons/upload/UploadModel.java | 5 +- .../UploadMediaDetailFragment.java | 12 ++- .../free/nrw/commons/utils/NetworkUtils.java | 4 + .../main/res/menu/fragment_image_detail.xml | 4 + app/src/main/res/values/arrays.xml | 7 +- app/src/main/res/values/strings.xml | 7 +- .../bookmarks/items/BookmarkItemsDaoTest.kt | 75 ++++++++++++++++ .../ContributionBoundaryCallbackTest.kt | 13 ++- .../RecentLanguagesDaoUnitTest.kt | 89 ++++++++++++++++++- .../nrw/commons/utils/NetworkUtilsTest.java | 4 + 29 files changed, 406 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 405b7d9de..06c2a2ad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Wikimedia Commons for Android +## v4.0.3 +- Added "Report" button for Google UGC policy + +## v4.0.2 +- Fixed bug with wrong dates taken from EXIF +- Fixed various crashes + +## v4.0.1 +- Fixed bug with no browser found +- Updated Mapbox SDK to fix hamburger crash + +## v4.0.0 +- Added map showing nearby Commons pictures +- Added custom SPARQL queries +- Added user profiles +- Added custom picture selector +- Various bugfixes +- Updated target SDK to 30 + ## v3.1.1 - Optimized Nearby query - Added Sweden's property for WLM 2021 diff --git a/app/build.gradle b/app/build.gradle index 4b71fb21c..684ad65df 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,10 +38,10 @@ dependencies { implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar' implementation 'com.github.chrisbanes:PhotoView:2.0.0' implementation 'com.github.pedrovgs:renderers:3.3.3' - implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.1.0' - implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v8:0.11.0' - implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-scalebar-v9:0.4.0' - implementation 'com.mapbox.mapboxsdk:mapbox-android-telemetry:6.1.0' + implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.2.1' + implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v9:0.12.0' + implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-scalebar-v9:0.5.0' + implementation 'com.mapbox.mapboxsdk:mapbox-android-telemetry:7.0.0' implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0' implementation 'com.dinuscxj:circleprogressbar:1.1.1' implementation 'com.karumi:dexter:5.0.0' @@ -173,8 +173,8 @@ android { defaultConfig { //applicationId 'fr.free.nrw.commons' - versionCode 1025 - versionName '3.1.1' + versionCode 1029 + versionName '4.0.3' setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) minSdkVersion 19 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fc66230f..cf245ed7c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,16 @@ + + + + + + + + + + diff --git a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java index e95196bbb..238777cce 100644 --- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java +++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java @@ -110,6 +110,10 @@ public class CommonsApplication extends MultiDexApplication { public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App Feedback"; + public static final String REPORT_EMAIL = "commons-app-android-private@googlegroups.com"; + + public static final String REPORT_EMAIL_SUBJECT = "Report a violation"; + public static final String NOTIFICATION_CHANNEL_ID_ALL = "CommonsNotificationAll"; public static final String FEEDBACK_EMAIL_TEMPLATE_HEADER = "-- Technical information --"; diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java index c8791e3c2..4af6fdd83 100644 --- a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java @@ -155,7 +155,7 @@ public class LocationPickerActivity extends BaseActivity implements OnMapReadyCa addCredits(); getToolbarUI(); - if (activity.equals("UploadActivity")) { + if ("UploadActivity".equals(activity)) { placeSelectedButton.setVisibility(View.GONE); modifyLocationButton.setVisibility(View.VISIBLE); showInMapButton.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java index 08e54a114..70c370836 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java @@ -309,22 +309,18 @@ public class BookmarkItemsDao { if (from == to) { return; } - if (from < 7) { + if (from < 18) { + // doesn't exist yet from++; onUpdate(db, from, to); return; } - if (from == 7) { + if (from == 18) { + // table added in version 19 onCreate(db); from++; onUpdate(db, from, to); - return; - } - - if (from == 8) { - from++; - onUpdate(db, from, to); } } } diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java index 4369350f4..b638fc508 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java @@ -93,9 +93,11 @@ public class CategoryDao { // fixme add a limit on the original query instead of falling out of the loop? while (cursor != null && cursor.moveToNext() && cursor.getPosition() < limit) { - items.add(new CategoryItem(fromCursor(cursor).getName(), - fromCursor(cursor).getDescription(), fromCursor(cursor).getThumbnail(), - false)); + if (fromCursor(cursor).getName() != null ) { + items.add(new CategoryItem(fromCursor(cursor).getName(), + fromCursor(cursor).getDescription(), fromCursor(cursor).getThumbnail(), + false)); + } } } catch (RemoteException e) { throw new RuntimeException(e); @@ -193,6 +195,13 @@ public class CategoryDao { onUpdate(db, from, to); return; } + if (from == 17) { + db.execSQL("ALTER TABLE categories ADD COLUMN description STRING;"); + db.execSQL("ALTER TABLE categories ADD COLUMN thumbnail STRING;"); + from++; + onUpdate(db, from, to); + return; + } } } } diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.kt index 8da0b6b6a..d4ecdfdd1 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.kt +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.kt @@ -4,8 +4,8 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -data class CategoryItem(val name: String, val description: String, - val thumbnail: String, var isSelected: Boolean) : Parcelable { +data class CategoryItem(val name: String, val description: String?, + val thumbnail: String?, var isSelected: Boolean) : Parcelable { override fun toString(): String { return "CategoryItem: '$name'" diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt index 76532d525..400bddfba 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt @@ -38,6 +38,7 @@ data class Contribution constructor( val localUri: Uri? = null, var dataLength: Long = 0, var dateCreated: Date? = null, + var dateCreatedString: String? = null, var dateModified: Date? = null, var hasInvalidLocation : Int = 0, var contentUri: Uri? = null, @@ -70,6 +71,7 @@ data class Contribution constructor( depictedItems = depictedItems, wikidataPlace = from(item.place), contentUri = item.contentUri, + dateCreatedString = item.fileCreatedDateString, imageSHA1 = imageSHA1 ) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt index 78fbe5bb3..b75332b73 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt @@ -21,7 +21,7 @@ class ContributionBoundaryCallback @Inject constructor( @param:Named(CommonsApplicationModule.IO_THREAD) private val ioThreadScheduler: Scheduler ) : BoundaryCallback() { private val compositeDisposable: CompositeDisposable = CompositeDisposable() - lateinit var userName: String + var userName: String? = null /** @@ -53,13 +53,13 @@ class ContributionBoundaryCallback @Inject constructor( /** * Fetches contributions using the MediaWiki API */ - fun fetchContributions() { + private fun fetchContributions() { if (sessionManager.userName != null) { - compositeDisposable.add( - mediaClient.getMediaListForUser(userName!!) + userName?.let { userName -> + mediaClient.getMediaListForUser(userName) .map { mediaList -> - mediaList.map { - Contribution(media = it, state = Contribution.STATE_COMPLETED) + mediaList.map { media -> + Contribution(media = media, state = Contribution.STATE_COMPLETED) } } .subscribeOn(ioThreadScheduler) @@ -69,11 +69,13 @@ class ContributionBoundaryCallback @Inject constructor( error.message ) } - ) - }else { - if (compositeDisposable != null){ - compositeDisposable.clear() + }?.let { + compositeDisposable.add( + it + ) } + }else { + compositeDisposable.clear() } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index 5f0d25eb9..63c812940 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -661,7 +661,7 @@ public class ContributionsFragment } public boolean backButtonClicked() { - if (null != mediaDetailPagerFragment && mediaDetailPagerFragment.isVisible()) { + if (mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible()) { if (store.getBoolean("displayNearbyCardView", true) && !isUserProfile) { if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) { nearbyNotificationCardView.setVisibility(View.VISIBLE); @@ -678,9 +678,10 @@ public class ContributionsFragment }else { fetchCampaigns(); } - if(getActivity() instanceof MainActivity) { + if (getActivity() instanceof MainActivity) { // Fragment is associated with MainActivity - ((MainActivity)getActivity()).showTabs(); + ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); + ((MainActivity) getActivity()).showTabs(); } return true; } diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java index 207101638..7ee417fbc 100644 --- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java +++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java @@ -4,9 +4,7 @@ import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; - import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao; -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao; import fr.free.nrw.commons.category.CategoryDao; @@ -16,7 +14,7 @@ import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao; public class DBOpenHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "commons.db"; - private static final int DATABASE_VERSION = 19; + private static final int DATABASE_VERSION = 20; public static final String CONTRIBUTIONS_TABLE = "contributions"; private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s"; diff --git a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt index fd85731d8..a877e14c4 100644 --- a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt +++ b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt @@ -14,7 +14,7 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao * The database for accessing the respective DAOs * */ -@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class], version = 12, exportSchema = false) +@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class], version = 13, exportSchema = false) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun contributionDao(): ContributionDao diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt index 04c90e492..8d7795f5e 100644 --- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt @@ -154,8 +154,7 @@ class DescriptionEditActivity : BaseActivity(), UploadMediaDetailAdapter.EventLi buffer.append("}}, ") } } - buffer.deleteCharAt(buffer.length - 1) - buffer.deleteCharAt(buffer.length - 1) + buffer.replace(", $".toRegex(), "") buffer.append(descriptionEnd) } val returningIntent = Intent() diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java index aa9379a20..7df1cc964 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java @@ -12,6 +12,8 @@ import androidx.exifinterface.media.ExifInterface; import java.io.File; import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Date; import fr.free.nrw.commons.upload.FileUtils; @@ -124,13 +126,30 @@ public class UploadableFile implements Parcelable { private DateTimeWithSource getDateTimeFromExif() { try { ExifInterface exif = new ExifInterface(file.getAbsolutePath()); - @SuppressLint("RestrictedApi") Long dateTime = exif.getDateTime(); - if(dateTime != null){ - Date date = new Date(dateTime); - return new DateTimeWithSource(date, DateTimeWithSource.EXIF_SOURCE); + // TAG_DATETIME returns the last edited date, we need TAG_DATETIME_ORIGINAL for creation date + // See issue https://github.com/commons-app/apps-android-commons/issues/1971 + String dateTimeSubString = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL); + if (dateTimeSubString!=null) { //getAttribute may return null + String year = dateTimeSubString.substring(0,4); + String month = dateTimeSubString.substring(5,7); + String day = dateTimeSubString.substring(8,10); + // This date is stored as a string (not as a date), the rason is we don't want to include timezones + String dateCreatedString = String.format("%04d-%02d-%02d", Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); + if (dateCreatedString.length() == 10) { //yyyy-MM-dd format of date is expected + @SuppressLint("RestrictedApi") Long dateTime = exif.getDateTimeOriginal(); + if(dateTime != null){ + Date date = new Date(dateTime); + return new DateTimeWithSource(date, dateCreatedString, DateTimeWithSource.EXIF_SOURCE); + } + } } + } catch (IOException e) { e.printStackTrace(); + } catch (NumberFormatException e) { + e.printStackTrace(); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); } return null; } @@ -149,6 +168,7 @@ public class UploadableFile implements Parcelable { public static final String EXIF_SOURCE = "exif"; private final long epochDate; + private String dateString; // this does not includes timezone information private final String source; public DateTimeWithSource(long epochDate, String source) { @@ -161,10 +181,20 @@ public class UploadableFile implements Parcelable { this.source = source; } + public DateTimeWithSource(Date date, String dateString, String source) { + this.epochDate = date.getTime(); + this.dateString = dateString; + this.source = source; + } + public long getEpochDate() { return epochDate; } + public String getDateString() { + return dateString; + } + public String getSource() { return source; } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index b1e74b447..eaede2484 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -3,6 +3,7 @@ package fr.free.nrw.commons.media; import static fr.free.nrw.commons.Utils.handleWebUrl; import android.annotation.SuppressLint; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -12,7 +13,9 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -22,6 +25,7 @@ import androidx.viewpager.widget.ViewPager; import butterknife.BindView; import butterknife.ButterKnife; import com.google.android.material.snackbar.Snackbar; +import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.Media; import fr.free.nrw.commons.R; import fr.free.nrw.commons.auth.SessionManager; @@ -33,7 +37,6 @@ import fr.free.nrw.commons.contributions.MainActivity; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; import fr.free.nrw.commons.profile.ProfileActivity; -import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.utils.DownloadUtils; import fr.free.nrw.commons.utils.ImageUtils; import fr.free.nrw.commons.utils.NetworkUtils; @@ -211,11 +214,83 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple ProfileActivity.startYourself(getActivity(), m.getUser(), !Objects.equals(sessionManager.getUserName(), m.getUser())); } + return true; + case R.id.menu_view_report: + showReportDialog(m); default: return super.onOptionsItemSelected(item); } } + private void showReportDialog(final Media media) { + if (media == null) { + return; + } + final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); + final String[] values = requireContext().getResources() + .getStringArray(R.array.report_violation_options); + builder.setTitle(R.string.report_violation); + builder.setItems(R.array.report_violation_options, (dialog, which) -> { + sendReportEmail(media, values[which]); + }); + builder.show(); + } + + private void sendReportEmail(final Media media, final String type) { + final String technicalInfo = getTechInfo(media, type); + + final Intent feedbackIntent = new Intent(Intent.ACTION_SENDTO); + feedbackIntent.setType("message/rfc822"); + feedbackIntent.setData(Uri.parse("mailto:")); + feedbackIntent.putExtra(Intent.EXTRA_EMAIL, + new String[]{CommonsApplication.REPORT_EMAIL}); + feedbackIntent.putExtra(Intent.EXTRA_SUBJECT, + CommonsApplication.REPORT_EMAIL_SUBJECT); + feedbackIntent.putExtra(Intent.EXTRA_TEXT, technicalInfo); + try { + startActivity(feedbackIntent); + } catch (final ActivityNotFoundException e) { + Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show(); + } + } + + private String getTechInfo(final Media media, final String type) { + final StringBuilder builder = new StringBuilder(); + + builder.append("Report type: ") + .append(type) + .append("\n\n"); + + builder.append("Image that you want to report: ") + .append(media.getImageUrl()) + .append("\n\n"); + + builder.append("User that you want to report: ") + .append(media.getAuthor()) + .append("\n\n"); + + if (sessionManager.getUserName() != null) { + builder.append("Your username: ") + .append(sessionManager.getUserName()) + .append("\n\n"); + } + + builder.append("Violation reason: ") + .append("\n"); + + builder.append("----------------------------------------------") + .append("\n") + .append("(please write reason here)") + .append("\n") + .append("----------------------------------------------") + .append("\n\n") + .append("Thank you for your report! Our team will investigate as soon as possible.") + .append("\n") + .append("Please note that images also have a `Nominate for deletion` button."); + + return builder.toString(); + } + /** * Set the media as the device's wallpaper if the imageUrl is not null * Fails silently if setting the wallpaper fails diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java index 1af1b50e6..c4a4bf518 100644 --- a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java +++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java @@ -185,23 +185,17 @@ public class RecentLanguagesDao { if (from == to) { return; } - if (from < 6) { + if (from < 19) { // doesn't exist yet from++; onUpdate(db, from, to); return; } - if (from == 6) { - // table added in version 7 + if (from == 19) { + // table added in version 20 onCreate(db); from++; onUpdate(db, from, to); - return; - } - if (from == 7) { - from++; - onUpdate(db, from, to); - return; } } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java index 4bf2de027..146967f4a 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java @@ -48,7 +48,7 @@ class PageContentsCreator { .append(media.getAuthor()).append("]]\n"); final String templatizedCreatedDate = getTemplatizedCreatedDate( - contribution.getDateCreated(), contribution.getDateCreatedSource()); + contribution.getDateCreatedString(), contribution.getDateCreated(), contribution.getDateCreatedSource()); if (!StringUtils.isBlank(templatizedCreatedDate)) { buffer.append("|date=").append(templatizedCreatedDate); } @@ -90,12 +90,12 @@ class PageContentsCreator { * @param dateCreatedSource * @return */ - private String getTemplatizedCreatedDate(Date dateCreated, String dateCreatedSource) { + private String getTemplatizedCreatedDate(String dateCreatedString, Date dateCreated, String dateCreatedSource) { if (dateCreated != null) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); return String.format(Locale.ENGLISH, isExif(dateCreatedSource) ? TEMPLATE_DATE_ACC_TO_EXIF : TEMPLATE_DATA_OTHER_SOURCE, - dateFormat.format(dateCreated) + isExif(dateCreatedSource) ? dateCreatedString: dateFormat.format(dateCreated) ) + "\n"; } return ""; diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java index 3d0f2066b..87050fb5c 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java @@ -25,6 +25,7 @@ public class UploadItem { private boolean hasInvalidLocation; private boolean isWLMUpload = false; private String countryCode; + private String fileCreatedDateString; //according to EXIF data /** * Uri of uploadItem @@ -40,7 +41,8 @@ public class UploadItem { final Place place, final long createdTimestamp, final String createdTimestampSource, - final Uri contentUri) { + final Uri contentUri, + final String fileCreatedDateString) { this.createdTimestampSource = createdTimestampSource; uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail())); this.place = place; @@ -50,6 +52,7 @@ public class UploadItem { this.createdTimestamp = createdTimestamp; this.contentUri = contentUri; imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT); + this.fileCreatedDateString = fileCreatedDateString; } public String getCreatedTimestampSource() { @@ -83,6 +86,8 @@ public class UploadItem { */ public Uri getContentUri() { return contentUri; } + public String getFileCreatedDateString() { return fileCreatedDateString; } + public void setImageQuality(final int imageQuality) { this.imageQuality.onNext(imageQuality); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java index e31f01986..d0ae2ead3 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java @@ -102,8 +102,10 @@ public class UploadModel { .getFileCreatedDate(context); long fileCreatedDate = -1; String createdTimestampSource = ""; + String fileCreatedDateString = ""; if (dateTimeWithSource != null) { fileCreatedDate = dateTimeWithSource.getEpochDate(); + fileCreatedDateString = dateTimeWithSource.getDateString(); createdTimestampSource = dateTimeWithSource.getSource(); } Timber.d("File created date is %d", fileCreatedDate); @@ -113,7 +115,8 @@ public class UploadModel { Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate, createdTimestampSource, - uploadableFile.getContentUri()); + uploadableFile.getContentUri(), + fileCreatedDateString); if (place != null) { uploadItem.getUploadMediaDetails().set(0, new UploadMediaDetail(place)); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java index d4200e52d..534ca61ee 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java @@ -148,18 +148,16 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ButterKnife.bind(this, view); - init(); + if (callback != null) { + init(); + } } private void init() { tvTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1, callback.getTotalNumberOfSteps(), getString(R.string.media_detail_step_title))); - tooltip.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - showInfoAlert(R.string.media_detail_step_title, R.string.media_details_tooltip); - } - }); + tooltip.setOnClickListener( + v -> showInfoAlert(R.string.media_detail_step_title, R.string.media_details_tooltip)); initPresenter(); presenter.receiveImage(uploadableFile, place); initRecyclerView(); diff --git a/app/src/main/java/fr/free/nrw/commons/utils/NetworkUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/NetworkUtils.java index 59e38f0c9..ce64cb031 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/NetworkUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/NetworkUtils.java @@ -49,6 +49,8 @@ public class NetworkUtils { return NetworkConnectionType.WIFI; } + // TODO for Android 12+ request permission from user is mandatory + /* int mobileNetwork = telephonyManager.getNetworkType(); switch (mobileNetwork) { case TelephonyManager.NETWORK_TYPE_GPRS: @@ -71,6 +73,8 @@ public class NetworkUtils { default: return NetworkConnectionType.UNKNOWN; } + */ + return NetworkConnectionType.UNKNOWN; } /** diff --git a/app/src/main/res/menu/fragment_image_detail.xml b/app/src/main/res/menu/fragment_image_detail.xml index c101ae9b2..65ad9ebbe 100644 --- a/app/src/main/res/menu/fragment_image_detail.xml +++ b/app/src/main/res/menu/fragment_image_detail.xml @@ -34,5 +34,9 @@ android:id="@+id/menu_view_user_page" android:title="@string/menu_view_user_page" app:showAsAction="never" /> + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 89ce57701..c09853373 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -70,5 +70,10 @@ yearly all_time - + + @string/report_user + @string/report_content + @string/request_user_block + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4fb470e32..524892ced 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -691,7 +691,7 @@ Upload your first media by tapping on the add button. Done Back Welcome to Custom Picture Selector - This picker shows differently pictures that are already to Commons. + This picker shows you which pictures you have already uploaded to Commons. Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded. \n Touch and hold for image preview. Awesome This image has already been uploaded to Commons. @@ -734,4 +734,9 @@ Upload your first media by tapping on the add button. Error while sending feedback What is your feedback? Your feedback + Report + Report violation + Report this user + Report this content + Request to block this user diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt index 73504acdc..92778dea7 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt @@ -271,9 +271,84 @@ class BookmarkItemsDaoTest { @Test fun migrateTableVersionFrom_v7_to_v8() { onUpdate(database, 7, 8) + // Table didn't change in version 8 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v8_to_v9() { + onUpdate(database, 8, 9) + // Table didn't change in version 9 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v9_to_v10() { + onUpdate(database, 9, 10) + // Table didn't change in version 10 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v10_to_v11() { + onUpdate(database, 10, 11) + // Table didn't change in version 11 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v11_to_v12() { + onUpdate(database, 11, 12) + // Table didn't change in version 12 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v12_to_v13() { + onUpdate(database, 12, 13) + // Table didn't change in version 13 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v13_to_v14() { + onUpdate(database, 13, 14) + // Table didn't change in version 14 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v14_to_v15() { + onUpdate(database, 14, 15) + // Table didn't change in version 15 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v15_to_v16() { + onUpdate(database, 15, 16) + // Table didn't change in version 16 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v16_to_v17() { + onUpdate(database, 16, 17) + // Table didn't change in version 17 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v18_to_v19() { + onUpdate(database, 18, 19) verify(database).execSQL(CREATE_TABLE_STATEMENT) } + @Test + fun migrateTableVersionFrom_v19_to_v19() { + onUpdate(database, 19, 19) + verifyZeroInteractions(database) + } private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply { diff --git a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt index f58342467..399b39aff 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt @@ -18,6 +18,7 @@ import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations +import java.lang.reflect.Method /** * The unit test class for ContributionBoundaryCallbackTest @@ -95,7 +96,11 @@ class ContributionBoundaryCallbackTest { whenever(mediaClient.getMediaListForUser(anyString())).thenReturn( Single.just(listOf(media())) ) - contributionBoundaryCallback.fetchContributions() + val method: Method = ContributionBoundaryCallback::class.java.getDeclaredMethod( + "fetchContributions" + ) + method.isAccessible = true + method.invoke(contributionBoundaryCallback) verify(repository).save(anyList()); verify(mediaClient).getMediaListForUser(anyString()); } @@ -104,7 +109,11 @@ class ContributionBoundaryCallbackTest { fun testFetchContributionsFailed() { whenever(sessionManager.userName).thenReturn("Test") whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(Single.error(Exception("Error"))) - contributionBoundaryCallback.fetchContributions() + val method: Method = ContributionBoundaryCallback::class.java.getDeclaredMethod( + "fetchContributions" + ) + method.isAccessible = true + method.invoke(contributionBoundaryCallback) verifyZeroInteractions(repository); verify(mediaClient).getMediaListForUser(anyString()); } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt index 122f2e49c..abe8708bf 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt @@ -177,20 +177,103 @@ class RecentLanguagesDaoUnitTest { @Test fun migrateTableVersionFrom_v5_to_v6() { onUpdate(database, 5, 6) - // Table didnt exist before v7 + // Table didnt exist in version 6 verifyZeroInteractions(database) } @Test fun migrateTableVersionFrom_v6_to_v7() { onUpdate(database, 6, 7) - verify(database).execSQL(CREATE_TABLE_STATEMENT) + // Table didnt exist in version 7 + verifyZeroInteractions(database) } @Test fun migrateTableVersionFrom_v7_to_v8() { onUpdate(database, 7, 8) - // Table didnt change in version 8 + // Table didnt exist in version 8 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v8_to_v9() { + onUpdate(database, 8, 9) + // Table didnt exist in version 9 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v9_to_v10() { + onUpdate(database, 9, 10) + // Table didnt exist in version 10 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v10_to_v11() { + onUpdate(database, 10, 11) + // Table didnt exist in version 11 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v11_to_v12() { + onUpdate(database, 11, 12) + // Table didnt exist in version 12 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v12_to_v13() { + onUpdate(database, 12, 13) + // Table didnt exist in version 13 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v13_to_v14() { + onUpdate(database, 13, 14) + // Table didnt exist in version 14 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v14_to_v15() { + onUpdate(database, 14, 15) + // Table didnt exist in version 15 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v15_to_v16() { + onUpdate(database, 15, 16) + // Table didnt exist in version 16 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v16_to_v17() { + onUpdate(database, 16, 17) + // Table didnt exist in version 17 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v18_to_v19() { + onUpdate(database, 18, 19) + // Table didnt exist in version 18 + verifyZeroInteractions(database) + } + + @Test + fun migrateTableVersionFrom_v19_to_v20() { + onUpdate(database, 19, 20) + verify(database).execSQL(CREATE_TABLE_STATEMENT) + } + + @Test + fun migrateTableVersionFrom_v20_to_v20() { + onUpdate(database, 20, 20) verifyZeroInteractions(database) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/utils/NetworkUtilsTest.java b/app/src/test/kotlin/fr/free/nrw/commons/utils/NetworkUtilsTest.java index 32a8791ab..13f4c4b9a 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/utils/NetworkUtilsTest.java +++ b/app/src/test/kotlin/fr/free/nrw/commons/utils/NetworkUtilsTest.java @@ -8,6 +8,7 @@ import android.telephony.TelephonyManager; import org.jetbrains.annotations.NotNull; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import fr.free.nrw.commons.utils.model.NetworkConnectionType; @@ -97,6 +98,7 @@ public class NetworkUtilsTest { } @Test + @Ignore("Fix these test with telemetry permission") public void testCellular2GNetwork() { Context mockContext = mock(Context.class); Application mockApplication = mock(Application.class); @@ -123,6 +125,7 @@ public class NetworkUtilsTest { } @Test + @Ignore("Fix these test with telemetry permission") public void testCellular3GNetwork() { Context mockContext = mock(Context.class); Application mockApplication = mock(Application.class); @@ -149,6 +152,7 @@ public class NetworkUtilsTest { } @Test + @Ignore("Fix these test with telemetry permission") public void testCellular4GNetwork() { Context mockContext = mock(Context.class); Application mockApplication = mock(Application.class);