diff --git a/CHANGELOG.md b/CHANGELOG.md index 1586f0db4..3c1e66e9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Wikimedia Commons for Android +## v2.13 +- New media details UI, ability to zoom and pan around image +- Added suggestions for a place that needs photos if user uploads a photo that is near one of them +- Modifications and fixes to Nearby filters based on user feedback +- Multiple crash and bug fixes + ## v2.12.3 - Fixed issue with EXIF data, including coords, being removed from uploads diff --git a/app/build.gradle b/app/build.gradle index 68a34b67d..a0451e5e4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -125,8 +125,8 @@ android { defaultConfig { //applicationId 'fr.free.nrw.commons' - versionCode 561 - versionName '2.12.3' + versionCode 709 + versionName '2.13' setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) minSdkVersion 19 diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java index caa66e433..e2030ee9a 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java @@ -123,9 +123,8 @@ public class CategoriesModel{ } //otherwise, search API for matching categories - //term passed as lower case to make search case-insensitive(taking only lower case for everything) return categoryClient - .searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT) + .searchCategoriesForPrefix(term, SEARCH_CATS_LIMIT) .map(name -> new CategoryItem(name, false)); } @@ -184,12 +183,11 @@ public class CategoriesModel{ /** * Return category for single title - * title is converted to lower case to make search case-insensitive * @param title * @return */ private Observable getTitleCategories(String title) { - return categoryClient.searchCategories(title.toLowerCase(), SEARCH_CATS_LIMIT) + return categoryClient.searchCategories(title, SEARCH_CATS_LIMIT) .map(name -> new CategoryItem(name, false)); } diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryClient.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryClient.java index 7a4d1718b..329c3635a 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryClient.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryClient.java @@ -55,7 +55,7 @@ public class CategoryClient { /** * Searches for categories starting with the specified string. - * + * * @param prefix The prefix to be searched * @param itemLimit How many results are returned * @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.java index c8f2a2713..bb89a11c4 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryItem.java @@ -3,6 +3,10 @@ package fr.free.nrw.commons.category; import android.os.Parcel; import android.os.Parcelable; +/** + * Represents a Category Item. + * Implemented as Parcelable so that its object could be parsed between activity components. + */ public class CategoryItem implements Parcelable { private final String name; private boolean selected; @@ -24,28 +28,53 @@ public class CategoryItem implements Parcelable { this.selected = selected; } + /** + * Reads from the received Parcel + * @param in + */ private CategoryItem(Parcel in) { name = in.readString(); selected = in.readInt() == 1; } + /** + * Gets Name + * @return + */ public String getName() { return name; } + /** + * Checks if that Category Item has been selected. + * @return + */ public boolean isSelected() { return selected; } + /** + * Selects the Category Item. + * @param selected + */ public void setSelected(boolean selected) { this.selected = selected; } + /** + * Used by Parcelable + * @return + */ @Override public int describeContents() { return 0; } + /** + * Writes to the received Parcel + * @param parcel + * @param flags + */ @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(name); @@ -67,13 +96,19 @@ public class CategoryItem implements Parcelable { } + /** + * Returns hash code for current object + */ @Override public int hashCode() { return name.hashCode(); } + /** + * Return String form of current object + */ @Override public String toString() { return "CategoryItem: '" + name + '\''; } -} \ No newline at end of file +} diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java index 5d168d6b1..698e2d51f 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java @@ -166,7 +166,7 @@ public class FilePicker implements Constants { public static List handleExternalImagesPicked(Intent data, Activity activity) { try { return getFilesFromGalleryPictures(data, activity); - } catch (IOException e) { + } catch (IOException | SecurityException e) { e.printStackTrace(); } return new ArrayList<>(); @@ -207,7 +207,7 @@ public class FilePicker implements Constants { } } - private static List getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException { + private static List getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException, SecurityException { List files = new ArrayList<>(); ClipData clipData = data.getClipData(); if (clipData == null) { diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java index 86da1326b..01e68c940 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java @@ -104,12 +104,15 @@ class PickedFiles implements Constants { }); } - static UploadableFile pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException { + static UploadableFile pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException, SecurityException {// SecurityException for those file providers who share URI but forget to grant necessary permissions InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri); File directory = tempImageDirectory(context); File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri)); - photoFile.createNewFile(); - writeToFile(pictureInputStream, photoFile); + if (photoFile.createNewFile()) { + writeToFile(pictureInputStream, photoFile); + } else { + throw new IOException("could not create photoFile to write upon"); + } return new UploadableFile(photoUri, photoFile); } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index 2349b6236..adc63fbd2 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 @@ -105,6 +105,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { @BindView(R.id.mediaDetailImageView) SimpleDraweeView image; + @BindView(R.id.mediaDetailImageViewSpacer) + LinearLayout imageSpacer; @BindView(R.id.mediaDetailTitle) TextView title; @BindView(R.id.mediaDetailDesc) @@ -205,12 +207,14 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { return view; } - @OnClick(R.id.mediaDetailImageView) + @OnClick(R.id.mediaDetailImageViewSpacer) public void launchZoomActivity(View view) { - Context ctx = view.getContext(); - ctx.startActivity( - new Intent(ctx,ZoomableActivity.class).setData(Uri.parse(media.getImageUrl())) - ); + if (media.getImageUrl() != null) { + Context ctx = view.getContext(); + ctx.startActivity( + new Intent(ctx, ZoomableActivity.class).setData(Uri.parse(media.getImageUrl())) + ); + } } @Override @@ -241,12 +245,21 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { compositeDisposable.add(disposable); } + /** + * The imageSpacer is Basically a transparent overlay for the SimpleDraweeView + * which holds the image to be displayed( moreover this image is out of + * the scroll view ) + * @param imageInfo used to calculate height of the ImageSpacer + */ private void updateAspectRatio(ImageInfo imageInfo) { if (imageInfo != null) { int finalHeight = (scrollView.getWidth()*imageInfo.getHeight()) / imageInfo.getWidth(); ViewGroup.LayoutParams params = image.getLayoutParams(); + ViewGroup.LayoutParams spacerParams = imageSpacer.getLayoutParams(); params.height = finalHeight; + spacerParams.height = finalHeight; image.setLayoutParams(params); + imageSpacer.setLayoutParams(spacerParams); } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java b/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java index e8fc70873..db2c1f5d9 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java @@ -1,8 +1,6 @@ package fr.free.nrw.commons.nearby; import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; import android.util.AttributeSet; import android.widget.CompoundButton; @@ -12,7 +10,6 @@ import androidx.appcompat.widget.AppCompatCheckBox; import java.util.List; import fr.free.nrw.commons.R; -import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter; /** * Base on https://stackoverflow.com/a/40939367/3950497 answer. @@ -25,7 +22,7 @@ public class CheckBoxTriStates extends AppCompatCheckBox { static public final int CHECKED = 1; - private int state; + private int state=UNKNOWN; private Callback callback; @@ -64,12 +61,6 @@ public class CheckBoxTriStates extends AppCompatCheckBox { */ private OnCheckedChangeListener clientListener; - /** - * This flag is needed to avoid accidentally changing the current {@link #state} when - * {@link #onRestoreInstanceState(Parcelable)} calls {@link #setChecked(boolean)} - * evoking our {@link #privateListener} and therefore changing the real state. - */ - private boolean restoring; public CheckBoxTriStates(Context context) { super(context); @@ -91,7 +82,7 @@ public class CheckBoxTriStates extends AppCompatCheckBox { } public void setState(int state) { - if(!this.restoring && this.state != state) { + if(this.state != state) { this.state = state; if(this.clientListener != null) { @@ -118,27 +109,6 @@ public class CheckBoxTriStates extends AppCompatCheckBox { super.setOnCheckedChangeListener(privateListener); } - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - - SavedState ss = new SavedState(superState); - - ss.state = state; - - return ss; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - this.restoring = true; // indicates that the ui is restoring its state - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - setState(ss.state); - requestLayout(); - this.restoring = false; - } - private void init() { state = UNKNOWN; updateBtn(); @@ -164,44 +134,4 @@ public class CheckBoxTriStates extends AppCompatCheckBox { setButtonDrawable(btnDrawable); } - - static class SavedState extends BaseSavedState { - int state; - - SavedState(Parcelable superState) { - super(superState); - } - - private SavedState(Parcel in) { - super(in); - state = in.readInt(); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeValue(state); - } - - @Override - public String toString() { - return "CheckboxTriState.SavedState{" - + Integer.toHexString(System.identityHashCode(this)) - + " state=" + state + "}"; - } - - @SuppressWarnings("hiding") - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapterFactory.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapterFactory.java index 2dc2e4e40..3f4dacd84 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapterFactory.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapterFactory.java @@ -41,4 +41,17 @@ public class NearbyAdapterFactory { rendererAdapter.notifyDataSetChanged(); rendererAdapter.diffUpdate(newPlaceList); } + + public void clear(RVRendererAdapter rendererAdapter){ + rendererAdapter.clear(); + } + + public void add(Place place, RVRendererAdapter rendererAdapter){ + rendererAdapter.add(place); + } + + public void update(RVRendererAdapter rendererAdapter){ + rendererAdapter.notifyDataSetChanged(); + } + } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyListFragment.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyMapFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyMapFragment.java deleted file mode 100644 index e69de29bb..000000000 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 3919b20b9..80cf1f33a 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 @@ -624,6 +624,18 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment adapterFactory.updateAdapterData(placeList, (RVRendererAdapter) rvNearbyList.getAdapter()); } + public void clearNearbyList() { + adapterFactory.clear((RVRendererAdapter) rvNearbyList.getAdapter()); + } + + public void updateNearbyList() { + adapterFactory.update((RVRendererAdapter) rvNearbyList.getAdapter()); + } + + public void addPlaceToNearbyList(Place place) { + adapterFactory.add(place, (RVRendererAdapter) rvNearbyList.getAdapter()); + } + @Override public fr.free.nrw.commons.location.LatLng getLastLocation() { return lastKnownLocation; @@ -636,7 +648,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public boolean isCurrentLocationMarkerVisible() { - if (latLngBounds == null) { + if (latLngBounds == null || currentLocationMarker==null) { Timber.d("Map projection bounds are null"); return false; } else { @@ -1078,7 +1090,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public void filterOutAllMarkers() { - hideAllMArkers(); + hideAllMarkers(); + updateNearbyList(); } /** @@ -1089,6 +1102,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment for (MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) { updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation); } + updateNearbyList(); } /** @@ -1100,65 +1114,44 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment * @param filterForAllNoneType true if we filter places with all none button */ @Override - public void filterMarkersByLabels(List