Compare commits

...

10 commits
main ... v2.13

Author SHA1 Message Date
Josephine Lim
d950f72193 Merge branch '2.13-release-2' of https://github.com/misaochan/apps-android-commons into 2.13-release-2 2020-05-20 02:57:41 +10:00
Josephine Lim
d97d40fbb9 Versioning for v2.13 2020-05-20 02:56:59 +10:00
Josephine Lim
741746892a
Update changelog.md 2020-05-20 02:55:46 +10:00
Ashish Kumar
bff923135e
Fix NullPointer when clicking on image in MediaDetailFragment (#3730)… (#3739) 2020-05-12 13:37:17 +03:00
Vitaly V. Pinchuk
63018fcbd5 Add #3723 and #3721 to 2.13 release, fix conflicts
Conflicts were caused by merging #3723 before #3721 , so I just rolled both into one commit.
2020-05-06 01:04:09 +10:00
Ashish Kumar
461249fc30
Fixes #3639 (Fix Save State implementation of CheckBoxTriState ) (#3686) 2020-04-26 12:05:08 +03:00
Kshitij Bhardwaj
d7c2480174 Fixes #3436 and #2881: Media Detail design Overhaul (#3505)
* ic_map_dark_24dp: map icon for white background

* ic_info_outline_dark_24dp: info icon for dark background

* MediaDetailFragment: update the spacer as per image aspect ratio

* fragment_media_detail: design overhaul

* fragment_media_detail: remove redundant background color statements

* make requested changes

* add dark mode support

* minor ui tweak

* white map icon in dark mode

* make rquested changes

* make requested changes to layout

* fix misalignment of category list

* subtle amendments

* convert comments to javadocs

* minor amendments

* minor changes

* add styles for media detail

* Media detail fragment refactored

* make suggested changes

* minor name fix

* fix the delete button border
2020-04-22 01:23:14 +10:00
Ashish Kumar
05a9aa8575
Bugfix/security exception (#3627)
* Fixes #3626
* Check is file is actually created before writing to file, file picker android

* Handle Security exception
2020-04-16 23:59:30 -07:00
Kaartic Sivaraam
c961099013
Revert "Fixes: #3179 Make category search non case-sensitive (#3326)" (#3636)
Simply lower casing the name of the category sent to the server
doesn't result in the server doing a case insensitive category
search. In fact, it reduces the category search space as only
categories that has a lower case character is searched even
if the search text contains upper case characters.

The test case did not catch this issue as the first character
of the title is case insensitive[1].

So, revert the changes done in commit afdeaae075.

See further disucssion in the issue thread of #3179 starting
from [2].

[1]: https://www.mediawiki.org/wiki/Manual:Page_title
[2]: https://github.com/commons-app/apps-android-commons/issues/3179#issuecomment-605462140
2020-04-10 10:38:51 +01:00
Seán Mac Gillicuddy
cf73e28623 #3624 DateTimeFormat wrong - match pattern returned from servers (#3625) 2020-04-09 02:52:09 +10:00
27 changed files with 353 additions and 358 deletions

View file

@ -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

View file

@ -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

View file

@ -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<CategoryItem> getTitleCategories(String title) {
return categoryClient.searchCategories(title.toLowerCase(), SEARCH_CATS_LIMIT)
return categoryClient.searchCategories(title, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false));
}

View file

@ -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

View file

@ -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 + '\'';
}
}
}

View file

