diff --git a/CHANGELOG.md b/CHANGELOG.md index 1586f0db4..383f725d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Wikimedia Commons for Android +## v2.13.1 +- Added OpenStreetMap attribution +- Fixed various crashes +- Fixed SQLite error in Nearby map +- Fixed issue with Nearby uploads not being associated with Wikidata p18 + +## v2.13.0 +- New media details UI, ability to zoom and pan around image +- Added suggestions for a place that needs photos if user uploads a photo that is near one of them +- Modifications and fixes to Nearby filters based on user feedback +- Multiple crash and bug fixes + ## v2.12.3 - Fixed issue with EXIF data, including coords, being removed from uploads diff --git a/app/build.gradle b/app/build.gradle index dd64aa19b..ff349217f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,7 +21,9 @@ dependencies { // Utils implementation 'in.yuvi:http.fluent:1.3' implementation 'com.google.code.gson:gson:2.8.5' - implementation 'com.squareup.okhttp3:okhttp:4.8.0' + implementation ("com.squareup.okhttp3:okhttp:$OKHTTP_VERSION"){ + force = true //API 19 support + } implementation 'com.squareup.okio:okio:2.2.2' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.3' @@ -35,6 +37,7 @@ dependencies { // UI 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:8.6.2' implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v8:0.11.0' implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-scalebar-v9:0.4.0' @@ -51,7 +54,7 @@ dependencies { testImplementation "androidx.paging:paging-common-ktx:$PAGING_VERSION" implementation "androidx.paging:paging-rxjava2-ktx:$PAGING_VERSION" implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02" - implementation 'com.squareup.okhttp3:okhttp-ws:3.4.1' + implementation "com.squareup.okhttp3:okhttp-ws:$OKHTTP_VERSION" // Logging implementation 'ch.acra:acra-dialog:5.3.0' @@ -60,7 +63,7 @@ dependencies { api('com.github.tony19:logback-android-classic:1.1.1-6') { exclude group: 'com.google.android', module: 'android' } - implementation "com.squareup.okhttp3:logging-interceptor:4.5.0" + implementation "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION" // Dependency injector implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION" @@ -81,7 +84,7 @@ dependencies { testImplementation 'junit:junit:4.13' testImplementation 'org.robolectric:robolectric:4.3' testImplementation 'androidx.test:core:1.2.0' - testImplementation "com.squareup.okhttp3:mockwebserver:4.8.0" + testImplementation "com.squareup.okhttp3:mockwebserver:$OKHTTP_VERSION" testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5" testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5" testImplementation 'org.mockito:mockito-core:2.23.0' @@ -139,8 +142,8 @@ android { defaultConfig { //applicationId 'fr.free.nrw.commons' - versionCode 561 - versionName '2.12.3' + versionCode 775 + versionName '2.13.1' setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) minSdkVersion 19 diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java index 4b9fe4b55..c58482f76 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java @@ -248,24 +248,44 @@ public class BookmarkLocationsDao { public static void onUpdate(SQLiteDatabase db, int from, int to) { Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to); - switch (from) { - case 7: onCreate(db); - case 8: // No change - case 9: // No change - case 10: - try { - db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;"); - } catch (SQLiteException exception){ - Timber.e(exception); - } - case 11: // No change - case 12: - try { - db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;"); - }catch (SQLiteException exception){ - Timber.e(exception); - } - break; + if (from == to) { + return; + } + if (from < 7) { + // doesn't exist yet + from++; + onUpdate(db, from, to); + return; + } + if (from == 7) { + // table added in version 8 + onCreate(db); + from++; + onUpdate(db, from, to); + return; + } + if (from == 8) { + from++; + onUpdate(db, from, to); + return; + } + if (from == 10) { + //This is safe, and can be called clean, as we/I do not remember the appropriate version for this + //We are anyways switching to room, these things won't be necessary then + try { + db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;"); + }catch (SQLiteException exception){ + Timber.e(exception);// + } + return; + } + if (from == 12) { + try { + db.execSQL( + "ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;"); + } catch (SQLiteException exception) { + Timber.e(exception); + } } } } 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 592ec9126..72f534c4d 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,7 +38,8 @@ data class Contribution constructor( val localUri: Uri? = null, var dataLength: Long = 0, var dateCreated: Date? = null, - var dateModified: Date? = null + var dateModified: Date? = null, + var hasInvalidLocation : Int = 0 ) : Parcelable { fun completeWith(media: Media): Contribution { @@ -65,6 +66,18 @@ data class Contribution constructor( wikidataPlace = from(item.place) ) + /** + * Set this true when ImageProcessor has said that the location is invalid + * @param hasInvalidLocation + */ + fun setHasInvalidLocation(hasInvalidLocation: Boolean) { + this.hasInvalidLocation = if (hasInvalidLocation) 1 else 0 + } + + fun hasInvalidLocation(): Boolean { + return hasInvalidLocation == 1 + } + companion object { const val STATE_COMPLETED = -1 const val STATE_FAILED = 1 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 13c9a10c2..ad7e7f8c7 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 @@ -22,6 +22,15 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager.OnBackStackChangedListener; import androidx.fragment.app.FragmentTransaction; + +import fr.free.nrw.commons.MediaDataExtractor; +import fr.free.nrw.commons.auth.SessionManager; +import io.reactivex.disposables.Disposable; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; + import butterknife.BindView; import butterknife.ButterKnife; import fr.free.nrw.commons.Media; @@ -83,6 +92,9 @@ public class ContributionsFragment @Inject ContributionsPresenter contributionsPresenter; + @Inject + SessionManager sessionManager; + private LatLng curLatLng; private boolean firstLocationUpdate = true; @@ -143,7 +155,14 @@ public class ContributionsFragment initFragments(); - if (!ConfigUtils.isBetaFlavour()) { + if(shouldShowMediaDetailsFragment){ + showMediaDetailPagerFragment(); + }else{ + showContributionsListFragment(); + } + + if (!ConfigUtils.isBetaFlavour() && sessionManager.isUserLoggedIn() + && sessionManager.getCurrentAccount() != null) { setUploadCount(); } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java index 998b5d510..4a435b28f 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java @@ -132,7 +132,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl getSpanCount(getResources().getConfiguration().orientation)); rvContributionsList.setLayoutManager(layoutManager); contributionsListPresenter.setup(); - contributionsListPresenter.contributionList.observe(this, adapter::submitList); + contributionsListPresenter.contributionList.observe(this.getViewLifecycleOwner(), adapter::submitList); rvContributionsList.setAdapter(adapter); } diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java index c76c54673..753836a6c 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java @@ -7,6 +7,8 @@ import android.content.Context; import android.view.inputmethod.InputMethodManager; import androidx.collection.LruCache; import androidx.room.Room; +import androidx.room.migration.Migration; +import androidx.sqlite.db.SupportSQLiteDatabase; import com.google.gson.Gson; import dagger.Module; import dagger.Provides; @@ -49,6 +51,15 @@ public class CommonsApplicationModule { private Context applicationContext; public static final String IO_THREAD="io_thread"; public static final String MAIN_THREAD="main_thread"; + private AppDatabase appDatabase; + + static final Migration MIGRATION_1_2 = new Migration(1, 2) { + @Override + public void migrate(SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE contribution " + + " ADD COLUMN hasInvalidLocation INTEGER NOT NULL DEFAULT 0"); + } + }; public CommonsApplicationModule(Context applicationContext) { this.applicationContext = applicationContext; @@ -103,11 +114,6 @@ public class CommonsApplicationModule { return context.getContentResolver().acquireContentProviderClient(BuildConfig.CATEGORY_AUTHORITY); } - /** - * This method is used to provide instance of DepictsContentProviderClient - * @param context context - * @return DepictsContentProviderClient*/ - /** * This method is used to provide instance of RecentSearchContentProviderClient * which provides content of Recent Searches from database @@ -224,9 +230,11 @@ public class CommonsApplicationModule { @Provides @Singleton public AppDatabase provideAppDataBase() { - return Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db") + appDatabase = Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db") + .addMigrations(MIGRATION_1_2) .fallbackToDestructiveMigration() .build(); + return appDatabase; } @Provides diff --git a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java index 84fdfe53a..f537b6f80 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java @@ -76,7 +76,7 @@ public class NetworkingModule { HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(message -> { Timber.tag("OkHttp").v(message); }); - httpLoggingInterceptor.level(BuildConfig.DEBUG ? Level.BODY: Level.BASIC); + httpLoggingInterceptor.setLevel(BuildConfig.DEBUG ? Level.BODY: Level.BASIC); return httpLoggingInterceptor; } diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java index 5d168d6b1..698e2d51f 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java @@ -166,7 +166,7 @@ public class FilePicker implements Constants { public static List handleExternalImagesPicked(Intent data, Activity activity) { try { return getFilesFromGalleryPictures(data, activity); - } catch (IOException e) { + } catch (IOException | SecurityException e) { e.printStackTrace(); } return new ArrayList<>(); @@ -207,7 +207,7 @@ public class FilePicker implements Constants { } } - private static List getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException { + private static List getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException, SecurityException { List files = new ArrayList<>(); ClipData clipData = data.getClipData(); if (clipData == null) { diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java index 86da1326b..01e68c940 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java @@ -104,12 +104,15 @@ class PickedFiles implements Constants { }); } - static UploadableFile pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException { + static UploadableFile pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException, SecurityException {// SecurityException for those file providers who share URI but forget to grant necessary permissions InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri); File directory = tempImageDirectory(context); File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri)); - photoFile.createNewFile(); - writeToFile(pictureInputStream, photoFile); + if (photoFile.createNewFile()) { + writeToFile(pictureInputStream, photoFile); + } else { + throw new IOException("could not create photoFile to write upon"); + } return new UploadableFile(photoUri, photoFile); } 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 e1112f957..84147eef9 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 @@ -295,7 +295,9 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple * The method notify the viewpager that number of items have changed. */ public void notifyDataSetChanged(){ - adapter.notifyDataSetChanged(); + if (null != adapter) { + adapter.notifyDataSetChanged(); + } } @Override diff --git a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java index 089d16a0a..63b582550 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java @@ -23,18 +23,11 @@ import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.interfaces.DraweeController; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.ProgressBar; -import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.imagepipeline.image.ImageInfo; -import com.github.chrisbanes.photoview.PhotoView; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; public class ZoomableActivity extends AppCompatActivity { private Uri imageUri; diff --git a/app/src/main/java/fr/free/nrw/commons/media/zoomControllers/zoomable/ZoomableDraweeView.java b/app/src/main/java/fr/free/nrw/commons/media/zoomControllers/zoomable/ZoomableDraweeView.java index 3cb2b2f91..02d93622b 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/zoomControllers/zoomable/ZoomableDraweeView.java +++ b/app/src/main/java/fr/free/nrw/commons/media/zoomControllers/zoomable/ZoomableDraweeView.java @@ -47,6 +47,7 @@ public class ZoomableDraweeView extends DraweeView private boolean mIsDialtoneEnabled = false; private boolean mZoomingEnabled = true; + private TransformationListener transformationListener; private final ControllerListener mControllerListener = new BaseControllerListener() { @@ -73,9 +74,18 @@ public class ZoomableDraweeView extends DraweeView } @Override - public void onTransformEnd(Matrix transform) {} + public void onTransformEnd(Matrix transform) { + if (null != transformationListener) { + transformationListener.onTransformationEnd(); + } + } }; + public void setTransformationListener( + TransformationListener transformationListener) { + this.transformationListener = transformationListener; + } + private final GestureListenerWrapper mTapListenerWrapper = new GestureListenerWrapper(); public ZoomableDraweeView(Context context, GenericDraweeHierarchy hierarchy) { @@ -397,4 +407,11 @@ public class ZoomableDraweeView extends DraweeView protected ZoomableController createZoomableController() { return AnimatedZoomableController.newInstance(); } + + /** + * Use this, If someone is willing to listen to scale change + */ + public interface TransformationListener{ + void onTransformationEnd(); + } } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java b/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java index e8fc70873..db2c1f5d9 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java @@ -1,8 +1,6 @@ package fr.free.nrw.commons.nearby; import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; import android.util.AttributeSet; import android.widget.CompoundButton; @@ -12,7 +10,6 @@ import androidx.appcompat.widget.AppCompatCheckBox; import java.util.List; import fr.free.nrw.commons.R; -import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter; /** * Base on https://stackoverflow.com/a/40939367/3950497 answer. @@ -25,7 +22,7 @@ public class CheckBoxTriStates extends AppCompatCheckBox { static public final int CHECKED = 1; - private int state; + private int state=UNKNOWN; private Callback callback; @@ -64,12 +61,6 @@ public class CheckBoxTriStates extends AppCompatCheckBox { */ private OnCheckedChangeListener clientListener; - /** - * This flag is needed to avoid accidentally changing the current {@link #state} when - * {@link #onRestoreInstanceState(Parcelable)} calls {@link #setChecked(boolean)} - * evoking our {@link #privateListener} and therefore changing the real state. - */ - private boolean restoring; public CheckBoxTriStates(Context context) { super(context); @@ -91,7 +82,7 @@ public class CheckBoxTriStates extends AppCompatCheckBox { } public void setState(int state) { - if(!this.restoring && this.state != state) { + if(this.state != state) { this.state = state; if(this.clientListener != null) { @@ -118,27 +109,6 @@ public class CheckBoxTriStates extends AppCompatCheckBox { super.setOnCheckedChangeListener(privateListener); } - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - - SavedState ss = new SavedState(superState); - - ss.state = state; - - return ss; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - this.restoring = true; // indicates that the ui is restoring its state - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - setState(ss.state); - requestLayout(); - this.restoring = false; - } - private void init() { state = UNKNOWN; updateBtn(); @@ -164,44 +134,4 @@ public class CheckBoxTriStates extends AppCompatCheckBox { setButtonDrawable(btnDrawable); } - - static class SavedState extends BaseSavedState { - int state; - - SavedState(Parcelable superState) { - super(superState); - } - - private SavedState(Parcel in) { - super(in); - state = in.readInt(); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeValue(state); - } - - @Override - public String toString() { - return "CheckboxTriState.SavedState{" - + Integer.toHexString(System.identityHashCode(this)) - + " state=" + state + "}"; - } - - @SuppressWarnings("hiding") - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index 90fe7f598..05086dd9b 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -19,6 +19,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.VectorDrawable; import android.os.Bundle; import android.provider.Settings; +import android.text.Html; +import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -39,6 +41,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; +import androidx.appcompat.widget.AppCompatTextView; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -75,7 +78,6 @@ import fr.free.nrw.commons.auth.LoginActivity; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.location.LocationServiceManager; @@ -154,6 +156,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @BindView(R.id.rv_nearby_list) RecyclerView rvNearbyList; @BindView(R.id.no_results_message) TextView noResultsView; + @BindView(R.id.tv_attribution) + AppCompatTextView tvAttribution; @Inject LocationServiceManager locationManager; @Inject NearbyController nearbyController; @@ -235,8 +239,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment final UiSettings uiSettings = mapBoxMap.getUiSettings(); uiSettings.setCompassGravity(Gravity.BOTTOM | Gravity.LEFT); uiSettings.setCompassMargins(12, 0, 0, 24); - uiSettings.setLogoEnabled(true); - uiSettings.setAttributionEnabled(true); + uiSettings.setLogoEnabled(false); + uiSettings.setAttributionEnabled(false); uiSettings.setRotateGesturesEnabled(false); isMapBoxReady =true; performMapReadyActions(); @@ -259,6 +263,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment scaleBarPlugin.create(scaleBarOptions); }); }); + + tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); + tvAttribution.setMovementMethod(LinkMovementMethod.getInstance()); } /** @@ -659,7 +666,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public boolean isCurrentLocationMarkerVisible() { - if (latLngBounds == null) { + if (latLngBounds == null || currentLocationMarker==null) { Timber.d("Map projection bounds are null"); return false; } else { diff --git a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java index 8f625205c..1e09bc37e 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java +++ b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java @@ -9,6 +9,7 @@ public class Prefs { public static final String MANAGED_EXIF_TAGS = "managed_exif_tags"; public static final String KEY_LANGUAGE_VALUE = "languageDescription"; public static final String KEY_THEME_VALUE = "appThemePref"; + public static final String TELEMETRY_PREFERENCE = "telemetryPref"; public static class Licenses { public static final String CC_BY_SA_3 = "CC BY-SA 3.0"; diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java index 53cf8d7fe..9013cebca 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java @@ -14,6 +14,8 @@ import com.google.android.material.snackbar.Snackbar; import com.karumi.dexter.Dexter; import com.karumi.dexter.listener.PermissionGrantedResponse; import com.karumi.dexter.listener.single.BasePermissionListener; +import com.mapbox.mapboxsdk.Mapbox; +import com.mapbox.mapboxsdk.maps.TelemetryDefinition; import fr.free.nrw.commons.R; import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.di.ApplicationlessInjection; @@ -85,6 +87,24 @@ public class SettingsFragment extends PreferenceFragmentCompat { findPreference("displayLocationPermissionForCardView").setEnabled(false); findPreference("displayCampaignsCardView").setEnabled(false); } + + findPreference("telemetryOptOut").setOnPreferenceChangeListener( + (preference, newValue) -> { + telemetryOptInOut((boolean)newValue); + defaultKvStore.putBoolean(Prefs.TELEMETRY_PREFERENCE,(boolean)newValue); + return false; + }); + } + + /** + * Opt in or out of MapBox telemetry + * @param shouldOptIn + */ + private void telemetryOptInOut(boolean shouldOptIn){ + TelemetryDefinition telemetry = Mapbox.getTelemetry(); + if (telemetry != null) { + telemetry.setUserTelemetryRequestState(shouldOptIn); + } } /** 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 140edad24..13a797006 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 @@ -22,6 +22,8 @@ public class UploadItem { private final long createdTimestamp; private final String createdTimestampSource; private final BehaviorSubject imageQuality; + private boolean hasInvalidLocation; + @SuppressLint("CheckResult") UploadItem(final Uri mediaUri, @@ -103,4 +105,11 @@ public class UploadItem { this.gpsCoords = gpsCoords; } + public void setHasInvalidLocation(boolean hasInvalidLocation) { + this.hasInvalidLocation=hasInvalidLocation; + } + + public boolean hasInvalidLocation() { + return hasInvalidLocation; + } } 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 6bf18bde9..b15e9ffab 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 @@ -141,6 +141,13 @@ public class UploadModel { { final Contribution contribution = new Contribution( item, sessionManager, newListOf(selectedDepictions), newListOf(selectedCategories)); + + contribution.setHasInvalidLocation(item.hasInvalidLocation()); + + Timber.d("Created timestamp while building contribution is %s, %s", + item.getCreatedTimestamp(), + new Date(item.getCreatedTimestamp())); + if (item.getCreatedTimestamp() != -1L) { contribution.setDateCreated(new Date(item.getCreatedTimestamp())); contribution.setDateCreatedSource(item.getCreatedTimestampSource()); @@ -188,4 +195,5 @@ public class UploadModel { public List getSelectedDepictions() { return selectedDepictions; } + } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java index 57e044019..909155303 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java @@ -22,6 +22,7 @@ import fr.free.nrw.commons.contributions.MainActivity; import fr.free.nrw.commons.di.CommonsApplicationModule; import fr.free.nrw.commons.di.CommonsDaggerService; import fr.free.nrw.commons.media.MediaClient; +import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.wikidata.WikidataEditService; import io.reactivex.Observable; import io.reactivex.Scheduler; @@ -350,8 +351,14 @@ public class UploadService extends CommonsDaggerService { .add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution)); WikidataPlace wikidataPlace = contribution.getWikidataPlace(); if (wikidataPlace != null && wikidataPlace.getImageValue() == null) { - wikidataEditService.createClaim(wikidataPlace, uploadResult.getFilename(), - contribution.getMedia().getCaptions()); + if (!contribution.hasInvalidLocation()) { + wikidataEditService.createClaim(wikidataPlace, uploadResult.getFilename(), + contribution.getMedia().getCaptions()); + } else { + ViewUtil.showShortToast(this, getString(R.string.wikidata_edit_failure)); + Timber + .d("Image location and nearby place location mismatched, so Wikidata item won't be edited"); + } } saveCompletedContribution(contribution, uploadResult); } 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 5a7506e93..0ffa54eea 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 @@ -45,7 +45,7 @@ import org.apache.commons.lang3.StringUtils; import timber.log.Timber; public class UploadMediaDetailFragment extends UploadBaseFragment implements - UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener { + UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener { @BindView(R.id.tv_title) TextView tvTitle; @@ -98,7 +98,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_upload_media_detail_fragment, container, false); } @@ -111,7 +111,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements private void init() { tvTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1, - callback.getTotalNumberOfSteps())); + callback.getTotalNumberOfSteps())); initRecyclerView(); initPresenter(); presenter.receiveImage(uploadableFile, place); @@ -140,10 +140,10 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements */ private void attachImageViewScaleChangeListener() { photoViewBackgroundImage.setOnScaleChangeListener( - (scaleFactor, focusX, focusY) -> { - //Whenever the uses plays with the image, lets collapse the media detail container - expandCollapseLlMediaDetail(false); - }); + (scaleFactor, focusX, focusY) -> { + //Whenever the uses plays with the image, lets collapse the media detail container + expandCollapseLlMediaDetail(false); + }); } /** @@ -228,16 +228,16 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements @Override public void onNearbyPlaceFound(UploadItem uploadItem, Place place) { DialogUtil.showAlertDialog(getActivity(), - getString(R.string.upload_nearby_place_found_title), - String.format(Locale.getDefault(), - getString(R.string.upload_nearby_place_found_description), - place.getName()), - () -> { - presenter.onUserConfirmedUploadIsOfPlace(place, callback.getIndexInViewFlipper(this)); - }, - () -> { + getString(R.string.upload_nearby_place_found_title), + String.format(Locale.getDefault(), + getString(R.string.upload_nearby_place_found_description), + place.getName()), + () -> { + presenter.onUserConfirmedUploadIsOfPlace(place, callback.getIndexInViewFlipper(this)); + }, + () -> { - }); + }); } @Override @@ -264,16 +264,16 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements public void showDuplicatePicturePopup(UploadItem uploadItem) { String uploadTitleFormat = getString(R.string.upload_title_duplicate); DialogUtil.showAlertDialog(getActivity(), - getString(R.string.duplicate_image_found), - String.format(Locale.getDefault(), - uploadTitleFormat, - uploadItem.getFileName()), - getString(R.string.upload), - getString(R.string.cancel), - () -> { - uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP); - onNextButtonClicked(); - }, null); + getString(R.string.duplicate_image_found), + String.format(Locale.getDefault(), + uploadTitleFormat, + uploadItem.getFileName()), + getString(R.string.upload), + getString(R.string.cancel), + () -> { + uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP); + onNextButtonClicked(); + }, null); } @@ -283,16 +283,16 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements String errorMessageForResult = getErrorMessageForResult(getContext(), errorCode); if (!StringUtils.isBlank(errorMessageForResult)) { DialogUtil.showAlertDialog(getActivity(), - getString(R.string.upload_problem_image), - errorMessageForResult, - getString(R.string.upload), - getString(R.string.cancel), + getString(R.string.upload_problem_image), + errorMessageForResult, + getString(R.string.upload), + getString(R.string.cancel), () -> { - uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP); - onNextButtonClicked(); - }, - () -> deleteThisPicture() - ); + uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP); + onNextButtonClicked(); + }, + () -> deleteThisPicture() + ); } //If the error message is null, we will probably not show anything } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java index ab1c229e8..69ba6b025 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java @@ -134,7 +134,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt .observeOn(mainThreadScheduler) .subscribe(imageResult -> { view.showProgress(false); - handleImageResult(imageResult, uploadItem); + handleImageResult(imageResult, uploadItem); }, throwable -> { view.showProgress(false); @@ -201,6 +201,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt UploadItem uploadItem) { if (imageResult == IMAGE_KEEP || imageResult == IMAGE_OK) { view.onImageValidationSuccess(); + uploadItem.setHasInvalidLocation(false); } else { handleBadImage(imageResult, uploadItem); } @@ -212,11 +213,12 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt * @param errorCode * @param uploadItem */ - public void handleBadImage(Integer errorCode, UploadItem uploadItem) { + public void handleBadImage(Integer errorCode, + UploadItem uploadItem) { Timber.d("Handle bad picture with error code %d", errorCode); - // If location of image and nearby does not match, then set shared preferences to disable wikidata edits - if (errorCode >= 8) { - defaultKVStore.putBoolean("Picture_Has_Correct_Location", false); + if (errorCode + >= 8) { // If location of image and nearby does not match + uploadItem.setHasInvalidLocation(true); } switch (errorCode) { diff --git a/app/src/main/res/layout/fragment_media_detail.xml b/app/src/main/res/layout/fragment_media_detail.xml index 863f2cb4f..d8af6179e 100644 --- a/app/src/main/res/layout/fragment_media_detail.xml +++ b/app/src/main/res/layout/fragment_media_detail.xml @@ -1,12 +1,12 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/mainBackground" + > + android:id="@+id/mediaDetailImageFailed" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center" + android:src="@android:drawable/ic_menu_close_clear_cancel" + android:visibility="gone" + android:contentDescription="@string/mediaimage_failed" + /> + android:id="@+id/mediaDetailImageView" + android:layout_width="match_parent" + android:layout_height="@dimen/dimen_250" + app:actualImageScaleType="none" /> + android:id="@+id/mediaDetailScrollView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:cacheColorHint="@android:color/transparent" + android:fillViewport="true"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + android:layout_width="match_parent" + android:layout_height="wrap_content" > + android:id="@+id/mediaDetailImageViewLandscape" + android:layout_width="match_parent" + android:layout_height="@dimen/dimen_250" + app:actualImageScaleType="none" + android:visibility="gone" /> @@ -77,32 +77,32 @@ + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/mainBackground" + android:orientation="vertical"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/primaryDarkColor" + android:orientation="horizontal" + android:padding="@dimen/quarter_standard_height"> + style="@style/MediaDetailTextLabelTitle" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:text="@string/media_detail_title" /> + style="@style/MediaDetailTextBody" + android:id="@+id/mediaDetailTitle" + android:layout_width="@dimen/widget_margin" + android:textColor="@android:color/white" + android:layout_height="match_parent" + tools:text="Title of the media" /> + style="@style/MediaDetailContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/authorLinearLayout" + android:orientation="horizontal"> + style="@style/MediaDetailTextLabelGeneric" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:text="@string/media_detail_author" /> + style="@style/MediaDetailTextBody" + android:id="@+id/mediaDetailAuthor" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + tools:text="Media author user name goes here." /> + style="@style/MediaDetailContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + style="@style/MediaDetailTextLabelGeneric" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:text="@string/media_detail_description" /> + android:id="@+id/mediaDetailDesc" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:layout_weight="70" + android:layout_gravity="start" + android:padding="@dimen/small_gap" + android:textColor="?attr/mediaDetailsText" + android:textSize="@dimen/description_text_size" + tools:text="Description of the media goes here. This can potentially be fairly long, and will need to wrap across multiple lines. We hope it looks nice though." /> + android:background="?attr/mediaDetailSpacerColor" + android:layout_width="match_parent" + android:layout_height="@dimen/tiny_gap"/> + style="@style/MediaDetailContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + style="@style/MediaDetailTextLabelGeneric" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:text="@string/media_detail_license" /> + android:id="@+id/mediaDetailLicense" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:layout_weight="70" + android:layout_gravity="start" + android:foreground="?attr/selectableItemBackground" + android:gravity="center_vertical" + android:padding="@dimen/small_gap" + android:textColor="?attr/mediaDetailsText" + android:textSize="@dimen/description_text_size" + android:drawablePadding="@dimen/tiny_gap" + android:drawableStart="?attr/iconInfo24" + tools:text="License link" /> + style="@style/MediaDetailContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + style="@style/MediaDetailTextLabelGeneric" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:text="@string/media_detail_coordinates" /> + android:id="@+id/mediaDetailCoordinates" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:layout_weight="70" + android:layout_gravity="start" + android:foreground="?attr/selectableItemBackground" + android:gravity="center_vertical" + android:padding="@dimen/small_gap" + android:textColor="?attr/mediaDetailsText" + android:textSize="@dimen/description_text_size" + android:drawablePadding="@dimen/tiny_gap" + android:drawableStart="?attr/iconMap24" + tools:text="Coordinates link" /> - + - + + style="@style/MediaDetailContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:textStyle="bold"> + style="@style/MediaDetailTextLabelGeneric" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:text="@string/detail_panel_cats_label" /> + android:id="@+id/mediaDetailCategoryContainer" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:layout_weight="70" + android:orientation="vertical"> @@ -310,90 +310,90 @@ android:background="@drawable/ic_baseline_edit_24" /> + style="@style/MediaDetailContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + style="@style/MediaDetailTextLabelGeneric" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:text="@string/media_detail_uploaded_date" /> + style="@style/MediaDetailTextBody" + android:id="@+id/mediaDetailuploadeddate" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + tools:text="Uploaded date" /> + android:id="@+id/nominatedDeletionBanner" + android:background="?attr/mediaDetailNominationBackground" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/quarter_standard_height" + android:visibility="gone"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:text="@string/nominated_for_deletion" + android:textColor="@color/primaryTextColor" + android:textSize="@dimen/normal_text" + android:textStyle="bold"/> + android:id="@+id/seeMore" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingTop="@dimen/standard_gap" + android:textColor="@color/primaryTextColor" + android:textSize="@dimen/normal_text" + android:textStyle="bold"/> + style="@style/MediaDetailContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + style="@style/MediaDetailTextLabelGeneric" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" + android:text="@string/media_detail_discussion" /> + style="@style/MediaDetailTextBody" + android:id="@+id/mediaDetailDisc" + android:layout_width="@dimen/widget_margin" + android:layout_height="match_parent" />