diff --git a/CHANGELOG.md b/CHANGELOG.md
index 61d11ecd8..bc03bc28a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Wikimedia Commons for Android
+## v3.0.1
+- Pre-fill desc in Nearby uploads with Wikidata item's label + description
+- Improved ACRA crash reporting
+- Fixed various crashes
+
## v3.0.0
- Added Structured Data to upload workflow, users can now add depicts
- Added Leaderboard in Achievements screen
diff --git a/app/build.gradle b/app/build.gradle
index c042fc5f4..13a606783 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -39,7 +39,7 @@ 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:8.6.2'
+ 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.github.deano2390:MaterialShowcaseView:1.2.0'
@@ -58,8 +58,8 @@ dependencies {
implementation "com.squareup.okhttp3:okhttp-ws:$OKHTTP_VERSION"
// Logging
- implementation 'ch.acra:acra-dialog:5.3.0'
- implementation 'ch.acra:acra-mail:5.3.0'
+ implementation 'ch.acra:acra-dialog:5.8.1-beta11'
+ implementation 'ch.acra:acra-mail:5.8.1-beta11'
implementation 'org.slf4j:slf4j-api:1.7.25'
api('com.github.tony19:logback-android-classic:1.1.1-6') {
exclude group: 'com.google.android', module: 'android'
@@ -153,8 +153,9 @@ android {
defaultConfig {
//applicationId 'fr.free.nrw.commons'
- versionCode 876
- versionName '3.0.0'
+
+ versionCode 1016
+ versionName '3.0.1'
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion 19
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 71ec71f0e..7ee650471 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -140,7 +140,11 @@
android:name=".review.ReviewActivity"
android:label="@string/title_activity_review" />
-
+
+
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 aad8e4321..48967e3ff 100644
--- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
+++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
@@ -70,7 +70,8 @@ import timber.log.Timber;
)
@AcraMailSender(
- mailTo = "commons-app-android-private@googlegroups.com"
+ mailTo = "commons-app-android-private@googlegroups.com",
+ reportAsFile = false
)
@AcraDialog(
diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPicker.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPicker.java
new file mode 100644
index 000000000..bd890d6ef
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPicker.java
@@ -0,0 +1,54 @@
+package fr.free.nrw.commons.LocationPicker;
+
+import android.app.Activity;
+import android.content.Intent;
+import androidx.annotation.NonNull;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+
+/**
+ * Helper class for starting the activity
+ */
+public final class LocationPicker {
+
+ /**
+ * Getting camera position from the intent using constants
+ * @param data intent
+ * @return CameraPosition
+ */
+ public static CameraPosition getCameraPosition(final Intent data) {
+ return data.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
+ }
+
+ public static class IntentBuilder {
+
+ private final Intent intent;
+
+ /**
+ * Creates a new builder that creates an intent to launch the place picker activity.
+ */
+ public IntentBuilder() {
+ intent = new Intent();
+ }
+
+ /**
+ * Gets and puts location in intent
+ * @param position CameraPosition
+ * @return LocationPicker.IntentBuilder
+ */
+ public LocationPicker.IntentBuilder defaultLocation(
+ final CameraPosition position) {
+ intent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION, position);
+ return this;
+ }
+
+ /**
+ * Gets and sets the activity
+ * @param activity Activity
+ * @return Intent
+ */
+ public Intent build(final Activity activity) {
+ intent.setClass(activity, LocationPickerActivity.class);
+ return intent;
+ }
+ }
+}
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
new file mode 100644
index 000000000..57da44a42
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java
@@ -0,0 +1,288 @@
+package fr.free.nrw.commons.LocationPicker;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.Html;
+import android.text.method.LinkMovementMethod;
+import android.view.Window;
+import android.view.animation.OvershootInterpolator;
+import android.widget.ImageView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.AppCompatTextView;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.mapbox.android.core.permissions.PermissionsManager;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import com.mapbox.mapboxsdk.camera.CameraPosition.Builder;
+import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.location.LocationComponent;
+import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
+import com.mapbox.mapboxsdk.location.modes.CameraMode;
+import com.mapbox.mapboxsdk.location.modes.RenderMode;
+import com.mapbox.mapboxsdk.maps.MapView;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener;
+import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener;
+import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
+import com.mapbox.mapboxsdk.maps.Style;
+import com.mapbox.mapboxsdk.maps.UiSettings;
+import fr.free.nrw.commons.R;
+import org.jetbrains.annotations.NotNull;
+import timber.log.Timber;
+
+/**
+ * Helps to pick location and return the result with an intent
+ */
+public class LocationPickerActivity extends AppCompatActivity implements OnMapReadyCallback,
+ OnCameraMoveStartedListener, OnCameraIdleListener, Observer {
+
+ /**
+ * cameraPosition : position of picker
+ */
+ private CameraPosition cameraPosition;
+ /**
+ * markerImage : picker image
+ */
+ private ImageView markerImage;
+ /**
+ * mapboxMap : map
+ */
+ private MapboxMap mapboxMap;
+ /**
+ * mapView : view of the map
+ */
+ private MapView mapView;
+ /**
+ * tvAttribution : credit
+ */
+ private AppCompatTextView tvAttribution;
+
+ @Override
+ protected void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.hide();
+ }
+ setContentView(R.layout.activity_location_picker);
+
+ if (savedInstanceState == null) {
+ cameraPosition = getIntent().getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
+ }
+
+ final LocationPickerViewModel viewModel = new ViewModelProvider(this)
+ .get(LocationPickerViewModel.class);
+ viewModel.getResult().observe(this, this);
+
+ bindViews();
+ addBackButtonListener();
+ addPlaceSelectedButton();
+ addCredits();
+ getToolbarUI();
+
+ mapView.onCreate(savedInstanceState);
+ mapView.getMapAsync(this);
+ }
+
+ /**
+ * For showing credits
+ */
+ private void addCredits() {
+ tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
+ tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+
+ /**
+ * Clicking back button destroy locationPickerActivity
+ */
+ private void addBackButtonListener() {
+ final ImageView backButton = findViewById(R.id.mapbox_place_picker_toolbar_back_button);
+ backButton.setOnClickListener(view -> finish());
+ }
+
+ /**
+ * Binds mapView and location picker icon
+ */
+ private void bindViews() {
+ mapView = findViewById(R.id.map_view);
+ markerImage = findViewById(R.id.location_picker_image_view_marker);
+ tvAttribution = findViewById(R.id.tv_attribution);
+ }
+
+ /**
+ * Binds the listeners
+ */
+ private void bindListeners() {
+ mapboxMap.addOnCameraMoveStartedListener(
+ this);
+ mapboxMap.addOnCameraIdleListener(
+ this);
+ }
+
+ /**
+ * Gets toolbar color
+ */
+ private void getToolbarUI() {
+ final ConstraintLayout toolbar = findViewById(R.id.location_picker_toolbar);
+ toolbar.setBackgroundColor(getResources().getColor(R.color.primaryColor));
+ }
+
+ /**
+ * Takes action when map is ready to show
+ * @param mapboxMap map
+ */
+ @Override
+ public void onMapReady(final MapboxMap mapboxMap) {
+ this.mapboxMap = mapboxMap;
+ mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
+ adjustCameraBasedOnOptions();
+ bindListeners();
+ enableLocationComponent(style);
+ });
+ }
+
+ /**
+ * move the location to the current media coordinates
+ */
+ private void adjustCameraBasedOnOptions() {
+ mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
+ }
+
+ /**
+ * Enables location components
+ * @param loadedMapStyle Style
+ */
+ @SuppressWarnings( {"MissingPermission"})
+ private void enableLocationComponent(@NonNull final Style loadedMapStyle) {
+ final UiSettings uiSettings = mapboxMap.getUiSettings();
+ uiSettings.setAttributionEnabled(false);
+
+ // Check if permissions are enabled and if not request
+ if (PermissionsManager.areLocationPermissionsGranted(this)) {
+
+ // Get an instance of the component
+ final LocationComponent locationComponent = mapboxMap.getLocationComponent();
+
+ // Activate with options
+ locationComponent.activateLocationComponent(
+ LocationComponentActivationOptions.builder(this, loadedMapStyle).build());
+
+ // Enable to make component visible
+ locationComponent.setLocationComponentEnabled(true);
+
+ // Set the component's camera mode
+ locationComponent.setCameraMode(CameraMode.NONE);
+
+ // Set the component's render mode
+ locationComponent.setRenderMode(RenderMode.NORMAL);
+
+ }
+ }
+
+ /**
+ * Acts on camera moving
+ * @param reason int
+ */
+ @Override
+ public void onCameraMoveStarted(final int reason) {
+ Timber.v("Map camera has begun moving.");
+ if (markerImage.getTranslationY() == 0) {
+ markerImage.animate().translationY(-75)
+ .setInterpolator(new OvershootInterpolator()).setDuration(250).start();
+ }
+ }
+
+ /**
+ * Acts on camera idle
+ */
+ @Override
+ public void onCameraIdle() {
+ Timber.v("Map camera is now idling.");
+ markerImage.animate().translationY(0)
+ .setInterpolator(new OvershootInterpolator()).setDuration(250).start();
+ }
+
+ /**
+ * Takes action on camera position
+ * @param position position of picker
+ */
+ @Override
+ public void onChanged(@Nullable CameraPosition position) {
+ if (position == null) {
+ position = new Builder()
+ .target(new LatLng(mapboxMap.getCameraPosition().target.getLatitude(),
+ mapboxMap.getCameraPosition().target.getLongitude()))
+ .zoom(16).build();
+ }
+ cameraPosition = position;
+ }
+
+ /**
+ * Select the preferable location
+ */
+ private void addPlaceSelectedButton() {
+ final FloatingActionButton placeSelectedButton = findViewById(R.id.location_chosen_button);
+ placeSelectedButton.setOnClickListener(view -> placeSelected());
+ }
+
+ /**
+ * Return the intent with required data
+ */
+ void placeSelected() {
+ final Intent returningIntent = new Intent();
+ returningIntent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION,
+ mapboxMap.getCameraPosition());
+ setResult(AppCompatActivity.RESULT_OK, returningIntent);
+ finish();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mapView.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mapView.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mapView.onStop();
+ }
+
+ @Override
+ protected void onSaveInstanceState(final @NotNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mapView.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mapView.onDestroy();
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mapView.onLowMemory();
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerConstants.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerConstants.java
new file mode 100644
index 000000000..1a2cab6d1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerConstants.java
@@ -0,0 +1,13 @@
+package fr.free.nrw.commons.LocationPicker;
+
+/**
+ * Constants need for location picking
+ */
+public final class LocationPickerConstants {
+
+ public static final String MAP_CAMERA_POSITION
+ = "location.picker.cameraPosition";
+
+ private LocationPickerConstants() {
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerViewModel.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerViewModel.java
new file mode 100644
index 000000000..c0b91cf71
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerViewModel.java
@@ -0,0 +1,60 @@
+package fr.free.nrw.commons.LocationPicker;
+
+import android.app.Application;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.MutableLiveData;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import org.jetbrains.annotations.NotNull;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import timber.log.Timber;
+
+/**
+ * Observes live camera position data
+ */
+public class LocationPickerViewModel extends AndroidViewModel implements Callback {
+
+ /**
+ * Wrapping CameraPosition with MutableLiveData
+ */
+ private final MutableLiveData result = new MutableLiveData<>();
+
+ /**
+ * Constructor for this class
+ * @param application Application
+ */
+ public LocationPickerViewModel(@NonNull final Application application) {
+ super(application);
+ }
+
+ /**
+ * Responses on camera position changing
+ * @param call Call
+ * @param response Response
+ */
+ @Override
+ public void onResponse(final @NotNull Call call,
+ final Response response) {
+ if(response.body()==null){
+ result.setValue(null);
+ return;
+ }
+ result.setValue(response.body());
+ }
+
+ @Override
+ public void onFailure(final @NotNull Call call, final @NotNull Throwable t) {
+ Timber.e(t);
+ }
+
+ /**
+ * Gets live CameraPosition
+ * @return MutableLiveData
+ */
+ public MutableLiveData getResult() {
+ return result;
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/Media.kt b/app/src/main/java/fr/free/nrw/commons/Media.kt
index 090d8e230..3987acb37 100644
--- a/app/src/main/java/fr/free/nrw/commons/Media.kt
+++ b/app/src/main/java/fr/free/nrw/commons/Media.kt
@@ -74,7 +74,7 @@ class Media constructor(
* Gets the coordinates of where the file was created.
* @return file coordinates as a LatLng
*/
- val coordinates: LatLng? = null,
+ var coordinates: LatLng? = null,
val captions: Map = emptyMap(),
val descriptions: Map = emptyMap(),
val depictionIds: List = emptyList()
diff --git a/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt b/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt
index 0ae7e42de..4dfce0f88 100644
--- a/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt
+++ b/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.actions
import io.reactivex.Observable
+import io.reactivex.Single
import org.wikipedia.csrf.CsrfTokenClient
/**
@@ -62,4 +63,15 @@ class PageEditClient(
Observable.just(false)
}
}
+
+ /**
+ * Get whole WikiText of required file
+ * @param title : Name of the file
+ * @return Observable
+ */
+ fun getCurrentWikiText(title: String): Single {
+ return pageEditInterface.getWikiText(title).map {
+ it.query()?.pages()?.get(0)?.revisions()?.get(0)?.content()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt b/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
index 8b27d1288..91e8230bf 100644
--- a/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
+++ b/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
@@ -1,12 +1,11 @@
package fr.free.nrw.commons.actions
import io.reactivex.Observable
+import io.reactivex.Single
import org.wikipedia.dataclient.Service
+import org.wikipedia.dataclient.mwapi.MwQueryResponse
import org.wikipedia.edit.Edit
-import retrofit2.http.Field
-import retrofit2.http.FormUrlEncoded
-import retrofit2.http.Headers
-import retrofit2.http.POST
+import retrofit2.http.*
/**
* This interface facilitates wiki commons page editing services to the Networking module
@@ -73,4 +72,17 @@ interface PageEditInterface {
@Field("prependtext") prependText: String,
@Field("token") token: String
): Observable
+
+ /**
+ * Get wiki text for provided file names
+ * @param titles : Name of the file
+ * @return Single
+ */
+ @GET(
+ Service.MW_API_PREFIX +
+ "action=query&prop=revisions&rvprop=content|timestamp&rvlimit=1&converttitles="
+ )
+ fun getWikiText(
+ @Query("titles") title: String
+ ): Single
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
index 680e15953..64563ca0e 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
@@ -339,6 +339,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
// no longer attached to activity!
return;
}
+ compositeDisposable.clear();
sessionManager.setUserLoggedIn(true);
AppAdapter.get().updateAccount(loginResult);
progressDialog.dismiss();
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 2fc741dda..e1a8b5b5c 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
@@ -269,7 +269,7 @@ public class BookmarkLocationsDao {
onUpdate(db, from, to);
return;
}
- if (from == 8) {
+ if (from < 10) {
from++;
onUpdate(db, from, to);
return;
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
index 778b1afdc..27cef1c0f 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
@@ -8,7 +8,6 @@ import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity;
import fr.free.nrw.commons.filepicker.DefaultCallback;
import fr.free.nrw.commons.filepicker.FilePicker;
import fr.free.nrw.commons.filepicker.FilePicker.ImageSource;
@@ -63,16 +62,11 @@ public class ContributionController {
* Initiate gallery picker with permission
*/
public void initiateCustomGalleryPickWithPermission(final Activity activity) {
- boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true);
- Intent intent = new Intent(activity,CustomSelectorActivity.class);
- if (!useExtStorage) {
- activity.startActivity(intent);
- return;
- }
+ setPickerConfiguration(activity,true);
PermissionUtils.checkPermissionsAndPerformAction(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
- () -> activity.startActivity(intent),
+ () -> FilePicker.openCustomSelector(activity, 0),
R.string.storage_permission_title,
R.string.write_storage_permission_rationale);
}
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 eda873e8a..6d0a3431d 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
@@ -6,6 +6,7 @@ import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import android.Manifest;
import android.annotation.SuppressLint;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -24,6 +25,14 @@ 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.CommonsApplication;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.notification.Notification;
+import fr.free.nrw.commons.notification.NotificationController;
+import fr.free.nrw.commons.theme.BaseActivity;
+import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
@@ -531,6 +540,13 @@ public class ContributionsFragment
presenter.onDetachView();
}
+ @Override
+ public void notifyDataSetChanged() {
+ if (mediaDetailPagerFragment != null) {
+ mediaDetailPagerFragment.notifyDataSetChanged();
+ }
+ }
+
/**
* Retry upload when it is failed
*
@@ -604,12 +620,8 @@ public class ContributionsFragment
return contributionsListFragment.getContributionStateAt(position);
}
- public void backButtonClicked() {
- if (mediaDetailPagerFragment.isVisible()) {
- if(mediaDetailPagerFragment.backButtonClicked()) {
- // MediaDetailed handled the backPressed no further action required.
- return;
- }
+ public boolean backButtonClicked() {
+ if (null != mediaDetailPagerFragment && mediaDetailPagerFragment.isVisible()) {
if (store.getBoolean("displayNearbyCardView", true)) {
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
nearbyNotificationCardView.setVisibility(View.VISIBLE);
@@ -622,7 +634,9 @@ public class ContributionsFragment
((BaseActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
((MainActivity)getActivity()).showTabs();
fetchCampaigns();
+ return true;
}
+ return false;
}
// Getter for mediaDetailPagerFragment
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 020a9a34e..58788ffb8 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
@@ -104,6 +104,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
private final int SPAN_COUNT_LANDSCAPE = 3;
private final int SPAN_COUNT_PORTRAIT = 1;
+ private int contributionsSize;
+
@Override
public View onCreateView(
@@ -154,7 +156,11 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
}
contributionsListPresenter.setup();
- contributionsListPresenter.contributionList.observe(this.getViewLifecycleOwner(), adapter::submitList);
+ contributionsListPresenter.contributionList.observe(this.getViewLifecycleOwner(), list -> {
+ contributionsSize = list.size();
+ adapter.submitList(list);
+ callback.notifyDataSetChanged();
+ });
rvContributionsList.setAdapter(adapter);
adapter.registerAdapterDataObserver(new AdapterDataObserver() {
@Override
@@ -405,10 +411,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
}
public int getTotalMediaCount() {
- if(adapter != null) {
- return adapter.getItemCount();
- }
- return 0;
+ return contributionsSize;
}
/**
@@ -435,6 +438,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
public interface Callback {
+ void notifyDataSetChanged();
+
void retryUpload(Contribution contribution);
void showDetail(int position, boolean isWikipediaButtonDisplayed);
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
index 460155cd2..dbedb05b0 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
@@ -99,7 +99,9 @@ public class MainActivity extends BaseActivity
@Override
public boolean onSupportNavigateUp() {
if (activeFragment == ActiveFragment.CONTRIBUTIONS) {
- contributionsFragment.backButtonClicked();
+ if (!contributionsFragment.backButtonClicked()) {
+ return false;
+ }
} else {
onBackPressed();
showTabs();
@@ -264,16 +266,10 @@ public class MainActivity extends BaseActivity
@Override
public void onBackPressed() {
if (contributionsFragment != null && activeFragment == ActiveFragment.CONTRIBUTIONS) {
- // Meas that contribution fragment is visible
- mediaDetailPagerFragment=contributionsFragment.getMediaDetailPagerFragment();
- if (mediaDetailPagerFragment ==null) { //means you open the app currently and not open mediaDetailPage fragment
+ // Means that contribution fragment is visible
+ if (!contributionsFragment.backButtonClicked()) {//If this one does not wan't to handle
+ // the back press, let the activity do so
super.onBackPressed();
- } else if (mediaDetailPagerFragment!=null) {
- if(!mediaDetailPagerFragment.isVisible()){ //means you are at contributions fragement
- super.onBackPressed();
- } else { //mean you are at mediaDetailPager Fragment
- contributionsFragment.backButtonClicked();
- }
}
} else if (nearbyParentFragment != null && activeFragment == ActiveFragment.NEARBY) {
// Means that nearby fragment is visible
@@ -322,7 +318,7 @@ public class MainActivity extends BaseActivity
} else {
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork(
UploadWorker.class.getSimpleName(),
- ExistingWorkPolicy.KEEP, OneTimeWorkRequest.from(UploadWorker.class));
+ ExistingWorkPolicy.APPEND_OR_REPLACE, OneTimeWorkRequest.from(UploadWorker.class));
viewUtilWrapper
.showShortToast(getBaseContext(), getString(R.string.limited_connection_disabled));
diff --git a/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java
new file mode 100644
index 000000000..1f3a75029
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java
@@ -0,0 +1,197 @@
+package fr.free.nrw.commons.coordinates;
+
+import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_COORDINATES;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import fr.free.nrw.commons.BuildConfig;
+import fr.free.nrw.commons.Media;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.actions.PageEditClient;
+import fr.free.nrw.commons.notification.NotificationHelper;
+import fr.free.nrw.commons.utils.ViewUtilWrapper;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.schedulers.Schedulers;
+import java.util.Objects;
+import javax.inject.Inject;
+import javax.inject.Named;
+import org.apache.commons.lang3.StringUtils;
+import timber.log.Timber;
+
+/**
+ * Helper class for edit and update given coordinates and showing notification about new coordinates
+ * upgradation
+ */
+public class CoordinateEditHelper {
+
+ /**
+ * notificationHelper: helps creating notification
+ */
+ private final NotificationHelper notificationHelper;
+ /**
+ * * pageEditClient: methods provided by this member posts the edited coordinates
+ * to the Media wiki api
+ */
+ public final PageEditClient pageEditClient;
+ /**
+ * viewUtil: helps to show Toast
+ */
+ private final ViewUtilWrapper viewUtil;
+
+ @Inject
+ public CoordinateEditHelper(final NotificationHelper notificationHelper,
+ @Named("commons-page-edit") final PageEditClient pageEditClient,
+ final ViewUtilWrapper viewUtil) {
+ this.notificationHelper = notificationHelper;
+ this.pageEditClient = pageEditClient;
+ this.viewUtil = viewUtil;
+ }
+
+ /**
+ * Public interface to edit coordinates
+ * @param context to be added
+ * @param media to be added
+ * @param Accuracy to be added
+ * @return Single
+ */
+ public Single makeCoordinatesEdit(final Context context, final Media media,
+ final String Latitude, final String Longitude, final String Accuracy) {
+ viewUtil.showShortToast(context,
+ context.getString(R.string.coordinates_edit_helper_make_edit_toast));
+ return addCoordinates(media, Latitude, Longitude, Accuracy)
+ .flatMapSingle(result -> Single.just(showCoordinatesEditNotification(context, media,
+ Latitude, Longitude, Accuracy, result)))
+ .firstOrError();
+ }
+
+ /**
+ * Replaces new coordinates
+ * @param media to be added
+ * @param Latitude to be added
+ * @param Longitude to be added
+ * @param Accuracy to be added
+ * @return Observable
+ */
+ private Observable addCoordinates(final Media media, final String Latitude,
+ final String Longitude, final String Accuracy) {
+ Timber.d("thread is coordinates adding %s", Thread.currentThread().getName());
+ final String summary = "Adding Coordinates";
+
+ final StringBuilder buffer = new StringBuilder();
+
+ final String wikiText = pageEditClient.getCurrentWikiText(media.getFilename())
+ .subscribeOn(Schedulers.io())
+ .blockingGet();
+
+ if (Latitude != null) {
+ buffer.append("\n{{Location|").append(Latitude).append("|").append(Longitude)
+ .append("|").append(Accuracy).append("}}");
+ }
+
+ final String editedLocation = buffer.toString();
+ final String appendText = getFormattedWikiText(wikiText, editedLocation);
+
+ return pageEditClient.edit(Objects.requireNonNull(media.getFilename())
+ , appendText, summary);
+ }
+
+ /**
+ * Helps to get formatted wikitext with upgraded location
+ * @param wikiText current wikitext
+ * @param editedLocation new location
+ * @return String
+ */
+ private String getFormattedWikiText(final String wikiText, final String editedLocation){
+
+ if (wikiText.contains("filedesc") && wikiText.contains("Location")) {
+
+ final String fromLocationToEnd = wikiText.substring(wikiText.indexOf("{{Location"));
+ final String firstHalf = wikiText.substring(0, wikiText.indexOf("{{Location"));
+ final String lastHalf = fromLocationToEnd.substring(
+ fromLocationToEnd.indexOf("}}") + 2);
+
+ final int startOfSecondSection = StringUtils.ordinalIndexOf(wikiText,
+ "==", 3);
+ final StringBuilder buffer = new StringBuilder();
+ if (wikiText.charAt(wikiText.indexOf("{{Location")-1) == '\n') {
+ buffer.append(editedLocation.substring(1));
+ } else {
+ buffer.append(editedLocation);
+ }
+ if (startOfSecondSection != -1 && wikiText.charAt(startOfSecondSection-1)!= '\n') {
+ buffer.append("\n");
+ }
+
+ return firstHalf + buffer + lastHalf;
+
+ }
+ if (wikiText.contains("filedesc") && !wikiText.contains("Location")) {
+
+ final int startOfSecondSection = StringUtils.ordinalIndexOf(wikiText,
+ "==", 3);
+
+ if (startOfSecondSection != -1) {
+ final String firstHalf = wikiText.substring(0, startOfSecondSection);
+ final String lastHalf = wikiText.substring(startOfSecondSection);
+ final String buffer = editedLocation.substring(1)
+ + "\n";
+ return firstHalf + buffer + lastHalf;
+ }
+
+ return wikiText + editedLocation;
+ }
+ if (!wikiText.contains("filedesc") && !wikiText.contains("Location")) {
+
+ return "== {{int:filedesc}} ==" + editedLocation + wikiText;
+
+ }
+ if (!wikiText.contains("filedesc") && wikiText.contains("Location")) {
+
+ return "== {{int:filedesc}} ==" + editedLocation + wikiText;
+
+ }
+ return null;
+ }
+
+ /**
+ * Update coordinates and shows notification about coordinates update
+ * @param context to be added
+ * @param media to be added
+ * @param latitude to be added
+ * @param longitude to be added
+ * @param Accuracy to be added
+ * @param result to be added
+ * @return boolean
+ */
+ private boolean showCoordinatesEditNotification(final Context context, final Media media,
+ final String latitude, final String longitude, final String Accuracy,
+ final boolean result) {
+ final String message;
+ String title = context.getString(R.string.coordinates_edit_helper_show_edit_title);
+
+ if (result) {
+ media.setCoordinates(
+ new fr.free.nrw.commons.location.LatLng(Double.parseDouble(latitude),
+ Double.parseDouble(longitude),
+ Float.parseFloat(Accuracy)));
+ title += ": " + context
+ .getString(R.string.coordinates_edit_helper_show_edit_title_success);
+ final StringBuilder coordinatesInMessage = new StringBuilder();
+ final String mediaCoordinate = String.valueOf(media.getCoordinates());
+ coordinatesInMessage.append(mediaCoordinate);
+ message = context.getString(R.string.coordinates_edit_helper_show_edit_message,
+ coordinatesInMessage.toString());
+ } else {
+ title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title);
+ message = context.getString(R.string.coordinates_edit_helper_edit_message_else) ;
+ }
+
+ final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
+ final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
+ notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_COORDINATES,
+ browserIntent);
+ return result;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
index 1b676b6e2..9228dc5ac 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
@@ -11,7 +11,7 @@ import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashMap
/**
- * Image Helper object, includes all the static functions required by custom selector
+ * Image Helper object, includes all the static functions required by custom selector.
*/
object ImageHelper {
@@ -49,6 +49,34 @@ object ImageHelper {
return filteredImages
}
+ /**
+ * getIndex: Returns the index of image in given list.
+ */
+ fun getIndex(list: ArrayList, image: Image): Int {
+ return list.indexOf(image)
+ }
+
+ /**
+ * Gets the list of indices from the master list.
+ */
+ fun getIndexList(list: ArrayList, masterList: ArrayList): ArrayList {
+
+ /**
+ * TODO
+ * Can be optimised as masterList is sorted by time.
+ */
+
+ val indexes = arrayListOf()
+ for(image in list) {
+ val index = getIndex(masterList,image)
+ if (index == -1) {
+ continue
+ }
+ indexes.add(index)
+ }
+ return indexes
+ }
+
/**
* Generates the file sha1 from file input stream.
*/
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
index 53de6de77..a38200463 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
@@ -10,6 +10,7 @@ import androidx.constraintlayout.widget.Group
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
+import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image
@@ -26,6 +27,16 @@ class ImageAdapter(
RecyclerViewAdapter(context) {
+ /**
+ * ImageSelectedOrUpdated payload class.
+ */
+ class ImageSelectedOrUpdated
+
+ /**
+ * ImageUnselected payload class.
+ */
+ class ImageUnselected
+
/**
* Currently selected images.
*/
@@ -49,18 +60,41 @@ class ImageAdapter(
*/
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val image=images[position]
- // todo load image thumbnail, set selected view.
+ val selectedIndex = ImageHelper.getIndex(selectedImages,image)
+ val isSelected = selectedIndex != -1
+ if(isSelected){
+ holder.itemSelected(selectedIndex+1)
+ }
+ else {
+ holder.itemUnselected();
+ }
Glide.with(context).load(image.uri).into(holder.image)
holder.itemView.setOnClickListener {
- selectOrRemoveImage(image, position)
+ selectOrRemoveImage(holder, position)
}
}
/**
* Handle click event on an image, update counter on images.
*/
- private fun selectOrRemoveImage(image:Image, position:Int){
- // todo select the image if not selected and remove it if already selected
+ private fun selectOrRemoveImage(holder:ImageViewHolder, position:Int){
+ val clickedIndex = ImageHelper.getIndex(selectedImages,images[position])
+ if (clickedIndex != -1) {
+ selectedImages.removeAt(clickedIndex)
+ notifyItemChanged(position,ImageUnselected())
+ val indexes = ImageHelper.getIndexList(selectedImages, images)
+ for (index in indexes) {
+ notifyItemChanged(index, ImageSelectedOrUpdated())
+ }
+ } else {
+ /**
+ * TODO
+ * Show toast on tapping an uploaded item.
+ */
+ selectedImages.add(images[position])
+ notifyItemChanged(position, ImageSelectedOrUpdated())
+ }
+ imageSelectListener.onSelectedImagesChanged(selectedImages)
}
/**
@@ -90,9 +124,39 @@ class ImageAdapter(
*/
class ImageViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val image: ImageView = itemView.findViewById(R.id.image_thumbnail)
- val selectedNumber: TextView = itemView.findViewById(R.id.selected_count)
- val uploadedGroup: Group = itemView.findViewById(R.id.uploaded_group)
- val selectedGroup: Group = itemView.findViewById(R.id.selected_group)
+ private val selectedNumber: TextView = itemView.findViewById(R.id.selected_count)
+ private val uploadedGroup: Group = itemView.findViewById(R.id.uploaded_group)
+ private val selectedGroup: Group = itemView.findViewById(R.id.selected_group)
+
+ /**
+ * Item selected view.
+ */
+ fun itemSelected(index: Int) {
+ selectedGroup.visibility = View.VISIBLE
+ selectedNumber.text = index.toString()
+ }
+
+ /**
+ * Item Unselected view.
+ */
+ fun itemUnselected() {
+ selectedGroup.visibility = View.GONE
+ }
+
+ /**
+ * Item Uploaded view.
+ */
+ fun itemUploaded() {
+ uploadedGroup.visibility = View.VISIBLE
+ }
+
+ /**
+ * Item Not Uploaded view.
+ */
+ fun itemNotUploaded() {
+ uploadedGroup.visibility = View.GONE
+ }
+
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
index 1ab30f67e..099c89a86 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
@@ -1,5 +1,7 @@
package fr.free.nrw.commons.customselector.ui.selector
+import android.app.Activity
+import android.content.Intent
import android.os.Bundle
import android.widget.ImageButton
import android.widget.TextView
@@ -10,6 +12,7 @@ import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Folder
import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.theme.BaseActivity
+import java.io.File
import javax.inject.Inject
class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectListener {
@@ -73,7 +76,8 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
val back : ImageButton = findViewById(R.id.back)
back.setOnClickListener { onBackPressed() }
- // todo done listener.
+ val done : ImageButton = findViewById(R.id.done)
+ done.setOnClickListener { onDone() }
}
/**
@@ -91,9 +95,44 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
* override Selected Images Change, update view model selected images.
*/
override fun onSelectedImagesChanged(selectedImages: ArrayList) {
+ viewModel.selectedImages.value = selectedImages
// todo update selected images in view model.
}
+ /**
+ * OnDone clicked.
+ * Get the selected images. Remove any non existent file, forward the data to finish selector.
+ */
+ fun onDone() {
+ val selectedImages = viewModel.selectedImages.value
+ if(selectedImages.isNullOrEmpty()) {
+ finishPickImages(arrayListOf())
+ return
+ }
+ var i = 0
+ while (i < selectedImages.size) {
+ val path = selectedImages[i].path
+ val file = File(path)
+ if (!file.exists()) {
+ selectedImages.removeAt(i)
+ i--
+ }
+ i++
+ }
+ finishPickImages(selectedImages)
+ }
+
+ /**
+ * finishPickImages, Load the data to the intent and set result.
+ * Finish the activity.
+ */
+ private fun finishPickImages(images: ArrayList) {
+ val data = Intent()
+ data.putParcelableArrayListExtra("Images", images)
+ setResult(Activity.RESULT_OK, data)
+ finish()
+ }
+
/**
* Back pressed.
* Change toolbar title.
@@ -106,16 +145,4 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
}
}
-
- /**
- *
- * TODO
- * Permission check.
- * OnDone
- * Activity Result.
- *
- *
- */
-
-
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
index 26b8033ba..4f56a808b 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
@@ -1,7 +1,6 @@
package fr.free.nrw.commons.customselector.ui.selector
import android.content.Context
-import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import fr.free.nrw.commons.customselector.listeners.ImageLoaderListener
@@ -14,10 +13,18 @@ import kotlinx.coroutines.cancel
class CustomSelectorViewModel(var context: Context,var imageFileLoader: ImageFileLoader) : ViewModel() {
+ /**
+ * Scope for coroutine task (image fetch).
+ */
private val scope = CoroutineScope(Dispatchers.Main)
/**
- * Result Live Data
+ * Stores selected images.
+ */
+ var selectedImages: MutableLiveData> = MutableLiveData()
+
+ /**
+ * Result Live Data.
*/
val result = MutableLiveData(Result(CallbackStatus.IDLE, arrayListOf()))
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 d5989e1b3..e37f1942b 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
@@ -13,7 +13,7 @@ import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "commons.db";
- private static final int DATABASE_VERSION = 15;
+ private static final int DATABASE_VERSION = 14;
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/explore/ExploreListRootFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java
index 2e7b0560f..ca005207a 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java
@@ -110,9 +110,8 @@ public class ExploreListRootFragment extends CommonsDaggerSupportFragment implem
@Override
public void onMediaClicked(int position) {
container.setVisibility(View.VISIBLE);
- ((ExploreFragment) getParentFragment()).tabLayout.setVisibility(View.GONE);
+ ((ExploreFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
mediaDetails = new MediaDetailPagerFragment(false, true);
- ((ExploreFragment) getParentFragment()).setScroll(false);
setFragment(mediaDetails, listFragment);
mediaDetails.showImage(position);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java b/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java
index 83d838bc2..4b5b91e68 100644
--- a/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java
+++ b/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java
@@ -10,6 +10,7 @@ public interface Constants {
int FILE_PICKER_IMAGE_IDENTIFICATOR = 0b1101101100; //876
int SOURCE_CHOOSER = 1 << 15;
+ int PICK_PICTURE_FROM_CUSTOM_SELECTOR = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 10);
int PICK_PICTURE_FROM_DOCUMENTS = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 11);
int PICK_PICTURE_FROM_GALLERY = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 12);
int TAKE_PICTURE = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 13);
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 698e2d51f..6d516abd9 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
@@ -15,6 +15,8 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import fr.free.nrw.commons.customselector.model.Image;
+import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
@@ -51,6 +53,11 @@ public class FilePicker implements Constants {
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery());
}
+ private static Intent createCustomSelectorIntent(@NonNull Context context, int type) {
+ storeType(context, type);
+ return new Intent(context, CustomSelectorActivity.class);
+ }
+
private static Intent createCameraForImageIntent(@NonNull Context context, int type) {
storeType(context, type);
@@ -97,6 +104,14 @@ public class FilePicker implements Constants {
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
}
+ /**
+ * Opens Custom Selector
+ */
+ public static void openCustomSelector(Activity activity, int type) {
+ Intent intent = createCustomSelectorIntent(activity, type);
+ activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR);
+ }
+
/**
* Opens the camera app to pick image clicked by user
*/
@@ -135,12 +150,15 @@ public class FilePicker implements Constants {
if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY ||
requestCode == RequestCodes.TAKE_PICTURE ||
requestCode == RequestCodes.CAPTURE_VIDEO ||
- requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
+ requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS ||
+ requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS && !isPhoto(data)) {
onPictureReturnedFromDocuments(data, activity, callbacks);
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY && !isPhoto(data)) {
onPictureReturnedFromGallery(data, activity, callbacks);
+ } else if (requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) {
+ onPictureReturnedFromCustomSelector(data, activity, callbacks);
} else if (requestCode == RequestCodes.TAKE_PICTURE) {
onPictureReturnedFromCamera(activity, callbacks);
} else if (requestCode == RequestCodes.CAPTURE_VIDEO) {
@@ -197,6 +215,32 @@ public class FilePicker implements Constants {
}
}
+ private static void onPictureReturnedFromCustomSelector(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
+ try {
+ List files = getFilesFromCustomSelector(data, activity);
+ callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity));
+ } catch (Exception e) {
+ e.printStackTrace();
+ callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity));
+ }
+ }
+
+ private static List getFilesFromCustomSelector(Intent data, Activity activity) throws IOException, SecurityException {
+ List files = new ArrayList<>();
+ ArrayList images = data.getParcelableArrayListExtra("Images");
+ for(Image image : images) {
+ Uri uri = image.getUri();
+ UploadableFile file = PickedFiles.pickedExistingPicture(activity, uri);
+ files.add(file);
+ }
+
+ if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) {
+ PickedFiles.copyFilesInSeparateThread(activity, files);
+ }
+
+ return files;
+ }
+
private static void onPictureReturnedFromGallery(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
try {
List files = getFilesFromGalleryPictures(data, activity);
@@ -301,7 +345,7 @@ public class FilePicker implements Constants {
public enum ImageSource {
- GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO
+ GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO, CUSTOM_SELECTOR
}
public interface Callbacks {
diff --git a/app/src/main/java/fr/free/nrw/commons/logging/CommonsLogSender.java b/app/src/main/java/fr/free/nrw/commons/logging/CommonsLogSender.java
index 4e8fe2e70..29c2c732e 100644
--- a/app/src/main/java/fr/free/nrw/commons/logging/CommonsLogSender.java
+++ b/app/src/main/java/fr/free/nrw/commons/logging/CommonsLogSender.java
@@ -2,12 +2,16 @@ package fr.free.nrw.commons.logging;
import android.content.Context;
+import android.os.Bundle;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.DeviceInfoUtil;
+import org.acra.data.CrashReportData;
+import org.acra.sender.ReportSenderException;
+import org.jetbrains.annotations.NotNull;
/**
* Class responsible for sending logs to developers
@@ -87,4 +91,15 @@ public class CommonsLogSender extends LogsSender {
return builder.toString();
}
+
+ @Override
+ public boolean requiresForeground() {
+ return false;
+ }
+
+ @Override
+ public void send(@NotNull Context context, @NotNull CrashReportData crashReportData,
+ @NotNull Bundle bundle) throws ReportSenderException {
+
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
index 6c9472ddd..8e6518c44 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
@@ -1,5 +1,7 @@
package fr.free.nrw.commons.media;
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_NEEDING_CATEGORIES;
@@ -16,11 +18,8 @@ import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.util.Log;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
@@ -50,6 +49,9 @@ import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxSearchView;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import fr.free.nrw.commons.LocationPicker.LocationPicker;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.R;
@@ -62,10 +64,11 @@ import fr.free.nrw.commons.category.CategoryEditHelper;
import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter;
import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter.Callback;
import fr.free.nrw.commons.contributions.ContributionsFragment;
+import fr.free.nrw.commons.coordinates.CoordinateEditHelper;
import fr.free.nrw.commons.delete.DeleteHelper;
import fr.free.nrw.commons.delete.ReasonBuilder;
-import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
+import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
@@ -77,8 +80,9 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
-import java.util.concurrent.TimeUnit;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
@@ -88,6 +92,7 @@ import timber.log.Timber;
public class MediaDetailFragment extends CommonsDaggerSupportFragment implements Callback,
CategoryEditHelper.Callback {
+ private static final int REQUEST_CODE = 1001 ;
private boolean editable;
private boolean isCategoryImage;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
@@ -125,6 +130,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
@Inject
CategoryEditHelper categoryEditHelper;
@Inject
+ CoordinateEditHelper coordinateEditHelper;
+ @Inject
ViewUtilWrapper viewUtil;
@Inject
CategoryClient categoryClient;
@@ -760,6 +767,75 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
}
}
+ @OnClick(R.id.coordinate_edit)
+ public void onUpdateCoordinatesClicked(){
+ goToLocationPickerActivity();
+ }
+
+ /**
+ * Start location picker activity with a request code and get the coordinates from the activity.
+ */
+ private void goToLocationPickerActivity() {
+ /*
+ If location is not provided in media this coordinates will act as a placeholder in
+ location picker activity
+ */
+ double defaultLatitude = 37.773972;
+ double defaultLongitude = -122.431297;
+
+ if (media.getCoordinates() != null) {
+ defaultLatitude = media.getCoordinates().getLatitude();
+ defaultLongitude = media.getCoordinates().getLongitude();
+ }
+ startActivityForResult(new LocationPicker.IntentBuilder()
+ .defaultLocation(new CameraPosition.Builder()
+ .target(new LatLng(defaultLatitude, defaultLongitude))
+ .zoom(16).build())
+ .build(getActivity()), REQUEST_CODE);
+ }
+
+ /**
+ * Get the coordinates and update the existing coordinates.
+ * @param requestCode
+ * @param resultCode
+ * @param data
+ */
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode,
+ @Nullable final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
+
+ assert data != null;
+ final CameraPosition cameraPosition = LocationPicker.getCameraPosition(data);
+
+ if (cameraPosition != null) {
+
+ final String latitude = String.valueOf(cameraPosition.target.getLatitude());
+ final String longitude = String.valueOf(cameraPosition.target.getLongitude());
+ final String accuracy = String.valueOf(cameraPosition.target.getAltitude());
+ String currentLatitude = null;
+ String currentLongitude = null;
+
+ if (media.getCoordinates() != null) {
+ currentLatitude = String.valueOf(media.getCoordinates().getLatitude());
+ currentLongitude = String.valueOf(media.getCoordinates().getLongitude());
+ }
+
+ if (!latitude.equals(currentLatitude) || !longitude.equals(currentLongitude)) {
+ updateCoordinates(latitude, longitude, accuracy);
+ } else if (media.getCoordinates() == null) {
+ updateCoordinates(latitude, longitude, accuracy);
+ }
+ }
+ } else if (resultCode == RESULT_CANCELED) {
+ viewUtil.showShortToast(getContext(),
+ Objects.requireNonNull(getContext())
+ .getString(R.string.coordinates_picking_unsuccessful));
+ }
+ }
+
@OnClick(R.id.update_categories_button)
public void onUpdateCategoriesClicked() {
updateCategories(categoryEditSearchRecyclerViewAdapter.getNewCategories());
@@ -783,6 +859,24 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
}));
}
+ /**
+ * Fetched coordinates are replaced with existing coordinates by a POST API call.
+ * @param Latitude to be added
+ * @param Longitude to be added
+ * @param Accuracy to be added
+ */
+ public void updateCoordinates(final String Latitude, final String Longitude,
+ final String Accuracy) {
+ compositeDisposable.add(coordinateEditHelper.makeCoordinatesEdit(getContext(), media,
+ Latitude, Longitude, Accuracy)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(s -> {
+ Timber.d("Coordinates are added.");
+ coordinates.setText(prettyCoordinates(media));
+ }));
+ }
+
@SuppressLint("StringFormatInvalid")
@OnClick(R.id.nominateDeletion)
public void onDeleteButtonClicked(){
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 b7c6c0768..328f50e3b 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
@@ -6,7 +6,6 @@ import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -101,19 +100,13 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
pager.addOnPageChangeListener(this);
adapter = new MediaDetailAdapter(getChildFragmentManager());
-
- if (((BaseActivity) getActivity()).getSupportActionBar() != null) {
- ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
pager.setAdapter(adapter);
if (savedInstanceState != null) {
final int pageNumber = savedInstanceState.getInt("current-page");
pager.setCurrentItem(pageNumber, false);
getActivity().invalidateOptionsMenu();
- adapter.notifyDataSetChanged();
-
}
+ adapter.notifyDataSetChanged();
if (getActivity() instanceof MainActivity) {
((MainActivity)getActivity()).hideTabs();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
index b32d81efb..a3e24635b 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
@@ -63,11 +63,27 @@ public class Place implements Parcelable {
if(!StringUtils.isBlank(itemClass)) {
classEntityId = itemClass.replace("http://www.wikidata.org/entity/", "");
}
+ // Set description when not null and not empty
+ String description = (item.getDescription().getValue() != null && !item.getDescription().getValue().isEmpty()) ? item.getDescription().getValue() : "";
+ // When description is "?" but we have a valid label, just use the label. So replace "?" by "" in description
+ description = (description.equals("?")
+ && (item.getLabel().getValue() != null
+ && !item.getLabel().getValue().isEmpty()) ? "" : description);
+ /*
+ * If we have a valid label
+ * - If have a valid label add the description at the end of the string with parenthesis
+ * - If we don't have a valid label, string will include only the description. So add it without paranthesis
+ */
+ description = ((item.getLabel().getValue() != null && !item.getLabel().getValue().isEmpty())
+ ? item.getLabel().getValue()
+ + ((description != null && !description.isEmpty())
+ ? " (" + description + ")" : "")
+ : description);
return new Place(
item.getLabel().getLanguage(),
item.getLabel().getValue(),
Label.fromText(classEntityId), // list
- item.getClassLabel().getValue(), // details
+ description, // description and label of Wikidata item
PlaceUtils.latLngFromPointString(item.getLocation().getValue()),
item.getCommonsCategory().getValue(),
new Sitelinks.Builder()
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt
index eaf32a48b..632eadd47 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt
@@ -51,7 +51,8 @@ fun placeAdapterDelegate(
tvDesc.setText(R.string.no_description_found)
tvDesc.visibility = INVISIBLE
} else {
- tvDesc.text = descriptionText
+ // Remove the label and display only texts inside pharentheses (description) since too long
+ tvDesc.text = descriptionText.substringAfter(tvName.text.toString() + " (").substringBeforeLast(")");
}
distance.text = item.distance
icon.setImageResource(item.label.icon)
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 453a23ed5..1767a12a4 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
@@ -1521,7 +1521,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
title.setText(selectedPlace.name);
distance.setText(selectedPlace.distance);
- description.setText(selectedPlace.getLongDescription());
+ // Remove label since it is double information
+ String descriptionText = selectedPlace.getLongDescription()
+ .replace(selectedPlace.getName() + " (","");
+ descriptionText = (descriptionText.equals(selectedPlace.getLongDescription()) ? descriptionText : descriptionText.replaceFirst(".$",""));
+ // Set the short description after we remove place name from long description
+ description.setText(descriptionText);
fabCamera.setOnClickListener(view -> {
if (fabCamera.isShown()) {
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt b/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt
index a39c68be1..10b957c57 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt
@@ -12,6 +12,7 @@ class NearbyResultItem(private val item: ResultTuple?,
@field:SerializedName("commonsCategory") private val commonsCategory: ResultTuple?,
@field:SerializedName("pic") private val pic: ResultTuple?,
@field:SerializedName("destroyed") private val destroyed: ResultTuple?,
+ @field:SerializedName("description") private val description: ResultTuple?,
@field:SerializedName("endTime") private val endTime: ResultTuple?) {
fun getItem(): ResultTuple {
@@ -58,8 +59,11 @@ class NearbyResultItem(private val item: ResultTuple?,
return destroyed ?: ResultTuple()
}
+ fun getDescription(): ResultTuple {
+ return description ?: ResultTuple()
+ }
+
fun getEndTime(): ResultTuple {
return endTime ?: ResultTuple()
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
index 76b51bb57..a23f350bd 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
@@ -25,6 +25,7 @@ public class NotificationHelper {
public static final int NOTIFICATION_DELETE = 1;
public static final int NOTIFICATION_EDIT_CATEGORY = 2;
+ public static final int NOTIFICATION_EDIT_COORDINATES = 3;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
index fc5f2307d..4c8346858 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
@@ -289,7 +289,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
public void makeUploadRequest() {
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork(
UploadWorker.class.getSimpleName(),
- ExistingWorkPolicy.KEEP, OneTimeWorkRequest.from(UploadWorker.class));
+ ExistingWorkPolicy.APPEND_OR_REPLACE, OneTimeWorkRequest.from(UploadWorker.class));
}
@Override
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
index ecfdb1100..15f7df517 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
@@ -7,6 +7,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
+import com.google.gson.Gson
import com.mapbox.mapboxsdk.plugins.localization.BuildConfig
import dagger.android.ContributesAndroidInjector
import fr.free.nrw.commons.CommonsApplication
@@ -146,61 +147,60 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL
)!!
withContext(Dispatchers.IO) {
- //Doing this so that retry requests do not create new work requests and while a work is
- // already running, all the requests should go through this, so kind of a queue
- while (contributionDao.getContribution(statesToProcess)
- .blockingGet().isNotEmpty()
- ) {
- val queuedContributions = contributionDao.getContribution(statesToProcess)
- .blockingGet()
- //Showing initial notification for the number of uploads being processed
+ val queuedContributions = contributionDao.getContribution(statesToProcess)
+ .blockingGet()
+ //Showing initial notification for the number of uploads being processed
- processingUploads.setContentTitle(appContext.getString(R.string.starting_uploads))
- processingUploads.setContentText(
- appContext.resources.getQuantityString(
- R.plurals.starting_multiple_uploads,
- queuedContributions.size,
- queuedContributions.size
- )
- )
- notificationManager?.notify(
- PROCESSING_UPLOADS_NOTIFICATION_TAG,
- PROCESSING_UPLOADS_NOTIFICATION_ID,
- processingUploads.build()
+ Timber.e("Queued Contributions: "+ queuedContributions.size)
+
+ processingUploads.setContentTitle(appContext.getString(R.string.starting_uploads))
+ processingUploads.setContentText(
+ appContext.resources.getQuantityString(
+ R.plurals.starting_multiple_uploads,
+ queuedContributions.size,
+ queuedContributions.size
)
+ )
+ notificationManager?.notify(
+ PROCESSING_UPLOADS_NOTIFICATION_TAG,
+ PROCESSING_UPLOADS_NOTIFICATION_ID,
+ processingUploads.build()
+ )
- queuedContributions.asFlow().map { contribution ->
- /**
- * If the limited connection mode is on, lets iterate through the queued
- * contributions
- * and set the state as STATE_QUEUED_LIMITED_CONNECTION_MODE ,
- * otherwise proceed with the upload
- */
- if(isLimitedConnectionModeEnabled()){
- if (contribution.state == Contribution.STATE_QUEUED) {
- contribution.state = Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE
- contributionDao.save(contribution)
- }
- } else {
- contribution.transferred = 0
- contribution.state = Contribution.STATE_IN_PROGRESS
- contributionDao.save(contribution)
- uploadContribution(contribution = contribution)
- }
- }.collect()
-
- //Dismiss the global notification
- notificationManager?.cancel(
- PROCESSING_UPLOADS_NOTIFICATION_TAG,
- PROCESSING_UPLOADS_NOTIFICATION_ID
- )
-
- //No need to keep looking if the limited connection mode is on,
- //If the user toggles it, the work manager will be started again
- if(isLimitedConnectionModeEnabled()){
- break;
- }
+ /**
+ * To avoid race condition when multiple of these workers are working, assign this state
+ so that the next one does not process these contribution again
+ */
+ queuedContributions.forEach {
+ it.state=Contribution.STATE_IN_PROGRESS
+ contributionDao.saveSynchronous(it)
}
+
+ queuedContributions.asFlow().map { contribution ->
+ /**
+ * If the limited connection mode is on, lets iterate through the queued
+ * contributions
+ * and set the state as STATE_QUEUED_LIMITED_CONNECTION_MODE ,
+ * otherwise proceed with the upload
+ */
+ if (isLimitedConnectionModeEnabled()) {
+ if (contribution.state == Contribution.STATE_QUEUED) {
+ contribution.state = Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE
+ contributionDao.saveSynchronous(contribution)
+ }
+ } else {
+ contribution.transferred = 0
+ contribution.state = Contribution.STATE_IN_PROGRESS
+ contributionDao.saveSynchronous(contribution)
+ uploadContribution(contribution = contribution)
+ }
+ }.collect()
+
+ //Dismiss the global notification
+ notificationManager?.cancel(
+ PROCESSING_UPLOADS_NOTIFICATION_TAG,
+ PROCESSING_UPLOADS_NOTIFICATION_ID
+ )
}
//TODO make this smart, think of handling retries in the future
return Result.success()
@@ -307,6 +307,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
Timber.e(exception)
Timber.e("Upload from stash failed for contribution : $filename")
showFailedNotification(contribution)
+ contribution.state=Contribution.STATE_FAILED
if (STASH_ERROR_CODES.contains(exception.message)) {
clearChunks(contribution)
}
@@ -315,26 +316,28 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
StashUploadState.PAUSED -> {
showPausedNotification(contribution)
contribution.state = Contribution.STATE_PAUSED
- contributionDao.save(contribution).blockingGet()
+ contributionDao.saveSynchronous(contribution)
}
else -> {
Timber.e("""upload file to stash failed with status: ${stashUploadResult.state}""")
showFailedNotification(contribution)
contribution.state = Contribution.STATE_FAILED
contribution.chunkInfo = null
- contributionDao.save(contribution).blockingAwait()
+ contributionDao.saveSynchronous(contribution)
}
}
}catch (exception: Exception){
Timber.e(exception)
Timber.e("Stash upload failed for contribution: $filename")
showFailedNotification(contribution)
+ contribution.state=Contribution.STATE_FAILED
+ clearChunks(contribution)
}
}
private fun clearChunks(contribution: Contribution) {
contribution.chunkInfo=null
- contributionDao.save(contribution).blockingAwait()
+ contributionDao.saveSynchronous(contribution)
}
/**
diff --git a/app/src/main/res/drawable-hdpi/map_default_map_marker.png b/app/src/main/res/drawable-hdpi/map_default_map_marker.png
new file mode 100644
index 000000000..ef993a0f5
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-hdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-hdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..c841453c1
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-hdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-hdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..7568387a8
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/drawable-mdpi/map_default_map_marker.png b/app/src/main/res/drawable-mdpi/map_default_map_marker.png
new file mode 100644
index 000000000..45ca4ab58
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-mdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-mdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..e8b49eefe
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-mdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-mdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..9cdec5151
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/drawable-xhdpi/map_default_map_marker.png b/app/src/main/res/drawable-xhdpi/map_default_map_marker.png
new file mode 100644
index 000000000..19a1f66a8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-xhdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-xhdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..b994fc39d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-xhdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-xhdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..7eec45f4b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/map_default_map_marker.png b/app/src/main/res/drawable-xxhdpi/map_default_map_marker.png
new file mode 100644
index 000000000..4a9abb9ee
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-xxhdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..7218f0caf
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-xxhdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..5876056f1
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/map_default_map_marker.png b/app/src/main/res/drawable-xxxhdpi/map_default_map_marker.png
new file mode 100644
index 000000000..65fe36d1b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-xxxhdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..b0027d86b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-xxxhdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..e5780ccc0
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/layout/activity_location_picker.xml b/app/src/main/res/layout/activity_location_picker.xml
new file mode 100644
index 000000000..a6aa85d71
--- /dev/null
+++ b/app/src/main/res/layout/activity_location_picker.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/bottom_container_location_picker.xml b/app/src/main/res/layout/bottom_container_location_picker.xml
new file mode 100644
index 000000000..95c0a2431
--- /dev/null
+++ b/app/src/main/res/layout/bottom_container_location_picker.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_location_picker.xml b/app/src/main/res/layout/content_location_picker.xml
new file mode 100644
index 000000000..2f846759c
--- /dev/null
+++ b/app/src/main/res/layout/content_location_picker.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_media_detail.xml b/app/src/main/res/layout/fragment_media_detail.xml
index d4fb2d18e..ef691c300 100644
--- a/app/src/main/res/layout/fragment_media_detail.xml
+++ b/app/src/main/res/layout/fragment_media_detail.xml
@@ -249,6 +249,16 @@
android:drawablePadding="@dimen/tiny_gap"
android:drawableStart="?attr/iconMap24"
tools:text="Coordinates link" />
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 0688c4c48..88c2e30d5 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -265,6 +265,7 @@
تم التقاط هذه الصورة في موقع مختلف.
من فضلك فقط ارفع الصور التي التقطها بنفسك. لا ترفع الصور التي وجدتها على حسابات الآخرين في فيسبوك.
هل ما زلت تريد رفع هذه الصورة؟
+ خطأ في الاتصال
تم العثور على مشاكل في الصورة
يُرجَى فقط رفع الصور التي التقطتها بنفسك، لا ترفع الصور التي قمت بتنزيلها من الإنترنت.
استخدم تخزينا خارجيا
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index d09c93a39..1007bfad3 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -246,6 +246,8 @@
Dieses Bild wurde an einem anderen Ort aufgenommen.
Lade bitte nur Bilder hoch, die du selber erstellt hast. Lade keine Bilder hoch, die du auf Facebook-Konten anderer Personen gefunden hast.
Möchtest du immer noch dieses Bild hochladen?
+ Verbindungsfehler
+ Der Hochlade-Vorgang erfordert einen aktiven Internetzugang. Bitte prüfe deine Netzwerkverbindung.
Im Bild gefundene Probleme
Lade bitte nur Bilder hoch, die du selber aufgenommen hast. Lade keine Bilder hoch, die du aus dem Internet heruntergeladen hast.
Speichern von In-App-Aufnahmen
@@ -498,6 +500,12 @@
Konnte keine Kategorien hinzufügen.
Kategorien aktualisieren
+ Versuche, die Koordinaten zu aktualisieren.
+ Koordinaten aktualisieren
+ Erfolgreich
+ Koordinaten %1$s werden hinzugefügt.
+ Koordinaten konnten nicht hinzugefügt werden.
+ Es können keine Koordinaten abgerufen werden.
Bild teilen via
Du hast noch keine Beiträge geleistet
Benutzerkonto erstellt!
@@ -574,8 +582,21 @@
Qualitätsbilder sind Diagramme oder Fotografien, die bestimmte Qualitätsstandards erfüllen (die meist technischer Natur sind) und für Wikimedia-Projekte wertvoll sind
Hochladen fortsetzen …
Hochladen pausieren …
+ Du hast den eingeschränkten Verbindungsmodus aktiviert. Alle Uploads sind angehalten und werden fortgesetzt, sobald du diesen Modus deaktivierst.
+ Der begrenzte Verbindungsmodus ist aktiv.
+ Bitte schreibe eine kurze Bildunterschrift, die sagt, was dein Bild zeigt. Sage in der Beschreibung, was das Bild interessant oder typisch oder selten macht, und erkläree den Kontext, sichtbar oder nicht. Verwende möglichst genaue Begriffe.
+ Bitte finde und wähle alle Benennungen, die dieses Bild darstellt. Sei so spezifisch wie möglich. Wenn das Bild mehrere Begriffe darstellt, wähle sie alle in angemessener Weise aus. Wähle keine generischen Begriffe, wenn spezifischere Begriffe verfügbar sind.
+ Bitte wähle die entsprechenden Kategorien aus. Im Gegensatz zu den Darstellungen sind die Kategorien nur in Englisch.
+ Commons macht deine Bilder wiederverwendbar und von jedem adaptierbar. Möchtest du auf alle Rechte verzichten? Möchtest du als Urheber genannt werden? Möchtest du, dass Bearbeitungen die gleiche Lizenz verwenden?
+ Stellt dar
Medienlizenz
Mediendetails
Kategorieseite anzeigen
+ Benutzeroberflächensprache
Mehr lesen
+ Wähle einen Ort
+ Schwenken und Zoomen zum Einstellen
+ Beenden der Ortswahl
+ Ort auswählen
+ Ortauswahl Bildansicht
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 24c42ef2e..436fc07d4 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -10,6 +10,7 @@
* Gomoko
* Happy13241
* Jean-Frédéric
+* JenyxGym
* KATRINE1992
* Melissadeba95
* Metroitendo
@@ -252,6 +253,8 @@
Cette image a été prise à un emplacement différent.
Veuillez ne téléverser que des images que vous avez prises vous-même. Ne téléversez aucune image prise sur les comptes Facebook d’autres personnes.
Voulez-vous tout de même téléverser cette image ?
+ Erreur de connexion
+ Le processus de téléchargement nécessite un accès internet\n actif. Veuillez vérifier votre connexion internet.
Problèmes trouvés dans l’image
Veuillez ne téléverser que des images que vous avez prises vous-même. Ne téléversez aucune image que vous avez téléchargée depuis Internet.
Enregistrer les prises de vue dans l’application
diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml
index ea9ce67f5..c8b6c0fa1 100644
--- a/app/src/main/res/values-ia/strings.xml
+++ b/app/src/main/res/values-ia/strings.xml
@@ -35,6 +35,8 @@
Mi incargamentos
Divider
Permission necessari: Scriber sur immagazinage externe. Le app non pote acceder a tu camera/galeria sin isto.
+ Error de connexion
+ Le processo de incargamento require un accesso a internet active. Per favor verifica tu connexion al rete.
Salveguardar photos prendite in app
Salveguardar le photos prendite con le camera del application sur le immagazinage de tu apparato
\"A proximitate\" poterea non functionar, perque le position geographic non es disponibile.
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 204476bc5..76579521d 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -3,6 +3,7 @@
* Arifin.wijaya
* Daud I.F. Argana
* Farras
+* Gombang
* Hidayatsrf
* Ilham151096
* Iwan Novirion
@@ -21,10 +22,10 @@
- (%1$d)
Memulai Unggahan
-
- - Memulai %1$d unggahan
+
+ - Memroses %1$d unggahan
-
+
- %1$d unggahan
@@ -61,7 +62,7 @@
Unggahan dalam antrian (mode sambungan terbatas diaktifkan)
%1$s terunggah!
Tekan untuk melihat unggahan Anda
- Memulai unggahan %1$s
+ Mengunggah berkas: %1$s
%1$s terunggah!
Menyelesaikan pengunggahan %1$s
Mengunggah %1$s gagal
@@ -229,6 +230,8 @@
Gambar diambil di lokasi berbeda.
Tolong hanya unggah gambar-gambar yang Anda ambil sendiri. Jangan unggah gambar yang Anda temukan di akun Facebook orang lain.
Apakah Anda masih ingin mengunggah gambar ini?
+ Kesalahan koneksi
+ Proses pengunggahan memerlukan akses internet aktif. Mohon periksa sambungan jaringan Anda.
Masalah dalam gambar ditemukan
Unggah hanya gambar yang sudah Anda potret sendiri. Jangan unggah gambar yang Anda ambil dari Internet.
Simpan foto dalam aplikasi
@@ -418,8 +421,8 @@
Gambar ini ada dalam kategori %1$s.
Di luar cakupan karena
Pelanggaran hak cipta karena
- Ya, mengapa tidak
- Gambar selanjutnya
+ Gambar selanjutnya
+ Ya, mengapa tidak
Menekan tombol ini akan memberikan Anda gambar lain yang baru diunggah dari Wikimedia Commons
Anda dapat meninjau gambar dan meningkatkan kualitas Wikimedia Commons.\n Empat parameter peninjauan adalah: \n - Apakah gambar ini di dalam cakupan? \n - Apakah gambar ini mengikut aturan hak cipta? \n - Apakah gambar ini dikategorikan dengan benar? \n - Jika semuanya berjalan lancar Anda juga bisa berterima kasih kepada kontributor.
Tidak ada gambar digunakan
@@ -435,6 +438,8 @@
Gambar pilihan adalah gambar dari fotografer dan ilustrator berkemampuan tinggi yang komunitas Wikimedia Commons telah pilih sebagai salah satu gambar dengan kualitas tertinggi di situs ini.
Gambar Diunggah via Tempat sekitar adalah gambar yang diunggah dengan menemukan tempat di peta.
Fitur ini memungkinkan penyunting mengirimkan sebuah pemberitahuan Terima kasih kepada pengguna yang membuat suntingan yang berguna – dengan menggunakan tautan terima kasih kecil di halaman riwayat atau \'\'diff\'\'.
+ Salin ke media berikutnya
+ Berhasil disalin
Contoh gambar yang sebaiknya diunggah ke Commons
Contoh gambar yang sebaiknya tidak diunggah
Lewati gambar ini
@@ -573,4 +578,7 @@
Detail Media
Lihat halaman kategori
Lihat halaman butir
+ Bahasa antarmuka pengguna aplikasi
+ Menghapus takarir dan deskripsi
+ Baca lebih lanjut
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index aa6427ab2..eb6f0da46 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -257,6 +257,8 @@
התמונה צולמה במקום אחר.
נא להעלות רק תמונות שהעלית בעצמך. לא להעלות תמונות שמצאת בחשבונות של כל מיני אנשים בפייסבוק.
האם ברצונך עדיין להעלות את התמונה הזאת?
+ שגיאת חיבור
+ תהליך ההעלאה דורש גישה פעילה לאינטרנט. נא לבדוק את חיבור הרשת שלך.
נמצאו בעיות בתמונה
נא להעלות רק תמונות שצילמת בעצמך. לא להעלות תמונות שהורדת מהאינטרנט.
שמירת צילומים מתוך היישומון
@@ -511,6 +513,12 @@
לא ניתן להוסיף קטגוריות.
עדכון קטגוריות
+ מנסים לעדכן נקודות ציון.
+ עדכון נקודות ציון
+ זה עבד
+ נקודות הציון %1$s נוספות.
+ לא היה אפשר להוסיף נקודות ציון.
+ לא ניתן לעדכן נקודות ציון.
שיתוף תמונה דרך
לא תרמת שום דבר עדיין
חשבון נוצר!
@@ -608,4 +616,9 @@
שפת ממשק המשתמש של היישום
הסרת כיתוב ותיאור
מידע נוסף
+ נא לבחור מיקום
+ לצדד וקרב כדי לכוונן
+ יציאה מבורר המיקום
+ נא לבחור מיקום
+ תצוגת תמונה של בורר המיקום
diff --git a/app/src/main/res/values-jv/strings.xml b/app/src/main/res/values-jv/strings.xml
index 64ca4e9da..d4acd1f0a 100644
--- a/app/src/main/res/values-jv/strings.xml
+++ b/app/src/main/res/values-jv/strings.xml
@@ -1,5 +1,6 @@