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" /> + +