@ -166,7 +166,7 @@ public class FilePicker implements Constants {
public static List<UploadableFile> 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<UploadableFile> getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException {
private static List<UploadableFile> getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException, SecurityException {
List<UploadableFile> files = new ArrayList<>();
ClipData clipData = data.getClipData();
if (clipData == null) {

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

View file

@ -41,4 +41,17 @@ public class NearbyAdapterFactory {
rendererAdapter.notifyDataSetChanged();
rendererAdapter.diffUpdate(newPlaceList);
}
public void clear(RVRendererAdapter<Place> rendererAdapter){
rendererAdapter.clear();
}
public void add(Place place, RVRendererAdapter<Place> rendererAdapter){
rendererAdapter.add(place);
}
public void update(RVRendererAdapter<Place> rendererAdapter){
rendererAdapter.notifyDataSetChanged();
}
}

View file

@ -624,6 +624,18 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
adapterFactory.updateAdapterData(placeList, (RVRendererAdapter<Place>) rvNearbyList.getAdapter());
}
public void clearNearbyList() {
adapterFactory.clear((RVRendererAdapter<Place>) rvNearbyList.getAdapter());
}
public void updateNearbyList() {
adapterFactory.update((RVRendererAdapter<Place>) rvNearbyList.getAdapter());
}
public void addPlaceToNearbyList(Place place) {
adapterFactory.add(place, (RVRendererAdapter<Place>) 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<Label> selectedLabels, boolean displayExists,
boolean displayNeedsPhoto,
boolean filterForPlaceState,
boolean filterForAllNoneType) {
if (selectedLabels.size() == 0 && filterForPlaceState) { // If nothing is selected, display all
// remove the previous markers before updating them
hideAllMArkers();
for (MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
if (displayExists && displayNeedsPhoto) {
// Exists and needs photo
if (markerPlaceGroup.getPlace().destroyed.trim().isEmpty() && markerPlaceGroup.getPlace().pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (displayExists && !displayNeedsPhoto) {
// Exists and all included needs and doesn't needs photo
if (markerPlaceGroup.getPlace().destroyed.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (!displayExists && displayNeedsPhoto) {
// All and only needs photo
if (markerPlaceGroup.getPlace().pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (!displayExists && !displayNeedsPhoto) {
// all
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
public void filterMarkersByLabels(List<Label> selectedLabels,
boolean displayExists,
boolean displayNeedsPhoto,
boolean filterForPlaceState,
boolean filterForAllNoneType) {
hideAllMarkers();
for (MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
Place place = markerPlaceGroup.getPlace();
// When label filter is engaged
// then compare it against place's label
if (selectedLabels != null && (selectedLabels.size() != 0 || !filterForPlaceState)
&& !selectedLabels.contains(place.getLabel())) {
continue;
}
} else {
// First remove all the markers
hideAllMArkers();
for (MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
for (Label label : selectedLabels) {
if (markerPlaceGroup.getPlace().getLabel().toString().equals(label.toString())) {
if (displayExists && displayNeedsPhoto) {
// Exists and needs photo
if (markerPlaceGroup.getPlace().destroyed.trim().isEmpty() && markerPlaceGroup.getPlace().pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (displayExists && !displayNeedsPhoto) {
// Exists and all included needs and doesn't needs photo
if (markerPlaceGroup.getPlace().destroyed.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (!displayExists && displayNeedsPhoto) {
// All and only needs photo
if (markerPlaceGroup.getPlace().pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
} else if (!displayExists && !displayNeedsPhoto) {
// all
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
}
if (displayExists && displayNeedsPhoto) {
// Exists and needs photo
if (place.destroyed.trim().isEmpty() && place.pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), place, NearbyController.currentLocation);
}
} else if (displayExists && !displayNeedsPhoto) {
// Exists and all included needs and doesn't needs photo
if (place.destroyed.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), place, NearbyController.currentLocation);
}
} else if (!displayExists && displayNeedsPhoto) {
// All and only needs photo
if (place.pic.trim().isEmpty()) {
updateMarker(markerPlaceGroup.getIsBookmarked(), place, NearbyController.currentLocation);
}
} else if (!displayExists && !displayNeedsPhoto) {
// all
updateMarker(markerPlaceGroup.getIsBookmarked(), place, NearbyController.currentLocation);
}
}
updateNearbyList();
}
@Override
@ -1173,6 +1166,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
* @param curLatLng current location
*/
public void updateMarker(boolean isBookmarked, Place place, @Nullable fr.free.nrw.commons.location.LatLng curLatLng) {
addPlaceToNearbyList(place);
VectorDrawableCompat vectorDrawable;
if (isBookmarked) {
vectorDrawable = VectorDrawableCompat.create(
@ -1219,10 +1214,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
* but it is transparent more than grey(as the name of the icon might suggest)
* since grey icon may lead the users to believe that it is disabled or prohibited contribution
*/
private void hideAllMArkers() {
if(currentLocationMarker==null){
return;
}
private void hideAllMarkers() {
VectorDrawableCompat vectorDrawable;
vectorDrawable = VectorDrawableCompat.create(
getContext().getResources(), R.drawable.ic_custom_greyed_out_marker, getContext().getTheme());
@ -1233,6 +1226,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
}
addCurrentLocationMarker(NearbyController.currentLocation);
clearNearbyList();
}
private void addNearbyMarkersToMapBoxMap(List<NearbyBaseMarker> nearbyBaseMarkers, Marker selectedMarker) {

View file

@ -284,7 +284,11 @@ public class NearbyParentFragmentPresenter
nearbyParentFragmentView.setRecyclerViewAdapterItemsGreyedOut();
break;
case CHECKED:
nearbyParentFragmentView.displayAllMarkers();
// Despite showing all labels NearbyFilterState still should be applied
nearbyParentFragmentView.filterMarkersByLabels(selectedLabels,
NearbyFilterState.getInstance().isExistsSelected(),
NearbyFilterState.getInstance().isNeedPhotoSelected(),
filterForPlaceState, false);
nearbyParentFragmentView.setRecyclerViewAdapterAllSelected();
break;
}

View file

@ -25,9 +25,10 @@ public class CommonsDateUtil {
* Gets the timestamp pattern for a date
* @return timestamp
*/
public static SimpleDateFormat getIso8601DateFormatTimestamp() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return simpleDateFormat;
public static SimpleDateFormat getIso8601DateFormatTimestamp() {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX",
Locale.ROOT);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return simpleDateFormat;
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@color/button_blue" />
<corners
android:radius="@dimen/progressbar_stroke" />
</shape>

View file

@ -9,12 +9,9 @@
<shape
android:shape="rectangle">
<solid
android:color="@color/deleteButton"/>
android:color="?attr/mediaDetailNominationBackground"/>
<corners
android:radius="@dimen/progressbar_stroke" />
<stroke
android:width="5px"
android:color="@color/deleteRed" />
</shape>
</item>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/half_standard_height"
android:height="@dimen/half_standard_height"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/button_background_dark"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/half_standard_height"
android:height="@dimen/half_standard_height"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/button_background_dark"
android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"/>
</vector>

View file

@ -10,15 +10,15 @@
android:id="@+id/mediaDetailCategoryItemText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:background="?attr/mainBackground"
android:foreground="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="@dimen/overflow_button_dimen"
android:padding="@dimen/quarter_standard_height"
android:textColor="@android:color/white"
android:padding="@dimen/small_gap"
android:textColor="?attr/mediaDetailsText"
android:textSize="@dimen/description_text_size"
app:drawablePadding="@dimen/small_gap"
app:drawableStart="@drawable/ic_info_outline_24dp"
app:drawablePadding="@dimen/tiny_gap"
app:drawableStart="?attr/iconInfo24"
/>
</LinearLayout>

View file

@ -19,11 +19,10 @@
/>
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/mediaDetailImage"
android:id="@+id/mediaDetailImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:actualImageScaleType="centerCrop"
/>
android:layout_height="@dimen/dimen_250"
app:actualImageScaleType="none" />
<ScrollView
android:id="@+id/mediaDetailScrollView"
@ -40,257 +39,200 @@
<!-- Placeholder. Height gets set at runtime based on container size; the initial value is a hack to keep
the detail info offscreen until it's placed properly. May be a better way to do this. -->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/mediaDetailImageView"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dimen_250"
app:actualImageScaleType="none" />
android:orientation="vertical"
android:background="@android:color/transparent"
android:id="@+id/mediaDetailImageViewSpacer"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/fragmentCategorisationBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
android:background="?attr/mainBackground"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
android:background="@color/primaryDarkColor"
android:orientation="horizontal"
android:padding="@dimen/quarter_standard_height">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/tiny_gap"
android:text="@string/media_detail_title"
android:textColor="@android:color/white"
android:textSize="@dimen/normal_text"
android:textStyle="bold" />
style="@style/MediaDetailTextLabelTitle"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:text="@string/media_detail_title" />
<TextView
style="@style/MediaDetailTextBody"
android:id="@+id/mediaDetailTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:background="?attr/subBackground"
android:padding="@dimen/small_gap"
android:layout_width="@dimen/widget_margin"
android:textColor="@android:color/white"
android:textSize="@dimen/description_text_size"
android:layout_height="match_parent"
tools:text="Title of the media" />
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
android:layout_width="match_parent"
android:layout_height="@dimen/small_gap" />
<LinearLayout
style="@style/MediaDetailContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/authorLinearLayout"
android:background="?attr/subBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/tiny_gap"
android:text="@string/media_detail_author"
android:textColor="@android:color/white"
android:textSize="@dimen/normal_text"
android:textStyle="bold" />
style="@style/MediaDetailTextLabelGeneric"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:text="@string/media_detail_author" />
<TextView
style="@style/MediaDetailTextBody"
android:id="@+id/mediaDetailAuthor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:background="?attr/subBackground"
android:padding="@dimen/small_gap"
android:textColor="@android:color/white"
android:textSize="@dimen/description_text_size"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
tools:text="Media author user name goes here." />
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
android:layout_width="match_parent"
android:layout_height="@dimen/small_gap" />
<LinearLayout
style="@style/MediaDetailContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/tiny_gap"
android:text="@string/media_detail_description"
android:textColor="@android:color/white"
android:textSize="@dimen/normal_text"
android:textStyle="bold" />
style="@style/MediaDetailTextLabelGeneric"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:text="@string/media_detail_description" />
<fr.free.nrw.commons.ui.widget.HtmlTextView
android:id="@+id/mediaDetailDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:layout_weight="70"
android:layout_gravity="start"
android:background="?attr/subBackground"
android:padding="@dimen/small_gap"
android:textColor="@android:color/white"
android:textColor="?attr/mediaDetailsText"
android:textSize="@dimen/description_text_size"
tools:text="Description of the media goes here. This can potentially be fairly long, and will need to wrap across multiple lines. We hope it looks nice though." />
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
<View
android:background="?attr/mediaDetailSpacerColor"
android:layout_width="match_parent"
android:layout_height="@dimen/small_gap" />
android:layout_height="@dimen/tiny_gap"/>
<LinearLayout
style="@style/MediaDetailContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/tiny_gap"
android:text="@string/media_detail_license"
android:textColor="@android:color/white"
android:textSize="@dimen/normal_text"
android:textStyle="bold" />
style="@style/MediaDetailTextLabelGeneric"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:text="@string/media_detail_license" />
<fr.free.nrw.commons.ui.widget.CompatTextView
android:id="@+id/mediaDetailLicense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:layout_weight="70"
android:layout_gravity="start"
android:background="?attr/subBackground"
android:foreground="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:padding="@dimen/small_gap"
android:textColor="@android:color/white"
android:textColor="?attr/mediaDetailsText"
android:textSize="@dimen/description_text_size"
app:drawablePadding="@dimen/tiny_gap"
app:drawableStart="@drawable/ic_info_outline_24dp"
app:drawableStart="?attr/iconInfo24"
tools:text="License link" />
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
android:layout_width="match_parent"
android:layout_height="@dimen/small_gap" />
<LinearLayout
style="@style/MediaDetailContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/tiny_gap"
android:text="@string/media_detail_coordinates"
android:textColor="@android:color/white"
android:textSize="@dimen/normal_text"
android:textStyle="bold" />
style="@style/MediaDetailTextLabelGeneric"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:text="@string/media_detail_coordinates" />
<fr.free.nrw.commons.ui.widget.CompatTextView
android:id="@+id/mediaDetailCoordinates"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:layout_weight="70"
android:layout_gravity="start"
android:background="?attr/subBackground"
android:foreground="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:padding="@dimen/small_gap"
android:textColor="@android:color/white"
android:textColor="?attr/mediaDetailsText"
android:textSize="@dimen/description_text_size"
app:drawablePadding="@dimen/tiny_gap"
app:drawableStart="@drawable/ic_map_white_24dp"
app:drawableStart="?attr/iconMap24"
tools:text="Coordinates link" />
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
android:layout_width="match_parent"
android:layout_height="@dimen/small_gap" />
<LinearLayout
style="@style/MediaDetailContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap"
android:orientation="horizontal"
android:textStyle="bold">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingBottom="@dimen/tiny_gap"
android:text="@string/detail_panel_cats_label"
android:textColor="@android:color/white"
android:textSize="@dimen/normal_text"
android:textStyle="bold" />
style="@style/MediaDetailTextLabelGeneric"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:text="@string/detail_panel_cats_label" />
<LinearLayout
android:id="@+id/mediaDetailCategoryContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:layout_weight="70"
android:orientation="vertical" />
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
android:layout_width="match_parent"
android:layout_height="@dimen/small_gap" />
<LinearLayout
style="@style/MediaDetailContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/tiny_gap"
android:text="@string/media_detail_uploaded_date"
android:textColor="@android:color/white"
android:textSize="@dimen/normal_text"
android:textStyle="bold" />
style="@style/MediaDetailTextLabelGeneric"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:text="@string/media_detail_uploaded_date" />
<TextView
style="@style/MediaDetailTextBody"
android:id="@+id/mediaDetailuploadeddate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:background="?attr/subBackground"
android:padding="@dimen/small_gap"
android:textColor="@android:color/white"
android:textSize="@dimen/description_text_size"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
tools:text="Uploaded date" />
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
android:layout_width="match_parent"
android:layout_height="@dimen/small_gap" />
<LinearLayout
android:id="@+id/nominatedDeletionBanner"
android:background="@color/deleteRed"
android:background="?attr/mediaDetailNominationBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/standard_gap"
android:padding="@dimen/quarter_standard_height"
android:visibility="gone">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -299,6 +241,7 @@
android:textColor="@color/primaryTextColor"
android:textSize="@dimen/normal_text"
android:textStyle="bold"/>
<TextView
android:id="@+id/seeMore"
android:layout_width="match_parent"
@ -310,35 +253,23 @@
android:textStyle="bold"/>
</LinearLayout>
<fr.free.nrw.commons.media.MediaDetailSpacer
android:layout_width="match_parent"
android:layout_height="@dimen/small_gap" />
<LinearLayout
style="@style/MediaDetailContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/tiny_gap"
android:text="@string/media_detail_discussion"
android:textColor="@android:color/white"
android:textSize="@dimen/normal_text"
android:textStyle="bold" />
style="@style/MediaDetailTextLabelGeneric"
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent"
android:text="@string/media_detail_discussion" />
<TextView
style="@style/MediaDetailTextBody"
android:id="@+id/mediaDetailDisc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:background="?attr/subBackground"
android:padding="@dimen/small_gap"
android:textColor="@android:color/white"
android:textSize="@dimen/description_text_size" />
android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent" />
</LinearLayout>
<Button
@ -346,7 +277,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_gap"
android:background="@color/button_blue"
android:background="@drawable/bg_copy_wikitext_button"
android:text="@string/copy_wikicode"
android:textColor="@color/primaryTextColor" />

View file

@ -26,6 +26,12 @@
<attr name="mainTabBackground" format="reference"/>
<attr name="mainCardBackground" format="reference"/>
<attr name="mainScreenNearbyPermissionbutton" format="reference"/>
<attr name="iconInfo24" format="reference" />
<attr name="iconMap24" format="reference" />
<attr name="mediaDetailsText" format="reference" />
<attr name="mediaDetailsHeadingText" format="reference" />
<attr name="mediaDetailNominationBackground" format="reference" />
<attr name="mediaDetailSpacerColor" format="reference" />
<attr name="icon" format="reference"/>
<attr name="aboutIconsColor" format="reference"/>

View file

@ -24,7 +24,8 @@
<color name="primaryTextColor">#ffffff</color>
<color name="secondaryTextColor">#000000</color>
<color name="deleteRed">#90960a0a</color>
<color name="deleteRed">#D32F2F</color>
<color name="deleteRedDark">#90960a0a</color>
<color name="deleteButton">#44000000</color>
<color name="deleteButtonDark">#88000000</color>
<color name="deleteButtonLight">#44ffffff</color>

View file

@ -34,6 +34,12 @@
<item name="textEnabled">@color/enabled_button_text_color_dark</item>
<item name="mainCardBackground">@color/main_background_dark</item>
<item name="mainScreenNearbyPermissionbutton">@style/DarkFlatNearbyPermissionButton</item>
<item name="iconInfo24">@drawable/ic_info_outline_24dp</item>
<item name="iconMap24" >@drawable/ic_map_white_24dp</item>
<item name="mediaDetailsText" >@color/white</item>
<item name="mediaDetailsHeadingText">@color/layout_light_grey</item>
<item name="mediaDetailNominationBackground">@color/deleteRedDark</item>
<item name="mediaDetailSpacerColor">@color/browser_actions_divider_color</item>
</style>
<style name="LightAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
@ -69,6 +75,12 @@
<item name="textEnabled">@color/enabled_button_text_color_light</item>
<item name="mainCardBackground">@color/primaryDarkColor</item>
<item name="mainScreenNearbyPermissionbutton">@style/LightFlatNearbyPermissionButton</item>
<item name="iconInfo24">@drawable/ic_info_outline_dark_24dp</item>
<item name="iconMap24">@drawable/ic_map_dark_24dp</item>
<item name="mediaDetailsText">@color/enabled_button_text_color_light</item>
<item name="mediaDetailsHeadingText">@color/primaryDarkColor</item>
<item name="mediaDetailNominationBackground">@color/deleteRed</item>
<item name="mediaDetailSpacerColor">@color/divider_grey</item>
</style>
<style name="WhiteSearchBarTheme" parent="DarkAppTheme">
@ -129,4 +141,35 @@
<item name="centerRegion">#906078</item>
</style>
<style name="MediaDetailContainer">
<item name="android:paddingLeft">@dimen/quarter_standard_height</item>
<item name="android:paddingRight">@dimen/quarter_standard_height</item>
<item name="android:paddingTop">@dimen/tiny_gap</item>
<item name="android:paddingBottom">@dimen/tiny_gap</item>
</style>
<style name="MediaDetailTextLabel">
<item name="android:layout_weight">30</item>
<item name="android:paddingTop">@dimen/dimen_6</item>
<item name="android:paddingLeft">@dimen/tiny_gap</item>
<item name="android:textSize">@dimen/normal_text</item>
<item name="android:textStyle">bold</item>
</style>
<style name="MediaDetailTextLabelTitle" parent="@style/MediaDetailTextLabel">
<item name="android:textColor">@android:color/white</item>
</style>
<style name="MediaDetailTextLabelGeneric" parent="@style/MediaDetailTextLabel">
<item name="android:textColor">?attr/mediaDetailsHeadingText</item>
</style>
<style name="MediaDetailTextBody">
<item name="android:layout_weight">70</item>
<item name="android:layout_gravity">start</item>
<item name="android:padding">@dimen/small_gap</item>
<item name="android:textColor">?attr/mediaDetailsText</item>
<item name="android:textSize">@dimen/description_text_size</item>
</style>
</resources>

View file

@ -17,9 +17,6 @@ class CategoriesModelTest {
@Mock
internal var categoryInterface: CategoryInterface? = null
@Mock
internal var categoryItem: CategoryItem? = null
@Spy
internal lateinit var gson: Gson
@ -43,28 +40,6 @@ class CategoriesModelTest {
MockitoAnnotations.initMocks(this)
}
// Test Case for verifying that Categories search (MW api calls) are case-insensitive
@Test
fun searchAllFoundCaseTest() {
val mwQueryPage = Mockito.mock(MwQueryPage::class.java)
Mockito.`when`(mwQueryPage.title()).thenReturn("Category:Test")
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
Mockito.`when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
val categoriesModel: CategoriesModel = CategoriesModel(categoryClient,null,null)
Mockito.`when`(categoryInterface!!.searchCategoriesForPrefix(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()))
.thenReturn(Observable.just(mockResponse))
// Checking if both return "Test"
val actualCategoryName = categoriesModel!!.searchAll("tes",null).blockingFirst()
assertEquals("Test", actualCategoryName.getName())
val actualCategoryNameCaps = categoriesModel!!.searchAll("Tes",null).blockingFirst()
assertEquals("Test", actualCategoryNameCaps.getName())
}
/**
* For testing the substring search algorithm for Categories search
* To be more precise it tests the In Between substring( ex: searching `atte`

View file

@ -58,7 +58,6 @@ class CategoryClientTest {
{ fail("SearchCategories returned element when it shouldn't have.") },
{ s -> throw s })
}
@Test
fun searchCategoriesForPrefixFound() {
val mwQueryPage = Mockito.mock(MwQueryPage::class.java)
@ -93,7 +92,6 @@ class CategoryClientTest {
{ fail("SearchCategories returned element when it shouldn't have.") },
{ s -> throw s })
}
@Test
fun getParentCategoryListFound() {
val mwQueryPage = Mockito.mock(MwQueryPage::class.java)

View file

@ -0,0 +1,20 @@
package fr.free.nrw.commons.utils
import org.hamcrest.core.IsEqual.equalTo
import org.junit.Assert.assertThat
import org.junit.Test
class CommonsDateUtilTest {
@Test
fun `Iso8601DateFormatTimestamp parses legal date`() {
val iso8601DateFormatTimestamp = CommonsDateUtil
.getIso8601DateFormatTimestamp()
val parsedDate = iso8601DateFormatTimestamp
.parse("2020-04-07T14:21:57Z")
assertThat(
"2020-04-07T14:21:57Z",
equalTo(iso8601DateFormatTimestamp.format(parsedDate))
)
}
}