Compare commits

...

30 commits

Author SHA1 Message Date
Josephine Lim
757c7b0087
Update changelog.md 2020-09-24 23:07:29 +10:00
Josephine Lim
9352e1d029 Versioning for v2.13.2 2020-09-24 23:03:25 +10:00
Josephine Lim
a75ec1e89b Merge branch '2.13-release' of https://github.com/commons-app/apps-android-commons into 2.13-release-3 2020-09-24 01:38:03 +10:00
Ashish
f496b9dfd2
Fixes #3926, (Upload fails immediately. App freshly reset, fast Internet) (#3928)
* Fixes #3926
* Encode media file name

* Revert "Fixes #3926"

This reverts commit b208b3d7fd.

* Encode fileName to support unicode characters in MultiPartUpload
Refer https://stackoverflow.com/questions/58869988/unexpected-char-0x662-at-42-in-content-disposition-value-form-data-name-userf
2020-09-16 23:34:46 +10:00
Ashish
e2ba80c342
Fixed logout problem #3547 (#3649) (#3927)
* fixed logout problem

fixed logout problem

fixed logout problem

fixed logout problem

fixed logout problem

* added error handling on logout

Co-authored-by: gouri-panda <gouripanda4@gmail.com>
2020-09-14 21:17:59 +10:00
Ashish
ece085d1d1
Fixes #3762 (#3931)
* Disable hidden marker actions
2020-09-14 21:14:24 +10:00
Josephine Lim
d4420860b7 Merge branch '2.13-release' of https://github.com/commons-app/apps-android-commons into 2.13-release-3 2020-09-09 01:37:16 +10:00
Ashish
624d351d4b
Fixes #3914 (#3915)
* Verify user login before setting upload count
2020-09-02 00:02:43 +10:00
Josephine Lim
87f4796504
Versioning and changelog for v2.13.1 (#3908)
* Update changelog.md

* Versioning for v2.13.1
2020-08-26 02:30:19 +10:00
Josephine Lim
917a944721 Merge branch '2.13-release-3' of https://github.com/misaochan/apps-android-commons into 2.13-release-3 2020-08-26 02:16:42 +10:00
Josephine Lim
d9aea26005 Versioning for v2.13.1 2020-08-26 02:15:00 +10:00
Josephine Lim
8c79447803
Update changelog.md 2020-08-26 02:14:07 +10:00
Ashish
fcc3053d40
Fixes #3766, Added OPENSTREET attribution (#3889)
* Fixes #3766
* Added OPENSTREET attribution in nearby

* Added custom text attribution in Nearby

* Deleted unused class CustomBorderTextView

* review suggested changes

* modified telemetry summary string
2020-08-12 05:37:13 +10:00
Ashish Kumar
aa1d6fe2aa
Fixes #3882 (#3883)
* Make hasInvalidLocation non-null integer with default value 0

Co-authored-by: Ashish Kumar <ashish@Ashishs-MacBook-Air.local>
2020-07-29 02:57:36 +10:00
Ashish Kumar
e7d93159d3
Bugfix/p18 uploads (#3869)
* updated gradle plugin version

* BugFix #3856
* Do not use preference for deciding acceptable lat long for nearby uploads, instead save the corresponding location in the Contribution via UploadItem

* Marshall contribution's hasInvalidLocation

* reset un-related changes

* Fixed test cases

* Minor code formatting and docs
2020-07-14 23:50:40 +10:00
Ashish Kumar
e471226405
DownSample Upload image to be shown in UploadMediaDetailFragment to handle OOM, Bitmap Too large exception (#3830)
* Fixes #3829
* DownSample Upload image to be shown in UploadMediaDetailFragment to handle OOM, Bitmap Too large exception

* removed unused imports, handled possible exceptions

* Let Fresco handle the downsampling of image

* invalidate in onTransformEnd

* Expose an interface TransformationListener in ZoomableDraweeView to listen to transformation change end

* removed photoView dependency

* removed unused imports in ZoomableActivity

* Bugfix, expand/collapse

* changed functio name
2020-06-24 09:57:25 +01:00
Ashish Kumar
78d519f83e
Handled null CompoundDrawable[2] in etTitle-> UploadMediaDetailsFragment (#3828) 2020-06-24 00:05:18 +10:00
Ashish Kumar
3e389be6f5
Fixes #3725 (#3795)
* Downgraded okhttp version to support Api 19 devices
2020-06-04 12:12:14 -07:00
Ashish Kumar
d02b959341
Fixed BookmarkLocationsDao DB migration (#3793) 2020-06-03 15:51:52 +03:00
Ashish Kumar
ac3e5158be
Fixes #3705 (Crash when viewing pic I just uploaded) (#3782)
* Fixes #3705
* Let the MediaDetailPager fragment know when the contributions have been updated

* Handle NPE, null check on adapter in MediaDetailPagerFragment
2020-06-03 03:19:38 +10:00
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
55 changed files with 632 additions and 472 deletions

View file

@ -1,5 +1,22 @@
# Wikimedia Commons for Android
## v2.13.2
- Fixed issue with invisible pins on Nearby map being targetable
- Fixed logout bug
- Fixed various crashes
## v2.13.1
- Added OpenStreetMap attribution
- Fixed various crashes
- Fixed SQLite error in Nearby map
- Fixed issue with Nearby uploads not being associated with Wikidata p18
## v2.13.0
- New media details UI, ability to zoom and pan around image
- Added suggestions for a place that needs photos if user uploads a photo that is near one of them
- Modifications and fixes to Nearby filters based on user feedback
- Multiple crash and bug fixes
## v2.12.3
- Fixed issue with EXIF data, including coords, being removed from uploads

View file

@ -21,7 +21,9 @@ dependencies {
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
implementation 'in.yuvi:http.fluent:1.3'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.okhttp3:okhttp:4.2.0'
implementation ("com.squareup.okhttp3:okhttp:$OKHTTP_VERSION"){
force = true //API 19 support
}
implementation 'com.squareup.okio:okio:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
@ -35,7 +37,6 @@ dependencies {
// UI
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.github.pedrovgs:renderers:3.3.3'
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.6.2'
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v8:0.11.0'
@ -53,7 +54,7 @@ dependencies {
api('com.github.tony19:logback-android-classic:1.1.1-6') {
exclude group: 'com.google.android', module: 'android'
}
implementation "com.squareup.okhttp3:logging-interceptor:4.2.0"
implementation "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION"
// Dependency injector
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
@ -125,8 +126,8 @@ android {
defaultConfig {
//applicationId 'fr.free.nrw.commons'
versionCode 561
versionName '2.12.3'
versionCode 791
versionName '2.13.2'
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion 19

View file

@ -20,6 +20,7 @@ import com.mapbox.mapboxsdk.Mapbox;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import io.reactivex.Completable;
import org.acra.ACRA;
import org.acra.annotation.AcraCore;
import org.acra.annotation.AcraDialog;
@ -267,17 +268,18 @@ public class CommonsApplication extends Application {
}
sessionManager.logout()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
Timber.d("All accounts have been removed");
clearImageCache();
//TODO: fix preference manager
defaultPrefs.clearAll();
defaultPrefs.putBoolean("firstrun", false);
updateAllDatabases();
logoutListener.onLogoutComplete();
});
.andThen(Completable.fromAction(() ->{
Timber.d("All accounts have been removed");
clearImageCache();
//TODO: fix preference manager
defaultPrefs.clearAll();
defaultPrefs.putBoolean("firstrun", false);
updateAllDatabases();
}
))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(logoutListener::onLogoutComplete, Timber::e);
}
/**

View file

@ -263,10 +263,9 @@ public class BookmarkLocationsDao {
onUpdate(db, from, to);
return;
}
if (from == 10 && to == 11) {
from++;
if (from == 10) {
//This is safe, and can be called clean, as we/I do not remember the appropriate version for this
//We are anyways switching to room, these things won't be nescessary then
//We are anyways switching to room, these things won't be necessary then
try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
}catch (SQLiteException exception){

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

@ -60,6 +60,19 @@ public class Contribution extends Media {
private String p18Value;
public Uri contentProviderUri;
public String dateCreatedSource;
public int hasInvalidLocation;
/**
* Set this true when ImageProcessor has said that the location is invalid
* @param hasInvalidLocation
*/
public void setHasInvalidLocation(boolean hasInvalidLocation) {
this.hasInvalidLocation = hasInvalidLocation ? 1 : 0;
}
public boolean isHasInvalidLocation() {
return hasInvalidLocation == 1;
}
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date dateCreated,
int state, long dataLength, Date dateUploaded, long transferred,
@ -269,6 +282,7 @@ public class Contribution extends Media {
this.contentProviderUri = contentProviderUri;
}
@Override
public int describeContents() {
return 0;
@ -290,6 +304,7 @@ public class Contribution extends Media {
dest.writeString(this.p18Value);
dest.writeParcelable(this.contentProviderUri, flags);
dest.writeString(this.dateCreatedSource);
dest.writeInt(this.hasInvalidLocation);
}
protected Contribution(Parcel in) {
@ -307,6 +322,7 @@ public class Contribution extends Media {
this.p18Value = in.readString();
this.contentProviderUri = in.readParcelable(Uri.class.getClassLoader());
this.dateCreatedSource = in.readString();
this.hasInvalidLocation = in.readInt();
}
public static final Creator<Contribution> CREATOR = new Creator<Contribution>() {

View file

@ -21,6 +21,7 @@ import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
import androidx.fragment.app.FragmentTransaction;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.auth.SessionManager;
import io.reactivex.disposables.Disposable;
import java.util.List;
@ -94,6 +95,9 @@ public class ContributionsFragment
@Inject ContributionsPresenter contributionsPresenter;
@Inject
SessionManager sessionManager;
private LatLng curLatLng;
private boolean firstLocationUpdate = true;
@ -162,7 +166,8 @@ public class ContributionsFragment
showContributionsListFragment();
}
if (!ConfigUtils.isBetaFlavour()) {
if (!ConfigUtils.isBetaFlavour() && sessionManager.isUserLoggedIn()
&& sessionManager.getCurrentAccount() != null) {
setUploadCount();
}
@ -224,6 +229,14 @@ public class ContributionsFragment
Timber.d("Fetching thumbnail for %s", contribution.filename);
contributionsPresenter.fetchMediaDetails(contribution);
}
@Override
public void onContributionsUpdated() {
//If the contributions are updated, let the pager fragment know
if (null != mediaDetailPagerFragment) {
mediaDetailPagerFragment.notifyDataSetChanged();
}
}
});
if(null==mediaDetailPagerFragment){

View file

@ -77,5 +77,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
Contribution getContributionForPosition(int position);
void fetchMediaUriFor(Contribution contribution);
void onContributionsUpdated();
}
}

View file

@ -188,6 +188,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
this.contributions.clear();
this.contributions.addAll(contributionList);
adapter.setContributions(contributions);
callback.onContributionsUpdated();
}
public interface SourceRefresher {

View file

@ -7,7 +7,7 @@ import androidx.room.TypeConverters;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionDao;
@Database(entities = {Contribution.class}, version = 1, exportSchema = false)
@Database(entities = {Contribution.class}, version = 2, exportSchema = false)
@TypeConverters({Converters.class})
abstract public class AppDatabase extends RoomDatabase {
public abstract ContributionDao getContributionDao();

View file

@ -7,6 +7,8 @@ import android.content.Context;
import android.view.inputmethod.InputMethodManager;
import androidx.collection.LruCache;
import androidx.room.Room;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import com.github.varunpant.quadtree.QuadTree;
import com.google.gson.Gson;
import dagger.Module;
@ -52,6 +54,14 @@ public class CommonsApplicationModule {
public static final String MAIN_THREAD="main_thread";
private AppDatabase appDatabase;
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE contribution "
+ " ADD COLUMN hasInvalidLocation INTEGER NOT NULL DEFAULT 0");
}
};
public CommonsApplicationModule(Context applicationContext) {
this.applicationContext = applicationContext;
}
@ -231,7 +241,9 @@ public class CommonsApplicationModule {
@Provides
@Singleton
public AppDatabase provideAppDataBase() {
appDatabase=Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db").build();
appDatabase=Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db")
.addMigrations(MIGRATION_1_2)
.build();
return appDatabase;
}

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

@ -320,7 +320,9 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
* The method notify the viewpager that number of items have changed.
*/
public void notifyDataSetChanged(){
adapter.notifyDataSetChanged();
if (null != adapter) {
adapter.notifyDataSetChanged();
}
}
@Override

View file

@ -23,18 +23,11 @@ import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.interfaces.DraweeController;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.image.ImageInfo;
import com.github.chrisbanes.photoview.PhotoView;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class ZoomableActivity extends AppCompatActivity {
private Uri imageUri;

View file

@ -47,6 +47,7 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
private boolean mIsDialtoneEnabled = false;
private boolean mZoomingEnabled = true;
private TransformationListener transformationListener;
private final ControllerListener mControllerListener =
new BaseControllerListener<Object>() {
@ -73,9 +74,18 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
}
@Override
public void onTransformEnd(Matrix transform) {}
public void onTransformEnd(Matrix transform) {
if (null != transformationListener) {
transformationListener.onTransformationEnd();
}
}
};
public void setTransformationListener(
TransformationListener transformationListener) {
this.transformationListener = transformationListener;
}
private final GestureListenerWrapper mTapListenerWrapper = new GestureListenerWrapper();
public ZoomableDraweeView(Context context, GenericDraweeHierarchy hierarchy) {
@ -397,4 +407,11 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
protected ZoomableController createZoomableController() {
return AnimatedZoomableController.newInstance();
}
/**
* Use this, If someone is willing to listen to scale change
*/
public interface TransformationListener{
void onTransformationEnd();
}
}

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

@ -5,6 +5,7 @@ import com.mapbox.mapboxsdk.annotations.Marker;
public class NearbyMarker extends Marker {
private final Place place;
private NearbyBaseMarker nearbyBaseMarker;
private boolean disabled;
/**
* Creates a instance of {@link Marker} using the builder of Marker.
@ -24,4 +25,12 @@ public class NearbyMarker extends Marker {
public Place getPlace() {
return place;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
public boolean isDisabled() {
return disabled;
}
}

View file

@ -1,5 +1,13 @@
package fr.free.nrw.commons.nearby.fragments;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
import static fr.free.nrw.commons.nearby.Label.TEXT_TO_DESCRIPTION;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import android.Manifest;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@ -11,6 +19,8 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@ -26,14 +36,15 @@ import android.widget.RelativeLayout;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
@ -58,17 +69,6 @@ import com.mapbox.mapboxsdk.maps.UiSettings;
import com.mapbox.pluginscalebar.ScaleBarOptions;
import com.mapbox.pluginscalebar.ScaleBarPlugin;
import com.pedrogomez.renderers.RVRendererAdapter;
import fr.free.nrw.commons.utils.DialogUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
@ -92,6 +92,7 @@ import fr.free.nrw.commons.nearby.NearbyMarker;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.ExecutorUtils;
import fr.free.nrw.commons.utils.LayoutUtils;
import fr.free.nrw.commons.utils.LocationUtils;
@ -105,16 +106,13 @@ import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
import static fr.free.nrw.commons.nearby.Label.TEXT_TO_DESCRIPTION;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
public class NearbyParentFragment extends CommonsDaggerSupportFragment
implements NearbyParentFragmentContract.View,
@ -154,6 +152,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
MapView mapView;
@BindView(R.id.rv_nearby_list)
RecyclerView rvNearbyList;
@BindView(R.id.tv_attribution)
AppCompatTextView tvAttribution;
@Inject LocationServiceManager locationManager;
@Inject NearbyController nearbyController;
@ -236,8 +236,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
UiSettings uiSettings = mapBoxMap.getUiSettings();
uiSettings.setCompassGravity(Gravity.BOTTOM | Gravity.LEFT);
uiSettings.setCompassMargins(12, 0, 0, 24);
uiSettings.setLogoEnabled(true);
uiSettings.setAttributionEnabled(true);
uiSettings.setLogoEnabled(false);
uiSettings.setAttributionEnabled(false);
uiSettings.setRotateGesturesEnabled(false);
NearbyParentFragment.this.isMapBoxReady=true;
performMapReadyActions();
@ -260,6 +260,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
scaleBarPlugin.create(scaleBarOptions);
});
});
tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
}
/**
@ -624,6 +627,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 +651,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 +1093,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override
public void filterOutAllMarkers() {
hideAllMArkers();
hideAllMarkers();
updateNearbyList();
}
/**
@ -1089,6 +1105,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
for (MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) {
updateMarker(markerPlaceGroup.getIsBookmarked(), markerPlaceGroup.getPlace(), NearbyController.currentLocation);
}
updateNearbyList();
}
/**
@ -1100,65 +1117,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 +1169,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(
@ -1210,6 +1208,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
nearbyBaseMarker.icon(IconFactory.getInstance(getContext())
.fromBitmap(icon));
marker.setIcon(IconFactory.getInstance(getContext()).fromBitmap(icon));
if(marker instanceof NearbyMarker){
((NearbyMarker) marker).setDisabled(false);
}
}
}
}
@ -1219,10 +1221,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());
@ -1230,9 +1230,13 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
for (Marker marker : mapBox.getMarkers()) {
if (!marker.equals(currentLocationMarker)) {
marker.setIcon(IconFactory.getInstance(getContext()).fromBitmap(icon));
if(marker instanceof NearbyMarker){
((NearbyMarker) marker).setDisabled(true);
}
}
}
addCurrentLocationMarker(NearbyController.currentLocation);
clearNearbyList();
}
private void addNearbyMarkersToMapBoxMap(List<NearbyBaseMarker> nearbyBaseMarkers, Marker selectedMarker) {
@ -1252,10 +1256,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
});
mapBox.setOnMarkerClickListener(marker -> {
if (marker instanceof NearbyMarker) {
if (marker instanceof NearbyMarker && !((NearbyMarker) marker).isDisabled()) {
presenter.markerSelected(marker);
}
return false;
return true;
});
}
}

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

@ -10,6 +10,7 @@ public class Prefs {
public static final String MANAGED_EXIF_TAGS = "managed_exif_tags";
public static final String KEY_LANGUAGE_VALUE = "languageDescription";
public static final String KEY_THEME_VALUE = "appThemePref";
public static final String TELEMETRY_PREFERENCE = "telemetryPref";
public static class Licenses {
public static final String CC_BY_SA_3 = "CC BY-SA 3.0";

View file

@ -14,6 +14,8 @@ import com.google.android.material.snackbar.Snackbar;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.single.BasePermissionListener;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.di.ApplicationlessInjection;
@ -123,6 +125,24 @@ public class SettingsFragment extends PreferenceFragmentCompat {
findPreference("displayCampaignsCardView").setEnabled(false);
uploadLimit.setEnabled(false);
}
findPreference("telemetryOptOut").setOnPreferenceChangeListener(
(preference, newValue) -> {
telemetryOptInOut((boolean)newValue);
defaultKvStore.putBoolean(Prefs.TELEMETRY_PREFERENCE,(boolean)newValue);
return false;
});
}
/**
* Opt in or out of MapBox telemetry
* @param shouldOptIn
*/
private void telemetryOptInOut(boolean shouldOptIn){
TelemetryDefinition telemetry = Mapbox.getTelemetry();
if (telemetry != null) {
telemetry.setUserTelemetryRequestState(shouldOptIn);
}
}
/**

View file

@ -3,6 +3,8 @@ package fr.free.nrw.commons.upload;
import android.content.Context;
import android.net.Uri;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import org.wikipedia.csrf.CsrfTokenClient;
import java.io.File;
@ -41,7 +43,15 @@ public class UploadClient {
(bytesWritten, contentLength) -> notificationUpdater
.onProgress(bytesWritten, contentLength));
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", filename, countingRequestBody);
MultipartBody.Part filePart;
try {
filePart = MultipartBody.Part
.createFormData("file", URLEncoder.encode(filename, "utf-8"), countingRequestBody);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
filePart = MultipartBody.Part
.createFormData("file", filename, countingRequestBody);
}
RequestBody fileNameRequestBody = RequestBody.create(okhttp3.MultipartBody.FORM, filename);
RequestBody tokenRequestBody;
try {

View file

@ -172,6 +172,7 @@ public class UploadModel {
contribution.setSource(item.source);
contribution.setContentProviderUri(item.mediaUri);
contribution.setDateUploaded(new Date());
contribution.setHasInvalidLocation(item.hasInvalidLocation);
Timber.d("Created timestamp while building contribution is %s, %s",
item.getCreatedTimestamp(),
@ -221,6 +222,7 @@ public class UploadModel {
private final String mimeType;
private final String source;
private ImageCoordinates gpsCoords;
private boolean hasInvalidLocation;
public void setGpsCoords(ImageCoordinates gpsCoords) {
this.gpsCoords = gpsCoords;
@ -326,6 +328,10 @@ public class UploadModel {
public int hashCode() {
return mediaUri.hashCode();
}
public void setHasInvalidLocation(boolean hasInvalidLocation) {
this.hasInvalidLocation=hasInvalidLocation;
}
}
}

View file

@ -282,7 +282,15 @@ public class UploadService extends HandlerService<Contribution> {
Timber.d("Contribution upload success. Initiating Wikidata edit for"
+ " entity id %s if necessary (if P18 is null). P18 value is %s",
contribution.getWikiDataEntityId(), contribution.getP18Value());
wikidataEditService.createClaimWithLogging(contribution.getWikiDataEntityId(), contribution.getWikiItemName(), canonicalFilename, contribution.getP18Value());
if (!contribution.isHasInvalidLocation()) {
wikidataEditService
.createClaimWithLogging(contribution.getWikiDataEntityId(),
contribution.getWikiItemName(), canonicalFilename,
contribution.getP18Value());
} else {
Timber
.d("Image location and nearby place location mismatched, so Wikidata item won't be edited");
}
contribution.setFilename(canonicalFilename);
contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl());
contribution.setState(Contribution.STATE_COMPLETED);

View file

@ -4,6 +4,7 @@ import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@ -23,13 +24,19 @@ import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.github.chrisbanes.photoview.PhotoView;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.interfaces.DraweeController;
import com.jakewharton.rxbinding2.widget.RxTextView;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.media.zoomControllers.zoomable.DoubleTapGestureListener;
import fr.free.nrw.commons.media.zoomControllers.zoomable.ZoomableDraweeView;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.Description;
@ -44,6 +51,7 @@ import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.disposables.Disposable;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -69,7 +77,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@BindView(R.id.rv_descriptions)
RecyclerView rvDescriptions;
@BindView(R.id.backgroundImage)
PhotoView photoViewBackgroundImage;
ZoomableDraweeView photoViewBackgroundImage;
@BindView(R.id.btn_next)
AppCompatButton btnNext;
@BindView(R.id.btn_previous)
@ -164,11 +172,26 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
btnCopyPreviousTitleDesc.setVisibility(View.VISIBLE);
}
attachImageViewScaleChangeListener();
addEtTitleTouchListener();
}
private void showImageWithLocalUri(Uri imageUri) {
if (imageUri != null) {
GenericDraweeHierarchy hierarchy = GenericDraweeHierarchyBuilder.newInstance(getResources())
.setActualImageScaleType(ScaleType.FIT_XY)
.build();
photoViewBackgroundImage.setHierarchy(hierarchy);
photoViewBackgroundImage
.setTapListener(new DoubleTapGestureListener(photoViewBackgroundImage));
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(Uri.fromFile(new File(imageUri.getPath())))
.build();
photoViewBackgroundImage.setTransformationListener(
() -> expandCollapseMediaDetail(false));
photoViewBackgroundImage.setController(controller);
}
}
/**
* Handles the drawable click listener for Edit Text
*/
@ -176,11 +199,13 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
etTitle.setOnTouchListener((v, event) -> {
//2 is for drawable right
float twelveDpInPixels = convertDpToPixel(12, getContext());
if (event.getAction() == MotionEvent.ACTION_UP && etTitle.getCompoundDrawables() != null
&& etTitle.getCompoundDrawables().length > 2 && etTitle
.getCompoundDrawables()[2].getBounds()
.contains((int) (etTitle.getWidth() - (event.getX() + twelveDpInPixels)),
(int) (event.getY() - twelveDpInPixels))) {
if ((event.getAction() == MotionEvent.ACTION_UP)
&& (etTitle.getCompoundDrawables() != null)
&& (etTitle.getCompoundDrawables().length > 2)
&& (etTitle.getCompoundDrawables()[2] != null)
&& etTitle.getCompoundDrawables()[2].getBounds()
.contains((int) (etTitle.getWidth() - (event.getX() + twelveDpInPixels)),
(int) (event.getY() - twelveDpInPixels))) {
showInfoAlert(R.string.media_detail_title, R.string.title_info);
return true;
}
@ -198,17 +223,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
/**
* Attaches the scale change listener to the image view
*/
private void attachImageViewScaleChangeListener() {
photoViewBackgroundImage.setOnScaleChangeListener(
(scaleFactor, focusX, focusY) -> {
//Whenever the uses plays with the image, lets collapse the media detail container
expandCollapseLlMediaDetail(false);
});
}
/**
* attach the presenter with the view
*/
@ -284,7 +298,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
}
descriptions = uploadItem.getDescriptions();
photoViewBackgroundImage.setImageURI(uploadItem.getMediaUri());
showImageWithLocalUri(uploadItem.getMediaUri());
setDescriptionsInAdapter(descriptions);
}
@ -393,14 +407,17 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@OnClick(R.id.rl_container_title)
public void onRlContainerTitleClicked() {
expandCollapseLlMediaDetail(!isExpanded);
expandCollapseMediaDetail(!isExpanded);
}
/**
* show hide media detail based on
* @param shouldExpand
*/
private void expandCollapseLlMediaDetail(boolean shouldExpand){
private void expandCollapseMediaDetail(boolean shouldExpand){
if (isExpanded == shouldExpand) {
return;
}
llContainerMediaDetail.setVisibility(shouldExpand ? View.VISIBLE : View.GONE);
isExpanded = !isExpanded;
ibExpandCollapse.setRotation(ibExpandCollapse.getRotation() + 180);

View file

@ -120,7 +120,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
.observeOn(mainThreadScheduler)
.subscribe(imageResult -> {
view.showProgress(false);
handleImageResult(imageResult);
handleImageResult(imageResult, uploadItem);
},
throwable -> {
view.showProgress(false);
@ -165,13 +165,16 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
/**
* handles image quality verifications
*
* @param imageResult
*/
public void handleImageResult(Integer imageResult) {
* @param imageResult
* @param uploadItem
*/
public void handleImageResult(Integer imageResult,
UploadItem uploadItem) {
if (imageResult == IMAGE_KEEP || imageResult == IMAGE_OK) {
view.onImageValidationSuccess();
uploadItem.setHasInvalidLocation(false);
} else {
handleBadImage(imageResult);
handleBadImage(imageResult, uploadItem);
}
}
@ -179,12 +182,14 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
* Handle images, say empty title, duplicate file name, bad picture(in all other cases)
*
* @param errorCode
* @param uploadItem
*/
public void handleBadImage(Integer errorCode) {
public void handleBadImage(Integer errorCode,
UploadItem uploadItem) {
Timber.d("Handle bad picture with error code %d", errorCode);
if (errorCode
>= 8) { // If location of image and nearby does not match, then set shared preferences to disable wikidata edits
repository.saveValue("Picture_Has_Correct_Location", false);
>= 8) { // If location of image and nearby does not match, then set shared preferences to disable wikidata edits
uploadItem.setHasInvalidLocation(true);
}
switch (errorCode) {

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

@ -61,11 +61,6 @@ public class WikidataEditService {
return;
}
if (!(directKvStore.getBoolean("Picture_Has_Correct_Location", true))) {
Timber.d("Image location and nearby place location mismatched, so Wikidata item won't be edited");
return;
}
if (p18Value != null && !p18Value.trim().isEmpty()) {
Timber.d("Skipping creation of claim as p18Value is not empty, we won't override existing image");
return;

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

@ -25,7 +25,7 @@
<!-- I have done this intentionally, the mapview because of some elevation or something,
sometimes hangs over the drawer layout and sometimes draws its onPaused state over the contributions, this seems to be the probable fix -->
<FrameLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/nearby_filter">
@ -40,7 +40,19 @@
android:layout_height="match_parent"
android:background="@android:color/transparent" />
</FrameLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_attribution"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:text="@string/map_attribution"
android:textAlignment="center"
android:textSize="10sp" />
</RelativeLayout>
<Button

View file

@ -6,11 +6,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.chrisbanes.photoview.PhotoView
<fr.free.nrw.commons.media.zoomControllers.zoomable.ZoomableDraweeView
android:id="@+id/backgroundImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:actualImageScaleType="fitXY" />
android:scaleType="fitXY"
/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"

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

@ -610,5 +610,7 @@ Upload your first media by tapping on the add button.</string>
<string name="ask_to_turn_location_on">Turn on location?</string>
<string name="nearby_needs_location">Nearby needs location enabled to work properly</string>
<string name="use_location_from_similar_image">Did you shoot these two pictures at the same place? Do you want to use the latitude/longitude of the picture on the right?</string>
<string name="mapbox_telemetry">Telemetry Opt Out</string>
<string name="telemetry_opt_out_summary">Send anonymized location and usage data to Mapbox when using Nearby feature</string>
<string name="map_attribution" translatable="false"><![CDATA[&#169; <a href="https://www.mapbox.com/about/maps/">Mapbox</a> &#169; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> <a href="https://www.mapbox.com/map-feedback/">Improve this map</a>]]></string>
</resources>

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

@ -85,6 +85,13 @@
android:summary="@string/manage_exif_tags_summary"
android:title="@string/manage_exif_tags" />
<SwitchPreference
android:defaultValue="true"
android:key="telemetryOptOut"
app:singleLineTitle="false"
android:summary="@string/telemetry_opt_out_summary"
android:title="@string/mapbox_telemetry" />
</PreferenceCategory>
<!-- The key 'allowGps' was used before and has since been removed based on the discussion at #1599.

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

@ -109,20 +109,20 @@ class UploadMediaPresenterTest {
@Test
fun handleImageResult() {
//Positive case test
uploadMediaPresenter.handleImageResult(IMAGE_KEEP)
uploadMediaPresenter.handleImageResult(IMAGE_KEEP, uploadItem)
verify(view).onImageValidationSuccess()
//Duplicate file name
uploadMediaPresenter.handleImageResult(FILE_NAME_EXISTS)
uploadMediaPresenter.handleImageResult(FILE_NAME_EXISTS, uploadItem)
verify(view).showDuplicatePicturePopup()
//Empty Title test
uploadMediaPresenter.handleImageResult(EMPTY_TITLE)
uploadMediaPresenter.handleImageResult(EMPTY_TITLE, uploadItem)
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
//Bad Picture test
//Empty Title test
uploadMediaPresenter.handleImageResult(-7)
uploadMediaPresenter.handleImageResult(-7, uploadItem)
verify(view).showBadImagePopup(ArgumentMatchers.anyInt())
}
@ -158,8 +158,8 @@ class UploadMediaPresenterTest {
*/
@Test
fun handleBadImageBaseTestInvalidLocation() {
uploadMediaPresenter.handleBadImage(8)
verify(repository).saveValue(ArgumentMatchers.anyString(), eq(false))
uploadMediaPresenter.handleBadImage(8, uploadItem)
verify(uploadItem).setHasInvalidLocation(true)
verify(view).showBadImagePopup(8)
}
@ -168,7 +168,7 @@ class UploadMediaPresenterTest {
*/
@Test
fun handleBadImageBaseTestEmptyTitle() {
uploadMediaPresenter.handleBadImage(-3)
uploadMediaPresenter.handleBadImage(-3, uploadItem)
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
}
@ -177,7 +177,7 @@ class UploadMediaPresenterTest {
*/
@Test
fun handleBadImageBaseTestFileNameExists() {
uploadMediaPresenter.handleBadImage(-4)
uploadMediaPresenter.handleBadImage(-4, uploadItem)
verify(view).showDuplicatePicturePopup()
}

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

View file

@ -50,14 +50,6 @@ class WikidataEditServiceTest {
verifyZeroInteractions(wikidataClient!!)
}
@Test
fun noClaimsWhenLocationIsNotCorrect() {
`when`(directKvStore!!.getBoolean("Picture_Has_Correct_Location", true))
.thenReturn(false)
wikidataEditService!!.createClaimWithLogging("Q1", "","Test.jpg","")
verifyZeroInteractions(wikidataClient!!)
}
@Test
fun createClaimWithLogging() {
`when`(directKvStore!!.getBoolean("Picture_Has_Correct_Location", true))

View file

@ -7,7 +7,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.1'
classpath 'com.android.tools.build:gradle:3.6.3'
classpath "com.hiya:jacoco-android:0.2"
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"

View file

@ -22,6 +22,7 @@ LEAK_CANARY_VERSION=1.6.2
DAGGER_VERSION=2.21
ROOM_VERSION=2.2.3
PREFERENCE_VERSION=1.1.0
OKHTTP_VERSION=3.12.1
systemProp.http.proxyPort=0
systemProp.http.proxyHost=